-
Notifications
You must be signed in to change notification settings - Fork 1
237 lines (202 loc) · 9.29 KB
/
gatekeeper_ai.yml
File metadata and controls
237 lines (202 loc) · 9.29 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
name: Codeijoe Gatekeeper v3.0 (AI-Powered)
# Trigger: pull_request_target mengizinkan akses ke Secrets (Gemini Key)
# Aman karena workflow yang jalan adalah versi dari branch 'main' (milik kita)
on:
pull_request_target:
branches: ["main"]
paths:
- "src/**"
- "tests/**"
- "README.md"
permissions:
contents: read
pull-requests: write # Wajib untuk Bot memberi label, komen, dan menutup PR
issues: write
jobs:
audit-submission:
name: Forensic Audit
runs-on: ubuntu-latest
steps:
# 1. Checkout Code Milik Challenger (HEAD)
- name: Checkout Challenger Code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 2
# ---------------------------------------------------------
# GATE 1: ANTI-CHEAT (File Integrity)
# Memastikan user tidak mengubah Test atau Config Bot
# ---------------------------------------------------------
- name: Detect Illegal Modifications
id: anti_cheat
uses: tj-actions/changed-files@v41
with:
files: |
tests/**
.github/**
GOVERNANCE.md
LICENSE
- name: Block Cheaters
if: steps.anti_cheat.outputs.any_changed == 'true'
run: |
echo "::error title=Integrity Violated::You modified restricted files (tests/ or .github/). CHEATING DETECTED."
exit 1
# ---------------------------------------------------------
# GATE 2: THE VOIGHT-KAMPFF TEST (Hybrid AI Audit)
# Pre-flight check (Length) + Deep Scan (Gemini 1.5 Flash)
# ---------------------------------------------------------
- name: AI Forensic Audit (Gemini 1.5 Flash)
uses: actions/github-script@v7
env:
GEMINI_KEY: ${{ secrets.GEMINI_API_KEY }}
# INJECT SYSTEM CORTEX DARI SECRET
SYSTEM_PROMPT: ${{ secrets.SYSTEM_CORTEX_PROMPT }}
with:
script: |
const pr = context.payload.pull_request;
const bodyClean = (pr.body || "").replace(/(\r\n|\n|\r)/gm, " ").trim();
// Bypass Audit jika Founder/Admin yang submit
if (pr.user.login === 'codeijoe' || pr.user.login === 'itwahjoedi') {
console.log("Admin override. Skipping AI Audit.");
return;
}
// --- LEVEL 1: PRE-FLIGHT CHECK (Hemat API Call) ---
// Tolak jika deskripsi terlalu pendek (< 100 char)
if (bodyClean.length < 100) {
const msg = "⛔ **AUDIT REJECTED (LAZINESS DETECTED)**\n\n**Reason:** PR Description is too short.\n\n*Protocol Requirement: Real engineers explain their decisions. Minimum 100 characters required.*";
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: msg
});
core.setFailed("PR Description too short (<100 chars).");
return;
}
// --- LEVEL 2: DEEP SCAN (Gemini AI) ---
// Ambil System Prompt dari Secret, atau fallback ke default jika kosong
const systemInstruction = process.env.SYSTEM_PROMPT || "You are a strict code auditor.";
const userPrompt = `
INPUT DATA:
- PR Title: "${pr.title}"
- PR Body: "${bodyClean}"
Based on the System Instructions provided to you, determine PASS or FAIL.
OUTPUT FORMAT (JSON ONLY): {"verdict": "PASS"|"FAIL", "reason": "..."}
`;
try {
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${process.env.GEMINI_KEY}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
contents: [
{
role: "user",
parts: [{ text: systemInstruction + "\n\n" + userPrompt }]
}
],
generationConfig: { responseMimeType: "application/json" }
})
});
if (!response.ok) {
throw new Error(`Gemini API Error: ${response.statusText}`);
}
const data = await response.json();
const textResponse = data.candidates[0].content.parts[0].text;
// Bersihkan markdown block (```json ... ```) jika ada, agar JSON.parse aman
const jsonClean = textResponse.replace(/```json/g, "").replace(/```/g, "").trim();
const analysis = JSON.parse(jsonClean);
console.log("AI Verdict:", analysis);
if (analysis.verdict === "FAIL") {
const failureMsg = `⛔ **AUDIT REJECTED (AI DETECTED)**\n\n**Reason:** ${analysis.reason}\n\n*Protocol Requirement: You must provide a valid 'Trade-off Analysis'. Rewrite your PR description to prove you understand the code.*`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: failureMsg
});
core.setFailed(`Voight-Kampff Failed: ${analysis.reason}`);
} else {
console.log("✅ AI Audit Passed: Reasoning verified.");
}
} catch (error) {
console.error("AI Audit Malfunction:", error);
// Fail Open: Jika API error, kita lanjut ke tes teknikal (opsional)
console.log("Warning: Proceeding to technical test despite AI outage.");
}
# ---------------------------------------------------------
# GATE 3: TECHNICAL GAUNTLET (Unit Tests)
# ---------------------------------------------------------
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install Dependencies
run: npm ci
- name: Run The Trap (Tests)
id: run_tests
# Output disimpan ke file untuk dibaca step berikutnya
run: npm test > test-results.txt 2>&1
continue-on-error: true
# ---------------------------------------------------------
# GATE 4: JUDGMENT & CLOSURE
# ---------------------------------------------------------
- name: Judge & Execute Protocol
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let testLogs = '';
try { testLogs = fs.readFileSync('test-results.txt', 'utf8'); } catch (e) { testLogs = 'No logs available.'; }
const outcome = '${{ steps.run_tests.outcome }}';
if (outcome === 'success') {
// --- VERIFIED ---
const msg = `### ✅ AUDIT PASSED: VERIFIED\n\n` +
`**Engineering Judgment Confirmed.**\n` +
`Your logic survived the trap and your reasoning passed the AI forensic scan.\n\n` +
`🔒 **PROTOCOL:** This PR is now **LOCKED** to preserve solution integrity.\n` +
`🏆 **PROOF OF WORK:** This URL is your permanent record.`;
// Label
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['audit-passed', 'verified']
});
} catch(e) {}
// Comment
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: msg
});
// Close PR
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
state: 'closed'
});
} else {
// --- REJECTED ---
// Hanya ambil 1500 karakter terakhir dari log untuk komentar agar tidak spam
const logSnippet = testLogs.slice(0, 1500);
const msg = `### ❌ AUDIT FAILED\n\n` +
`**System Integrity Check:** FAILED.\n` +
`Your code did not pass the automated tests.\n\n` +
`<details><summary>Expand Error Logs</summary>\n\n` +
`\`\`\`\n${logSnippet}...\n\`\`\`\n` +
`\n</details>\n\n` +
`**Directive:** Debug locally. Do not waste the Auditor's time.`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: msg
});
core.setFailed("Technical Audit Failed");
}
- name: Fail Workflow if Tests Failed
if: steps.run_tests.outcome != 'success'
run: exit 1