Skip to content

cdrrazan/Boardly

Repository files navigation

🤖 Boardly

Put your GitHub Projects board on autopilot.

A config-driven GitHub Action that automates GitHub Projects (v2) — sprint rollover, stale-card nudges, sub-issue gating, digests, standups, priority sorting, and Slack/email notifications — all from one YAML file.


Website CI Release License: MIT Made with TypeScript Ruby edition Node GitHub Projects v2 Tests PRs Welcome Open Source

🌐 boardly-gh.pages.dev · 💎 Prefer Ruby? cdrrazan/Boardly-ruby

Getting started · Quick start · Features · Use cases · Config · Roadmap · Contributing · Sponsor


✨ What it does

You point the action at a Project (v2), describe your rules in .github/project-automation.yml, and schedule it. On every run it reads the board, applies your enabled features, and writes an audit trail to the Actions job summary so you can see exactly what happened.

flowchart LR
    cron([⏰ Schedule / manual]) --> action[🤖 Boardly]
    cfg[[📄 project-automation.yml]] --> action
    action -->|GraphQL| gh[(🗂️ GitHub Project v2)]
    gh --> action
    action --> f1[🔁 Rollover]
    action --> f2[🔔 Stale nudges]
    action --> f3[🧩 Sub-issue gate]
    action --> f4[🏁 Digest]
    action --> f5[🗓️ Standup]
    action --> f6[🔼 Priority sort]
    f1 & f2 & f3 & f4 & f5 & f6 --> audit[[📋 Audit trail → Job Summary]]
Loading

🚀 Features

Feature What it does
🔁 Sprint rollover When an iteration ends, move unfinished items into the next iteration so nothing is stranded in a closed sprint. Optionally tag each rolled card with the new sprint's label (created if missing) and strip stale labels like pulled-in.
▶️ Sprint start When a sprint becomes active, promote cards you pre-parked in it (e.g. Backlog → Ready). Only touches cards parked before the sprint started, so a deliberate mid-sprint move back is respected.
👤 Auto-assign by label A CODEOWNERS-style map from label → owner. Unassigned tickets in a status (e.g. Ready) get the configured assignee — UI → @zach, security → @rajan. Opt-in; never overrides an existing assignee.
🛬 Sprint runway warning GitHub never auto-creates iterations. Boardly warns (job summary + annotation) when too few future sprints are planned, so you add the next one before rollover/sprint-start run dry.
🔔 Stale-card nudges @-mention owners when a card sits in a status past a threshold. De-duped so it never spams.
🧩 Sub-issue gating + roll-up Block a card from staying Done while it has open sub-issues; optionally write completion % into a progress field.
🏁 Sprint digest At iteration end, post completed-vs-carried-over counts and velocity.
🗓️ Daily standup Post what moved in the last N hours, grouped by assignee.
🔼 Priority auto-sort Reorder the board so higher-priority cards float to the top.
📣 Slack & email notifications Also deliver digests, standups, and stale alerts to a Slack channel and/or over email — not just GitHub comments.
📋 Audit trail Every action (or, in dry-run, every intended action) is written to the job summary.

📚 Use cases

Every feature comes with a standalone, copy-pasteable recipe — who it's for, the config, and what happens. Browse them all in docs/use-cases, or jump straight in:

# Use case Feature(s)
01 Carry unfinished work into the next sprint 🔁 Rollover
02 Nudge owners about stale cards 🔔 Stale nudges
03 Stop premature "Done" on parent issues 🧩 Sub-issue gate
04 Show live epic progress on the board 🧩 Sub-issue roll-up
05 Auto-post a sprint retro digest 🏁 Digest
06 Async daily standup for a distributed team 🗓️ Standup
07 Keep the backlog sorted by priority 🔼 Priority sort
08 Preview everything safely with dry-run 📋 All + audit
09 Automate a project spanning many repos ⚙️ All
10 Different schedules per feature ⚙️ All
11 Solo maintainer / personal project board ⚙️ All
12 Escalate cards ignored after a nudge 🔔 Stale + 🧩 gate
13 Send digests & alerts to Slack and email 📣 Notifications
14 Promote pre-parked cards when a sprint starts ▶️ Sprint start
15 Auto-assign tickets by label 👤 Auto-assign
16 Get warned before you run out of sprints 🛬 Sprint runway

New here? Start with 01 · Sprint rollover and 08 · Dry-run preview.

⚡ Quick start

New here? The Getting started guide is a full, linear zero-to-running walkthrough (~15 min). The steps below are the condensed version.

flowchart TD
    A[1 · Create a token<br/>Projects + Issues scope] --> B[2 · Add config<br/>.github/project-automation.yml]
    B --> C[3 · Add workflow<br/>.github/workflows/project-automation.yml]
    C --> D[4 · Dry-run to preview] --> E[5 · Flip dry-run off 🎉]
Loading
  1. Create a token. The default GITHUB_TOKEN generally can't read org Projects. Create a fine-grained PAT or GitHub App token with Projects: read & write and Issues: read & write, and save it as a secret (e.g. PROJECT_AUTOMATION_TOKEN).

  2. Add config at .github/project-automation.yml — start from project-automation.example.yml.

  3. Add a workflow — see .github/workflows/example.yml:

    - uses: cdrrazan/Boardly@v1
      with:
        token: ${{ secrets.PROJECT_AUTOMATION_TOKEN }}
        config-path: .github/project-automation.yml
        dry-run: "true"   # preview first; remove once it looks right

🔑 Secrets you'll need

Set these as repository (or org) Actions secrets, then map them into the workflow. Only the token is required.

Secret Required? What it is Needed when
PROJECT_AUTOMATION_TOKEN Yes Fine-grained PAT / GitHub App token — Projects: read & write + Issues: read & write Always (the default GITHUB_TOKEN can't read org Projects)
SLACK_WEBHOOK_URL No Slack Incoming Webhook URL You enable notifications.slack
SMTP_USER No SMTP username You enable notifications.email with auth
SMTP_PASS No SMTP password You enable notifications.email with auth

The token is passed via the token: input; the notification secrets are passed via the workflow's env: (referenced in config by env-var name, never inlined). See Notifications.

Versioning: pin to @v1 to always get the latest v1.x (bug-fixes and features, no breaking changes), or @v1.0.0 to freeze an exact version.

🏢 Using it in an organization

Boardly is a published Action — there's nothing to install, fork, or host. Any repo in your org just references cdrrazan/Boardly@v1. To roll it out across a team:

  1. Allowlist the Action (only if your org restricts Actions). Org admin → Settings → Actions → General → Allow select actions → add cdrrazan/Boardly@*. Skip if your org already permits all or marketplace actions.

  2. Create one token with access to the org's Project (v2) — a GitHub App token (recommended for teams; not tied to a single person) or an org-scoped fine-grained PAT, with Projects: read & write + Issues: read & write.

  3. Store it once as an org-level secret named PROJECT_AUTOMATION_TOKEN (Org Settings → Secrets and variables → Actions) and share it to the repos that need it — one secret, many repos.

  4. Host the workflow in a single repo. A Project (v2) is owned by the org or user, not a repo, and the config targets it by owner + number — so the scheduled workflow lives in one repo, even when the board spans many. It does not need to be added to every repo.

  5. Point the config at the org project and dry-run first:

    # .github/project-automation.yml
    project:
      owner: my-org     # your org login
      type: org
      number: 7         # from the project URL: /orgs/my-org/projects/7

For a board that pulls issues from several repositories, see the multi-repo recipe.

🧾 Inputs & outputs

Input Default Description
token (required) Token with project + issues access to the target project.
config-path .github/project-automation.yml Path to the config file.
only "" Run just one feature: rollover, sprint-start, sprint-runway, auto-assign, stale-nudge, sub-issue-gate, digest, standup, priority-sort. Empty runs every enabled feature.
dry-run false Log every intended action to the audit trail without making changes.

Output: actions-count — number of mutating actions taken (or that would be taken in dry-run).

⚙️ Configuration

Everything is declared in one YAML file. Minimal example:

project:
  owner: my-org
  type: org        # or "user"
  number: 5
fields:
  status: Status
  iteration: Sprint
  priority: Priority
doneStatuses: ["Done"]
features:
  rollover:
    enabled: true
  staleNudge:
    enabled: true
    rules:
      - status: "In Progress"
        days: 3
        notify: assignees

Full reference: project-automation.example.yml.

📣 Notifications (Slack & email)

By default, digests, standups, and stale alerts are posted to GitHub. You can also deliver them to a Slack channel and/or over email by adding a notifications block. Secrets are referenced by environment-variable name — never inline the webhook URL or SMTP password in config; pass them from encrypted secrets via the workflow's env:.

# in project-automation.yml
notifications:
  slack:
    enabled: true
    webhookEnv: SLACK_WEBHOOK_URL      # env var with a Slack Incoming Webhook URL
  email:
    enabled: true
    host: smtp.example.com
    port: 587
    secure: false                      # true for port 465
    userEnv: SMTP_USER
    passwordEnv: SMTP_PASS
    from: "Boardly <bot@example.com>"
    to: ["team@example.com"]
# in your workflow — map the secrets into the environment
- uses: cdrrazan/Boardly@v1
  with:
    token: ${{ secrets.PROJECT_AUTOMATION_TOKEN }}
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    SMTP_USER: ${{ secrets.SMTP_USER }}
    SMTP_PASS: ${{ secrets.SMTP_PASS }}

Both channels are optional and independent — enable either, both, or neither. A channel failure is logged as a warning and never aborts the run, and nothing is sent under dry-run. See the notifications recipe.

Configured but paused? To keep your creds and settings in place but stop sending, just set that channel's enabled: false. The webhook/SMTP config stays untouched — no delivery happens until you flip it back to true. You don't need to remove secrets to go quiet.

🧠 How it decides things

  • "Time in status" is approximated by when the Status field value was last changed (Projects v2 exposes each field value's updatedAt). It is not a full status-history walk.
  • Iterations come from the iteration field's configuration. Rollover and digest act on the most recently completed iteration; new work rolls into the first active iteration.
  • Sub-issues use the native GitHub sub-issues API (subIssuesSummary), requested with the sub_issues GraphQL feature header.
  • Priority sort uses updateProjectV2ItemPosition. Manual order only shows on a board view whose sort is set to manual — a field-sorted view overrides it.

🛠️ Development

npm install
npm run typecheck   # tsc --noEmit
npm test            # unit tests (node:test + tsx)
npm run build       # bundle src -> dist/index.js (committed; required for JS actions)
npm run all         # typecheck + test + build

The bundled dist/index.js is committed so the action runs without a build step. Rebuild and commit it whenever you change src/.

Built with: TypeScript · Node 20 · Octokit GraphQL · Zod · ncc — see the full Tech Stack and Architecture.

🗺️ Roadmap

Working-days awareness, escalation ladders, iteration auto-assignment, capacity warnings, and more — see ROADMAP.md.

🤝 Contributing

Contributions are very welcome! Read the Contributing Guide and our Code of Conduct to get started. Found a security issue? See SECURITY.md.

❤️ Support the project

Boardly is free and open source. If it saves your team time, please consider sponsoring — it directly funds maintenance and new features.

Sponsor on GitHub Buy Me a Momo

Starring the repo also helps a lot.

📄 License

Released under the MIT License — see also our open-source note.

Built with ❤️ for teams who'd rather ship than babysit a board.

About

A config-driven GitHub Action that automates GitHub Projects (v2) — sprint rollover, stale-card nudges, sub-issue gating, digests, standups, and priority sorting — all from one YAML file.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

8 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors