Skip to content

chore: add GitHub-reward form, scripts & actions#221

Open
TechQuery wants to merge 3 commits intoiflytek:mainfrom
Open-Source-Bazaar:GitHub-reward
Open

chore: add GitHub-reward form, scripts & actions#221
TechQuery wants to merge 3 commits intoiflytek:mainfrom
Open-Source-Bazaar:GitHub-reward

Conversation

@TechQuery
Copy link
Copy Markdown

@TechQuery TechQuery commented Apr 2, 2026

This pull request introduces a complete workflow for managing, distributing, and reporting rewards for completed issues, primarily through GitHub Actions, custom scripts, and templates. It includes new automation for reward assignment, tagging, distribution, and monthly statistics, as well as supporting scripts and type definitions.

Features

Reward Workflow Automation

  • Added a new issue template (reward-task.yml) for creating reward-based tasks, capturing details like description, currency, amount, and payer.
  • Introduced the claim-issue-reward.yml workflow to automatically distribute rewards when an issue is closed, extracting relevant data and invoking the reward-sharing script.
  • Implemented the share-reward.ts script to determine eligible users (excluding bots), split the reward, tag the merge commit with reward data, and comment the reward distribution on the issue.
  • Defined a Reward TypeScript interface to standardize reward data across scripts.

Reward Statistics and Reporting

  • Added the statistic-member-reward.yml workflow to run monthly, checking for new reward data and generating a summary of rewards per user and currency.
  • Created the count-reward.ts script to aggregate and summarize reward tags from the past month, group them by payee, and publish the statistics as a new tag and GitHub release.

Supporting Configuration

  • Added a minimal deno.json configuration file for script execution.

References

Copilot AI review requested due to automatic review settings April 2, 2026 18:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an automated “issue reward” workflow powered by GitHub Actions plus Deno scripts, enabling reward task creation via an issue template, automatic reward splitting/tagging on issue closure, and monthly reward statistics generation.

Changes:

  • Added a reward issue template to capture payer/currency/amount for “reward” issues.
  • Introduced an “issue closed” workflow that computes and records reward distribution (git tag + issue comment).
  • Added a scheduled monthly workflow + script to aggregate reward tags and publish per-user statistics (tag + GitHub release).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
.github/workflows/claim-issue-reward.yml Runs on issue close to invoke the reward-sharing Deno script and persist distribution metadata.
.github/workflows/statistic-member-reward.yml Scheduled workflow to produce monthly reward statistics from reward tags.
.github/scripts/type.ts Shared Reward interface for consistent reward data serialization.
.github/scripts/share-reward.ts Computes eligible users, splits reward, tags merge commit with reward YAML, and comments on the issue.
.github/scripts/count-reward.ts Aggregates recent reward tags, groups totals per payee/currency, and publishes a monthly statistic tag + release.
.github/scripts/deno.json Deno configuration for running scripts under .github/scripts.
.github/ISSUE_TEMPLATE/reward-task.yml Issue template for creating “reward”-labeled tasks with structured fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +40
- name: Check for new commits since last statistic
run: |
last_tag=$(git describe --tags --abbrev=0 --match "statistic-*" || echo "")

if [ -z "$last_tag" ]; then
echo "No previous statistic tags found."
echo "NEW_COMMITS=true" >> $GITHUB_ENV
else
new_commits=$(git log $last_tag..HEAD --oneline)
if [ -z "$new_commits" ]; then
echo "No new commits since last statistic tag."
echo "NEW_COMMITS=false" >> $GITHUB_ENV
else
echo "New commits found."
echo "NEW_COMMITS=true" >> $GITHUB_ENV
fi
fi
- uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
if: env.NEW_COMMITS == 'true'
with:
deno-version: v2.x

- name: Statistic rewards
if: env.NEW_COMMITS == 'true'
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The workflow gates reward statistics on git log $last_tag..HEAD, which checks for new commits, not for new reward-* tags. It’s possible to create new reward tags (e.g., closing older issues) without any new commits on HEAD, and this logic would then skip statistics even though there is new reward data. Consider checking for new reward-* tags since the last statistic-* tag (or just run the statistic step unconditionally and have the script no-op when there’s no data).

Suggested change
- name: Check for new commits since last statistic
run: |
last_tag=$(git describe --tags --abbrev=0 --match "statistic-*" || echo "")
if [ -z "$last_tag" ]; then
echo "No previous statistic tags found."
echo "NEW_COMMITS=true" >> $GITHUB_ENV
else
new_commits=$(git log $last_tag..HEAD --oneline)
if [ -z "$new_commits" ]; then
echo "No new commits since last statistic tag."
echo "NEW_COMMITS=false" >> $GITHUB_ENV
else
echo "New commits found."
echo "NEW_COMMITS=true" >> $GITHUB_ENV
fi
fi
- uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
if: env.NEW_COMMITS == 'true'
with:
deno-version: v2.x
- name: Statistic rewards
if: env.NEW_COMMITS == 'true'
# Commit-based gating removed; statistics now run unconditionally.
- uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: v2.x
- name: Statistic rewards

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +99
const averageReward = (rewardNumber / users.length).toFixed(2);

const list: Reward[] = users.map((login) => ({
issue: `#${issueNumber}`,
payer: `@${payer}`,
payee: `@${login}`,
currency,
reward: parseFloat(averageReward),
}));
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

averageReward is rounded to 2 decimals and then assigned to every payee. For amounts that don’t divide evenly (e.g., 100 split across 3 users), the total distributed reward will not equal the original reward due to rounding. Consider doing the split in the smallest currency unit (e.g., cents) and distributing the remainder deterministically (e.g., +$0.01 to the first N payees) so the sums match exactly.

Suggested change
const averageReward = (rewardNumber / users.length).toFixed(2);
const list: Reward[] = users.map((login) => ({
issue: `#${issueNumber}`,
payer: `@${payer}`,
payee: `@${login}`,
currency,
reward: parseFloat(averageReward),
}));
// Perform splitting in the smallest currency unit (cents) to avoid rounding loss.
const totalCents = Math.round(rewardNumber * 100);
const baseCents = Math.floor(totalCents / users.length);
const remainder = totalCents - baseCents * users.length;
const list: Reward[] = users.map((login, index) => {
const userCents = baseCents + (index < remainder ? 1 : 0);
return {
issue: `#${issueNumber}`,
payer: `@${payer}`,
payee: `@${login}`,
currency,
reward: userCents / 100,
};
});

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +108
await $`git tag -a "reward-${issueNumber}" ${mergeCommitSha} -m ${listText}`;
await $`git push origin --tags --no-verify`;

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The tag name is deterministic (reward-${issueNumber}). If the workflow re-runs (e.g., issue reopened/closed again, manual rerun, or transient failure after tagging), git tag -a will fail with “tag already exists” and the workflow won’t be idempotent. Consider checking for an existing tag and skipping, or deleting/replacing it (e.g., git tag -d + git push --delete), or using a unique tag name that includes the merge SHA/date.

Suggested change
await $`git tag -a "reward-${issueNumber}" ${mergeCommitSha} -m ${listText}`;
await $`git push origin --tags --no-verify`;
const tagName = `reward-${issueNumber}`;
const existingTag = (await $`git tag -l ${tagName}`).text().trim();
if (existingTag) {
// Delete existing tag locally and on remote to make tagging idempotent
await $`git tag -d ${tagName}`;
await $`git push origin :refs/tags/${tagName} --no-verify`;
}
await $`git tag -a ${tagName} ${mergeCommitSha} -m ${listText}`;
await $`git push origin ${tagName} --no-verify`;

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +25
if (!rawYAML.trim())
throw new ReferenceError("No reward data is found for the last month.");
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

When there are no reward-* tags in the last month, the script throws and fails the scheduled workflow. For a monthly scheduled statistic job, it’s usually better to exit successfully with a clear log message (no release/tag created) rather than failing the run.

Suggested change
if (!rawYAML.trim())
throw new ReferenceError("No reward data is found for the last month.");
if (!rawYAML.trim()) {
console.log(
"No reward data found for the last month. Skipping statistic tag and release creation.",
);
process.exit(0);
}

Copilot uses AI. Check for mistakes.
let rawYAML = "";

for (const tag of rewardTags)
rawYAML += (await $`git tag -l --format="%(contents)" ${tag}`) + "\n";
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This concatenates a zx ProcessOutput into a string via implicit coercion: rawYAML += (await $...) + "\n";. To avoid relying on toString() behavior, use the explicit output (.stdout / .text()) when appending tag contents.

Suggested change
rawYAML += (await $`git tag -l --format="%(contents)" ${tag}`) + "\n";
rawYAML += (await $`git tag -l --format="%(contents)" ${tag}`).stdout + "\n";

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +57
await $`git tag -a ${tagName} $(git rev-parse HEAD) -m ${summaryText}`;
await $`git push origin --tags --no-verify`;

await $`gh release create ${tagName} --notes ${summaryText}`;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

statistic-${YYYY-MM} is deterministic for the month. If the workflow is re-run (manual rerun, retry after a transient failure, etc.), git tag -a / gh release create will fail because the tag/release already exists. Consider making the script idempotent by checking for the existing tag/release and updating or skipping accordingly.

Suggested change
await $`git tag -a ${tagName} $(git rev-parse HEAD) -m ${summaryText}`;
await $`git push origin --tags --no-verify`;
await $`gh release create ${tagName} --notes ${summaryText}`;
const existingTag = (await $`git tag --list ${tagName}`).stdout.trim();
if (!existingTag) {
await $`git tag -a ${tagName} $(git rev-parse HEAD) -m ${summaryText}`;
await $`git push origin --tags --no-verify`;
} else {
console.log(`Tag ${tagName} already exists, skipping tag creation.`);
}
let releaseExists = false;
try {
await $`gh release view ${tagName}`;
releaseExists = true;
} catch {
releaseExists = false;
}
if (releaseExists) {
await $`gh release edit ${tagName} --notes ${summaryText}`;
} else {
await $`gh release create ${tagName} --notes ${summaryText}`;
}

Copilot uses AI. Check for mistakes.
@wowo-zZ
Copy link
Copy Markdown
Collaborator

wowo-zZ commented Apr 3, 2026

  1. share-reward.ts 里作者和 assignee 需要先去重,不然作者 self-assign 的情况下会被重复计入,分账会失真。
  2. reward- 这条 tag 创建逻辑需要做成幂等的,不然 workflow 重跑、或者 issue reopen 后再 close,会直接失败。
  3. 月度统计建议在“本月没有 reward tag”时直接跳过,不要把定时任务跑红。

@TechQuery
Copy link
Copy Markdown
Author

  1. share-reward.ts 里作者和 assignee 需要先去重,不然作者 self-assign 的情况下会被重复计入,分账会失真。

现在用的是平均分账法,单一 issue 悬赏被除开后,月度统计又会重新加起来,不会重复统计,最多可能会遇到浮点数精度问题。

  1. reward- 这条 tag 创建逻辑需要做成幂等的,不然 workflow 重跑、或者 issue reopen 后再 close,会直接失败。

我的设计是借鉴了区块链的理念,验收记账了就不再改了,除非悬赏工作流出 bug 需要人工删 tag 重算。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants