Skip to content

feat(git): 添加控制台输出显示 #55

feat(git): 添加控制台输出显示

feat(git): 添加控制台输出显示 #55

name: Lithe PR review
on:
issue_comment:
types: [created]
permissions:
contents: read
env:
LITHE_CODEX_MODEL: gpt-5.6-sol
jobs:
prepare:
if: >-
github.event.issue.pull_request != null &&
github.event.comment.body == '@lithe review'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
issues: write
pull-requests: write
outputs:
authorized: ${{ steps.prepare.outputs.authorized }}
base_sha: ${{ steps.prepare.outputs.base_sha }}
head_sha: ${{ steps.prepare.outputs.head_sha }}
review_mode: ${{ steps.prepare.outputs.review_mode }}
steps:
- name: Check out trusted review tooling
uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 1
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
- name: Test review context builder
run: node --test scripts/test-prepare-lithe-*review*.mjs
- name: Prepare trusted review context
id: prepare
env:
GH_TOKEN: ${{ github.token }}
LITHE_ALLOWED_REVIEWERS: ${{ vars.LITHE_ALLOWED_REVIEWERS }}
LITHE_PROMPT_OUTPUT: ${{ runner.temp }}/lithe-pr-review-prompt.md
run: node scripts/prepare-lithe-pr-review.mjs
- name: Publish review acknowledgement
if: steps.prepare.outputs.authorized == 'true'
uses: actions/github-script@v7
env:
LITHE_HEAD_SHA: ${{ steps.prepare.outputs.head_sha }}
LITHE_REVIEW_MODE: ${{ steps.prepare.outputs.review_mode }}
with:
github-token: ${{ github.token }}
script: |
const marker = `<!-- lithe-review:${context.issue.number}:${process.env.LITHE_HEAD_SHA} -->`;
const mode = process.env.LITHE_REVIEW_MODE === 'large' ? '大型变更' : '标准';
const body = `${marker}\n## Lithe Review\n\n⏳ 正在以${mode}模式审查,完成或失败后会更新本评论。`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
});
const previous = comments.find((comment) =>
comment.user?.login === 'github-actions[bot]' && comment.body?.startsWith(marker)
);
if (previous) {
await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: previous.id, body });
} else {
await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body });
}
- name: Upload trusted review input
if: steps.prepare.outputs.authorized == 'true'
uses: actions/upload-artifact@v4
with:
name: lithe-review-input-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/lithe-pr-review-prompt.md
if-no-files-found: error
retention-days: 1
review:
needs: prepare
if: needs.prepare.outputs.authorized == 'true'
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: lithe-pr-review-${{ github.repository }}-${{ github.event.issue.number }}-${{ needs.prepare.outputs.head_sha }}
cancel-in-progress: false
permissions:
contents: read
outputs:
final_message: ${{ steps.finalize.outputs.final_message }}
review_outcome: ${{ steps.finalize.outputs.review_outcome }}
steps:
- name: Check out trusted review tooling
uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 1
persist-credentials: false
- name: Stage trusted review tooling
env:
LITHE_TOOLING: ${{ runner.temp }}/lithe-review-tooling
run: |
set -euo pipefail
mkdir -p "$LITHE_TOOLING"
install -m 755 scripts/prepare-lithe-review-stage.mjs "$LITHE_TOOLING/stage.mjs"
install -m 600 .github/lithe-review/final.schema.json "$LITHE_TOOLING/final.schema.json"
- name: Download trusted review input
uses: actions/download-artifact@v4
with:
name: lithe-review-input-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/lithe-review-input
- name: Check out the complete pull request head
uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.head_sha }}
fetch-depth: 0
persist-credentials: false
- name: Verify checked-out revision
env:
LITHE_BASE_SHA: ${{ needs.prepare.outputs.base_sha }}
LITHE_HEAD_SHA: ${{ needs.prepare.outputs.head_sha }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$LITHE_HEAD_SHA"
git cat-file -e "${LITHE_BASE_SHA}^{commit}"
- name: Review pull request with Codex
id: codex
continue-on-error: true
uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.LITHE_CODEX_API_KEY }}
responses-api-endpoint: ${{ secrets.LITHE_CODEX_RESPONSES_URL }}
prompt-file: ${{ runner.temp }}/lithe-review-input/lithe-pr-review-prompt.md
output-file: ${{ runner.temp }}/lithe-review-final-candidate.json
output-schema-file: ${{ runner.temp }}/lithe-review-tooling/final.schema.json
model: ${{ env.LITHE_CODEX_MODEL }}
effort: medium
sandbox: read-only
safety-strategy: drop-sudo
allow-users: ${{ github.actor }}
- name: Finalize review output
if: always()
id: finalize
run: >-
node "${{ runner.temp }}/lithe-review-tooling/stage.mjs" finalize
--candidate "${{ runner.temp }}/lithe-review-final-candidate.json"
--output "${{ runner.temp }}/lithe-review-final.md"
- name: Upload review diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: lithe-review-${{ github.event.issue.number }}-${{ github.run_attempt }}
path: |
${{ runner.temp }}/lithe-review-final-candidate.json
${{ runner.temp }}/lithe-review-final.md
if-no-files-found: warn
retention-days: 14
- name: Report review failure
if: always() && steps.finalize.outputs.review_outcome != 'success'
run: |
echo "Codex review did not produce a valid final result" >&2
exit 1
publish:
needs: [prepare, review]
if: >-
always() &&
needs.prepare.outputs.authorized == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: write
pull-requests: write
steps:
- name: Publish or update the review
uses: actions/github-script@v7
env:
LITHE_FINAL_MESSAGE: ${{ needs.review.outputs.final_message }}
LITHE_HEAD_SHA: ${{ needs.prepare.outputs.head_sha }}
LITHE_REVIEW_OUTCOME: ${{ needs.review.outputs.review_outcome }}
LITHE_REVIEW_JOB_RESULT: ${{ needs.review.result }}
LITHE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
github-token: ${{ github.token }}
script: |
const marker = `<!-- lithe-review:${context.issue.number}:${process.env.LITHE_HEAD_SHA} -->`;
const message = (process.env.LITHE_FINAL_MESSAGE ?? '').trim();
const limit = 65_000;
const failureMessage = [
'## Lithe Review',
'',
'**结论:** ❓ 审查未完成',
'',
`审查状态:\`${process.env.LITHE_REVIEW_OUTCOME || process.env.LITHE_REVIEW_JOB_RESULT}\`。`,
`请查看[本次 Actions 日志与诊断附件](${process.env.LITHE_RUN_URL})后重新召唤。`,
].join('\n');
const body = `${marker}\n${message || failureMessage}`.slice(0, limit);
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
});
const previous = comments.find((comment) =>
comment.user?.login === 'github-actions[bot]' && comment.body?.startsWith(marker)
);
if (previous) {
await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: previous.id, body });
} else {
await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body });
}