落地日期:2026-07-25 Server:
http://192.168.50.171:9000(SonarQube 26.7.0.124771) 涵蓋專案:guidant-ai-backend/guidant-ai-feNotion case:CM-891「SonarQube Quality Gate 設 new-code 策略」
Sonar CODE_SMELL 存量 BE 789 / FE 681 條,其中含既有裁決禁區(S3776 認知複雜度 ×241、 S3516 BLOCKER ×8)——靠人力清不完,也不該全清。若 CI 接入時 Quality Gate 沿用 overall(存量)條件,CI 第一天就會被千條存量打紅,團隊會習慣「CI 紅是正常的」, gate 形同虛設。
正確做法:gate 只看 new code(新程式碼零新增 issue 才過),存量當 baseline 不擋版, 日後分批派 runner 清機械性桶(註解碼 / 未用參數等),禁區維持不動。
Guidant-NewCode自訂 gate,兩專案皆已指派(api/qualitygates/select)。條件只有兩條、全在 new code:
| # | Metric | 條件 | 意義 |
|---|---|---|---|
| 1 | new_violations |
> 0 即 FAIL | 新程式碼上任何新 issue(BUG / VULNERABILITY / CODE_SMELL,含所有 severity)都擋版——零新增量 |
| 2 | new_security_hotspots_reviewed |
< 100% 即 FAIL | 新程式碼引入的 Security Hotspot 必須全部完成人工審查(標 SAFE 或 FIXED)才放行 |
new_coverage < 80% FAIL):
new_duplicated_lines_density > 3%): 本專案大量 DDD 樣板碼(repo / mapper / DTO)天然重複率高,貿然設 3% 會產生 假陽性擋版;先以 new_violations 為主,日後有需要再評估加回。實作備註:SonarQube 26.x
api/qualitygates/create建立 gate 時會自動帶入 CaYC(Clean as You Code)四條預設條件(new_violations / new_coverage / new_duplicated_lines_density / new_security_hotspots_reviewed), 因此實際操作是建立後用api/qualitygates/delete_condition移除 coverage 與 duplication 兩條,保留另外兩條。移除後 gate 會被標caycStatus: non-compliant, 這只是 Sonar 對「未含 coverage 條件」的標註,不影響 gate 運作。
PREVIOUS_VERSIONinherited: true, type: PREVIOUS_VERSION)。api/new_code_periods/set 對兩專案顯式確認為 PREVIOUS_VERSION (與繼承值相同,Sonar 回報仍為 inherited,行為一致)。main(含本機 working tree)上執行,沒有 branch/PR 分析(Community 版也不支援 branch analysis)——若設 REFERENCE_BRANCH=main,main 自己跟自己比,new code 永遠為空,gate 形同虛設。PREVIOUS_VERSION 以 sonar.projectVersion 切分:版本未變時,new code 累積 「自上一版以來」的新增碼,正好對齊本專案「一版一 release」的節奏 (version-bump skill 進版 → 下一次掃描重置 baseline)。sonar.projectVersion(Sonar 記為 not provided), new code period 起點落在該專案第一次被掃描的分析(BE 2026-07-25T03:27Z / FE 2026-07-25T03:31Z)。建議 CI 接入時在 scanner 參數帶上 -Dsonar.projectVersion=$(pyproject 版號)(FE 用 package.json version), 讓 new code 隨產品版本正確重置。兩 repo 各重跑一次掃描(BE scripts/sonar_scan.sh、FE 根目錄 sonar-scanner, 指令見 docs/security-reports/README.md)後查 api/qualitygates/project_status:
| 專案 | Gate | 狀態 | new_violations 實際值 |
|---|---|---|---|
| guidant-ai-backend | Guidant-NewCode | OK(PASSED) | 0 |
| guidant-ai-fe | Guidant-NewCode | OK(PASSED) | 0 |
存量 smell(BE 789 / FE 681)完全不影響 gate 判定,符合「存量不擋版」目標。
兩種擇一,推薦方案 A:
方案 A — scanner 內建等待(推薦,最簡單)
sonar-scanner \
-Dsonar.projectKey=guidant-ai-backend \
-Dsonar.projectVersion="${PROJECT_VERSION}" \
-Dsonar.qualitygate.wait=true \
-Dsonar.qualitygate.timeout=300 \
... # 其餘參數同 scripts/sonar_scan.shqualitygate.wait=true 會讓 scanner 阻塞等 server 端分析完成,gate FAIL 時 scanner 以非零 exit code 結束 → CI job 直接紅。
方案 B — 掃後輪詢 API(scanner 版本不支援 wait 時的 fallback)
report-task.txt 的 ceTaskId)。api/ce/task?id=<ceTaskId> 直到 status=SUCCESS。api/qualitygates/project_status?projectKey=<key>, projectStatus.status != "OK" 則 exit 1。注意事項:
scripts/sonar_scan.sh 未加 qualitygate.wait,維持「掃完即走」, gate 判定留給 CI;本機要看結果可查 dashboard 或 project_status API。補 new_coverage(建議門檻 80%,對齊 Sonar way)前,須先完成:
pytest --cov --cov-report=xml 產出 coverage.xml,scanner 加 -Dsonar.python.coverage.reportPaths=coverage.xml。--coverage 產 lcov),scanner 加 -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info。Guidant-NewCode,若只有一邊就緒,需拆成兩個 gate(如 Guidant-NewCode-BE / -FE)或等兩邊都就緒再統一加,屆時再裁決。| 動作 | API | 結果 |
|---|---|---|
| 建 gate | POST api/qualitygates/create name=Guidant-NewCode |
建立成功(自動含 CaYC 四條件) |
| 刪 coverage 條件 | POST api/qualitygates/delete_condition |
204 |
| 刪 duplication 條件 | POST api/qualitygates/delete_condition |
204 |
| BE 指派 gate | POST api/qualitygates/select projectKey=guidant-ai-backend |
204 |
| FE 指派 gate | POST api/qualitygates/select projectKey=guidant-ai-fe |
204 |
| new code period | POST api/new_code_periods/set type=PREVIOUS_VERSION(兩專案) |
200 |
| 驗證 | GET api/qualitygates/project_status(兩專案) |
皆 OK |