Skip to content

Commit d307709

Browse files
author
StackMemory Bot (CLI)
committed
ci: add PR CI gate and AI review bot workflows
- pr-ci.yml: lint, test, build in parallel on PRs to main - pr-review.yml: Claude Sonnet reviews diffs, posts findings as PR comment Updates existing bot comment on force-push instead of spamming
1 parent 79ab15f commit d307709

2 files changed

Lines changed: 171 additions & 0 deletions

File tree

.github/workflows/pr-ci.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: PR CI
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
types: [opened, synchronize, reopened]
7+
8+
concurrency:
9+
group: pr-ci-${{ github.event.pull_request.number }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
lint:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version-file: '.nvmrc'
20+
cache: 'npm'
21+
- run: npm ci
22+
- run: npm run lint
23+
24+
test:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
- uses: actions/setup-node@v4
29+
with:
30+
node-version-file: '.nvmrc'
31+
cache: 'npm'
32+
- run: npm ci
33+
- run: npm run test:run
34+
env:
35+
CI: true
36+
37+
build:
38+
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@v4
41+
- uses: actions/setup-node@v4
42+
with:
43+
node-version-file: '.nvmrc'
44+
cache: 'npm'
45+
- run: npm ci
46+
- run: npm run build

.github/workflows/pr-review.yml

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: AI PR Review
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
types: [opened, synchronize]
7+
8+
concurrency:
9+
group: pr-review-${{ github.event.pull_request.number }}
10+
cancel-in-progress: true
11+
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
16+
jobs:
17+
review:
18+
runs-on: ubuntu-latest
19+
if: github.event.pull_request.draft == false
20+
steps:
21+
- uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Get PR diff
26+
id: diff
27+
run: |
28+
git fetch origin ${{ github.event.pull_request.base.ref }}
29+
DIFF=$(git diff origin/${{ github.event.pull_request.base.ref }}...HEAD -- '*.ts' '*.tsx' | head -c 80000)
30+
echo "diff<<DIFF_EOF" >> "$GITHUB_OUTPUT"
31+
echo "$DIFF" >> "$GITHUB_OUTPUT"
32+
echo "DIFF_EOF" >> "$GITHUB_OUTPUT"
33+
34+
- name: Get changed files
35+
id: files
36+
run: |
37+
git fetch origin ${{ github.event.pull_request.base.ref }}
38+
FILES=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD -- '*.ts' '*.tsx')
39+
echo "files<<FILES_EOF" >> "$GITHUB_OUTPUT"
40+
echo "$FILES" >> "$GITHUB_OUTPUT"
41+
echo "FILES_EOF" >> "$GITHUB_OUTPUT"
42+
43+
- name: AI Review
44+
id: review
45+
env:
46+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
47+
run: |
48+
BODY=$(cat <<'PROMPT'
49+
You are a senior TypeScript reviewer for StackMemory (AI context management, SQLite/FTS5, MCP tools).
50+
Review this PR diff for:
51+
1. **Bugs** — logic errors, off-by-one, null/undefined issues, resource leaks
52+
2. **Security** — SQL injection, secret exposure, unsafe input handling
53+
3. **Performance** — N+1 queries, missing indexes, unbounded loops, memory leaks
54+
4. **API design** — breaking changes, missing validation, inconsistent naming
55+
5. **Test gaps** — untested edge cases, missing error path coverage
56+
57+
Skip: style nits, import order, minor naming preferences.
58+
For each finding: file:line, severity (bug/warn/nit), what's wrong, suggested fix.
59+
If the PR looks good, say so briefly.
60+
61+
## Changed files
62+
${{ steps.files.outputs.files }}
63+
64+
## Diff
65+
```diff
66+
${{ steps.diff.outputs.diff }}
67+
```
68+
PROMPT
69+
)
70+
71+
RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
72+
-H "Content-Type: application/json" \
73+
-H "x-api-key: $ANTHROPIC_API_KEY" \
74+
-H "anthropic-version: 2023-06-01" \
75+
-d "$(jq -n \
76+
--arg body "$BODY" \
77+
'{
78+
model: "claude-sonnet-4-20250514",
79+
max_tokens: 4096,
80+
messages: [{role: "user", content: $body}]
81+
}')")
82+
83+
REVIEW=$(echo "$RESPONSE" | jq -r '.content[0].text // "Review failed: " + (.error.message // "unknown error")')
84+
85+
echo "review<<REVIEW_EOF" >> "$GITHUB_OUTPUT"
86+
echo "$REVIEW" >> "$GITHUB_OUTPUT"
87+
echo "REVIEW_EOF" >> "$GITHUB_OUTPUT"
88+
89+
- name: Post review comment
90+
uses: actions/github-script@v7
91+
with:
92+
script: |
93+
const review = `${{ steps.review.outputs.review }}`;
94+
if (!review || review.startsWith('Review failed:')) {
95+
core.warning('AI review failed: ' + review);
96+
return;
97+
}
98+
99+
// Find existing bot comment to update
100+
const { data: comments } = await github.rest.issues.listComments({
101+
owner: context.repo.owner,
102+
repo: context.repo.repo,
103+
issue_number: context.issue.number,
104+
});
105+
const botComment = comments.find(c =>
106+
c.user.type === 'Bot' && c.body.includes('<!-- ai-pr-review -->')
107+
);
108+
109+
const body = `<!-- ai-pr-review -->\n## AI Review\n\n${review}\n\n---\n*Automated review by Claude Sonnet*`;
110+
111+
if (botComment) {
112+
await github.rest.issues.updateComment({
113+
owner: context.repo.owner,
114+
repo: context.repo.repo,
115+
comment_id: botComment.id,
116+
body,
117+
});
118+
} else {
119+
await github.rest.issues.createComment({
120+
owner: context.repo.owner,
121+
repo: context.repo.repo,
122+
issue_number: context.issue.number,
123+
body,
124+
});
125+
}

0 commit comments

Comments
 (0)