Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 38 additions & 0 deletions .github/contributor-trust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Contributor Trust Policy — opensource-bazaar
#
# Tunables for .github/workflows/pr-contributor-trust.yml.
# Keep this file flat YAML so maintainers can tweak thresholds without
# touching the workflow source.

version: 1

# Minimum acceptable GitHub account age for human PRs.
# Anything below this triggers the `trust/medium` label.
min_account_age_days: 7

# Maximum diff size (additions + deletions) before flagging as `trust/medium`.
# Set generously: 开源市集 accepts sizable refactors, only true drive-by
# mega-PRs should be flagged here.
max_diff_lines: 500

# Minimum PR description length (characters, after trim).
# Less than this is treated as a "low effort" signal.
min_description_chars: 20

# Risk levels → label mapping. Colors follow GitHub's standard palette:
# - low: 0e8a16 (green) → no label applied (skip)
# - medium: fbca04 (yellow) → trust/medium
# - high: d93f0b (red) → trust/high
risk_labels:
medium: trust/medium
high: trust/high

# PRs authored by accounts whose `type === "Bot"` OR whose login ends
# with `[bot]` are unconditionally skipped (the `preflight` job early-exits).
# Add additional bot-patterns below if needed.
bot_patterns:
- "[bot]" # GitHub Apps convention
- "-bot" # some self-hosted bots
- "dependabot"
- "renovate"
- "github-actions"
190 changes: 190 additions & 0 deletions .github/workflows/pr-contributor-trust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
name: PR - Contributor Trust (Bot Killer)

on:
pull_request_target:
types:
- opened
- reopened
- synchronize
- ready_for_review
workflow_dispatch:
inputs:
pr_number:
description: Pull request number to re-score
required: true
type: number

# Cancel in-progress re-runs for the same PR
concurrency:
group: contributor-trust-${{ github.event.pull_request.number || inputs.pr_number || github.run_id }}
cancel-in-progress: true

jobs:
preflight:
name: Preflight — detect bot authors
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
author_login: ${{ steps.inspect.outputs.author_login }}
pr_number: ${{ steps.inspect.outputs.pr_number }}
should_skip: ${{ steps.inspect.outputs.should_skip }}
skip_reason: ${{ steps.inspect.outputs.skip_reason }}
steps:
- name: Resolve PR metadata
id: inspect
uses: actions/github-script@v7
with:
script: |
const prNumber = context.eventName === "pull_request_target"
? context.payload.pull_request?.number
: Number(context.payload.inputs?.pr_number ?? "");

if (!Number.isInteger(prNumber) || prNumber <= 0)
throw new Error(`Unable to resolve a valid pull request number for event ${context.eventName}.`);

const { data: pullRequest } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});

const authorLogin = pullRequest.user?.login ?? "";
const authorType = pullRequest.user?.type ?? "";
// Skip if (a) GitHub recognises the author as a Bot account
// or (b) the username ends with `[bot]` (e.g. dependabot[bot])
const shouldSkip = authorType === "Bot" || authorLogin.toLowerCase().endsWith("[bot]");

core.setOutput("author_login", authorLogin);
core.setOutput("pr_number", String(prNumber));
core.setOutput("should_skip", shouldSkip ? "true" : "false");
core.setOutput(
"skip_reason",
shouldSkip ? `bot-authored PR by @${authorLogin}` : ""
);

- name: Write skip summary
if: steps.inspect.outputs.should_skip == 'true'
run: |
{
echo "## Contributor trust automation"
echo ""
echo "- Event: ${{ github.event_name }}"
echo "- PR: #${{ steps.inspect.outputs.pr_number }}"
echo "- Author: @${{ steps.inspect.outputs.author_login }}"
echo "- Result: skipped — ${{ steps.inspect.outputs.skip_reason }}"
} >> "$GITHUB_STEP_SUMMARY"

# ───────────────────────────────────────────────────────────────────────
# Phase 2 — Lightweight trust signals for human authors
#
# We deliberately avoid the heavy 100-point scoring system used by read-frog
# (which weighs repo stars, followers, etc.) — 开源市集 is a small community
# repo, so over-scoring would create more friction than safety.
#
# Instead we apply three cheap signals that catch the common Bot / low-quality
# PR patterns observed in this repo:
# 1. Account age (< 7 days = suspicious)
# 2. PR diff size ( > 500 lines OR zero-line change = suspicious)
# 3. Description length ( < 20 chars = suspicious)
#
# Combined signal is surfaced as a sticky PR comment + labels.
# ───────────────────────────────────────────────────────────────────────
trust-signals:
name: Compute trust signals
needs: preflight
if: needs.preflight.outputs.should_skip != 'true'
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Inspect PR (author age, diff size, description)
id: inspect
uses: actions/github-script@v7
with:
script: |
const prNumber = Number("${{ needs.preflight.outputs.pr_number }}");

const [{ data: pr }, { data: files }] = await Promise.all([
github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
}),
github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
per_page: 100,
}),
]);

const authorLogin = pr.user?.login ?? "";
const authorCreatedAt = pr.user?.created_at ?? pr.head?.user?.created_at;
const description = pr.body ?? "";
const totalAdditions = files.reduce((sum, f) => sum + (f.additions ?? 0), 0);
const totalDeletions = files.reduce((sum, f) => sum + (f.deletions ?? 0), 0);

const accountAgeDays = authorCreatedAt
? Math.floor((Date.now() - new Date(authorCreatedAt).getTime()) / (1000 * 60 * 60 * 24))
: null;

const flags = [];
if (accountAgeDays !== null && accountAgeDays < 7) {
flags.push(`account age ${accountAgeDays}d < 7d`);
}
if (totalAdditions + totalDeletions === 0) {
flags.push("empty diff");
} else if (totalAdditions + totalDeletions > 500) {
flags.push(`large diff (${totalAdditions + totalDeletions} lines)`);
}
if (description.trim().length < 20) {
flags.push(`short description (${description.trim().length} chars)`);
}

const riskLevel = flags.length >= 2 ? "high" : flags.length === 1 ? "medium" : "low";

const summary = [
`### PR contributor trust signals`,
``,
`- **Author**: @${authorLogin}${accountAgeDays !== null ? ` (account age: ${accountAgeDays}d)` : ""}`,
`- **Diff size**: +${totalAdditions} / -${totalDeletions} across ${files.length} files`,
`- **Description length**: ${description.trim().length} chars`,
`- **Risk level**: **${riskLevel}**`,
...(flags.length ? [`- **Flags**: ${flags.join(", ")}`] : ["- **Flags**: none"]),
].join("\n");

core.setOutput("risk_level", riskLevel);
core.setOutput("flags", flags.join("|"));
core.setOutput("summary", summary);
core.setOutput("author_login", authorLogin);
core.setOutput("pr_number", String(prNumber));

- name: Label PR by risk level
env:
RISK: ${{ steps.inspect.outputs.risk_level }}
PR_NUMBER: ${{ steps.inspect.outputs.pr_number }}
run: |
LABEL="trust/$RISK"
# Create the label if it doesn't exist
gh label create "$LABEL" \
--description "Auto-computed by pr-contributor-trust.yml" \
--color "$( [ "$RISK" = "high" ] && echo "d93f0b" || ( [ "$RISK" = "medium" ] && echo "fbca04" || echo "0e8a16" ) )" \
--force || true
gh issue edit "$PR_NUMBER" --add-label "$LABEL"

- name: Post trust summary as PR comment
if: steps.inspect.outputs.risk_level != 'low'
uses: actions/github-script@v7
with:
script: |
const summary = `${{ steps.inspect.outputs.summary }}`;
const prNumber = Number("${{ steps.inspect.outputs.pr_number }}");
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: summary + "\n\n— posted by `pr-contributor-trust.yml`",
});
63 changes: 63 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# AGENTS.md — Open Source Bazaar Agent Quick Reference

> 配套 [CONTRIBUTING.md](./CONTRIBUTING.md) § 7。本文件是给 AI Agent 的**精简指令卡**——可被 `read` 工具一次性加载。

## 项目一句话

Next.js v16 + TypeScript v5 + Bootstrap v5 多模块站点(开源市集官网),承载 `/finance` 等公益开源子项目。

## 命令速查

```bash
pnpm install # 装依赖(必须 pnpm)
pnpm dev # 本地起 http://localhost:3000
pnpm lint # ESLint
pnpm build # Next.js build
```

## 文件结构

```
components/ # 可复用 React 组件(PascalCase.tsx)
constants/ # 全局常量(SCREAMING_SNAKE_CASE)
lib/ # 工具函数 / Hooks(camelCase.ts)
models/ # 数据模型 / 类型定义
pages/ # Next.js 路由(kebab-case.ts/tsx)
public/ # 静态资源
styles/ # CSS Module + 全局样式
translation/ # i18n 文案(禁止硬编码)
types/ # 全局 TypeScript 类型
```

## 修改优先级

1. **i18n 必走** `translation/` —— 任何用户可见字符串必须走 t(key)
2. **不破坏 `master`** —— PR 必须从 feat/fix 分支提
3. **类型严格** —— 禁 `any`,导出去必须显式 `export`
4. **CI 必过** —— lint + build 全绿才允许 merge

## 任务接单 SOP(接 bounties 适用)

1. 扫镜像:Vikingr2023/awesome-agent-bounties open issues
2. 评估能力:跳过 mobile/unity/bluetooth 类
3. `/claim #<n>` 在 issue 下评论
4. fork → branch → 改 → lint/build → push → 提 PR → comment 链接
5. 48h 内交付;超时 `/unclaim`

## 禁止

- ❌ 任何凭据写入代码
- ❌ 修改非关联文件
- ❌ `[skip ci]` 未经批准
- ❌ 直接 push master

## 资源链接

- 上游:[idea2app/Lark-Next-Bootstrap-ts](https://github.com/idea2app/Lark-Next-Bootstrap-ts)
- CI:[GitHub Actions](https://github.com/Open-Source-Bazaar/Open-Source-Bazaar.github.io/actions)
- 部署:[Vercel](https://vercel.com)
- 镜像仓库:[Vikingr2023/awesome-agent-bounties](https://github.com/Vikingr2023/awesome-agent-bounties)

---

由 **laomao-31day001**(31day.cloud agent)· 2026-07-13 贡献。
Loading