dash-devtools 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. dash_devtools/__init__.py +8 -0
  2. dash_devtools/__main__.py +11 -0
  3. dash_devtools/ai_engine.py +441 -0
  4. dash_devtools/browser.py +541 -0
  5. dash_devtools/cli.py +1452 -0
  6. dash_devtools/database.py +338 -0
  7. dash_devtools/dbdiagram.py +183 -0
  8. dash_devtools/e2e.py +329 -0
  9. dash_devtools/fixers/__init__.py +57 -0
  10. dash_devtools/fixers/migration_fixer.py +115 -0
  11. dash_devtools/fixers/ux_fixer.py +106 -0
  12. dash_devtools/fixers/version_bumper.py +115 -0
  13. dash_devtools/gas_mes_test.py +1241 -0
  14. dash_devtools/generators/__init__.py +84 -0
  15. dash_devtools/health.py +476 -0
  16. dash_devtools/hooks/__init__.py +250 -0
  17. dash_devtools/hooks/pre_commit.py +161 -0
  18. dash_devtools/hooks/pre_push.py +275 -0
  19. dash_devtools/init_test.py +352 -0
  20. dash_devtools/markdown_report.py +309 -0
  21. dash_devtools/migrators/__init__.py +21 -0
  22. dash_devtools/perf.py +321 -0
  23. dash_devtools/report.py +667 -0
  24. dash_devtools/reporters/__init__.py +11 -0
  25. dash_devtools/spec.py +230 -0
  26. dash_devtools/stats.py +355 -0
  27. dash_devtools/test_suite.py +690 -0
  28. dash_devtools/testing.py +416 -0
  29. dash_devtools/validators/__init__.py +157 -0
  30. dash_devtools/validators/backend/__init__.py +12 -0
  31. dash_devtools/validators/backend/nodejs.py +245 -0
  32. dash_devtools/validators/backend/python.py +439 -0
  33. dash_devtools/validators/code_quality.py +243 -0
  34. dash_devtools/validators/common/__init__.py +11 -0
  35. dash_devtools/validators/common/quality.py +319 -0
  36. dash_devtools/validators/common/security.py +270 -0
  37. dash_devtools/validators/common/spec.py +273 -0
  38. dash_devtools/validators/detector.py +394 -0
  39. dash_devtools/validators/frontend/__init__.py +14 -0
  40. dash_devtools/validators/frontend/angular.py +245 -0
  41. dash_devtools/validators/frontend/gas.py +310 -0
  42. dash_devtools/validators/frontend/vite.py +539 -0
  43. dash_devtools/validators/migration.py +292 -0
  44. dash_devtools/validators/performance.py +167 -0
  45. dash_devtools/validators/security.py +205 -0
  46. dash_devtools/vision/__init__.py +368 -0
  47. dash_devtools/watch.py +266 -0
  48. dash_devtools/word_report.py +690 -0
  49. dash_devtools-1.0.0.dist-info/METADATA +834 -0
  50. dash_devtools-1.0.0.dist-info/RECORD +53 -0
  51. dash_devtools-1.0.0.dist-info/WHEEL +5 -0
  52. dash_devtools-1.0.0.dist-info/entry_points.txt +2 -0
  53. dash_devtools-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,368 @@
1
+ """
2
+ 視覺 AI 工具
3
+ 使用 agent-browser 截圖 + Gemini AI 進行 UI/UX 分析
4
+
5
+ 功能:
6
+ - 網頁截圖並分析 UI/UX 問題
7
+ - 視覺回歸測試
8
+ - 無障礙設計檢查
9
+ - 效能視覺指標分析
10
+ """
11
+
12
+ import subprocess
13
+ import shutil
14
+ import base64
15
+ import tempfile
16
+ from pathlib import Path
17
+ from typing import Optional, Dict, List
18
+ from dataclasses import dataclass
19
+
20
+
21
+ @dataclass
22
+ class VisionResult:
23
+ """視覺分析結果"""
24
+ success: bool
25
+ url: str = ""
26
+ screenshot_path: str = ""
27
+ analysis: str = ""
28
+ issues: List[str] = None
29
+ recommendations: List[str] = None
30
+ error: str = ""
31
+
32
+ def __post_init__(self):
33
+ if self.issues is None:
34
+ self.issues = []
35
+ if self.recommendations is None:
36
+ self.recommendations = []
37
+
38
+
39
+ def check_dependencies() -> Dict[str, bool]:
40
+ """檢查依賴是否已安裝"""
41
+ return {
42
+ 'agent-browser': shutil.which('agent-browser') is not None,
43
+ 'gemini': _check_gemini_available()
44
+ }
45
+
46
+
47
+ def _check_gemini_available() -> bool:
48
+ """檢查 Gemini API 是否可用"""
49
+ try:
50
+ from ..ai_engine import AIEngine
51
+ AIEngine()
52
+ return True
53
+ except Exception:
54
+ return False
55
+
56
+
57
+ def take_screenshot(url: str, output_path: Optional[str] = None, wait_ms: int = 3000) -> Optional[str]:
58
+ """
59
+ 使用 agent-browser 截圖
60
+
61
+ Args:
62
+ url: 要截圖的網址
63
+ output_path: 輸出路徑 (預設建立臨時檔案)
64
+ wait_ms: 等待時間 (毫秒)
65
+
66
+ Returns:
67
+ 截圖路徑,失敗回傳 None
68
+ """
69
+ if not shutil.which('agent-browser'):
70
+ return None
71
+
72
+ if not output_path:
73
+ output_path = tempfile.mktemp(suffix='.png')
74
+
75
+ try:
76
+ # 開啟頁面
77
+ result = subprocess.run(
78
+ ['agent-browser', 'open', url],
79
+ capture_output=True,
80
+ text=True,
81
+ timeout=30
82
+ )
83
+
84
+ if result.returncode != 0:
85
+ subprocess.run(['agent-browser', 'close'], capture_output=True)
86
+ return None
87
+
88
+ # 等待載入
89
+ subprocess.run(
90
+ ['agent-browser', 'wait', '--load', 'networkidle'],
91
+ capture_output=True,
92
+ timeout=15
93
+ )
94
+
95
+ # 額外等待 JS 渲染
96
+ subprocess.run(
97
+ ['agent-browser', 'wait', str(wait_ms)],
98
+ capture_output=True,
99
+ timeout=10
100
+ )
101
+
102
+ # 截圖
103
+ result = subprocess.run(
104
+ ['agent-browser', 'screenshot', output_path, '--full'],
105
+ capture_output=True,
106
+ timeout=30
107
+ )
108
+
109
+ subprocess.run(['agent-browser', 'close'], capture_output=True)
110
+
111
+ if result.returncode == 0 and Path(output_path).exists():
112
+ return output_path
113
+
114
+ except Exception:
115
+ subprocess.run(['agent-browser', 'close'], capture_output=True)
116
+
117
+ return None
118
+
119
+
120
+ def analyze_image(
121
+ image_path: str,
122
+ prompt: Optional[str] = None,
123
+ analysis_type: str = "general"
124
+ ) -> VisionResult:
125
+ """
126
+ 使用 AI 分析圖片
127
+
128
+ Args:
129
+ image_path: 圖片路徑
130
+ prompt: 自訂分析提示 (可選)
131
+ analysis_type: 分析類型 (general, ui_ux, accessibility, performance)
132
+
133
+ Returns:
134
+ VisionResult 物件
135
+ """
136
+ result = VisionResult(success=False)
137
+
138
+ # 檢查檔案
139
+ if not Path(image_path).exists():
140
+ result.error = f"圖片不存在: {image_path}"
141
+ return result
142
+
143
+ result.screenshot_path = image_path
144
+
145
+ # 預設提示
146
+ prompts = {
147
+ "general": """分析這張網頁截圖,提供:
148
+ 1. 整體 UI/UX 評估
149
+ 2. 發現的問題列表
150
+ 3. 改善建議
151
+
152
+ 請用正體中文回答,以清單格式呈現。""",
153
+
154
+ "ui_ux": """以 UI/UX 專家的角度分析這張網頁截圖:
155
+
156
+ 評估項目:
157
+ 1. 視覺層次 - 資訊是否清晰分層
158
+ 2. 色彩搭配 - 是否協調、對比是否足夠
159
+ 3. 排版布局 - 是否整齊、間距是否合理
160
+ 4. 互動元素 - 按鈕、連結是否明顯可點擊
161
+ 5. 一致性 - 元件風格是否統一
162
+
163
+ 請列出具體問題和改善建議。用正體中文回答。""",
164
+
165
+ "accessibility": """以無障礙設計 (WCAG 2.1) 的角度分析這張網頁截圖:
166
+
167
+ 檢查項目:
168
+ 1. 色彩對比度是否足夠 (至少 4.5:1)
169
+ 2. 文字大小是否易讀 (至少 16px)
170
+ 3. 互動元素是否有足夠點擊範圍 (至少 44x44px)
171
+ 4. 是否有僅靠顏色傳達資訊的問題
172
+ 5. 表單元素是否有明確標籤
173
+
174
+ 請列出具體問題和改善建議。用正體中文回答。""",
175
+
176
+ "performance": """分析這張網頁截圖的視覺效能指標:
177
+
178
+ 評估項目:
179
+ 1. 內容是否完整載入
180
+ 2. 是否有明顯的版面跳動風險
181
+ 3. 圖片是否適當優化
182
+ 4. 是否有過多視覺元素影響效能
183
+ 5. 首屏內容是否有效利用
184
+
185
+ 請列出具體問題和改善建議。用正體中文回答。"""
186
+ }
187
+
188
+ analysis_prompt = prompt or prompts.get(analysis_type, prompts["general"])
189
+
190
+ try:
191
+ from ..ai_engine import AIEngine
192
+
193
+ ai = AIEngine()
194
+
195
+ # 讀取圖片並轉為 base64
196
+ with open(image_path, 'rb') as f:
197
+ image_data = base64.b64encode(f.read()).decode()
198
+
199
+ # 使用 Gemini 的多模態功能
200
+ from google.genai import types
201
+
202
+ # 建立圖片內容
203
+ image_part = types.Part.from_bytes(
204
+ data=base64.b64decode(image_data),
205
+ mime_type="image/png"
206
+ )
207
+
208
+ # 呼叫 API
209
+ response = ai.client.models.generate_content(
210
+ model=ai.model_name,
211
+ contents=[analysis_prompt, image_part]
212
+ )
213
+
214
+ result.success = True
215
+ result.analysis = response.text
216
+
217
+ # 解析問題和建議
218
+ lines = response.text.split('\n')
219
+ current_section = None
220
+
221
+ for line in lines:
222
+ line = line.strip()
223
+ if not line:
224
+ continue
225
+
226
+ lower_line = line.lower()
227
+ if '問題' in line or 'issue' in lower_line or 'problem' in lower_line:
228
+ current_section = 'issues'
229
+ elif '建議' in line or '改善' in line or 'recommend' in lower_line or 'suggest' in lower_line:
230
+ current_section = 'recommendations'
231
+ elif line.startswith(('-', '*', '•', '1', '2', '3', '4', '5', '6', '7', '8', '9')):
232
+ # 移除列表符號
233
+ clean_line = line.lstrip('-*•0123456789. ')
234
+ if clean_line:
235
+ if current_section == 'issues':
236
+ result.issues.append(clean_line)
237
+ elif current_section == 'recommendations':
238
+ result.recommendations.append(clean_line)
239
+
240
+ except ImportError:
241
+ result.error = "AI 引擎未安裝。請執行: pip install google-genai"
242
+ except Exception as e:
243
+ result.error = str(e)
244
+
245
+ return result
246
+
247
+
248
+ def analyze_url(
249
+ url: str,
250
+ analysis_type: str = "general",
251
+ output_path: Optional[str] = None
252
+ ) -> VisionResult:
253
+ """
254
+ 截圖並分析網頁
255
+
256
+ Args:
257
+ url: 要分析的網址
258
+ analysis_type: 分析類型
259
+ output_path: 截圖儲存路徑 (可選)
260
+
261
+ Returns:
262
+ VisionResult 物件
263
+ """
264
+ result = VisionResult(success=False, url=url)
265
+
266
+ # 檢查 agent-browser
267
+ if not shutil.which('agent-browser'):
268
+ result.error = "agent-browser 未安裝。請執行: npm install -g agent-browser && agent-browser install"
269
+ return result
270
+
271
+ # 截圖
272
+ screenshot = take_screenshot(url, output_path)
273
+ if not screenshot:
274
+ result.error = "截圖失敗"
275
+ return result
276
+
277
+ result.screenshot_path = screenshot
278
+
279
+ # 分析
280
+ analysis_result = analyze_image(screenshot, analysis_type=analysis_type)
281
+
282
+ result.success = analysis_result.success
283
+ result.analysis = analysis_result.analysis
284
+ result.issues = analysis_result.issues
285
+ result.recommendations = analysis_result.recommendations
286
+ result.error = analysis_result.error
287
+
288
+ return result
289
+
290
+
291
+ def compare_screenshots(
292
+ screenshot1: str,
293
+ screenshot2: str,
294
+ description1: str = "版本 A",
295
+ description2: str = "版本 B"
296
+ ) -> VisionResult:
297
+ """
298
+ 比較兩張截圖 (視覺回歸測試)
299
+
300
+ Args:
301
+ screenshot1: 第一張截圖路徑
302
+ screenshot2: 第二張截圖路徑
303
+ description1: 第一張截圖描述
304
+ description2: 第二張截圖描述
305
+
306
+ Returns:
307
+ VisionResult 物件
308
+ """
309
+ result = VisionResult(success=False)
310
+
311
+ if not Path(screenshot1).exists():
312
+ result.error = f"截圖不存在: {screenshot1}"
313
+ return result
314
+
315
+ if not Path(screenshot2).exists():
316
+ result.error = f"截圖不存在: {screenshot2}"
317
+ return result
318
+
319
+ prompt = f"""比較這兩張網頁截圖的差異:
320
+
321
+ 第一張:{description1}
322
+ 第二張:{description2}
323
+
324
+ 請分析:
325
+ 1. 視覺上的主要差異
326
+ 2. 版面布局的變化
327
+ 3. 可能的回歸問題
328
+ 4. 改善的地方
329
+
330
+ 用正體中文回答,以清單格式呈現。"""
331
+
332
+ try:
333
+ from ..ai_engine import AIEngine
334
+ from google.genai import types
335
+
336
+ ai = AIEngine()
337
+
338
+ # 讀取兩張圖片
339
+ with open(screenshot1, 'rb') as f:
340
+ img1_data = f.read()
341
+ with open(screenshot2, 'rb') as f:
342
+ img2_data = f.read()
343
+
344
+ image1_part = types.Part.from_bytes(data=img1_data, mime_type="image/png")
345
+ image2_part = types.Part.from_bytes(data=img2_data, mime_type="image/png")
346
+
347
+ response = ai.client.models.generate_content(
348
+ model=ai.model_name,
349
+ contents=[prompt, image1_part, image2_part]
350
+ )
351
+
352
+ result.success = True
353
+ result.analysis = response.text
354
+
355
+ except Exception as e:
356
+ result.error = str(e)
357
+
358
+ return result
359
+
360
+
361
+ __all__ = [
362
+ 'VisionResult',
363
+ 'check_dependencies',
364
+ 'take_screenshot',
365
+ 'analyze_image',
366
+ 'analyze_url',
367
+ 'compare_screenshots'
368
+ ]
dash_devtools/watch.py ADDED
@@ -0,0 +1,266 @@
1
+ """
2
+ 即時監控模式
3
+
4
+ 監控檔案變更並自動執行驗證:
5
+ - 檔案儲存時自動驗證
6
+ - 即時顯示問題
7
+ - 可選自動修復
8
+ """
9
+
10
+ import time
11
+ import hashlib
12
+ from pathlib import Path
13
+ from datetime import datetime
14
+ from typing import Dict, Set, Optional
15
+ from rich.console import Console
16
+ from rich.live import Live
17
+ from rich.panel import Panel
18
+ from rich.table import Table
19
+ from rich.text import Text
20
+ from rich.layout import Layout
21
+
22
+ console = Console()
23
+
24
+ # 監控的檔案類型
25
+ WATCH_EXTENSIONS = {'.js', '.ts', '.jsx', '.tsx', '.py', '.html', '.css', '.scss', '.vue'}
26
+
27
+ # 忽略的目錄
28
+ IGNORE_DIRS = {
29
+ 'node_modules', '.git', 'dist', 'build', '.next', '__pycache__',
30
+ 'venv', '.venv', '.angular', '.cache', 'coverage'
31
+ }
32
+
33
+
34
+ class FileWatcher:
35
+ """檔案監控器"""
36
+
37
+ def __init__(self, project_path: str, auto_fix: bool = False):
38
+ self.project_path = Path(project_path)
39
+ self.project_name = self.project_path.name
40
+ self.auto_fix = auto_fix
41
+ self.file_hashes: Dict[str, str] = {}
42
+ self.change_log: list = []
43
+ self.error_count = 0
44
+ self.warning_count = 0
45
+ self.fix_count = 0
46
+ self.start_time = datetime.now()
47
+
48
+ def _get_file_hash(self, file_path: Path) -> Optional[str]:
49
+ """計算檔案 hash"""
50
+ try:
51
+ content = file_path.read_bytes()
52
+ return hashlib.md5(content).hexdigest()
53
+ except Exception:
54
+ return None
55
+
56
+ def _scan_files(self) -> Dict[str, str]:
57
+ """掃描所有監控的檔案"""
58
+ files = {}
59
+ for file_path in self.project_path.rglob('*'):
60
+ if file_path.is_dir():
61
+ continue
62
+
63
+ # 跳過忽略的目錄
64
+ if any(ignore in file_path.parts for ignore in IGNORE_DIRS):
65
+ continue
66
+
67
+ # 只監控特定類型
68
+ if file_path.suffix.lower() not in WATCH_EXTENSIONS:
69
+ continue
70
+
71
+ file_hash = self._get_file_hash(file_path)
72
+ if file_hash:
73
+ rel_path = str(file_path.relative_to(self.project_path))
74
+ files[rel_path] = file_hash
75
+
76
+ return files
77
+
78
+ def _check_changes(self) -> Set[str]:
79
+ """檢查檔案變更"""
80
+ current_files = self._scan_files()
81
+ changed_files = set()
82
+
83
+ # 檢查修改和新增的檔案
84
+ for path, hash_value in current_files.items():
85
+ if path not in self.file_hashes:
86
+ changed_files.add(path)
87
+ self._log_change(path, 'new')
88
+ elif self.file_hashes[path] != hash_value:
89
+ changed_files.add(path)
90
+ self._log_change(path, 'modified')
91
+
92
+ # 檢查刪除的檔案
93
+ for path in self.file_hashes:
94
+ if path not in current_files:
95
+ self._log_change(path, 'deleted')
96
+
97
+ self.file_hashes = current_files
98
+ return changed_files
99
+
100
+ def _log_change(self, path: str, change_type: str):
101
+ """記錄變更"""
102
+ timestamp = datetime.now().strftime('%H:%M:%S')
103
+ icons = {'new': '+', 'modified': '*', 'deleted': '-'}
104
+ colors = {'new': 'green', 'modified': 'yellow', 'deleted': 'red'}
105
+
106
+ self.change_log.append({
107
+ 'time': timestamp,
108
+ 'path': path,
109
+ 'type': change_type,
110
+ 'icon': icons[change_type],
111
+ 'color': colors[change_type]
112
+ })
113
+
114
+ # 只保留最近 20 筆
115
+ if len(self.change_log) > 20:
116
+ self.change_log = self.change_log[-20:]
117
+
118
+ def _validate_file(self, file_path: str) -> dict:
119
+ """驗證單一檔案"""
120
+ full_path = self.project_path / file_path
121
+ issues = []
122
+ warnings = []
123
+ fixes = []
124
+
125
+ try:
126
+ content = full_path.read_text(encoding='utf-8')
127
+ lines = content.splitlines()
128
+
129
+ # 檢查檔案行數
130
+ if len(lines) > 500:
131
+ warnings.append(f'檔案過長 ({len(lines)} 行)')
132
+
133
+ # 檢查 console.log (JS/TS)
134
+ if full_path.suffix in ['.js', '.ts', '.jsx', '.tsx']:
135
+ for i, line in enumerate(lines, 1):
136
+ if 'console.log' in line and '//' not in line.split('console.log')[0]:
137
+ warnings.append(f'第 {i} 行: console.log 需移除')
138
+
139
+ # 檢查 TODO/FIXME
140
+ for i, line in enumerate(lines, 1):
141
+ if 'TODO' in line or 'FIXME' in line:
142
+ warnings.append(f'第 {i} 行: 有待處理項目')
143
+
144
+ # 檢查 Emoji(禁止在程式碼中使用)
145
+ import re
146
+ emoji_pattern = re.compile(
147
+ "["
148
+ "\U0001F600-\U0001F64F"
149
+ "\U0001F300-\U0001F5FF"
150
+ "\U0001F680-\U0001F6FF"
151
+ "\U0001F1E0-\U0001F1FF"
152
+ "]+",
153
+ flags=re.UNICODE
154
+ )
155
+ for i, line in enumerate(lines, 1):
156
+ if emoji_pattern.search(line):
157
+ issues.append(f'第 {i} 行: 程式碼中禁止使用 Emoji')
158
+
159
+ except Exception as e:
160
+ issues.append(f'讀取錯誤: {e}')
161
+
162
+ return {
163
+ 'path': file_path,
164
+ 'issues': issues,
165
+ 'warnings': warnings,
166
+ 'fixes': fixes
167
+ }
168
+
169
+ def generate_display(self) -> Panel:
170
+ """產生顯示畫面"""
171
+ layout = Layout()
172
+
173
+ # 標題
174
+ elapsed = datetime.now() - self.start_time
175
+ elapsed_str = f"{int(elapsed.total_seconds() // 60)}:{int(elapsed.total_seconds() % 60):02d}"
176
+
177
+ header = Text()
178
+ header.append(f" {self.project_name}", style="bold cyan")
179
+ header.append(f" [監控中]", style="bold green")
180
+ header.append(f" {elapsed_str}", style="dim")
181
+ if self.auto_fix:
182
+ header.append(" [自動修復]", style="yellow")
183
+
184
+ # 狀態列
185
+ status = Text()
186
+ status.append(f"\n 檔案: {len(self.file_hashes)}", style="dim")
187
+ status.append(f" | 錯誤: ", style="dim")
188
+ status.append(f"{self.error_count}", style="red" if self.error_count else "green")
189
+ status.append(f" | 警告: ", style="dim")
190
+ status.append(f"{self.warning_count}", style="yellow" if self.warning_count else "green")
191
+ if self.fix_count:
192
+ status.append(f" | 已修復: ", style="dim")
193
+ status.append(f"{self.fix_count}", style="cyan")
194
+
195
+ # 變更日誌
196
+ log_text = Text()
197
+ log_text.append("\n 最近變更:\n", style="dim")
198
+
199
+ if self.change_log:
200
+ for entry in reversed(self.change_log[-8:]):
201
+ log_text.append(f" {entry['time']} ", style="dim")
202
+ log_text.append(f"[{entry['icon']}] ", style=entry['color'])
203
+ log_text.append(f"{entry['path']}\n", style="white")
204
+ else:
205
+ log_text.append(" 等待檔案變更...\n", style="dim")
206
+
207
+ # 說明
208
+ footer = Text()
209
+ footer.append("\n 按 Ctrl+C 停止監控", style="dim")
210
+
211
+ content = Text()
212
+ content.append_text(header)
213
+ content.append_text(status)
214
+ content.append_text(log_text)
215
+ content.append_text(footer)
216
+
217
+ return Panel(
218
+ content,
219
+ title="[bold]Dash Watch[/bold]",
220
+ border_style="cyan",
221
+ padding=(1, 2)
222
+ )
223
+
224
+ def run(self, interval: float = 1.0):
225
+ """執行監控"""
226
+ # 初始掃描
227
+ self.file_hashes = self._scan_files()
228
+
229
+ console.print(f"\n[cyan]開始監控 {self.project_name}...[/cyan]")
230
+ console.print(f"[dim]監控 {len(self.file_hashes)} 個檔案[/dim]\n")
231
+
232
+ try:
233
+ with Live(self.generate_display(), refresh_per_second=2, console=console) as live:
234
+ while True:
235
+ changed_files = self._check_changes()
236
+
237
+ # 驗證變更的檔案
238
+ for file_path in changed_files:
239
+ result = self._validate_file(file_path)
240
+
241
+ if result['issues']:
242
+ self.error_count += len(result['issues'])
243
+ if result['warnings']:
244
+ self.warning_count += len(result['warnings'])
245
+
246
+ # 更新顯示
247
+ live.update(self.generate_display())
248
+
249
+ time.sleep(interval)
250
+
251
+ except KeyboardInterrupt:
252
+ console.print("\n[yellow]監控已停止[/yellow]")
253
+
254
+ return {
255
+ 'duration': str(datetime.now() - self.start_time),
256
+ 'files_watched': len(self.file_hashes),
257
+ 'changes': len(self.change_log),
258
+ 'errors': self.error_count,
259
+ 'warnings': self.warning_count
260
+ }
261
+
262
+
263
+ def run_watch(project_path: str, auto_fix: bool = False, interval: float = 1.0) -> dict:
264
+ """執行監控模式"""
265
+ watcher = FileWatcher(project_path, auto_fix=auto_fix)
266
+ return watcher.run(interval=interval)