Files
hikari ec58c9c843
CI / dependency-pin-check-typescript (push) Successful in 5s
CI / dependency-pin-check-python (push) Successful in 4s
CI / python (push) Successful in 9m28s
CI / typescript (push) Successful in 9m42s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m39s
feat: reorganise bash scripts and add comprehensive documentation (#6)
## Summary

This PR completes the bash script restructuring and adds comprehensive documentation across all script categories.

### Bash Restructuring

- Moved cohort shell scripts (`remove_github_members.sh`, `update_github_teams.sh`) from `python/cohort/` into a new `bash/cohort/` directory
- Moved existing bash utilities (`add-keys-to-git.sh`, `fix-yubikey-perms.sh`, `list-yubikey-ssh-keys.sh`) into a new `bash/yubikey/` subdirectory
- Updated `run.sh` to support **Bash** as a third language option alongside TypeScript and Python
  - Bash scripts are run directly (no 1Password secret injection needed)
  - Category discovery and script listing works the same as for TS/Python
  - Removed dead "Root Scripts" logic that was no longer needed

### Documentation

Added `README.md` files for all script categories that were missing them:

- `bash/cohort/README.md` — cohort GitHub team management scripts
- `bash/yubikey/README.md` — YubiKey SSH key and permission utilities
- `typescript/src/crowdin/README.md` — Crowdin translation management scripts
- `typescript/src/discord/README.md` — Discord bot utility scripts
- `typescript/src/discourse/README.md` — Discourse forum management scripts
- `typescript/src/gitea/README.md` — Gitea bulk repository operation scripts
- `typescript/src/github/README.md` — GitHub API interaction scripts
- `typescript/src/music/README.md` — Music file metadata tools
- `typescript/src/s3/README.md` — S3-compatible object storage scripts
- `typescript/src/security/README.md` — Security analysis and reporting scripts
- `python/cohort/README.md` — Updated to remove moved shell scripts, fix usage commands

Also updated project-level docs:

- **`README.md`** — Corrected project structure, fixed running instructions (removed references to non-existent `make run-ts`/`make run-py` targets), added Bash prerequisites
- **`CLAUDE.md`** — Updated project overview, structure, development standards, and script-adding guides to reflect the current state of the project

 This PR was created with help from Hikari~ 🌸

Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Reviewed-on: #6
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
2026-02-23 20:18:41 -08:00
..

Cohort Scripts

Scripts for managing the NHCarrigan spring cohort programme. Covers the full lifecycle: applicant evaluation, Discord server setup, team assignment, member onboarding, activity tracking, and member removal.

Most scripts interact with the Discord API and require a DISCORD_BOT_TOKEN. Several also use the gh CLI for GitHub operations.

Getting Started

Run scripts via the interactive runner from the project root:

make run
# Select: Python → cohort → <script>

Or run directly:

cd python && op run --env-file=../prod.env -- uv run python cohort/<script>.py

Prerequisites:

  • Run make install-py to set up the Python virtual environment.
  • Most scripts require DISCORD_BOT_TOKEN set in prod.env.
  • Scripts that manage GitHub teams require gh auth login to be run first.

Table of Contents

Applicant Evaluation

Server Setup

Member Onboarding

Ongoing Management

Activity Tracking

Member Removal


verify_discord.py

Reads Discord user IDs from a markdown table and verifies each one against the Discord API, checking whether the user is a member of the freeCodeCamp guild. Handles rate limits and retries automatically.

Usage

make run
# Select: Python → cohort → verify_discord.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
table.md Markdown table Applicant data; the script reads Discord IDs from the table rows

Output (written to data/):

File Format Description
discord_verification.json JSON object Results grouped as verified (with username), missing, and errors lists

evaluate_technical_proficiency.py

Evaluates each applicant's technical proficiency by analysing their GitHub profile and self-described expertise. Scores applicants as Beginner, Intermediate, or Advanced based on public repository count, follower count, language variety, and keywords in their self-assessment text.

Usage

make run
# Select: Python → cohort → evaluate_technical_proficiency.py

Environment Variables

None. Uses the public GitHub API (unauthenticated, subject to rate limiting).

Data Files

Input (expected in data/):

File Format Description
applicants_to_evaluate.json JSON array Applicant records, each with a GitHub profile URL and a self-description field

Output (written to data/):

File Format Description
proficiency_evaluations.json JSON array Proficiency scores (Beginner/Intermediate/Advanced) and detected tech stacks per applicant

Notes

  • Unauthenticated GitHub API requests are rate-limited to 60 per hour. For large batches of applicants, consider adding a GitHub token or running the script in smaller chunks.

analyse_availability.py

Parses a markdown table of applicant availability responses with timezone information, converts local timeslot selections to UTC, and produces a JSON summary of UTC coverage across morning, afternoon, evening, and night blocks per applicant.

Usage

make run
# Select: Python → cohort → analyse_availability.py

Environment Variables

None.

Data Files

Input (expected in data/):

File Format Description
table.md Markdown table Availability responses with timezone column
discord_verification.json JSON object Output of verify_discord.py — used to link applicants to Discord usernames

Output (written to data/):

File Format Description
availability_analysis.json JSON object UTC block distribution (morning/afternoon/evening/night) per applicant

generate_member_files.py

Consolidates all evaluation data into two markdown files: one for participants and one for team leaders. Each entry includes the member's tech stack, availability, proficiency level, and (for leaders) their leadership assessment.

Usage

make run
# Select: Python → cohort → generate_member_files.py

Environment Variables

None.

Data Files

Input (expected in data/):

File Format Description
discord_verification.json JSON object Verified applicant Discord info
proficiency_evaluations.json JSON array Technical proficiency scores
availability_analysis.json JSON object UTC availability blocks
leadership_candidates.json JSON array Applicants being considered for leadership roles
leadership_evaluations.json JSON array Leadership assessment results

Output (written to data/):

File Format Description
participants.md Markdown Profile summary for each participant
leaders.md Markdown Profile summary for each team leader candidate

generate_timeslots.py

Generates a list of hourly timeslots for use with the Crabfit scheduling tool, covering a date range at Los Angeles timezone.

Usage

make run
# Select: Python → cohort → generate_timeslots.py

Environment Variables

None.

Data Files

Output (written to data/):

File Format Description
crabfit_timeslots.json JSON array of ISO-format strings One entry per hour across the configured date range

Notes

  • The date range and timezone are hardcoded in the script. Update the start/end date constants before running.

create_team_voice_channels.py

Creates private voice channels for each cohort team in a specified Discord category. Each channel is visible and joinable only by members who have that team's role.

Usage

make run
# Select: Python → cohort → create_team_voice_channels.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. Team role IDs and the target category ID are hardcoded in the script.

Notes

  • Update the team role IDs, channel names, and category ID constants in the script before running.

fix_channel_permissions.py

Denies Send Messages and Send Messages in Threads permissions for the @everyone and @cohort roles on a specific Discord channel. Used to lock down a channel so only designated roles can post.

Usage

make run
# Select: Python → cohort → fix_channel_permissions.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. The target channel ID is hardcoded in the script.

Notes

  • Update the CHANNEL_ID constant before running.

update_cohort_leads_permissions.py

Grants the Cohort Leads role MENTION_EVERYONE and PIN_MESSAGES permissions across all 14 team channels.

Usage

make run
# Select: Python → cohort → update_cohort_leads_permissions.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. Team channel IDs are hardcoded in the script.


list_all_guild_roles.py

Lists all roles in the freeCodeCamp Discord guild, highlighting team and cohort-related roles. Useful for finding role IDs to use in other scripts.

Usage

make run
# Select: Python → cohort → list_all_guild_roles.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. Output is printed to stdout.


list_discord_roles.py

Lists Discord roles in the freeCodeCamp server, filtering for cohort-, leader-, and 2026-related roles.

Usage

make run
# Select: Python → cohort → list_discord_roles.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. Output is printed to stdout.


check_channel_permissions.py

Audits cohort-team-* Discord channels to identify incorrect Send Messages or Send Messages in Threads permissions on the @everyone or @cohort roles.

Usage

make run
# Select: Python → cohort → check_channel_permissions.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. Results are printed to stdout.


send_team_messages.py

Sends initial welcome and roster messages to all 14 team Discord channels, pins them, and saves the resulting message IDs, channel IDs, and role IDs to data/team_message_ids.json. This file is required by several other scripts.

Usage

make run
# Select: Python → cohort → send_team_messages.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_assignments.json JSON object Team rosters (leaders and participants per team)
applicants_to_evaluate.json JSON array Applicant data used to populate roster messages

Output (written to data/):

File Format Description
team_message_ids.json JSON object Channel ID, pinned message ID, and role ID per team name

Notes

  • Run this once at the start of the cohort. Other scripts (send_checkin.py, update_roster_messages.py, etc.) depend on team_message_ids.json.

assign_cohort_role.py

Assigns the Cohort Discord role to all cohort participants. Reads the full list of unique member Discord IDs from team_assignments.json and assigns the role to each one, with exponential backoff to handle rate limits.

Usage

make run
# Select: Python → cohort → assign_cohort_role.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_assignments.json JSON object Team rosters; all unique user IDs are extracted from here

assign_team_roles.py

Assigns team-specific Discord roles to all cohort participants based on their team membership in team_assignments.json. Uses exponential backoff for rate limit handling.

Usage

make run
# Select: Python → cohort → assign_team_roles.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_assignments.json JSON object Team rosters with Discord IDs and corresponding role IDs

add_github_team_members.py

Adds cohort members to their corresponding GitHub teams in the nhcarrigan-spring-2026-cohort organisation. Leaders are added to both the main team and the corresponding -leaders sub-team; participants are added to the main team only.

Usage

make run
# Select: Python → cohort → add_github_team_members.py

Environment Variables

None. Uses the gh CLI for authentication (run gh auth login first).

Data Files

Input (expected in data/):

File Format Description
team_assignments.json JSON object Team rosters with leader/participant distinction
discord_to_github.json JSON object Maps Discord user IDs to GitHub usernames

get_cohort_members.py

Fetches all Discord guild members with the Cohort role and writes their IDs, usernames, and display names to a JSON file. Also prints a formatted list to stdout.

Usage

make run
# Select: Python → cohort → get_cohort_members.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Output (written to data/):

File Format Description
active_cohort_members.json JSON array Objects with id, username, and display_name for each Cohort role member

check_member_status.py

Verifies whether specific Discord member IDs are still in the freeCodeCamp guild by querying the Discord API. Used to confirm that removed members have been successfully ejected.

Usage

make run
# Select: Python → cohort → check_member_status.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. The member IDs to check are hardcoded in the script. Update the list before running.


fetch_roster.py

Fetches pinned messages from a specific team channel to retrieve the current roster.

Usage

make run
# Select: Python → cohort → fetch_roster.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. Output is printed to stdout as JSON.

Notes

  • The target channel ID is hardcoded. Update it before running.

update_roster_messages.py

Edits the pinned team roster messages in each Discord channel with the latest data from team_assignments.json and discord_to_github.json. Run this after any membership changes to keep the pinned rosters up to date.

Usage

make run
# Select: Python → cohort → update_roster_messages.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_message_ids.json JSON object Channel and message IDs per team (output of send_team_messages.py)
team_assignments.json JSON object Current team rosters
discord_to_github.json JSON object Discord ID → GitHub username mapping

send_checkin.py

Sends biweekly check-in prompts to all team Discord channels (except Jade Jasmine), automatically creating threads for responses. Members who do not respond face removal for inactivity.

Usage

make run
# Select: Python → cohort → send_checkin.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_message_ids.json JSON object Channel IDs per team

send_team_checkin.py

Sends a capacity check-in message to each team channel asking whether the team feels able to complete their project with their current member count, and inviting them to request support if needed.

Usage

make run
# Select: Python → cohort → send_team_checkin.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_message_ids.json JSON object Channel IDs per team
team_assignments.json JSON object Current team rosters (used to mention team members)

discord_activity_checker.py

Scans each team's Discord channel and threads to identify members who have not sent a message within the last 36 hours. Can optionally send notification messages directly to inactive members via the --send CLI flag.

Usage

# Check only (no messages sent)
make run
# Select: Python → cohort → discord_activity_checker.py

# Check and notify inactive members
cd python && op run --env-file=../prod.env -- uv run python cohort/discord_activity_checker.py --send

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_assignments.json JSON object Member lists per team

Output (written to data/):

File Format Description
discord_activity_report.json JSON object Inactive members per team with their last message timestamp

catch_up_report.py

Generates a detailed markdown activity report covering Discord messages (in team channels and their threads) and GitHub activity (PRs, issues, comments, reviews, commits) since a configured start date. Uses async API calls for efficiency.

Usage

make run
# Select: Python → cohort → catch_up_report.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
team_assignments.json JSON object Team rosters
discord_to_github.json JSON object Discord ID → GitHub username mapping

Output (written to data/):

File Format Description
catch_up_report.md Markdown table Activity counts per member (Discord messages + GitHub contributions) per team

Notes

  • The report start date is hardcoded in the script. Update it before each use.
  • GitHub activity is fetched via the unauthenticated public API; rate limiting may apply for large cohorts.

check_lengths.py

Dry-run validation that parses catch_up_report.md and formats each team's data into Discord monospace table strings, checking whether any would exceed Discord's 2,000-character message limit before actually sending them.

Usage

make run
# Select: Python → cohort → check_lengths.py

Environment Variables

None.

Data Files

Input (expected in data/):

File Format Description
catch_up_report.md Markdown table Output of catch_up_report.py

Notes

  • Run this after catch_up_report.py and before send_activity_report.py to catch any messages that would be rejected by Discord.

send_activity_report.py

Parses catch_up_report.md and sends the formatted activity table for each team to its Discord channel as a monospace code block.

Usage

make run
# Select: Python → cohort → send_activity_report.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input (expected in data/):

File Format Description
catch_up_report.md Markdown table Output of catch_up_report.py

Notes

  • Run check_lengths.py first to verify that no messages exceed Discord's character limit.

remove_member.py

Comprehensive member removal script. Given a Discord ID as a CLI argument, it:

  1. Removes the member from team_assignments.json.
  2. Removes the member from discord_to_github.json.
  3. Prints GitHub removal instructions.
  4. Removes the member's Discord Cohort and team roles.
  5. Sends an announcement message to the team's Discord channel.
  6. Outputs markdown notes suitable for pasting into COHORT_NOTES.md.

Usage

cd python && op run --env-file=../prod.env -- uv run python cohort/remove_member.py <discord_id>

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

Input/Output (read and updated in data/):

File Format Description
team_assignments.json JSON object Updated: member removed from their team
discord_to_github.json JSON object Updated: Discord→GitHub mapping removed
team_message_ids.json JSON object Read: used to find the team's Discord channel ID

remove_resigned_members.py

Removes a list of resigned member Discord IDs from team_assignments.json, updating team rosters and reporting which teams were affected. Does not interact with Discord or GitHub.

Usage

make run
# Select: Python → cohort → remove_resigned_members.py

Environment Variables

None.

Data Files

Input/Output (read and updated in data/):

File Format Description
team_assignments.json JSON object Updated in place; affected teams are reported to stdout

Notes

  • The list of Discord IDs to remove is hardcoded in the script. Update it before running.
  • This script only updates the local JSON file. Run update_roster_messages.py and handle GitHub/Discord role removal separately.

remove_discord_roles.py

Removes the Cohort and team-specific Discord roles from a hardcoded list of inactive members.

Usage

make run
# Select: Python → cohort → remove_discord_roles.py

Environment Variables

Variable Description
DISCORD_BOT_TOKEN Discord bot token

Data Files

None. Member IDs and their team-to-role mappings are hardcoded in the script. Update them before running.