mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 10:53:31 +00:00
f366f762cc
## Description:
# Issue Lifecycle Actions
Adds two GitHub Actions workflows that enforce OpenFront's
issue-lifecycle invariants. No LLM calls — only the default
`GITHUB_TOKEN`. Layer B (Claude-powered triage) will build on this
foundation.
## Summary
- **Stale closer** — daily cron. Unmilestoned issues get warned at 5
days of inactivity, auto-closed at 10. Exempt: milestoned or
`keep-open`. Bot comments don't reset the timer.
- **Assignment invariant** — event + cron backstop. You cannot assign
anyone to an unmilestoned issue. Violators are unassigned automatically
with an explanatory comment.
- **Approval label sync** — event + cron backstop. The `not-approved`
(red) and `approved` (green) labels are derived from milestone state.
These labels are *only* ever touched by this Action.
## Rollout
Both workflows ship gated by `vars.ISSUE_LIFECYCLE_DRY_RUN` (defaults to
`'true'`). They log decisions but do not mutate anything until the
maintainer flips that variable in **Settings → Variables**.
Suggested rollout:
1. Merge with dry-run on.
2. Watch the cron logs for ~1 week. Verify the action list matches
expectations.
3. Flip `ISSUE_LIFECYCLE_DRY_RUN=false` to go live.
## File layout
```
.github/workflows/
issue-lifecycle-cron.yml # daily 06:00 UTC + workflow_dispatch
issue-lifecycle-events.yml # issues: [opened, assigned, milestoned, demilestoned]
scripts/issue-lifecycle/
config.ts # labels, colors, thresholds, comment templates
github.ts # Octokit wrapper, Action applier, label idempotent-creation
rules/
approval-label-sync.ts # pure function — idempotent
assignment-invariant.ts # pure function
stale-closer.ts # async — reads comment history, filters bots
cron.ts # daily sweep orchestrator
events.ts # event-mode dispatcher
index.ts # entrypoint, CLI arg parser
README.md
```
Structure mirrors `scripts/pr-gate/` from Unit 2 — same Octokit/Action
patterns, same dry-run convention.
## Self-installing labels
On every run, the Action ensures the six labels exist (`not-approved`,
`approved`, `stale`, `keep-open`, `needs-info`, `auto-closed-stale`)
with the correct colors and descriptions. No manual setup required.
## Local testing
```bash
cd scripts/issue-lifecycle
npm install
export GITHUB_TOKEN=ghp_...
# Full cron sweep, dry-run (default for CLI):
npx tsx index.ts --mode cron
# Simulate an event:
EVENT_NAME=assigned npx tsx index.ts --mode event --issue 1234
```
CLI invocations are dry-run unless `--no-dry-run` is passed explicitly.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
63 lines
2.9 KiB
TypeScript
63 lines
2.9 KiB
TypeScript
export const REPO = { owner: "openfrontio", repo: "OpenFrontIO" } as const;
|
|
|
|
export const STALE_WARN_DAYS = 7;
|
|
export const STALE_CLOSE_DAYS = 14;
|
|
|
|
export const LABELS = {
|
|
NOT_APPROVED: "not-approved",
|
|
APPROVED: "approved",
|
|
STALE: "stale",
|
|
KEEP_OPEN: "keep-open",
|
|
NEEDS_INFO: "needs-info",
|
|
AUTO_CLOSED_STALE: "auto-closed-stale",
|
|
} as const;
|
|
|
|
export const LABEL_COLORS: Record<string, string> = {
|
|
[LABELS.NOT_APPROVED]: "B60205",
|
|
[LABELS.APPROVED]: "0E8A16",
|
|
[LABELS.STALE]: "BFD4F2",
|
|
[LABELS.KEEP_OPEN]: "FFFFFF",
|
|
[LABELS.NEEDS_INFO]: "FBCA04",
|
|
[LABELS.AUTO_CLOSED_STALE]: "586069",
|
|
};
|
|
|
|
export const LABEL_DESCRIPTIONS: Record<string, string> = {
|
|
[LABELS.NOT_APPROVED]: `Not yet approved by maintainer; will auto-close after ${STALE_CLOSE_DAYS} days if no milestone is set`,
|
|
[LABELS.APPROVED]: "Maintainer has assigned a milestone — work is approved",
|
|
[LABELS.STALE]: `No activity recently; will auto-close in ${STALE_CLOSE_DAYS - STALE_WARN_DAYS} days unless updated`,
|
|
[LABELS.KEEP_OPEN]: "Exempt from auto-close",
|
|
[LABELS.NEEDS_INFO]:
|
|
"Reporter was asked for more info; no special timer — standard stale-close still applies",
|
|
[LABELS.AUTO_CLOSED_STALE]: "Closed automatically due to inactivity",
|
|
};
|
|
|
|
export const COMMENTS = {
|
|
STALE_WARNING: (author: string): string =>
|
|
`Hi @${author}, this issue hasn't had activity in ${STALE_WARN_DAYS} days and doesn't yet have a milestone assigned.
|
|
|
|
If a maintainer doesn't milestone this issue (or you don't update it) within the next ${STALE_CLOSE_DAYS - STALE_WARN_DAYS} days, it will be **automatically closed**.
|
|
|
|
If you believe this issue is important, consider:
|
|
- Adding more context, repro steps, or examples
|
|
- Discussing in our [Discord](https://discord.gg/K9zernJB5z)
|
|
- Requesting the \`${LABELS.KEEP_OPEN}\` label if it should be exempt from auto-close
|
|
|
|
— *Automated. See [CONTRIBUTING.md](https://github.com/${REPO.owner}/${REPO.repo}/blob/main/CONTRIBUTING.md).*`,
|
|
|
|
AUTO_CLOSED_STALE: (author: string): string =>
|
|
`Closing this issue as it hasn't been milestoned and has had no recent activity.
|
|
|
|
This isn't a judgment of the issue's merit — just routine triage. @${author}, if you believe this should be reconsidered, please reopen with additional context or discuss in [Discord](https://discord.gg/K9zernJB5z).
|
|
|
|
— *Automated.*`,
|
|
|
|
UNASSIGNED_NO_MILESTONE: (assignees: string[]): string =>
|
|
`${assignees.map((u) => "@" + u).join(", ")} — you've been unassigned from this issue automatically because it doesn't have a milestone set.
|
|
|
|
In OpenFront's workflow, an issue must have a milestone (\`backlog\` or a version like \`v30\`) before anyone can be assigned. This ensures only approved work has people working on it.
|
|
|
|
If this is approved work, a maintainer needs to milestone the issue first, then re-assign you.
|
|
|
|
— *Automated. See [CONTRIBUTING.md](https://github.com/${REPO.owner}/${REPO.repo}/blob/main/CONTRIBUTING.md).*`,
|
|
} as const;
|