Skip to content
Closed
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ac27ac2
ci(review): use bundled PR review workflow
yiliang114 May 11, 2026
65b4111
fix(ci): correct misleading comment about quote stripping in review w…
yiliang114 May 12, 2026
7275f86
fix(ci): satisfy workflow yaml lint
yiliang114 May 12, 2026
c02ac19
ci(review): add manual dry-run mode
yiliang114 May 12, 2026
03e0dbc
fix(ci): harden bundled PR review against prompt injection
yiliang114 May 12, 2026
6818309
Merge remote-tracking branch 'origin/main' into codex/bundled-pr-revi…
yiliang114 May 12, 2026
79fd36c
style(ci): format review workflow action pins
yiliang114 May 12, 2026
9b86ab8
fix(ci): scope OpenAI secrets to the review step + document --ci
yiliang114 May 12, 2026
4cd8549
docs(review): align --ci command allowlist with what Step 9 actually …
yiliang114 May 12, 2026
596a780
ci(review): use review-specific OpenAI credentials
yiliang114 May 12, 2026
8d5e041
ci(review): add smoke mode for PR diff checks
yiliang114 May 12, 2026
0e1eb30
fix(ci): correct smoke review summary output
yiliang114 May 12, 2026
0919a11
fix(ci): tighten @qwen /review grep boundary and fix sed multi-line e…
yiliang114 May 13, 2026
dbb875b
refactor(ci): use QwenLM/qwen-code-action instead of building CLI fro…
yiliang114 May 13, 2026
0c9ccb9
fix(ci): align prompt indentation in review_prompt construction
yiliang114 May 13, 2026
bcb2127
refactor(ci): add checkout step, move review rules to .github/, rever…
yiliang114 May 14, 2026
596edce
fix(ci): strip leading blanks from additional_instructions and clarif…
yiliang114 May 14, 2026
11a491e
fix(ci): add reopened/ready_for_review triggers and edited comment types
yiliang114 May 14, 2026
f157309
fix(ci): align sed extraction boundary with grep for @qwen /review
yiliang114 May 14, 2026
7e83c5e
fix(ci): guard review-rules copy against missing file before merge
yiliang114 May 14, 2026
39a314a
refactor(ci): move review-rules to .qwen/, harden with cross-repo che…
yiliang114 May 14, 2026
db9bc44
refactor(ci): move review-rules to .qwen/, harden with cross-repo che…
yiliang114 May 14, 2026
b3c2361
docs(review): make product-direction gate blocking by default
yiliang114 May 14, 2026
204837c
fix(ci): prevent review workflow from approving PRs
yiliang114 May 14, 2026
f00f2af
docs(review): document code review automation design
yiliang114 May 18, 2026
2e77544
ci(review): add synchronize trigger and persist incremental review cache
yiliang114 May 18, 2026
fa3dbd9
fix(ci): include base SHA in review-cache key to prevent cross-base c…
yiliang114 May 18, 2026
5217988
fix(ci): use merge-base SHA in cache key and gate save on comment pub…
yiliang114 May 18, 2026
e542b0d
fix(ci): skip merge-base lookup for cross-repository PRs
yiliang114 May 18, 2026
e9ac834
fix(ci): address review workflow gate issues
yiliang114 May 18, 2026
d253035
feat(review): add design gate preflight scripts
yiliang114 May 18, 2026
11d45c7
feat(review): add design gate rerun controls
yiliang114 May 18, 2026
4140257
test(review): expand act smoke coverage
yiliang114 May 18, 2026
660398a
fix(review): improve direction gate history recall
yiliang114 May 19, 2026
584fe30
docs(code-review): format comparison tables
yiliang114 May 19, 2026
36043db
fix(review): harden design-gate scripts per dogfooded review
yiliang114 May 19, 2026
424b459
docs(review): separate review criteria from workflow process in revie…
yiliang114 May 19, 2026
f63a944
fix: update copyright year to 2026 in new test file [skip ci]
yiliang114 May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 231 additions & 0 deletions .github/scripts/act-smoke-qwen-review.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#!/usr/bin/env bash
set -euo pipefail

repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"

usage() {
cat >&2 <<'EOF'
Usage:
.github/scripts/act-smoke-qwen-review.sh [pr-number] [case]

Cases:
pr-edited pull_request_target.edited body change; runs Design Gate only
design-gate-comment issue_comment "@qwen /design-gate"; runs Design Gate only
oversized pull_request_target.edited with a low size threshold; stops at size gate
all runs all safe local act cases above

The smoke cases run with ACT=true, do not post PR comments, and do not run the
deep /review action.
EOF
}

if ! command -v colima >/dev/null 2>&1; then
echo "colima is required. Install it with: brew install colima" >&2
exit 1
fi

if ! command -v docker >/dev/null 2>&1; then
echo "docker CLI is required. Install it with: brew install docker" >&2
exit 1
fi

if ! docker info >/dev/null 2>&1; then
echo "Starting Colima for local GitHub Actions smoke..."
colima start --cpu 2 --memory 4 --disk 20
fi

if ! command -v act >/dev/null 2>&1; then
echo "act is required. Install it with: brew install act" >&2
exit 1
fi

pr_number="${1:-}"
case_name="${2:-pr-edited}"
if [ -z "$pr_number" ] && command -v gh >/dev/null 2>&1; then
pr_number="$(gh pr view --json number --jq '.number' 2>/dev/null || true)"
fi

if [ -z "$pr_number" ]; then
usage
echo "Could not infer a PR number from the current branch." >&2
exit 1
fi

token="${GITHUB_TOKEN:-}"
if [ -z "$token" ] && command -v gh >/dev/null 2>&1; then
token="$(gh auth token 2>/dev/null || true)"
fi

if [ -z "$token" ]; then
echo "GITHUB_TOKEN is required for gh pr view/diff calls inside the workflow." >&2
echo "Set GITHUB_TOKEN or authenticate gh before running this smoke." >&2
exit 1
fi

tmp_dir="$(mktemp -d)"
log_dir="${ACT_SMOKE_LOG_DIR:-.qwen/tmp/act-smoke/$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$log_dir"

secret_file="$tmp_dir/secrets"
chmod 700 "$tmp_dir"
printf 'GITHUB_TOKEN=%s\n' "$token" > "$secret_file"
chmod 600 "$secret_file"

cleanup_generated_files() {
rm -f \
qwen-pr-review-size-comment.md \
qwen-pr-review-fork-comment.md \
qwen-design-gate-override-comment.md \
.qwen/tmp/qwen-review-pr-"$pr_number"-shape.json \
.qwen/tmp/qwen-review-pr-"$pr_number"-history.json \
.qwen/tmp/qwen-review-pr-"$pr_number"-design-gate.json \
.qwen/tmp/qwen-review-pr-"$pr_number"-design-gate-comment.md \
.qwen/tmp/qwen-review-pr-"$pr_number"-design-gate-prompt.md
}

trap 'rm -rf "$tmp_dir"; cleanup_generated_files' EXIT

platform_args=()
case "$(uname -m)" in
arm64|aarch64)
platform_args=(--container-architecture linux/arm64)
;;
esac

write_event() {
local name="$1"
local event_file="$2"

case "$name" in
pr-edited|oversized)
cat > "$event_file" <<JSON
{
"action": "edited",
"changes": {
"body": {
"from": "previous body"
}
},
"pull_request": {
"number": $pr_number,
"author_association": "MEMBER"
},
"repository": {
"full_name": "${GITHUB_REPOSITORY:-QwenLM/qwen-code}"
},
"sender": {
"login": "${GITHUB_ACTOR:-local-act-smoke}"
}
}
JSON
;;
design-gate-comment)
cat > "$event_file" <<JSON
{
"action": "created",
"issue": {
"number": $pr_number,
"pull_request": {
"url": "https://api.github.com/repos/${GITHUB_REPOSITORY:-QwenLM/qwen-code}/pulls/$pr_number"
}
},
"comment": {
"body": "@qwen /design-gate",
"author_association": "MEMBER"
},
"repository": {
"full_name": "${GITHUB_REPOSITORY:-QwenLM/qwen-code}"
},
"sender": {
"login": "${GITHUB_ACTOR:-local-act-smoke}"
}
}
JSON
;;
*)
usage
echo "Unknown smoke case: $name" >&2
exit 1
;;
esac
}

event_name_for_case() {
case "$1" in
pr-edited|oversized)
printf 'pull_request_target'
;;
design-gate-comment)
printf 'issue_comment'
;;
esac
}

max_lines_for_case() {
case "$1" in
oversized)
printf '100'
;;
*)
printf '50000'
;;
esac
}

summarize_log() {
local name="$1"
local log_file="$2"

echo "----- $name summary -----"
grep -E 'Run Main (Resolve PR context|Check PR size|Generate PR shape|Scan review history|Run Design Gate)|::set-output:: (should_run_review|gate_only|bypass_design_gate|should_review|status)=|Review scope:|Design Gate status:|Job succeeded|Job failed|Failure -' "$log_file" || true
echo "Log: $log_file"
}

run_case() {
local name="$1"
local event_file="$tmp_dir/$name.json"
local log_file="$log_dir/$name.log"
local event_name
local max_lines

event_name="$(event_name_for_case "$name")"
max_lines="$(max_lines_for_case "$name")"
write_event "$name" "$event_file"
cleanup_generated_files

echo "===== Running act smoke case: $name ($event_name) ====="
if act "$event_name" \
--bind \
-W .github/workflows/qwen-code-pr-review.yml \
-j review-pr \
-e "$event_file" \
-P ubuntu-latest="${ACT_UBUNTU_IMAGE:-catthehacker/ubuntu:act-latest}" \
--var QWEN_PR_REVIEW_MODEL=local-act-smoke \
--var QWEN_PR_REVIEW_MAX_CHANGED_LINES="$max_lines" \
--var QWEN_DESIGN_GATE_LLM=false \
--env ACT=true \
--secret-file "$secret_file" \
"${platform_args[@]}" 2>&1 | tee "$log_file"; then
summarize_log "$name" "$log_file"
else
summarize_log "$name" "$log_file"
exit 1
fi
}

case "$case_name" in
all)
run_case pr-edited
run_case design-gate-comment
run_case oversized
;;
pr-edited|design-gate-comment|oversized)
run_case "$case_name"
;;
*)
usage
echo "Unknown smoke case: $case_name" >&2
exit 1
;;
esac
98 changes: 98 additions & 0 deletions .github/scripts/design-gate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env node

import {
parseArgs,
readJson,
requireArg,
run,
writeJson,
writeText,
} from './lib/cli.mjs';
import { loadAnchors } from './lib/anchors.mjs';
import {
buildDesignGateLlmPrompt,
evaluateDesignGate,
formatProcessComment,
formatPromptAppend,
} from './lib/design-gate-core.mjs';
import { runQwenJson } from './lib/llm.mjs';

async function maybeRunLlm({ pr, shape, history, anchors }) {
if (process.env.QWEN_DESIGN_GATE_LLM !== 'true') {

Check failure on line 21 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'process' is not defined

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] These new .github/scripts/**/*.mjs helpers are not covered by the repo's Node-script ESLint override, so npm run lint:ci -- --quiet now fails on the added scripts with no-undef for process / console (for example this line, plus the new console.log calls and other helpers). The existing override in eslint.config.js only covers ./scripts/**/*.js, ./scripts/**/*.mjs, esbuild.config.js, and packages/*/scripts/**/*.js.

Please extend that override to include the new GitHub helper scripts, e.g. add .github/scripts/**/*.mjs (and .github/scripts/**/*.js if desired) so the repository lint job can pass.

— gpt-5.5 via Qwen Code /review

return { findings: [] };
}

try {
const result = await runQwenJson({
prompt: buildDesignGateLlmPrompt({ pr, shape, history, anchors }),
});
return result.json && Array.isArray(result.json.findings)
? result.json
: { findings: [] };
} catch (error) {
return {
findings: [
{
gate: 'product_direction',
severity: 'advisory',
message: `Design Gate LLM check skipped: ${error.message}`,
citations: [],
},
],
};
}
}

async function main() {
const args = parseArgs();
const repo = requireArg(args, 'repo');
const prNumber = requireArg(args, 'pr');
console.log('Design Gate: reading PR shape');

Check failure on line 50 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'console' is not defined
const shape = await readJson(requireArg(args, 'shape'));
console.log('Design Gate: reading history scan');

Check failure on line 52 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'console' is not defined
const history = await readJson(requireArg(args, 'history'), { findings: [] });
const out = requireArg(args, 'out');

console.log('Design Gate: resolving PR metadata');

Check failure on line 56 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'console' is not defined
const pr =
(await readJson(args['pr-json'], undefined)) ??
Comment thread
yiliang114 marked this conversation as resolved.
JSON.parse(
await run('gh', [
'pr',
'view',
prNumber,
'--repo',
repo,
'--json',
'title,body,url,baseRefName,headRefName',
]),
);

console.log('Design Gate: loading anchors');

Check failure on line 71 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'console' is not defined
const anchors = await loadAnchors({
rootDir: args.root ?? process.cwd(),

Check failure on line 73 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'process' is not defined
changedFiles: shape.changed_files,
});
console.log(

Check failure on line 76 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'console' is not defined
`Design Gate: running LLM check (enabled=${process.env.QWEN_DESIGN_GATE_LLM === 'true'})`,

Check failure on line 77 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'process' is not defined
);
const llm = await maybeRunLlm({ pr, shape, history, anchors });
console.log('Design Gate: evaluating findings');

Check failure on line 80 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'console' is not defined
const result = evaluateDesignGate({ pr, shape, history, anchors, llm });

await writeJson(out, result);
if (args['process-comment-out']) {
await writeText(args['process-comment-out'], formatProcessComment(result));
}
if (args['prompt-append-out']) {
await writeText(args['prompt-append-out'], formatPromptAppend(result));
}

console.log(`Design Gate status: ${result.status}`);

Check failure on line 91 in .github/scripts/design-gate.mjs

View workflow job for this annotation

GitHub Actions / Lint

'console' is not defined
console.log(result.summary);
}

main().catch((error) => {
console.error(error.stack ?? error.message);
process.exit(1);
});
41 changes: 41 additions & 0 deletions .github/scripts/history-scan.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node

import { parseArgs, readJson, requireArg, run, writeJson } from './lib/cli.mjs';
import { scanHistory } from './lib/gh-search.mjs';

async function main() {
const args = parseArgs();
const repo = requireArg(args, 'repo');
const prNumber = requireArg(args, 'pr');
const shape = await readJson(requireArg(args, 'shape'));
const out = requireArg(args, 'out');

const pr =
(await readJson(args['pr-json'], undefined)) ??
JSON.parse(
await run('gh', [
'pr',
'view',
prNumber,
'--repo',
repo,
'--json',
'title,body,url',
]),
);

const history = await scanHistory({
repo,
pr,
shape,
limit: Number(args.limit ?? 20),
});

await writeJson(out, history);
console.log(`Wrote history scan to ${out}`);
}

main().catch((error) => {
console.error(error.stack ?? error.message);
process.exit(1);
});
Loading
Loading