@prism-d1/cli 1.0.73 → 1.0.74
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.
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- Unbounded database queries (no LIMIT, no pagination, no streaming for large result sets)
|
|
13
13
|
- Disabled security controls (authentication bypassed, authorization checks removed)
|
|
14
14
|
- Use of `eval()`, `Function()` constructor, or `child_process.exec()` with user input
|
|
15
|
+
- Stub or placeholder implementations (functions that ignore parameters and return hardcoded/empty values)
|
|
15
16
|
|
|
16
17
|
## Warnings (comment but don't block)
|
|
17
18
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# prism-eval-gate.yml
|
|
1
|
+
# prism-eval-gate.yml -- AI code quality gate using kiro-cli headless mode.
|
|
2
2
|
#
|
|
3
3
|
# Copy this file to .github/workflows/prism-eval-gate.yml in your repo.
|
|
4
4
|
#
|
|
@@ -42,49 +42,35 @@ jobs:
|
|
|
42
42
|
TEAM_ID=$(jq -r '.team_id // "no_team"' .prism/config.json 2>/dev/null || echo "no_team")
|
|
43
43
|
echo "team_id=${TEAM_ID}" >> "$GITHUB_OUTPUT"
|
|
44
44
|
|
|
45
|
-
- name: Identify AI-generated changes
|
|
46
|
-
id: detect-ai
|
|
47
|
-
env:
|
|
48
|
-
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
49
|
-
HEAD_SHA: ${{ github.sha }}
|
|
50
|
-
run: |
|
|
51
|
-
AI_FILES=""
|
|
52
|
-
for SHA in $(git rev-list "${BASE_SHA}..${HEAD_SHA}"); do
|
|
53
|
-
if git log -1 --format='%B' "${SHA}" | grep -q 'AI-Origin: ai-generated\|AI-Origin: ai-assisted'; then
|
|
54
|
-
FILES=$(git diff-tree --no-commit-id --name-only -r "${SHA}" \
|
|
55
|
-
| grep -E '\.(ts|js|py|go|java|rs|tsx|jsx)$' \
|
|
56
|
-
| grep -vE '\.test\.|\.spec\.|__test__|_test\.' || true)
|
|
57
|
-
AI_FILES="${AI_FILES} ${FILES}"
|
|
58
|
-
fi
|
|
59
|
-
done
|
|
60
|
-
|
|
61
|
-
UNIQUE_FILES=$(echo "${AI_FILES}" | tr ' ' '\n' | sort -u | grep -v '^$' | head -30)
|
|
62
|
-
FILE_COUNT=$(echo "${UNIQUE_FILES}" | grep -c . || echo "0")
|
|
63
|
-
|
|
64
|
-
if [ "${FILE_COUNT}" -eq 0 ]; then
|
|
65
|
-
echo "has_ai_changes=false" >> "$GITHUB_OUTPUT"
|
|
66
|
-
else
|
|
67
|
-
echo "has_ai_changes=true" >> "$GITHUB_OUTPUT"
|
|
68
|
-
echo "file_count=${FILE_COUNT}" >> "$GITHUB_OUTPUT"
|
|
69
|
-
echo "${UNIQUE_FILES}" > /tmp/ai-changed-files.txt
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
45
|
- name: Kiro code quality review
|
|
73
46
|
id: review
|
|
74
|
-
if: steps.detect-ai.outputs.has_ai_changes == 'true'
|
|
75
47
|
env:
|
|
76
48
|
KIRO_API_KEY: ${{ secrets.KIRO_API_KEY }}
|
|
77
49
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
78
50
|
HEAD_SHA: ${{ github.sha }}
|
|
79
51
|
run: |
|
|
80
|
-
#
|
|
52
|
+
# Trim whitespace/newlines from API key (GitHub Secrets can add trailing chars)
|
|
53
|
+
export KIRO_API_KEY=$(echo -n "${KIRO_API_KEY}" | tr -d '[:space:]')
|
|
54
|
+
|
|
55
|
+
# Fork PRs don't receive secrets -- report neutral, never silently pass
|
|
81
56
|
if [ "${HAS_KIRO_KEY}" != "true" ]; then
|
|
82
|
-
echo "::warning::KIRO_API_KEY unavailable (fork PR or missing secret)
|
|
57
|
+
echo "::warning::KIRO_API_KEY unavailable (fork PR or missing secret) -- review skipped"
|
|
58
|
+
echo "result=SKIPPED" >> "$GITHUB_OUTPUT"
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Get all changed source files in this PR
|
|
63
|
+
FILES=$(git diff --name-only "${BASE_SHA}..${HEAD_SHA}" \
|
|
64
|
+
| grep -E '\.(ts|js|py|go|java|rs|tsx|jsx)$' \
|
|
65
|
+
| grep -vE '\.test\.|\.spec\.|__test__|_test\.' \
|
|
66
|
+
| head -30 | tr '\n' ' ')
|
|
67
|
+
|
|
68
|
+
if [ -z "${FILES}" ]; then
|
|
69
|
+
echo "No source files changed -- skipping review"
|
|
83
70
|
echo "result=SKIPPED" >> "$GITHUB_OUTPUT"
|
|
84
71
|
exit 0
|
|
85
72
|
fi
|
|
86
73
|
|
|
87
|
-
FILES=$(cat /tmp/ai-changed-files.txt | tr '\n' ' ')
|
|
88
74
|
DIFF=$(git diff "${BASE_SHA}..${HEAD_SHA}" -- ${FILES})
|
|
89
75
|
|
|
90
76
|
# Find spec reference from commit trailers
|
|
@@ -95,22 +81,24 @@ jobs:
|
|
|
95
81
|
fi
|
|
96
82
|
|
|
97
83
|
# Run kiro-cli in headless mode
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"You are a code reviewer running in CI. Review this PR diff for code quality issues.
|
|
102
|
-
|
|
103
|
-
Changed files: ${FILES}
|
|
84
|
+
echo "::group::kiro-cli version"
|
|
85
|
+
which kiro-cli && kiro-cli --version || echo "kiro-cli not found on PATH"
|
|
86
|
+
echo "::endgroup::"
|
|
104
87
|
|
|
105
|
-
|
|
88
|
+
echo "::group::kiro-cli smoke test"
|
|
89
|
+
echo "Say hello in JSON" | kiro-cli chat --no-interactive --model auto || echo "SMOKE TEST FAILED"
|
|
90
|
+
echo "::endgroup::"
|
|
106
91
|
|
|
107
|
-
|
|
108
|
-
Follow the review rules in .kiro/steering/code-review.md if it exists.
|
|
92
|
+
echo "Reviewing ${FILES}"
|
|
109
93
|
|
|
110
|
-
|
|
94
|
+
# Keep prompt short -- kiro-cli v2.16.2 has a bug putting stdin into HTTP headers
|
|
95
|
+
# on long content. Use a minimal CLI arg prompt and let kiro read files itself.
|
|
96
|
+
git diff "${BASE_SHA}..${HEAD_SHA}" -- ${FILES} > /tmp/pr-diff.patch
|
|
111
97
|
|
|
112
|
-
|
|
113
|
-
|
|
98
|
+
REVIEW_OUTPUT=$(kiro-cli chat --no-interactive \
|
|
99
|
+
--trust-all-tools \
|
|
100
|
+
--model auto \
|
|
101
|
+
"Review /tmp/pr-diff.patch for code quality. Read it, then output ONLY JSON: {\"findings\":[{\"file\":\"path\",\"line\":1,\"severity\":\"high|medium|low\",\"category\":\"str\",\"message\":\"str\"}],\"score\":0.0,\"summary\":\"str\"}") || true
|
|
114
102
|
|
|
115
103
|
# Strip ANSI escapes, join lines, extract the last JSON object
|
|
116
104
|
CLEANED=$(echo "${REVIEW_OUTPUT}" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | tr -d '\r')
|
|
@@ -118,7 +106,15 @@ jobs:
|
|
|
118
106
|
|
|
119
107
|
if [ -z "${REVIEW_JSON}" ] || ! echo "${REVIEW_JSON}" | jq -e '.score' > /dev/null 2>&1; then
|
|
120
108
|
# FAIL CLOSED: a gate that can't evaluate must not pass
|
|
121
|
-
echo "::
|
|
109
|
+
echo "::group::Debug: raw kiro-cli output (first 2000 chars)"
|
|
110
|
+
echo "${REVIEW_OUTPUT:-<empty>}" | head -c 2000
|
|
111
|
+
echo ""
|
|
112
|
+
echo "::endgroup::"
|
|
113
|
+
echo "::group::Debug: cleaned output"
|
|
114
|
+
echo "${CLEANED:-<empty>}" | head -c 2000
|
|
115
|
+
echo ""
|
|
116
|
+
echo "::endgroup::"
|
|
117
|
+
echo "::error::Could not parse kiro-cli review output -- failing closed"
|
|
122
118
|
echo "result=ERROR" >> "$GITHUB_OUTPUT"
|
|
123
119
|
exit 0
|
|
124
120
|
fi
|
|
@@ -127,7 +123,7 @@ jobs:
|
|
|
127
123
|
FINDINGS=$(echo "${REVIEW_JSON}" | jq '.findings | length')
|
|
128
124
|
HIGH=$(echo "${REVIEW_JSON}" | jq '[.findings[] | select(.severity=="high")] | length')
|
|
129
125
|
MEDIUM=$(echo "${REVIEW_JSON}" | jq '[.findings[] | select(.severity=="medium")] | length')
|
|
130
|
-
# Single line, quotes stripped
|
|
126
|
+
# Single line, quotes stripped -- safe for GITHUB_OUTPUT and downstream env use
|
|
131
127
|
SUMMARY=$(echo "${REVIEW_JSON}" | jq -r '.summary // "No summary"' | head -1 | tr -d '"`'"'" | cut -c1-200)
|
|
132
128
|
|
|
133
129
|
echo "score=${SCORE}" >> "$GITHUB_OUTPUT"
|
|
@@ -137,7 +133,7 @@ jobs:
|
|
|
137
133
|
echo "summary=${SUMMARY}" >> "$GITHUB_OUTPUT"
|
|
138
134
|
echo "${REVIEW_JSON}" > /tmp/kiro-review.json
|
|
139
135
|
|
|
140
|
-
# Determine pass/fail (awk instead of bc
|
|
136
|
+
# Determine pass/fail (awk instead of bc -- no extra package needed)
|
|
141
137
|
PASS=$(awk -v s="${SCORE}" 'BEGIN { print (s >= 0.82) ? "true" : "false" }')
|
|
142
138
|
if [ "${HIGH}" -gt 0 ] || [ "${PASS}" != "true" ]; then
|
|
143
139
|
echo "result=FAIL" >> "$GITHUB_OUTPUT"
|
|
@@ -149,7 +145,6 @@ jobs:
|
|
|
149
145
|
if: always()
|
|
150
146
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
151
147
|
env:
|
|
152
|
-
HAS_AI: ${{ steps.detect-ai.outputs.has_ai_changes }}
|
|
153
148
|
REVIEW_SCORE: ${{ steps.review.outputs.score }}
|
|
154
149
|
REVIEW_FINDINGS: ${{ steps.review.outputs.findings }}
|
|
155
150
|
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|
|
@@ -159,20 +154,16 @@ jobs:
|
|
|
159
154
|
with:
|
|
160
155
|
script: |
|
|
161
156
|
const fs = require('fs');
|
|
162
|
-
// All values read from env
|
|
157
|
+
// All values read from env -- never interpolate secrets into script source
|
|
163
158
|
// (LLM-generated text could contain quotes/backticks = script injection)
|
|
164
|
-
const hasAI = process.env.HAS_AI === 'true';
|
|
165
159
|
|
|
166
160
|
let body;
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
const result = process.env.REVIEW_RESULT || 'ERROR';
|
|
162
|
+
if (result === 'SKIPPED') {
|
|
163
|
+
body = '⚠️ **PRISM Eval Gate** -- Review skipped (no source files changed or no API key on fork PRs).';
|
|
164
|
+
} else if (result === 'ERROR') {
|
|
165
|
+
body = '❌ **PRISM Eval Gate** -- Review output could not be parsed. Failing closed; re-run the workflow or review manually.';
|
|
169
166
|
} else {
|
|
170
|
-
const result = process.env.REVIEW_RESULT || 'ERROR';
|
|
171
|
-
if (result === 'SKIPPED') {
|
|
172
|
-
body = '⚠️ **PRISM Eval Gate** — Review unavailable (no API key on fork PRs). A maintainer must review manually.';
|
|
173
|
-
} else if (result === 'ERROR') {
|
|
174
|
-
body = '❌ **PRISM Eval Gate** — Review output could not be parsed. Failing closed; re-run the workflow or review manually.';
|
|
175
|
-
} else {
|
|
176
167
|
const score = process.env.REVIEW_SCORE || '?';
|
|
177
168
|
const findings = process.env.REVIEW_FINDINGS || '0';
|
|
178
169
|
const high = process.env.REVIEW_HIGH || '0';
|
|
@@ -192,7 +183,7 @@ jobs:
|
|
|
192
183
|
}
|
|
193
184
|
} catch(e) { /* no detail available */ }
|
|
194
185
|
|
|
195
|
-
body = `## ${emoji} PRISM Eval Gate
|
|
186
|
+
body = `## ${emoji} PRISM Eval Gate -- ${result}\n\n` +
|
|
196
187
|
`**Score**: ${score} | **Findings**: ${findings} (${high} high, ${medium} medium)\n\n` +
|
|
197
188
|
`**Summary**: ${summary}\n\n`;
|
|
198
189
|
|
|
@@ -200,8 +191,7 @@ jobs:
|
|
|
200
191
|
body += `| File | Line | Severity | Category | Issue |\n|---|---|---|---|---|\n${table}\n\n`;
|
|
201
192
|
}
|
|
202
193
|
|
|
203
|
-
body += `> Powered by kiro-cli headless
|
|
204
|
-
}
|
|
194
|
+
body += `> Powered by kiro-cli headless - Model: claude-haiku-4.5 - Threshold: 0.82`;
|
|
205
195
|
}
|
|
206
196
|
|
|
207
197
|
const { data: comments } = await github.rest.issues.listComments({
|
|
@@ -217,9 +207,9 @@ jobs:
|
|
|
217
207
|
await github.rest.issues.createComment({ ...params, issue_number: context.issue.number });
|
|
218
208
|
}
|
|
219
209
|
|
|
220
|
-
- name: Configure AWS credentials (OIDC)
|
|
210
|
+
- name: Configure AWS credentials (OIDC) -- for metrics + security scan
|
|
221
211
|
id: aws-creds
|
|
222
|
-
if:
|
|
212
|
+
if: env.METRICS_ROLE_ARN != ''
|
|
223
213
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
224
214
|
with:
|
|
225
215
|
role-to-assume: ${{ env.METRICS_ROLE_ARN }}
|
|
@@ -227,7 +217,7 @@ jobs:
|
|
|
227
217
|
continue-on-error: true
|
|
228
218
|
|
|
229
219
|
- name: Emit prism.d1.eval event
|
|
230
|
-
if: steps.
|
|
220
|
+
if: steps.aws-creds.outcome == 'success'
|
|
231
221
|
env:
|
|
232
222
|
AWS_REGION: us-west-2
|
|
233
223
|
TEAM_ID: ${{ steps.prism-config.outputs.team_id }}
|
|
@@ -262,9 +252,9 @@ jobs:
|
|
|
262
252
|
aws events put-events --region us-west-2 --entries \
|
|
263
253
|
"[{\"Source\":\"prism.d1.velocity\",\"DetailType\":\"prism.d1.eval\",\"EventBusName\":\"prism-d1-metrics\",\"Detail\":$(echo "${DETAIL}" | jq -Rs '.')}]" || true
|
|
264
254
|
|
|
265
|
-
- name: AWS Continuum security scan (Tier 2
|
|
255
|
+
- name: AWS Continuum security scan (Tier 2 -- deterministic)
|
|
266
256
|
id: security-gate
|
|
267
|
-
if: steps.
|
|
257
|
+
if: steps.aws-creds.outcome == 'success'
|
|
268
258
|
env:
|
|
269
259
|
AWS_REGION: us-west-2
|
|
270
260
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
@@ -293,7 +283,7 @@ jobs:
|
|
|
293
283
|
REPO_SLUG=$(echo "${REPO}" | tr '/' '-')
|
|
294
284
|
CODE_REVIEW_ID=$(aws ssm get-parameter --name "/prism/continuum/code-review-id/${REPO_SLUG}" --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
295
285
|
if [ -z "$CODE_REVIEW_ID" ]; then
|
|
296
|
-
echo "::warning::No Code Review resource for ${REPO_SLUG}
|
|
286
|
+
echo "::warning::No Code Review resource for ${REPO_SLUG} -- run 'prism-cli securityagent setup' first. Skipping."
|
|
297
287
|
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
298
288
|
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
299
289
|
exit 0
|
|
@@ -327,7 +317,7 @@ jobs:
|
|
|
327
317
|
done
|
|
328
318
|
|
|
329
319
|
if [ "$STATUS" != "COMPLETED" ]; then
|
|
330
|
-
echo "::warning::Continuum scan did not complete (status: ${STATUS})
|
|
320
|
+
echo "::warning::Continuum scan did not complete (status: ${STATUS}) -- treating as skipped"
|
|
331
321
|
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
332
322
|
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
333
323
|
exit 0
|
|
@@ -354,11 +344,7 @@ jobs:
|
|
|
354
344
|
fi
|
|
355
345
|
|
|
356
346
|
- name: Fail on eval gate failure
|
|
357
|
-
if:
|
|
358
|
-
steps.review.outputs.result == 'FAIL' ||
|
|
359
|
-
steps.review.outputs.result == 'ERROR' ||
|
|
360
|
-
(steps.security-gate.outputs.skipped != 'true' &&
|
|
361
|
-
(steps.security-gate.outputs.critical > 0 || steps.security-gate.outputs.high > 0))
|
|
347
|
+
if: ${{ steps.review.outputs.result == 'FAIL' || steps.review.outputs.result == 'ERROR' || (steps.security-gate.outputs.skipped != 'true' && (steps.security-gate.outputs.critical != '0' || steps.security-gate.outputs.high != '0')) }}
|
|
362
348
|
env:
|
|
363
349
|
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
364
350
|
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|