@prism-d1/cli 1.0.72 → 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.
- package/dist/assets/eval-harness/steering/code-review.md +62 -0
- package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +365 -0
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts +1 -0
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-eval-harness.js +126 -60
- package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Code Review Rules
|
|
2
|
+
|
|
3
|
+
> This file configures what Kiro looks for when reviewing PR diffs in CI.
|
|
4
|
+
> Customize these rules for your team's standards and concerns.
|
|
5
|
+
> See: https://kiro.dev/docs/steering/
|
|
6
|
+
|
|
7
|
+
## Hard Failures (block merge if found)
|
|
8
|
+
|
|
9
|
+
- SQL injection or command injection via string concatenation or template literals with user input
|
|
10
|
+
- Hardcoded secrets, API keys, passwords, or connection strings in source code
|
|
11
|
+
- Missing error handling on async operations (unhandled promise rejections, missing try/catch)
|
|
12
|
+
- Unbounded database queries (no LIMIT, no pagination, no streaming for large result sets)
|
|
13
|
+
- Disabled security controls (authentication bypassed, authorization checks removed)
|
|
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)
|
|
16
|
+
|
|
17
|
+
## Warnings (comment but don't block)
|
|
18
|
+
|
|
19
|
+
- Functions exceeding 50 lines (suggest decomposition)
|
|
20
|
+
- TypeScript `any` type usage without a justifying comment
|
|
21
|
+
- `console.log` or `print()` in production code paths (use structured logger)
|
|
22
|
+
- Missing input validation on API endpoint parameters
|
|
23
|
+
- Catching errors without logging or re-throwing (`catch(e) {}`)
|
|
24
|
+
- TODO/FIXME/HACK comments introduced in new code
|
|
25
|
+
- Magic numbers without named constants
|
|
26
|
+
- Duplicated logic that could be extracted to a shared function
|
|
27
|
+
|
|
28
|
+
## What NOT to Flag
|
|
29
|
+
|
|
30
|
+
- Test files (*.test.ts, *.spec.ts, *_test.py, test_*.py)
|
|
31
|
+
- Generated code (files with "generated" or "auto-generated" in header comment)
|
|
32
|
+
- Configuration files (*.json, *.yml, *.toml) unless they contain secrets
|
|
33
|
+
- Lock files (package-lock.json, yarn.lock, poetry.lock)
|
|
34
|
+
- Markdown documentation changes
|
|
35
|
+
|
|
36
|
+
## Output Format
|
|
37
|
+
|
|
38
|
+
Always respond with ONLY valid JSON (no markdown, no explanation outside the JSON):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"findings": [
|
|
43
|
+
{
|
|
44
|
+
"file": "relative/path/to/file.ts",
|
|
45
|
+
"line": 42,
|
|
46
|
+
"severity": "high|medium|low",
|
|
47
|
+
"category": "security|correctness|performance|maintainability|reliability",
|
|
48
|
+
"message": "Brief, actionable description of the issue"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"score": 0.85,
|
|
52
|
+
"summary": "One-line overall verdict"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Scoring Guide
|
|
57
|
+
|
|
58
|
+
- **1.0**: No issues found
|
|
59
|
+
- **0.9-0.99**: Only low-severity style/maintainability suggestions
|
|
60
|
+
- **0.8-0.89**: Medium findings but no blocking issues
|
|
61
|
+
- **0.7-0.79**: Has issues that should be addressed but aren't critical
|
|
62
|
+
- **Below 0.7**: Has high-severity issues that should block merge
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# prism-eval-gate.yml -- AI code quality gate using kiro-cli headless mode.
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to .github/workflows/prism-eval-gate.yml in your repo.
|
|
4
|
+
#
|
|
5
|
+
# Required setup:
|
|
6
|
+
# - Kiro API key stored as repository secret: KIRO_API_KEY
|
|
7
|
+
# - .kiro/steering/code-review.md in your repo (review rules)
|
|
8
|
+
# - (Optional) PRISM_METRICS_ROLE_ARN for EventBridge metrics emission
|
|
9
|
+
# - (Optional) AWS Continuum configured for security scanning
|
|
10
|
+
|
|
11
|
+
name: PRISM Eval Gate
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
pull_request:
|
|
15
|
+
types: [opened, synchronize, reopened]
|
|
16
|
+
branches: [main, master]
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
id-token: write
|
|
20
|
+
contents: read
|
|
21
|
+
pull-requests: write
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
eval-gate:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
env:
|
|
27
|
+
# Secrets mapped to env so steps can gate on them (secrets context not allowed in `if`)
|
|
28
|
+
METRICS_ROLE_ARN: ${{ secrets.PRISM_METRICS_ROLE_ARN }}
|
|
29
|
+
HAS_KIRO_KEY: ${{ secrets.KIRO_API_KEY != '' && 'true' || 'false' }}
|
|
30
|
+
steps:
|
|
31
|
+
- name: Checkout
|
|
32
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
|
|
36
|
+
- name: Install Kiro CLI
|
|
37
|
+
run: curl -fsSL https://cli.kiro.dev/install | bash
|
|
38
|
+
|
|
39
|
+
- name: Read team ID
|
|
40
|
+
id: prism-config
|
|
41
|
+
run: |
|
|
42
|
+
TEAM_ID=$(jq -r '.team_id // "no_team"' .prism/config.json 2>/dev/null || echo "no_team")
|
|
43
|
+
echo "team_id=${TEAM_ID}" >> "$GITHUB_OUTPUT"
|
|
44
|
+
|
|
45
|
+
- name: Kiro code quality review
|
|
46
|
+
id: review
|
|
47
|
+
env:
|
|
48
|
+
KIRO_API_KEY: ${{ secrets.KIRO_API_KEY }}
|
|
49
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
50
|
+
HEAD_SHA: ${{ github.sha }}
|
|
51
|
+
run: |
|
|
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
|
|
56
|
+
if [ "${HAS_KIRO_KEY}" != "true" ]; then
|
|
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"
|
|
70
|
+
echo "result=SKIPPED" >> "$GITHUB_OUTPUT"
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
DIFF=$(git diff "${BASE_SHA}..${HEAD_SHA}" -- ${FILES})
|
|
75
|
+
|
|
76
|
+
# Find spec reference from commit trailers
|
|
77
|
+
SPEC_REF=$(git log "${BASE_SHA}..${HEAD_SHA}" --format='%(trailers:key=Spec-Ref,valueonly)' | head -1 | tr -d '[:space:]' || true)
|
|
78
|
+
SPEC_CONTEXT=""
|
|
79
|
+
if [ -n "${SPEC_REF}" ] && [ -f "${SPEC_REF}" ]; then
|
|
80
|
+
SPEC_CONTEXT="The spec for this work is at ${SPEC_REF}. Read it and verify the code implements all acceptance criteria."
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Run kiro-cli in headless mode
|
|
84
|
+
echo "::group::kiro-cli version"
|
|
85
|
+
which kiro-cli && kiro-cli --version || echo "kiro-cli not found on PATH"
|
|
86
|
+
echo "::endgroup::"
|
|
87
|
+
|
|
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::"
|
|
91
|
+
|
|
92
|
+
echo "Reviewing ${FILES}"
|
|
93
|
+
|
|
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
|
|
97
|
+
|
|
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
|
|
102
|
+
|
|
103
|
+
# Strip ANSI escapes, join lines, extract the last JSON object
|
|
104
|
+
CLEANED=$(echo "${REVIEW_OUTPUT}" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | tr -d '\r')
|
|
105
|
+
REVIEW_JSON=$(echo "${CLEANED}" | tr '\n' ' ' | grep -o '{.*}' || true)
|
|
106
|
+
|
|
107
|
+
if [ -z "${REVIEW_JSON}" ] || ! echo "${REVIEW_JSON}" | jq -e '.score' > /dev/null 2>&1; then
|
|
108
|
+
# FAIL CLOSED: a gate that can't evaluate must not pass
|
|
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"
|
|
118
|
+
echo "result=ERROR" >> "$GITHUB_OUTPUT"
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
SCORE=$(echo "${REVIEW_JSON}" | jq -r '.score')
|
|
123
|
+
FINDINGS=$(echo "${REVIEW_JSON}" | jq '.findings | length')
|
|
124
|
+
HIGH=$(echo "${REVIEW_JSON}" | jq '[.findings[] | select(.severity=="high")] | length')
|
|
125
|
+
MEDIUM=$(echo "${REVIEW_JSON}" | jq '[.findings[] | select(.severity=="medium")] | length')
|
|
126
|
+
# Single line, quotes stripped -- safe for GITHUB_OUTPUT and downstream env use
|
|
127
|
+
SUMMARY=$(echo "${REVIEW_JSON}" | jq -r '.summary // "No summary"' | head -1 | tr -d '"`'"'" | cut -c1-200)
|
|
128
|
+
|
|
129
|
+
echo "score=${SCORE}" >> "$GITHUB_OUTPUT"
|
|
130
|
+
echo "findings=${FINDINGS}" >> "$GITHUB_OUTPUT"
|
|
131
|
+
echo "high=${HIGH}" >> "$GITHUB_OUTPUT"
|
|
132
|
+
echo "medium=${MEDIUM}" >> "$GITHUB_OUTPUT"
|
|
133
|
+
echo "summary=${SUMMARY}" >> "$GITHUB_OUTPUT"
|
|
134
|
+
echo "${REVIEW_JSON}" > /tmp/kiro-review.json
|
|
135
|
+
|
|
136
|
+
# Determine pass/fail (awk instead of bc -- no extra package needed)
|
|
137
|
+
PASS=$(awk -v s="${SCORE}" 'BEGIN { print (s >= 0.82) ? "true" : "false" }')
|
|
138
|
+
if [ "${HIGH}" -gt 0 ] || [ "${PASS}" != "true" ]; then
|
|
139
|
+
echo "result=FAIL" >> "$GITHUB_OUTPUT"
|
|
140
|
+
else
|
|
141
|
+
echo "result=PASS" >> "$GITHUB_OUTPUT"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
- name: Post PR comment
|
|
145
|
+
if: always()
|
|
146
|
+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
147
|
+
env:
|
|
148
|
+
REVIEW_SCORE: ${{ steps.review.outputs.score }}
|
|
149
|
+
REVIEW_FINDINGS: ${{ steps.review.outputs.findings }}
|
|
150
|
+
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|
|
151
|
+
REVIEW_MEDIUM: ${{ steps.review.outputs.medium }}
|
|
152
|
+
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
153
|
+
REVIEW_SUMMARY: ${{ steps.review.outputs.summary }}
|
|
154
|
+
with:
|
|
155
|
+
script: |
|
|
156
|
+
const fs = require('fs');
|
|
157
|
+
// All values read from env -- never interpolate secrets into script source
|
|
158
|
+
// (LLM-generated text could contain quotes/backticks = script injection)
|
|
159
|
+
|
|
160
|
+
let body;
|
|
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.';
|
|
166
|
+
} else {
|
|
167
|
+
const score = process.env.REVIEW_SCORE || '?';
|
|
168
|
+
const findings = process.env.REVIEW_FINDINGS || '0';
|
|
169
|
+
const high = process.env.REVIEW_HIGH || '0';
|
|
170
|
+
const medium = process.env.REVIEW_MEDIUM || '0';
|
|
171
|
+
// Sanitize LLM text before embedding in markdown
|
|
172
|
+
const summary = (process.env.REVIEW_SUMMARY || 'No summary').replace(/[<>|]/g, ' ');
|
|
173
|
+
const emoji = result === 'PASS' ? '✅' : '❌';
|
|
174
|
+
|
|
175
|
+
let table = '';
|
|
176
|
+
try {
|
|
177
|
+
const review = JSON.parse(fs.readFileSync('/tmp/kiro-review.json', 'utf8'));
|
|
178
|
+
if (review.findings && review.findings.length > 0) {
|
|
179
|
+
const clean = (s) => String(s ?? '-').replace(/[|<>`]/g, ' ').slice(0, 200);
|
|
180
|
+
table = review.findings
|
|
181
|
+
.map(f => `| \`${clean(f.file)}\` | ${Number(f.line) || '-'} | ${clean(f.severity)} | ${clean(f.category)} | ${clean(f.message)} |`)
|
|
182
|
+
.join('\n');
|
|
183
|
+
}
|
|
184
|
+
} catch(e) { /* no detail available */ }
|
|
185
|
+
|
|
186
|
+
body = `## ${emoji} PRISM Eval Gate -- ${result}\n\n` +
|
|
187
|
+
`**Score**: ${score} | **Findings**: ${findings} (${high} high, ${medium} medium)\n\n` +
|
|
188
|
+
`**Summary**: ${summary}\n\n`;
|
|
189
|
+
|
|
190
|
+
if (table) {
|
|
191
|
+
body += `| File | Line | Severity | Category | Issue |\n|---|---|---|---|---|\n${table}\n\n`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
body += `> Powered by kiro-cli headless - Model: claude-haiku-4.5 - Threshold: 0.82`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
198
|
+
owner: context.repo.owner, repo: context.repo.repo,
|
|
199
|
+
issue_number: context.issue.number
|
|
200
|
+
});
|
|
201
|
+
const existing = comments.find(c => c.body.includes('PRISM Eval Gate'));
|
|
202
|
+
const params = { owner: context.repo.owner, repo: context.repo.repo, body };
|
|
203
|
+
|
|
204
|
+
if (existing) {
|
|
205
|
+
await github.rest.issues.updateComment({ ...params, comment_id: existing.id });
|
|
206
|
+
} else {
|
|
207
|
+
await github.rest.issues.createComment({ ...params, issue_number: context.issue.number });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
- name: Configure AWS credentials (OIDC) -- for metrics + security scan
|
|
211
|
+
id: aws-creds
|
|
212
|
+
if: env.METRICS_ROLE_ARN != ''
|
|
213
|
+
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
214
|
+
with:
|
|
215
|
+
role-to-assume: ${{ env.METRICS_ROLE_ARN }}
|
|
216
|
+
aws-region: us-west-2
|
|
217
|
+
continue-on-error: true
|
|
218
|
+
|
|
219
|
+
- name: Emit prism.d1.eval event
|
|
220
|
+
if: steps.aws-creds.outcome == 'success'
|
|
221
|
+
env:
|
|
222
|
+
AWS_REGION: us-west-2
|
|
223
|
+
TEAM_ID: ${{ steps.prism-config.outputs.team_id }}
|
|
224
|
+
REPO: ${{ github.repository }}
|
|
225
|
+
REVIEW_SCORE: ${{ steps.review.outputs.score }}
|
|
226
|
+
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
227
|
+
REVIEW_FINDINGS: ${{ steps.review.outputs.findings }}
|
|
228
|
+
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|
|
229
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
230
|
+
run: |
|
|
231
|
+
# Only emit when we have a real score (not SKIPPED/ERROR)
|
|
232
|
+
if [ -z "${REVIEW_SCORE}" ]; then
|
|
233
|
+
echo "No score to emit (result: ${REVIEW_RESULT:-none})"
|
|
234
|
+
exit 0
|
|
235
|
+
fi
|
|
236
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
237
|
+
DETAIL=$(jq -n -c \
|
|
238
|
+
--arg team_id "${TEAM_ID}" \
|
|
239
|
+
--arg repo "${REPO}" \
|
|
240
|
+
--arg timestamp "${TIMESTAMP}" \
|
|
241
|
+
--arg score "${REVIEW_SCORE}" \
|
|
242
|
+
--arg result "${REVIEW_RESULT}" \
|
|
243
|
+
--arg findings "${REVIEW_FINDINGS:-0}" \
|
|
244
|
+
--arg high "${REVIEW_HIGH:-0}" \
|
|
245
|
+
--arg pr_number "${PR_NUMBER}" \
|
|
246
|
+
'{team_id:$team_id,repo:$repo,timestamp:$timestamp,prism_level:2,
|
|
247
|
+
metric:{name:"eval_score",value:($score|tonumber),unit:"score"},
|
|
248
|
+
ai_context:{tool:"kiro-cli",model:"claude-haiku-4.5",origin:"ai-generated"},
|
|
249
|
+
eval:{score:($score|tonumber),result:$result,rubric:"kiro-headless",
|
|
250
|
+
findings:($findings|tonumber),high_findings:($high|tonumber),
|
|
251
|
+
pr_number:($pr_number|tonumber),method:"kiro-headless"}}')
|
|
252
|
+
aws events put-events --region us-west-2 --entries \
|
|
253
|
+
"[{\"Source\":\"prism.d1.velocity\",\"DetailType\":\"prism.d1.eval\",\"EventBusName\":\"prism-d1-metrics\",\"Detail\":$(echo "${DETAIL}" | jq -Rs '.')}]" || true
|
|
254
|
+
|
|
255
|
+
- name: AWS Continuum security scan (Tier 2 -- deterministic)
|
|
256
|
+
id: security-gate
|
|
257
|
+
if: steps.aws-creds.outcome == 'success'
|
|
258
|
+
env:
|
|
259
|
+
AWS_REGION: us-west-2
|
|
260
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
261
|
+
HEAD_SHA: ${{ github.sha }}
|
|
262
|
+
REPO: ${{ github.repository }}
|
|
263
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
264
|
+
run: |
|
|
265
|
+
# Pre-flight: verify the specific subcommand exists (AWS CLI v2.36+)
|
|
266
|
+
if ! aws securityagent start-code-review-job help > /dev/null 2>&1; then
|
|
267
|
+
echo "::warning::AWS CLI lacks securityagent code review commands (need v2.36+). Skipping scan."
|
|
268
|
+
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
269
|
+
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
270
|
+
exit 0
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
AGENT_SPACE_ID=$(aws ssm get-parameter --name /prism/continuum/agent-space-id --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
274
|
+
SCAN_BUCKET=$(aws ssm get-parameter --name /prism/continuum/scan-bucket --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
275
|
+
|
|
276
|
+
if [ -z "$AGENT_SPACE_ID" ] || [ -z "$SCAN_BUCKET" ]; then
|
|
277
|
+
echo "AWS Continuum not configured (SSM params missing). Skipping security scan."
|
|
278
|
+
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
279
|
+
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
280
|
+
exit 0
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
REPO_SLUG=$(echo "${REPO}" | tr '/' '-')
|
|
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 "")
|
|
285
|
+
if [ -z "$CODE_REVIEW_ID" ]; then
|
|
286
|
+
echo "::warning::No Code Review resource for ${REPO_SLUG} -- run 'prism-cli securityagent setup' first. Skipping."
|
|
287
|
+
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
288
|
+
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
289
|
+
exit 0
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# Diff must be uploaded with .patch extension (API requirement)
|
|
293
|
+
git diff "${BASE_SHA}..${HEAD_SHA}" > /tmp/changes.patch
|
|
294
|
+
if [ ! -s /tmp/changes.patch ]; then
|
|
295
|
+
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
296
|
+
exit 0
|
|
297
|
+
fi
|
|
298
|
+
DIFF_KEY="diffs/${REPO_SLUG}/${PR_NUMBER}/${HEAD_SHA}.patch"
|
|
299
|
+
aws s3 cp /tmp/changes.patch "s3://${SCAN_BUCKET}/${DIFF_KEY}" --quiet
|
|
300
|
+
|
|
301
|
+
JOB_ID=$(aws securityagent start-code-review-job \
|
|
302
|
+
--agent-space-id "${AGENT_SPACE_ID}" \
|
|
303
|
+
--code-review-id "${CODE_REVIEW_ID}" \
|
|
304
|
+
--diff-source "{\"s3Uri\":\"s3://${SCAN_BUCKET}/${DIFF_KEY}\"}" \
|
|
305
|
+
--query 'codeReviewJobId' --output text)
|
|
306
|
+
echo "Continuum job started: ${JOB_ID}"
|
|
307
|
+
|
|
308
|
+
STATUS="UNKNOWN"
|
|
309
|
+
for i in $(seq 1 30); do
|
|
310
|
+
sleep 30
|
|
311
|
+
STATUS=$(aws securityagent batch-get-code-review-jobs \
|
|
312
|
+
--agent-space-id "${AGENT_SPACE_ID}" \
|
|
313
|
+
--code-review-job-ids "${JOB_ID}" \
|
|
314
|
+
--query 'codeReviewJobs[0].status' --output text 2>/dev/null || echo "UNKNOWN")
|
|
315
|
+
echo " Poll ${i}/30: ${STATUS}"
|
|
316
|
+
case "$STATUS" in COMPLETED|FAILED|STOPPED) break ;; esac
|
|
317
|
+
done
|
|
318
|
+
|
|
319
|
+
if [ "$STATUS" != "COMPLETED" ]; then
|
|
320
|
+
echo "::warning::Continuum scan did not complete (status: ${STATUS}) -- treating as skipped"
|
|
321
|
+
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
322
|
+
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
323
|
+
exit 0
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# NOTE: list-findings takes agent-space-id + code-review-job-id only.
|
|
327
|
+
# Response field is 'findingsSummaries', risk field is 'riskLevel'.
|
|
328
|
+
FINDINGS_JSON=$(aws securityagent list-findings \
|
|
329
|
+
--agent-space-id "${AGENT_SPACE_ID}" \
|
|
330
|
+
--code-review-job-id "${JOB_ID}" \
|
|
331
|
+
--output json 2>/dev/null || echo '{"findingsSummaries":[]}')
|
|
332
|
+
|
|
333
|
+
TOTAL=$(echo "$FINDINGS_JSON" | jq '.findingsSummaries | length')
|
|
334
|
+
CRITICAL=$(echo "$FINDINGS_JSON" | jq '[.findingsSummaries[] | select(.riskLevel == "CRITICAL")] | length')
|
|
335
|
+
HIGH=$(echo "$FINDINGS_JSON" | jq '[.findingsSummaries[] | select(.riskLevel == "HIGH")] | length')
|
|
336
|
+
|
|
337
|
+
echo "Continuum findings: ${TOTAL} total (${CRITICAL} critical, ${HIGH} high)"
|
|
338
|
+
echo "findings=${TOTAL}" >> "$GITHUB_OUTPUT"
|
|
339
|
+
echo "critical=${CRITICAL}" >> "$GITHUB_OUTPUT"
|
|
340
|
+
echo "high=${HIGH}" >> "$GITHUB_OUTPUT"
|
|
341
|
+
|
|
342
|
+
if [ "$TOTAL" -gt 0 ]; then
|
|
343
|
+
echo "$FINDINGS_JSON" | jq -r '.findingsSummaries[] | "::warning::[\(.riskLevel)] \(.name)"'
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
- name: Fail on eval gate failure
|
|
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')) }}
|
|
348
|
+
env:
|
|
349
|
+
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
350
|
+
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|
|
351
|
+
REVIEW_SCORE: ${{ steps.review.outputs.score }}
|
|
352
|
+
REVIEW_SUMMARY: ${{ steps.review.outputs.summary }}
|
|
353
|
+
SEC_CRITICAL: ${{ steps.security-gate.outputs.critical }}
|
|
354
|
+
SEC_HIGH: ${{ steps.security-gate.outputs.high }}
|
|
355
|
+
run: |
|
|
356
|
+
if [ "${REVIEW_RESULT}" = "ERROR" ]; then
|
|
357
|
+
echo "❌ Eval gate failed closed: review output could not be parsed"
|
|
358
|
+
elif [ "${REVIEW_RESULT}" = "FAIL" ]; then
|
|
359
|
+
echo "❌ Kiro found ${REVIEW_HIGH:-?} high-severity issues (score: ${REVIEW_SCORE:-?})"
|
|
360
|
+
echo "Summary: ${REVIEW_SUMMARY}"
|
|
361
|
+
fi
|
|
362
|
+
if [ "${SEC_CRITICAL:-0}" -gt 0 ] || [ "${SEC_HIGH:-0}" -gt 0 ]; then
|
|
363
|
+
echo "❌ Continuum found ${SEC_CRITICAL:-0} critical, ${SEC_HIGH:-0} high security findings"
|
|
364
|
+
fi
|
|
365
|
+
exit 1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-eval-harness.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/install-eval-harness.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"install-eval-harness.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/install-eval-harness.ts"],"names":[],"mappings":";;;;;;iBAyBqB;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE;;AATtH,wBAmCE"}
|
|
@@ -14,10 +14,11 @@ function prompt(question, defaultValue) {
|
|
|
14
14
|
export default {
|
|
15
15
|
description: 'Install eval harness (script + config + optional rubrics)',
|
|
16
16
|
options: [
|
|
17
|
-
{ flags: '--
|
|
18
|
-
{ flags: '--
|
|
17
|
+
{ flags: '--mode <mode>', description: 'Eval mode: "kiro" (recommended, headless kiro-cli) or "bedrock" (legacy run-eval.sh)' },
|
|
18
|
+
{ flags: '--with-rubrics', description: 'Include production rubrics (bedrock mode only)' },
|
|
19
|
+
{ flags: '--model <id>', description: 'Bedrock model ID for evaluation (bedrock mode only)' },
|
|
19
20
|
{ flags: '--threshold <n>', description: 'Pass threshold (0-1)' },
|
|
20
|
-
{ flags: '--uninstall', description: 'Remove eval-harness directory' },
|
|
21
|
+
{ flags: '--uninstall', description: 'Remove eval-harness directory and steering files' },
|
|
21
22
|
],
|
|
22
23
|
async action(opts) {
|
|
23
24
|
const gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
@@ -27,74 +28,139 @@ export default {
|
|
|
27
28
|
execSync(`rm -rf "${targetDir}"`);
|
|
28
29
|
console.log('✓ Removed .prism/eval-harness/');
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const steeringFile = resolve(gitRoot, '.kiro/steering/code-review.md');
|
|
32
|
+
if (existsSync(steeringFile)) {
|
|
33
|
+
execSync(`rm -f "${steeringFile}"`);
|
|
34
|
+
console.log('✓ Removed .kiro/steering/code-review.md');
|
|
32
35
|
}
|
|
33
36
|
return;
|
|
34
37
|
}
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
// Determine mode
|
|
39
|
+
const mode = opts.mode || await prompt('Eval mode (kiro = recommended, bedrock = legacy)', 'kiro');
|
|
40
|
+
if (mode === 'kiro') {
|
|
41
|
+
await installKiroMode(gitRoot, opts);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await installBedrockMode(gitRoot, targetDir, opts);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
async function installKiroMode(gitRoot, opts) {
|
|
49
|
+
// --- Install steering file ---
|
|
50
|
+
const steeringDir = resolve(gitRoot, '.kiro/steering');
|
|
51
|
+
mkdirSync(steeringDir, { recursive: true });
|
|
52
|
+
const steeringSrc = resolve(EVAL_SOURCE, 'steering/code-review.md');
|
|
53
|
+
const steeringDest = resolve(steeringDir, 'code-review.md');
|
|
54
|
+
if (existsSync(steeringDest)) {
|
|
55
|
+
const overwrite = await prompt('Steering file already exists. Overwrite? [y/N]', 'n');
|
|
56
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
57
|
+
console.log(' Kept existing .kiro/steering/code-review.md');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
copyFileSync(steeringSrc, steeringDest);
|
|
61
|
+
console.log('✓ Updated .kiro/steering/code-review.md');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
copyFileSync(steeringSrc, steeringDest);
|
|
66
|
+
console.log('✓ Installed .kiro/steering/code-review.md');
|
|
67
|
+
}
|
|
68
|
+
// --- Install workflow ---
|
|
69
|
+
const workflowsDir = resolve(gitRoot, '.github/workflows');
|
|
70
|
+
const workflowSrc = getAssetPath(import.meta.url, 'bootstrapper/github-workflows/prism-eval-gate-kiro.yml');
|
|
71
|
+
if (existsSync(workflowSrc)) {
|
|
72
|
+
mkdirSync(workflowsDir, { recursive: true });
|
|
73
|
+
const dest = resolve(workflowsDir, 'prism-eval-gate.yml');
|
|
74
|
+
if (existsSync(dest)) {
|
|
75
|
+
const overwrite = await prompt('Workflow already exists. Overwrite? [y/N]', 'n');
|
|
76
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
77
|
+
console.log(' Skipped workflow.');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
copyFileSync(workflowSrc, dest);
|
|
81
|
+
console.log('✓ Updated .github/workflows/prism-eval-gate.yml');
|
|
60
82
|
}
|
|
61
|
-
console.log(`✓ Installed ${readdirSync(resolve(targetDir, 'rubrics')).length} production rubrics`);
|
|
62
83
|
}
|
|
63
84
|
else {
|
|
64
|
-
|
|
85
|
+
copyFileSync(workflowSrc, dest);
|
|
86
|
+
console.log('✓ Installed .github/workflows/prism-eval-gate.yml');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.log('\n════════════════════════════════════════════════');
|
|
90
|
+
console.log(' ✅ Kiro eval gate installed!');
|
|
91
|
+
console.log('════════════════════════════════════════════════');
|
|
92
|
+
console.log('\n Mode: kiro-cli headless');
|
|
93
|
+
console.log(' Rules: .kiro/steering/code-review.md');
|
|
94
|
+
console.log(' Workflow: .github/workflows/prism-eval-gate.yml');
|
|
95
|
+
console.log('\n Required: Add KIRO_API_KEY as a GitHub repository secret');
|
|
96
|
+
console.log(' Generate at: https://app.kiro.dev → Settings → API Keys');
|
|
97
|
+
console.log('\n Optional: Add PRISM_METRICS_ROLE_ARN for EventBridge metrics');
|
|
98
|
+
console.log('');
|
|
99
|
+
}
|
|
100
|
+
async function installBedrockMode(gitRoot, targetDir, opts) {
|
|
101
|
+
// --- Config ---
|
|
102
|
+
const model = opts.model || await prompt('Eval model ID', 'us.anthropic.claude-haiku-4-5-20251001-v1:0');
|
|
103
|
+
const threshold = opts.threshold || await prompt('Pass threshold', '0.82');
|
|
104
|
+
const region = await prompt('AWS region', 'us-west-2');
|
|
105
|
+
// --- Install script + config ---
|
|
106
|
+
mkdirSync(resolve(targetDir, 'rubrics'), { recursive: true });
|
|
107
|
+
// Copy run-eval.sh
|
|
108
|
+
copyFileSync(resolve(EVAL_SOURCE, 'run-eval.sh'), resolve(targetDir, 'run-eval.sh'));
|
|
109
|
+
chmodSync(resolve(targetDir, 'run-eval.sh'), 0o755);
|
|
110
|
+
console.log('✓ Installed .prism/eval-harness/run-eval.sh');
|
|
111
|
+
// Write eval-config.json
|
|
112
|
+
const config = {
|
|
113
|
+
pass_threshold: parseFloat(threshold),
|
|
114
|
+
eval_model_id: model,
|
|
115
|
+
aws_region: region,
|
|
116
|
+
event_bus: 'prism-d1-metrics',
|
|
117
|
+
emit_to_eventbridge: true,
|
|
118
|
+
};
|
|
119
|
+
writeFileSync(resolve(targetDir, 'eval-config.json'), JSON.stringify(config, null, 2) + '\n');
|
|
120
|
+
console.log('✓ Created .prism/eval-harness/eval-config.json');
|
|
121
|
+
// --- Rubrics ---
|
|
122
|
+
if (opts.withRubrics) {
|
|
123
|
+
const rubricsSrc = resolve(EVAL_SOURCE, 'rubrics');
|
|
124
|
+
for (const file of readdirSync(rubricsSrc).filter(f => f.endsWith('.json'))) {
|
|
125
|
+
copyFileSync(resolve(rubricsSrc, file), resolve(targetDir, 'rubrics', file));
|
|
65
126
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
127
|
+
console.log(`✓ Installed ${readdirSync(resolve(targetDir, 'rubrics')).length} production rubrics`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log('✓ rubrics/ directory created (empty — add your own rubric JSON files)');
|
|
131
|
+
}
|
|
132
|
+
// --- Workflow ---
|
|
133
|
+
const workflowsDir = resolve(gitRoot, '.github/workflows');
|
|
134
|
+
const workflowSrc = getAssetPath(import.meta.url, 'bootstrapper/github-workflows/prism-eval-gate.yml');
|
|
135
|
+
if (existsSync(workflowSrc)) {
|
|
136
|
+
mkdirSync(workflowsDir, { recursive: true });
|
|
137
|
+
const dest = resolve(workflowsDir, 'prism-eval-gate.yml');
|
|
138
|
+
if (existsSync(dest)) {
|
|
139
|
+
const overwrite = await prompt('Workflow already exists. Overwrite? [y/N]', 'n');
|
|
140
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
141
|
+
console.log(' Skipped workflow.');
|
|
81
142
|
}
|
|
82
143
|
else {
|
|
83
144
|
copyFileSync(workflowSrc, dest);
|
|
84
|
-
console.log('✓
|
|
145
|
+
console.log('✓ Updated .github/workflows/prism-eval-gate.yml');
|
|
85
146
|
}
|
|
86
147
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
console.log(`\n Model: ${model}`);
|
|
91
|
-
console.log(` Threshold: ${threshold}`);
|
|
92
|
-
console.log(` Rubrics: ${opts.withRubrics ? 'production set' : 'empty (add your own)'}`);
|
|
93
|
-
if (!opts.withRubrics) {
|
|
94
|
-
console.log('\n Next: Create a rubric at .prism/eval-harness/rubrics/my-rubric.json');
|
|
95
|
-
console.log(' See: bootstrapper/eval-harness/rubrics/ for examples');
|
|
148
|
+
else {
|
|
149
|
+
copyFileSync(workflowSrc, dest);
|
|
150
|
+
console.log('✓ Installed .github/workflows/prism-eval-gate.yml');
|
|
96
151
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
152
|
+
}
|
|
153
|
+
console.log('\n════════════════════════════════════════════════');
|
|
154
|
+
console.log(' ✅ Eval harness installed (legacy Bedrock mode)!');
|
|
155
|
+
console.log('════════════════════════════════════════════════');
|
|
156
|
+
console.log(`\n Model: ${model}`);
|
|
157
|
+
console.log(` Threshold: ${threshold}`);
|
|
158
|
+
console.log(` Rubrics: ${opts.withRubrics ? 'production set' : 'empty (add your own)'}`);
|
|
159
|
+
if (!opts.withRubrics) {
|
|
160
|
+
console.log('\n Next: Create a rubric at .prism/eval-harness/rubrics/my-rubric.json');
|
|
161
|
+
console.log(' See: bootstrapper/eval-harness/rubrics/ for examples');
|
|
162
|
+
}
|
|
163
|
+
console.log('\n 💡 Consider upgrading to kiro mode: prism-cli bootstrapper install-eval-harness --mode kiro');
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
100
166
|
//# sourceMappingURL=install-eval-harness.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-eval-harness.js","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/install-eval-harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAgB,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACnH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,2BAA2B,CAAC,CAAC;AAE/E,SAAS,MAAM,CAAC,QAAgB,EAAE,YAAqB;IACrD,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9H,CAAC;AAED,eAAe;IACb,WAAW,EAAE,2DAA2D;IACxE,OAAO,EAAE;QACP,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"install-eval-harness.js","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/install-eval-harness.ts"],"names":[],"mappings":"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"}
|