Day 24,我們把 Agent 的結果整理成可追蹤的 Findings Schema。
每一項 Finding 不只保留標題與嚴重度,也包含:
穩定 ID 與 Fingerprint
Verdict 與處理狀態
Source Location
Verification Record
Missing Evidence
Remediation
Status History
Run、Rule、Tool 與 Model Version
不過,Schema 只解決了資料 Contract。
開發者仍然需要自己:
啟動 Recon
執行 Hunter 與 Challenger
等待 Emulator Test
尋找輸出檔案
判斷哪些 Finding 會阻擋提交
確認指令到底成功還是失敗
如果每次檢查都要記住五、六條指令,Production Readiness Review 很快就會被跳過。
今天要把目前的流程包成 Vibe Guard CLI。
開發者只需要在 Terminal 執行:
npm run vibe-guard -- scan .
CLI 會負責:
解析 Target 與 Policy
啟動既有驗證流程
讀取 Challenger Artifact
轉換成 Day 24 Findings Schema
驗證 Schema 與語意規則
套用本機 Gate Policy
輸出 Terminal Summary
保存 JSON 與 Markdown Artifact
使用 Exit Code 回報結果
今天的重點不是替既有 JavaScript 多包一層漂亮文字。
真正的目標是建立一個穩定的命令列 Contract,讓人類、Shell 與未來的 CI 都能用相同方式執行檢查。
目前的 Vibe Guard 已經有不同責任:
Recon
建立系統事實與 Unknown。
Hunter
提出待驗證的 Candidate。
Challenger
使用獨立來源與測試嘗試推翻 Candidate。
Findings Schema
保存通過驗證的結論、狀態與證據。
CLI 不應再次閱讀所有資料,然後自行決定:
這看起來很危險,我把它改成 Critical。
CLI 的責任應該是 Deterministic Orchestration:
接收參數
執行既定步驟
驗證 Artifact
套用明確政策
輸出結果
回傳 Exit Code
因此今天沒有再加入一個 CLI Agent。
命令列程式不能改寫 Challenger 的 Verdict,也不能替缺少證據的 Finding 猜 Severity。
正確的資料流是:
Agent Workflow
→ Validated Findings Report
→ Policy Evaluation
→ Terminal / JSON / Markdown
→ Exit Code
其中只有 Agent Workflow 負責安全推理。
Policy Evaluation 只回答:
依照這次執行參數,目前結果是否允許通過?
package.json 新增:
{
"scripts": {
"vibe-guard": "node vibe-guard-cli/index.js"
}
}
最基本的使用方式是:
cd /media/mickey/777/ithome/demo-app
npm run vibe-guard -- scan .
npm run 後面的 -- 很重要。
它將後面的參數傳給 vibe-guard-cli/index.js,而不是讓 npm 自己解析。
CLI 的介面為:
Usage:
npm run vibe-guard -- scan [target] [options]
Options:
--output <directory> Artifact directory
--format <terminal|json|markdown>
--fail-on <severity|none> Blocking severity
--evidence-policy <warn|fail>
--no-run Reuse existing validation artifacts
--help Show help
第一版只有一個 Subcommand:
scan
即使目前只有一項功能,仍保留 Subcommand 層級。
因為後續可能加入:
vibe-guard scan
vibe-guard explain AUTH-01
vibe-guard compare baseline.json current.json
vibe-guard export --format sarif
vibe-guard validate findings-report.json
如果第一版直接設計成:
vibe-guard .
未來加入第二項功能時,就要重新解釋每個位置參數的意思。
今天使用以下預設:
| 參數 | 預設值 | 意義 |
|---|---|---|
target |
. |
檢查目前目錄 |
--output |
vibe-guard-output |
保存 Artifact 的目錄 |
--format |
terminal |
人類可讀的標準輸出 |
--fail-on |
high |
Confirmed High 以上阻擋 |
--evidence-policy |
warn |
缺少證據先警告,不直接阻擋 |
| Pipeline | 執行 | 預設重新產生驗證結果 |
這些預設代表:
Confirmed High / Critical
→ Gate Fail
Confirmed Medium / Low
→ 顯示,但目前不阻擋
Requires Evidence
→ 顯示警告,但目前不阻擋
Rejected
→ 保留紀錄,不建立修正工作
這不是唯一正確政策。
不同 Repository 可以選擇:
正式支付服務:Medium 以上全部阻擋。
內部 Prototype:只阻擋 Critical。
部署證據完整的服務:Requires Evidence 也阻擋。
純本機研究專案:只輸出報告,不建立 Gate。
但預設值必須清楚,不能因為使用者沒有傳參數,就讓模型臨時決定。
CLI 會接收使用者提供的:
Target Path
Output Path
Format
Severity
Evidence Policy
其中 Path 是不可信輸入。
如果使用 Shell 字串:
exec(`npm run review ${target}`);
Target 中的空白、引號或 Shell Metacharacter 都可能改變指令語意。
今天使用 execFile,把執行檔與參數分開:
await execFileAsync(
"npm",
["run", "adversarial-validation:demo", "--silent"],
{
cwd: target,
maxBuffer: 10 * 1024 * 1024
}
);
這裡沒有:
shell: true
也沒有把 Target 插入命令字串。
Target 只用來設定 Child Process 的 cwd。
CLI 參數也使用 Allowlist:
if (!["terminal", "json", "markdown"].includes(options.format)) {
fail("--format must be terminal, json, or markdown");
}
if (!["warn", "fail"].includes(options.evidencePolicy)) {
fail("--evidence-policy must be warn or fail");
}
Severity 同樣只能是:
critical
high
medium
low
informational
none
無效參數應直接失敗。
不要偷偷改用預設值,因為:
--fail-on hihg
很可能只是把 high 拼錯。
若 CLI 靜默套用其他值,使用者會以為 High Finding 已經被阻擋。
第一版 CLI 沒有把 Day 16 到 Day 24 的所有程式重寫一次。
它重用已經驗證過的流程:
npm run adversarial-validation:demo
這條指令會:
adversarial-validation-demo/output/challenges.json。CLI 再將結果轉換成 Day 24 Schema:
AUTH-01
confirmed / open / high
SECRET-01
rejected / false_positive / severity=null
RATE-01
requires_evidence / awaiting_evidence / severity=null
這裡有一項刻意的差異。
Day 24 的 AUTH-01 是生命週期範例,因此已經走到:
confirmed / resolved
今天 CLI 掃描目前的 firestore.rules。
這份檔案仍然是刻意保留的弱點版本:
allow read, write: if request.auth != null;
所以本次工作目錄的結果必須是:
confirmed / open
不能因為 Day 24 曾展示安全版本的 Regression Test,就假裝目前 Target 已經修正。
CLI 的結果必須描述這次 Target 與這次 Run,而不是複製上一篇文章的狀態。
challenges.json 保存 Candidate、Challenge、Test 與必要的 Execution Result。
CLI 將每項結果映射成正式 Finding。
Confirmed Candidate 會得到:
{
verdict: "confirmed",
status: "open",
severity: "high",
confidence: "high"
}
Rejected Candidate 會得到:
{
verdict: "rejected",
status: "false_positive",
severity: null,
confidence: "high"
}
Requires Evidence Candidate 會得到:
{
verdict: "requires_evidence",
status: "awaiting_evidence",
severity: null,
confidence: "low"
}
轉換完成後,CLI 必須再次執行:
validateFindingsReport(report);
這一步不能省略。
上游 Artifact 即使昨天符合格式,今天也可能因為:
欄位名稱改變
Schema Version 不相容
缺少 Verification
Status 與 Verdict 不一致
時間格式錯誤
Severity 被錯誤保留
而無法安全使用。
CLI 不能把「JSON.parse 成功」當成「報告可信」。
AUTH-01 的 Verdict 是:
confirmed
這是驗證流程對證據的判斷。
但它是否阻擋目前命令,要由 Policy 決定。
今天的 Severity 順序為:
const severities = [
"informational",
"low",
"medium",
"high",
"critical"
];
Gate 判斷:
function severityBlocks(severity, failOn) {
if (severity === null || failOn === "none") {
return false;
}
return (
severities.indexOf(severity) >=
severities.indexOf(failOn)
);
}
因此:
--fail-on high
Critical → Block
High → Block
Medium → Report
Low → Report
如果改成:
npm run vibe-guard -- scan . --fail-on medium
Confirmed Medium 也會阻擋。
如果只是想產生報告:
npm run vibe-guard -- scan . --fail-on none
所有 Severity 都不會改變 Exit Code。
但 Finding 本身仍然保持:
confirmed / high
Policy 不能把 Finding 降級成:
informational
它只能決定這個 Run 是否通過。
RATE-01 的問題不是已確認的 High DoS。
它缺少:
Production Hosting Architecture
CDN、Gateway、App Check 與 Quota 設定
具有 Availability 或 Cost Threshold 的 Load Test
預設政策是:
--evidence-policy warn
因此它會出現在 Terminal 與 Artifact,但不改變 Exit Code。
若團隊要求所有 Production Boundary 都必須有證據,可以使用:
npm run vibe-guard -- scan . \
--fail-on high \
--evidence-policy fail
此時下列任一條件都會讓 Gate Fail:
存在 Confirmed High / Critical
存在 Requires Evidence
需要注意的是:
requires_evidence被 Policy 阻擋,不代表它被升級成漏洞。
它的 Severity 仍然是 null。
Gate Fail 的原因是流程不允許在關鍵證據缺失時繼續,而不是 Agent 已證明攻擊成立。
人類會閱讀 Terminal。
Shell、Git Hook 與 CI 則先看 Exit Code。
今天定義:
| Exit Code | 意義 |
|---|---|
0 |
CLI 成功,Policy Gate 通過 |
1 |
CLI 成功,報告有效,但 Policy Gate 不通過 |
2 |
CLI 參數、執行流程、Artifact 或 Schema 發生錯誤 |
1 與 2 必須分開。
如果都回傳 1,自動化系統無法分辨:
真的找到 High Finding
或:
Firebase Emulator 根本沒有啟動
Artifact 損壞
CLI 參數錯誤
Schema Version 不支援
兩者的處理方式完全不同。
Policy Fail 應該交給開發者修正或接受風險。
Execution Error 則要先修復工具、環境或 Pipeline。
Node.js 程式不需要在每條路徑立即呼叫:
process.exit(1);
今天使用:
process.exitCode = 1;
讓目前尚未完成的輸出與檔案寫入可以正常結束,再由 Process 回傳指定狀態。
執行:
cd /media/mickey/777/ithome/demo-app
npm run vibe-guard -- scan .
實際輸出:
VIBE GUARD LOCAL REVIEW
Target: /media/mickey/777/ithome/demo-app
Run: run-2026-09-13T11:31:56.174Z
FINDINGS
[HIGH] AUTH-01 confirmed/open
Any signed-in user can access another user's project
[REJECTED] SECRET-01 rejected/false_positive
Exposed Firebase API key grants production database access
[EVIDENCE] RATE-01 requires_evidence/awaiting_evidence
Missing rate-limit middleware enables denial of service
Confirmed: 1
Requires evidence: 1
Rejected: 1
Gate: FAIL (1 severity finding(s), 0 evidence gap(s))
Artifacts: vibe-guard-output/
這次命令回傳:
Exit Code 1
原因是預設 Policy 為:
--fail-on high
而 AUTH-01 是 Confirmed High Finding。
如果只想確認 Report Generation,不建立 Severity Gate:
npm run vibe-guard -- scan . --fail-on none
輸出中的 Finding 不變,但最後會變成:
Gate: PASS
並回傳:
Exit Code 0
Terminal Summary 適合立即閱讀,但不適合保存完整證據。
今天預設建立:
demo-app/
└── vibe-guard-output/
├── findings-report.json
└── summary.md
findings-report.json 是 Canonical Artifact。
它符合 Day 24 的:
schemaVersion = 2.0.0
並包含:
Run Metadata
完整 Findings
Verification Record
Missing Evidence
Remediation
Status History
summary.md 則提供方便貼到 Issue、Pull Request 或 Build Summary 的版本。
內容類似:
# Vibe Guard Local Review
- Target: `/media/mickey/777/ithome/demo-app`
- Run: `run-2026-09-13T11:31:56.174Z`
- Gate: **FAIL**
| ID | Verdict | Severity | Status | Location |
|---|---|---|---|---|
| AUTH-01 | confirmed | high | open | firestore.rules:6 |
| SECRET-01 | rejected | - | false_positive | - |
| RATE-01 | requires_evidence | - | awaiting_evidence | - |
這裡再次維持單一資料來源。
Markdown Reporter 不會重新呼叫模型,也不會重新判斷 Severity。
它只從已驗證的 Findings Report 轉換內容。
預設:
npm run vibe-guard -- scan .
使用:
--format terminal
適合開發者閱讀。
若另一個程式需要直接讀取結果:
npm run vibe-guard -- scan . --format json
Standard Output 會包含:
{
"report": {},
"policy": {
"passed": false,
"blockingFindings": [],
"evidenceGaps": [],
"evidenceBlocked": false
}
}
若要產生可以直接放進說明或 Build Summary 的內容:
npm run vibe-guard -- scan . --format markdown
三種 Format 不應有三套判斷邏輯。
它們共用:
同一份 Validated Report
同一個 Policy Result
差別只在呈現方式。
--no-run?完整 Scan 會啟動 Firebase Emulator 並執行動態測試。
在開發 CLI Formatter、Dashboard 或 Exporter 時,如果每次都重新跑整套驗證,會浪費時間。
因此提供:
npm run vibe-guard -- scan . --no-run
它會重用:
adversarial-validation-demo/output/challenges.json
但仍會重新:
讀取 Artifact
轉換 Findings Schema
執行 Zod Validation
套用 Policy
寫入新的輸出
--no-run 適合:
開發 Reporter
測試不同 Gate Policy
重現既有 Run 的顯示問題
離線檢視保存的 Artifact
它不適合取代提交前的正常 Scan。
因為舊 Artifact 可能不再對應目前 Working Tree。
正式版本應在 Artifact 中保存:
Git Commit SHA
Working Tree Digest
Target Path
Rule Set Digest
Prompt Version
Fixture Version
並在使用 --no-run 時核對。
今天的 revision 暫時使用:
working-tree
這也明確表示第一版尚未完成跨 Revision Baseline。
執行 Child Process 時,今天不把所有 Pipeline Log 直接倒進 Terminal Summary。
原因是:
Emulator Log 很長
每個 Agent 階段都有自己的輸出
CI Summary 會難以閱讀
JSON Format 不能混入非 JSON 文字
CLI 會先捕捉 stdout 與 stderr。
成功時,只顯示最後的 Findings Summary。
失敗時,則將實際錯誤帶回:
const detail =
error.stderr?.trim() ||
error.stdout?.trim() ||
error.message;
fail(`Validation pipeline failed:\n${detail}`);
這不是 Silent Failure。
成功路徑降低雜訊,失敗路徑保留可診斷資訊。
正式版本還應將完整 Pipeline Log 保存成 Artifact,而不是只依賴 Terminal Buffer。
今天完成的是第一個可執行的本機入口。
它會重用 Demo Repository 已存在的:
Recon
Candidate
Challenger
Firestore Emulator Scenario
Findings Schema
因此它目前要求 Target 已經具備:
package.json
adversarial-validation:demo Script
adversarial-validation-demo/output/
findings-schema-demo/schema.js
它還不是把任何 GitHub Repository 丟進去都能自動完成 Review 的通用產品。
要走到那一步,還需要:
今天先固定 Demo Workflow,是刻意縮小範圍。
若在 Agent、Schema、CLI、通用 Plugin System 與多語言掃描器都還不穩定時同時抽象化,最後只會得到一個介面很大、行為卻無法驗證的框架。
在 Day 24 以前,Vibe Guard 比較像一條研究 Pipeline。
開發者要特地進入各個 Demo,才能看到結果。
今天之後,它可以進入實際的開發循環:
修改程式碼
→ npm run vibe-guard -- scan .
→ 查看 AUTH-01
→ 修正 firestore.rules
→ 重新執行
→ 確認 Regression
→ 再提交程式碼
這個位置很重要。
越晚發現問題,修正成本通常越高。
如果只有 Pull Request 或正式部署才執行:
開發者等待遠端 Job
Reviewer 才第一次看到 Finding
Context 已經切換到其他工作
修正需要再推一次 Commit
本機 CLI 讓最短 Feedback Loop 變成:
同一個 Terminal
同一個 Working Tree
同一次開發工作
但本機檢查不能取代遠端檢查。
開發者可以忘記執行,也可以使用:
--fail-on none
甚至直接跳過。
所以明天仍要將相同 Contract 放進 Pull Request。
把 Agent 放進 Terminal,不只是增加一條 npm Script。
一個能進入工程流程的 CLI,至少要明確定義:
0、1 與 2 各自代表什麼。今天完成的本機入口是:
npm run vibe-guard -- scan .
它會把目前的多階段 Review 收斂成:
一條命令
一份已驗證報告
一組明確政策
一個可供自動化判斷的 Exit Code
這讓 Vibe Guard 從「可以執行的 Demo」向「可以放進開發流程的工具」前進一步。
明天,我們會重用同一條 CLI Contract,把 Vibe Guard 放進 Pull Request:
Checkout Revision
→ 執行 Local Scan
→ 保存 Findings Artifact
→ 將 Gate Result 回報給 GitHub
→ 讓 Reviewer 在程式碼變更旁看到問題
重點不是在 CI 裡再寫一套 Reviewer。
而是讓本機與 Pull Request 使用相同的 Schema、Policy 與 Exit Code,避免出現:
本機通過,CI 卻用另一套規則失敗。