V10.502 修正 AiderHeal 自動修復診斷
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.502 修正 AiderHeal 自動修復診斷鏈:先做 ADR-020 檔案白名單再打 110 preflight,`tests/` finding 會明確略過而不誤報 repo preflight;Code Review 完成通知會把全數不在白名單的 finding 標成需人工處理,不再宣稱已觸發 AiderHeal;白名單放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶 stderr/stdout 細節,健康檢查同時接受 `/health` 回 `ok` 與 `healthy`。
|
||||
- V10.501 新增市場情報 MCP Fetch Candidate Queue Writer Post-Closeout Inventory Review 安全預覽 gate:只審核 closeout review 後由操作員 shell 完成的 live inventory read-only 摘要,確認 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmation;API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler。
|
||||
- V10.500 新增市場情報 MCP Fetch Candidate Queue Writer Run Closeout Review 安全預覽 gate:只審核 receipt review 通過後的 operator closeout 摘要,確認 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmation;API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler。
|
||||
- V10.499 新增市場情報 MCP Fetch Candidate Queue Writer Run Receipt Review 安全預覽 gate:只審核操作員 shell writer run 後貼回的 receipt 摘要,確認 readiness linkage、run package id、候選/dedupe keys、writer output、post-write smoke、backup path 與 operator confirmation;API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler。
|
||||
|
||||
@@ -350,7 +350,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.501"
|
||||
SYSTEM_VERSION = "V10.502"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
- 2026-05-31 起,`V10.499` 新增市場情報 MCP Fetch Candidate Queue Writer Run Receipt Review gate:在 run readiness 通過後只審核操作員 shell writer run 的 receipt 摘要,要求 readiness linkage、run package id、候選/dedupe keys、writer output、post-write smoke、backup path 與 operator confirmation 對齊;仍不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler,只放行到 closeout review。
|
||||
- 2026-05-31 起,`V10.500` 新增市場情報 MCP Fetch Candidate Queue Writer Run Closeout Review gate:在 receipt review 通過後只審核 operator closeout 摘要,要求 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmation 對齊;仍不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler,只放行到 read-only post-closeout inventory review。
|
||||
- 2026-05-31 起,`V10.501` 新增市場情報 MCP Fetch Candidate Queue Writer Post-Closeout Inventory Review gate:在 closeout review 通過後只審核 operator live inventory read-only 摘要,要求 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler,只放行到 candidate queue review handoff。
|
||||
- 2026-05-31 起,`V10.502` 修正 AiderHeal 自動修復診斷鏈:先檢查 ADR-020 檔案白名單再執行 110 preflight,`tests/` finding 會明確略過而不誤報 repo preflight;Code Review 完成通知會把全數不在白名單的 finding 標成需人工處理,不再宣稱已觸發 AiderHeal;白名單放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶遠端錯誤細節,健康檢查接受 `/health` 回 `healthy`。
|
||||
|
||||
## 3. 12 Agent 決策信封整合
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
## 📅 詳細更新日誌 (考古存檔)
|
||||
|
||||
### 2026-05-24:PChome 近門檻身份回收第二輪
|
||||
- **V10.502 AiderHeal 自動修復診斷鏈修正**: `execute_code_fix()` 改為先檢查 ADR-020 檔案白名單再執行 110 preflight,避免 `tests/...` 這類不得自動修的 finding 被誤報成 `/home/wooo/ewoooc` preflight 失敗;Code Review 完成通知會把全數不在白名單的 finding 標成需人工處理,不再宣稱已觸發 AiderHeal;白名單同步放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶 stderr/stdout 細節,健康檢查接受正式 `/health` 回傳的 `healthy`。
|
||||
- **V10.501 市場情報 MCP Fetch Candidate Queue Writer Post-Closeout Inventory Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_post_closeout_inventory_review` 與 UI preview,只審核 closeout review 通過後的 operator live inventory read-only 摘要;要求 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmation 對齊,且 API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler,只放行到 candidate queue review handoff。
|
||||
- **V10.500 市場情報 MCP Fetch Candidate Queue Writer Run Closeout Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_closeout_review` 與 UI preview,只審核 receipt review 通過後的 operator closeout 摘要;要求 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmation 對齊,且 API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler,只放行到 read-only post-closeout inventory review。
|
||||
- **V10.499 市場情報 MCP Fetch Candidate Queue Writer Run Receipt Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_receipt_review` 與 UI preview,只審核操作員 shell writer run 後貼回的 receipt 摘要;要求 readiness linkage、run package id、候選/dedupe keys、writer output、post-write smoke、backup path 與 operator confirmation 對齊,且 API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler,只放行到 closeout review。
|
||||
|
||||
@@ -182,8 +182,8 @@ ssh ollama@192.168.0.188 'docker logs momo-pro-system --since 10m 2>&1 | grep -E
|
||||
|
||||
| L | 機制 | 觸發點 |
|
||||
|---|------|-------|
|
||||
| L0 | preflight 路徑檢查 | `aider_heal_executor.py:execute_code_fix` 開頭 |
|
||||
| L1 | 檔案白名單 `^(services\|routes\|database)/[a-zA-Z0-9_]+\.py$` | `ALLOWED_FILE_PATTERN` |
|
||||
| L0 | preflight 路徑檢查 | `aider_heal_executor.py:execute_code_fix` 白名單通過後 |
|
||||
| L1 | 檔案白名單 `^(services\|routes\|database)/(?:[a-zA-Z0-9_]+/)*[a-zA-Z0-9_]+\.py$`,允許子目錄但不允許 `tests/` | `ALLOWED_FILE_PATTERN` |
|
||||
| L2 | diff > 50 行拒絕 push | `AIDER_MAX_DIFF_LINES` |
|
||||
| L3 | 每小時最多 5 次 CODE_FIX | `_enforce_rate_limit` |
|
||||
| L4 | health check 失敗自動 git revert | `_revert_last_commit` |
|
||||
|
||||
@@ -22,10 +22,10 @@ import re
|
||||
import time
|
||||
import threading
|
||||
import shlex
|
||||
import html
|
||||
import requests
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pathlib import Path
|
||||
|
||||
from services.logger_manager import SystemLogger
|
||||
from utils.ssh_helper import run_ssh_command
|
||||
@@ -79,8 +79,10 @@ except Exception:
|
||||
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "")
|
||||
|
||||
# 允許 Aider 修改的路徑(正規表示式)
|
||||
# ADR-020 白名單允許 services/routes/database 底下的 Python 模組,含子目錄;
|
||||
# tests/docs/config 等檔案仍需人工處理,避免 Aider 以「修測試」掩蓋產品問題。
|
||||
ALLOWED_FILE_PATTERN = re.compile(
|
||||
r"^(services|routes|database)/[a-zA-Z0-9_]+\.py$"
|
||||
r"^(services|routes|database)/(?:[a-zA-Z0-9_]+/)*[a-zA-Z0-9_]+\.py$"
|
||||
)
|
||||
|
||||
# ── 速率控制(執行緒安全) ────────────────────────────────────────────────────
|
||||
@@ -159,7 +161,7 @@ def _wait_for_health(
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
while time.monotonic() < deadline:
|
||||
data = _http_get_json(url)
|
||||
if data and data.get("status") == "ok":
|
||||
if data and str(data.get("status", "")).lower() in {"ok", "healthy"}:
|
||||
return True
|
||||
time.sleep(interval_seconds)
|
||||
return False
|
||||
@@ -225,24 +227,57 @@ def execute_code_fix(
|
||||
"""
|
||||
ts = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
|
||||
ctx: Dict[str, Any] = context or {}
|
||||
repo = Path(REPO_PATH_110).expanduser()
|
||||
|
||||
# L1:檔案白名單。必須先於 110 preflight 執行,否則 tests/docs 等
|
||||
# 本來就不能自動修的 finding 會被誤報成「110 repo 不存在」。
|
||||
if not ALLOWED_FILE_PATTERN.match(target_file):
|
||||
reason = f"[AiderHeal] 檔案不在 ADR-020 自動修復白名單:{target_file}"
|
||||
logger.warning("event=heal_reject reason=path_not_allowed file=%s", target_file)
|
||||
_notify_telegram(
|
||||
f"⚠️ <b>AiderHeal 已略過自動修復</b>\n"
|
||||
f"├ 檔案:<code>{html.escape(target_file[:200])}</code>\n"
|
||||
f"├ 原因:不在 ADR-020 自動修復白名單(僅允許 services/routes/database 內 Python 檔案)\n"
|
||||
f"└ 動作:請人工確認 finding,或調整白名單後重跑 Code Review"
|
||||
)
|
||||
return {
|
||||
"success": False,
|
||||
"action": "CODE_FIX",
|
||||
"message": reason,
|
||||
"commit_sha": None,
|
||||
"reverted": False,
|
||||
}
|
||||
|
||||
# L0:preflight — 確認 110 上的 repo 路徑真的存在且是 git repo
|
||||
# 沒有這個檢查時,後續 cd $REPO_PATH 失敗會被 shell `|| true` 吞掉,
|
||||
# 導致整條 pipeline 走完卻 0 次 push,靜默 100% no-op(2026-05-03 實測)
|
||||
rc_pre, _, _ = _ssh_exec(
|
||||
f"test -d {shlex.quote(REPO_PATH_110)}/.git", timeout=10
|
||||
preflight_cmd = (
|
||||
f"test -d {shlex.quote(REPO_PATH_110)} && "
|
||||
f"test -d {shlex.quote(REPO_PATH_110)}/.git && "
|
||||
f"cd {shlex.quote(REPO_PATH_110)} && "
|
||||
f"git rev-parse --is-inside-work-tree 2>&1"
|
||||
)
|
||||
rc_pre, out_pre, err_pre = _ssh_exec(preflight_cmd, timeout=10)
|
||||
if rc_pre != 0:
|
||||
preflight_detail = (err_pre or out_pre or "").strip()
|
||||
if not preflight_detail:
|
||||
preflight_detail = "SSH 逾時、repo 路徑不存在,或目標不是 git repo"
|
||||
msg = (
|
||||
f"[AiderHeal] preflight 失敗:110 主機上 {REPO_PATH_110} 不存在或不是 git repo。"
|
||||
f"請檢查 AIDER_REPO_PATH env / 在 110 上 git clone repo(見 ADR-020 SOP)"
|
||||
f"請檢查 AIDER_REPO_PATH env / 在 110 上 git clone repo(見 ADR-020 SOP)。"
|
||||
f"detail={preflight_detail[:300]}"
|
||||
)
|
||||
logger.error(
|
||||
"event=preflight_failed path=%s rc=%s stderr=%s stdout=%s",
|
||||
REPO_PATH_110,
|
||||
rc_pre,
|
||||
err_pre,
|
||||
out_pre,
|
||||
)
|
||||
logger.error("event=preflight_failed path=%s", REPO_PATH_110)
|
||||
_notify_telegram(
|
||||
f"🚨 <b>AiderHeal preflight 失敗</b>\n"
|
||||
f"├ 路徑:<code>{REPO_PATH_110}</code>\n"
|
||||
f"├ 主機:<code>{HEAL_SSH_HOST}</code>\n"
|
||||
f"├ 細節:<code>{html.escape(preflight_detail[:240])}</code>\n"
|
||||
f"└ 動作:請依 ADR-020 SOP 在 110 上 clone repo 並設好 push 權限"
|
||||
)
|
||||
return {
|
||||
@@ -253,18 +288,6 @@ def execute_code_fix(
|
||||
"reverted": False,
|
||||
}
|
||||
|
||||
# L1:檔案白名單
|
||||
if not ALLOWED_FILE_PATTERN.match(target_file):
|
||||
reason = f"[AiderHeal] 檔案不在白名單:{target_file}"
|
||||
logger.warning("event=heal_reject reason=%s file=%s", reason, target_file)
|
||||
return {
|
||||
"success": False,
|
||||
"action": "CODE_FIX",
|
||||
"message": reason,
|
||||
"commit_sha": None,
|
||||
"reverted": False,
|
||||
}
|
||||
|
||||
# L3:速率限制
|
||||
if not _enforce_rate_limit():
|
||||
reason = f"[AiderHeal] 每小時上限 {MAX_HOURLY_FIX} 次,跳過"
|
||||
|
||||
@@ -97,12 +97,20 @@ CODE_REVIEW_HERMES_LLM_SCAN_ENABLED = (
|
||||
INTERNAL_TOKEN = os.getenv("INTERNAL_WEBHOOK_TOKEN", "")
|
||||
AUTO_FIX_ENABLED = os.getenv("CODE_REVIEW_AUTO_FIX_ENABLED", "true").lower() == "true"
|
||||
ALLOW_INSECURE_WEBHOOK = os.getenv("MOMO_ALLOW_INSECURE_INTERNAL_WEBHOOK_FOR_DEV", "").lower() == "true"
|
||||
AIDER_AUTO_FIX_FILE_PATTERN = re.compile(
|
||||
r"^(services|routes|database)/(?:[a-zA-Z0-9_]+/)*[a-zA-Z0-9_]+\.py$"
|
||||
)
|
||||
|
||||
# Phase 7 Frontier 升級 feature flag — 預設 OFF;啟用後只作 Ollama 失敗後的雲端備援。
|
||||
CODE_REVIEW_USE_CLAUDE = os.getenv("CODE_REVIEW_USE_CLAUDE", "false").lower() == "true"
|
||||
CLAUDE_REVIEW_MODEL = os.getenv("CLAUDE_MODEL", "claude-opus-4-7")
|
||||
|
||||
|
||||
def _aider_allowed_fix_files(files: List[str]) -> List[str]:
|
||||
"""回傳 ADR-020 允許交給 AiderHeal 自動修復的檔案。"""
|
||||
return [f for f in files if AIDER_AUTO_FIX_FILE_PATTERN.match(f or "")]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Pipeline Class
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -821,6 +829,7 @@ class CodeReviewPipeline:
|
||||
def _nemotron_dispatch(self, ea: Dict, findings: List[Dict]) -> Dict:
|
||||
auto_fix = ea.get("auto_fix", False)
|
||||
fix_files = ea.get("fix_files", [])
|
||||
allowed_fix_files = _aider_allowed_fix_files(fix_files)
|
||||
priority_map = {"critical": 1, "high": 2, "medium": 3, "low": 4}
|
||||
priority_num = priority_map.get(ea.get("priority", "low"), 4)
|
||||
|
||||
@@ -841,13 +850,20 @@ class CodeReviewPipeline:
|
||||
('code_review_fix', :desc, :status, :priority, :meta, NOW())
|
||||
"""), {
|
||||
"desc": desc[:500],
|
||||
"status": "auto_pending" if auto_fix else "auto_disabled",
|
||||
"status": (
|
||||
"auto_pending"
|
||||
if auto_fix and fpath in allowed_fix_files
|
||||
else "auto_skipped_whitelist"
|
||||
if auto_fix
|
||||
else "auto_disabled"
|
||||
),
|
||||
"priority": priority_num,
|
||||
"meta": json.dumps({
|
||||
"pipeline_id": self.pipeline_id,
|
||||
"commit_sha": self.commit_sha,
|
||||
"file": fpath,
|
||||
"auto_fix": auto_fix,
|
||||
"aider_auto_fix_allowed": fpath in allowed_fix_files,
|
||||
"ea_priority": ea.get("priority"),
|
||||
"findings": related,
|
||||
}, ensure_ascii=False),
|
||||
@@ -861,12 +877,20 @@ class CodeReviewPipeline:
|
||||
session.close()
|
||||
|
||||
# 觸發 AiderHeal(非阻塞)
|
||||
if auto_fix and fix_files:
|
||||
if auto_fix and allowed_fix_files:
|
||||
self.state["auto_fix_triggered"] = True
|
||||
self._sync_global()
|
||||
self._trigger_aider_heal(findings, fix_files)
|
||||
self._trigger_aider_heal(findings, allowed_fix_files)
|
||||
elif auto_fix and fix_files:
|
||||
self.state["auto_fix_skipped_whitelist"] = True
|
||||
self._sync_global()
|
||||
logger.info("[CodeReview] AiderHeal skipped: no files matched ADR-020 whitelist files=%s", fix_files)
|
||||
|
||||
return {"actions": actions_created, "auto_fix": auto_fix}
|
||||
return {
|
||||
"actions": actions_created,
|
||||
"auto_fix": auto_fix,
|
||||
"aider_fix_files": allowed_fix_files,
|
||||
}
|
||||
|
||||
def _trigger_aider_heal(self, findings: List[Dict], fix_files: List[str]):
|
||||
"""非阻塞觸發 AiderHeal 自動修復"""
|
||||
@@ -965,6 +989,8 @@ class CodeReviewPipeline:
|
||||
sev = self.state["severity_summary"]
|
||||
priority = ea.get("priority", "medium")
|
||||
auto_fix = ea.get("auto_fix", False)
|
||||
fix_files = ea.get("fix_files", [])
|
||||
allowed_fix_files = _aider_allowed_fix_files(fix_files)
|
||||
|
||||
icon = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🟢"}.get(priority, "🟡")
|
||||
top_issues = [f for f in findings if f.get("severity") in ("CRITICAL", "HIGH")][:3]
|
||||
@@ -986,8 +1012,10 @@ class CodeReviewPipeline:
|
||||
if openclaw_report:
|
||||
msg += f"\n{openclaw_report[:400]}\n"
|
||||
|
||||
if auto_fix:
|
||||
if auto_fix and allowed_fix_files:
|
||||
fix_status = "🔧 已觸發自動修復(AiderHeal)"
|
||||
elif auto_fix and fix_files:
|
||||
fix_status = "⚠️ 不在自動修復白名單,需人工處理"
|
||||
elif sev['critical'] + sev['high'] + sev['medium'] + sev['low'] == 0:
|
||||
fix_status = "✅ 無需修復動作"
|
||||
else:
|
||||
|
||||
45
tests/test_aider_heal_executor.py
Normal file
45
tests/test_aider_heal_executor.py
Normal file
@@ -0,0 +1,45 @@
|
||||
def test_aider_heal_allowed_file_pattern_accepts_nested_service_modules():
|
||||
from services import aider_heal_executor as svc
|
||||
|
||||
assert svc.ALLOWED_FILE_PATTERN.match("services/market_intel/phase.py")
|
||||
assert svc.ALLOWED_FILE_PATTERN.match("routes/market_intel_mcp_run_routes.py")
|
||||
assert svc.ALLOWED_FILE_PATTERN.match("database/ai_models.py")
|
||||
assert not svc.ALLOWED_FILE_PATTERN.match("tests/test_market_intel_skeleton.py")
|
||||
assert not svc.ALLOWED_FILE_PATTERN.match("config.py")
|
||||
|
||||
|
||||
def test_aider_heal_rejects_disallowed_file_before_ssh(monkeypatch):
|
||||
from services import aider_heal_executor as svc
|
||||
|
||||
messages = []
|
||||
|
||||
def fail_if_called(*_args, **_kwargs):
|
||||
raise AssertionError("SSH preflight should not run for disallowed files")
|
||||
|
||||
monkeypatch.setattr(svc, "_ssh_exec", fail_if_called)
|
||||
monkeypatch.setattr(svc, "_notify_telegram", messages.append)
|
||||
|
||||
result = svc.execute_code_fix(
|
||||
error_type="code_review_security",
|
||||
error_message="疑似硬編碼敏感字串",
|
||||
target_file="tests/test_market_intel_skeleton.py",
|
||||
)
|
||||
|
||||
assert result["success"] is False
|
||||
assert result["commit_sha"] is None
|
||||
assert result["reverted"] is False
|
||||
assert "不在 ADR-020 自動修復白名單" in result["message"]
|
||||
assert messages
|
||||
assert "已略過自動修復" in messages[0]
|
||||
|
||||
|
||||
def test_aider_heal_health_accepts_current_healthy_status(monkeypatch):
|
||||
from services import aider_heal_executor as svc
|
||||
|
||||
monkeypatch.setattr(svc, "_http_get_json", lambda _url: {"status": "healthy"})
|
||||
|
||||
assert svc._wait_for_health(
|
||||
"https://mo.wooo.work/health",
|
||||
timeout_seconds=1,
|
||||
interval_seconds=0,
|
||||
) is True
|
||||
@@ -130,3 +130,43 @@ def test_guard_upgrades_llm_human_review_true_to_false(monkeypatch):
|
||||
|
||||
assert guarded["auto_fix"] is True
|
||||
assert guarded["human_review_needed"] is False
|
||||
|
||||
|
||||
def test_complete_notification_marks_non_whitelisted_aider_files(monkeypatch):
|
||||
"""tests/docs/config 等 finding 不應在完成通知中宣稱 AiderHeal 會自動修復。"""
|
||||
import services.telegram_templates as telegram_templates
|
||||
import services.code_review_pipeline_service as module
|
||||
|
||||
messages = []
|
||||
monkeypatch.setattr(telegram_templates, "_send_telegram_raw", messages.append)
|
||||
|
||||
pipeline = module.CodeReviewPipeline(
|
||||
"abcdef123456",
|
||||
["tests/test_market_intel_skeleton.py"],
|
||||
)
|
||||
pipeline.state["severity_summary"] = {
|
||||
"critical": 0,
|
||||
"high": 1,
|
||||
"medium": 0,
|
||||
"low": 0,
|
||||
}
|
||||
|
||||
pipeline._notify_complete(
|
||||
[
|
||||
{
|
||||
"severity": "HIGH",
|
||||
"description": "疑似硬編碼敏感字串",
|
||||
"file": "tests/test_market_intel_skeleton.py",
|
||||
}
|
||||
],
|
||||
"",
|
||||
{
|
||||
"priority": "high",
|
||||
"auto_fix": True,
|
||||
"fix_files": ["tests/test_market_intel_skeleton.py"],
|
||||
},
|
||||
)
|
||||
|
||||
assert messages
|
||||
assert "不在自動修復白名單" in messages[0]
|
||||
assert "已觸發自動修復" not in messages[0]
|
||||
|
||||
Reference in New Issue
Block a user