Skip to content

Commit 95e8b7e

Browse files
committed
performed triage on boards and created agentic workflow tools for organzing.
1 parent 3efe6b1 commit 95e8b7e

41 files changed

Lines changed: 3398 additions & 43 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# .env.example
2+
# Copy this file to .env and fill in your values.
3+
# NEVER commit .env — it is gitignored.
4+
5+
# ─── GitHub Auth ─────────────────────────────────────────────────────────────
6+
7+
# A GitHub personal access token (PAT) authorized for the open-austin org.
8+
#
9+
# Required scopes:
10+
# - repo (read/write issues, labels, milestones, metadata)
11+
# - project (read/write Projects v2 boards)
12+
#
13+
# Create at: https://github.com/settings/tokens
14+
#
15+
# If the open-austin org enforces SAML SSO, you must also authorize this token
16+
# for the org at: https://github.com/settings/tokens → "Configure SSO"
17+
#
18+
# This value is also used by the `gh` CLI when set as GH_TOKEN.
19+
GH_TOKEN=ghp_your_token_here
20+
21+
# ─── Target Org ───────────────────────────────────────────────────────────────
22+
23+
# The GitHub org this tooling manages.
24+
GITHUB_ORG=open-austin
25+
26+
# ─── Slack ───────────────────────────────────────────────────────────────────
27+
28+
# Slack Incoming Webhook URLs for posting automated reports.
29+
# Create at: https://api.slack.com/apps → your app → Incoming Webhooks
30+
# Each channel needs its own webhook URL.
31+
#
32+
# Used by: tools/notify/ and GitHub Actions scheduled reports
33+
SLACK_WEBHOOK_ENGAGEMENT=https://hooks.slack.com/services/your/webhook/url
34+
SLACK_WEBHOOK_ORG=https://hooks.slack.com/services/your/webhook/url
Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,89 @@
11
name: Post open role for project
2-
description: Use this template when you need to fill an open role in a project.
3-
title: "Open Role:{Role name/brief-description}"
4-
labels: ["open role"]
5-
assignees:
6-
- octocat
2+
description: Use this template when you need to fill an open role in a project or team.
3+
title: "Open Role: [Role name]"
4+
labels: ["open role", "engagement"]
75
body:
6+
- type: dropdown
7+
id: team
8+
attributes:
9+
label: What kind of role is this?
10+
description: Select the best fit. Product Team roles are for specific civic tech projects; Engagement is for standing org-level engagement work.
11+
options:
12+
- Product Team
13+
- Engagement
14+
- Communications
15+
- Education
16+
- Finance
17+
- Fundraising
18+
- Infrastructure
19+
- Board
20+
validations:
21+
required: true
22+
823
- type: textarea
924
id: project-description
1025
attributes:
11-
label: Description of project
12-
description: Brief description of the project.
13-
placeholder:
14-
value:
26+
label: Description of project or team
27+
description: Brief description of the project or standing team this role supports.
28+
placeholder: What does this project/team do and why does it matter?
1529
validations:
1630
required: true
31+
1732
- type: textarea
1833
id: role-description
1934
attributes:
2035
label: Description of role
21-
description: Brief description of the role.
22-
placeholder:
23-
value:
36+
description: What will this person actually do? What does success look like?
37+
placeholder: Specific responsibilities, expected outputs, who they'll work with.
2438
validations:
2539
required: true
40+
41+
- type: dropdown
42+
id: commitment
43+
attributes:
44+
label: Estimated commitment
45+
description: Approximate hours per week.
46+
options:
47+
- "1-2 hrs/week"
48+
- "3-5 hrs/week"
49+
- "5-10 hrs/week"
50+
- "10+ hrs/week"
51+
- Flexible / project-based
52+
validations:
53+
required: true
54+
55+
- type: textarea
56+
id: skills
57+
attributes:
58+
label: Skills or experience needed
59+
description: List the key skills, tools, or experience this role requires. Distinguish must-have from nice-to-have if possible.
60+
placeholder: |
61+
Must have:
62+
- ...
63+
Nice to have:
64+
- ...
65+
validations:
66+
required: true
67+
2668
- type: textarea
2769
id: timeframe
2870
attributes:
2971
label: Timeframe
3072
description: How soon do you need this role filled? How long do you expect it to be needed?
31-
placeholder:
32-
value:
3373
validations:
3474
required: true
75+
3576
- type: textarea
3677
id: docs
3778
attributes:
38-
label: Project docs
39-
description: Link to project docs
40-
placeholder:
41-
value:
42-
validations:
43-
required: true
79+
label: Project or team docs
80+
description: Link to any relevant docs, planning sheets, or resources.
81+
4482
- type: textarea
4583
id: contact
4684
attributes:
4785
label: Who to contact?
48-
description: Who should be contacted to begin work on this project? Can include slack channel, emails, etc (NOTE! This is a public repo)
49-
placeholder:
50-
value:
86+
description: Who should a prospective volunteer reach out to? Slack channel, GitHub username, email — whatever works.
87+
placeholder: "@githubhandle or #slack-channel"
5188
validations:
52-
required: true
89+
required: true
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Add new issue to Org Kanban
2+
3+
on:
4+
issues:
5+
types:
6+
- opened
7+
- labeled
8+
9+
env:
10+
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
11+
ORG_KANBAN_PROJECT_ID: PVT_kwDOAA7NZM4AqQF8
12+
STATUS_FIELD_ID: PVTSSF_lADOAA7NZM4AqQF8zghh1hc
13+
TODO_OPTION_ID: f75ad846
14+
15+
jobs:
16+
add-to-kanban:
17+
# Skip pull requests, skip open role issues (they go to Open Roles board instead)
18+
if: |
19+
github.event.issue != null &&
20+
!contains(github.event.issue.labels.*.name, 'open role')
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- name: Add issue to Org Kanban under "To Do"
25+
run: |
26+
ISSUE_NODE_ID="${{ github.event.issue.node_id }}"
27+
28+
# Add the issue to the project (returns item ID)
29+
ITEM_ID=$(gh api graphql -f query='
30+
mutation($project: ID!, $content: ID!) {
31+
addProjectV2ItemById(input: { projectId: $project, contentId: $content }) {
32+
item { id }
33+
}
34+
}' \
35+
-f project="$ORG_KANBAN_PROJECT_ID" \
36+
-f content="$ISSUE_NODE_ID" \
37+
--jq '.data.addProjectV2ItemById.item.id')
38+
39+
if [ -z "$ITEM_ID" ]; then
40+
echo "Issue already on board or could not be added — skipping status set."
41+
exit 0
42+
fi
43+
44+
echo "Added item $ITEM_ID — setting status to 'To Do'..."
45+
46+
# Set status to "To Do"
47+
gh api graphql -f query='
48+
mutation($project: ID!, $item: ID!, $field: ID!, $option: String!) {
49+
updateProjectV2ItemFieldValue(input: {
50+
projectId: $project,
51+
itemId: $item,
52+
fieldId: $field,
53+
value: { singleSelectOptionId: $option }
54+
}) {
55+
projectV2Item { id }
56+
}
57+
}' \
58+
-f project="$ORG_KANBAN_PROJECT_ID" \
59+
-f item="$ITEM_ID" \
60+
-f field="$STATUS_FIELD_ID" \
61+
-f option="$TODO_OPTION_ID"
62+
63+
echo "Done — issue is now on Org Kanban in 'To Do'."
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: Archive old Done items from Org Kanban
2+
3+
on:
4+
schedule:
5+
# Every Monday at 9am UTC
6+
- cron: '0 9 * * 1'
7+
workflow_dispatch:
8+
inputs:
9+
dry_run:
10+
description: 'Dry run (log without archiving)'
11+
required: false
12+
default: 'true'
13+
type: boolean
14+
15+
env:
16+
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
17+
ORG_KANBAN_PROJECT_ID: PVT_kwDOAA7NZM4AqQF8
18+
DONE_OPTION_ID: "98236657"
19+
# Items in Done older than this many days get archived
20+
STALE_DAYS: "180"
21+
22+
jobs:
23+
archive-old-done:
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- name: Archive Done items older than 6 months
28+
run: |
29+
DRY_RUN="${{ github.event.inputs.dry_run || 'false' }}"
30+
CUTOFF=$(date -d "-${STALE_DAYS} days" +%s 2>/dev/null || date -v "-${STALE_DAYS}d" +%s)
31+
32+
echo "Fetching Done items from Org Kanban..."
33+
echo "Cutoff: items not updated since $(date -d @$CUTOFF 2>/dev/null || date -r $CUTOFF)"
34+
echo "Dry run: $DRY_RUN"
35+
echo "---"
36+
37+
ARCHIVED=0
38+
CURSOR=""
39+
40+
while true; do
41+
if [ -n "$CURSOR" ]; then
42+
RESULT=$(gh api graphql -f query='
43+
query($project: ID!, $cursor: String!) {
44+
node(id: $project) {
45+
... on ProjectV2 {
46+
items(first: 100, after: $cursor) {
47+
nodes {
48+
id
49+
updatedAt
50+
fieldValueByName(name: "Status") {
51+
... on ProjectV2ItemFieldSingleSelectValue {
52+
optionId
53+
}
54+
}
55+
content {
56+
... on Issue { number title }
57+
... on DraftIssue { title }
58+
}
59+
}
60+
pageInfo { hasNextPage endCursor }
61+
}
62+
}
63+
}
64+
}' -f project="$ORG_KANBAN_PROJECT_ID" -f cursor="$CURSOR")
65+
else
66+
RESULT=$(gh api graphql -f query='
67+
query($project: ID!) {
68+
node(id: $project) {
69+
... on ProjectV2 {
70+
items(first: 100) {
71+
nodes {
72+
id
73+
updatedAt
74+
fieldValueByName(name: "Status") {
75+
... on ProjectV2ItemFieldSingleSelectValue {
76+
optionId
77+
}
78+
}
79+
content {
80+
... on Issue { number title }
81+
... on DraftIssue { title }
82+
}
83+
}
84+
pageInfo { hasNextPage endCursor }
85+
}
86+
}
87+
}
88+
}' -f project="$ORG_KANBAN_PROJECT_ID")
89+
fi
90+
91+
HAS_NEXT=$(echo "$RESULT" | jq -r '.data.node.items.pageInfo.hasNextPage')
92+
CURSOR=$(echo "$RESULT" | jq -r '.data.node.items.pageInfo.endCursor')
93+
94+
# Process each item
95+
while IFS= read -r item; do
96+
ITEM_ID=$(echo "$item" | jq -r '.id')
97+
OPTION_ID=$(echo "$item" | jq -r '.fieldValueByName.optionId // ""')
98+
UPDATED_AT=$(echo "$item" | jq -r '.updatedAt')
99+
TITLE=$(echo "$item" | jq -r '.content.title // "(no title)"')
100+
ISSUE_NUM=$(echo "$item" | jq -r '.content.number // ""')
101+
102+
# Skip items not in Done
103+
[ "$OPTION_ID" != "$DONE_OPTION_ID" ] && continue
104+
105+
# Check age
106+
UPDATED_TS=$(date -d "$UPDATED_AT" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$UPDATED_AT" +%s)
107+
[ "$UPDATED_TS" -gt "$CUTOFF" ] && continue
108+
109+
LABEL=""
110+
[ -n "$ISSUE_NUM" ] && LABEL="#$ISSUE_NUM: "
111+
echo "Would archive: ${LABEL}${TITLE} (last updated: $UPDATED_AT)"
112+
113+
if [ "$DRY_RUN" != "true" ]; then
114+
gh api graphql -f query='
115+
mutation($project: ID!, $item: ID!) {
116+
archiveProjectV2Item(input: { projectId: $project, itemId: $item }) {
117+
item { id }
118+
}
119+
}' -f project="$ORG_KANBAN_PROJECT_ID" -f item="$ITEM_ID" > /dev/null
120+
echo " → Archived."
121+
ARCHIVED=$((ARCHIVED + 1))
122+
fi
123+
124+
done < <(echo "$RESULT" | jq -c '.data.node.items.nodes[]')
125+
126+
[ "$HAS_NEXT" != "true" ] && break
127+
done
128+
129+
echo "---"
130+
if [ "$DRY_RUN" == "true" ]; then
131+
echo "Dry run complete. Run with dry_run=false to apply."
132+
else
133+
echo "Archived $ARCHIVED item(s)."
134+
fi

0 commit comments

Comments
 (0)