diff --git a/.github/vouch/AGENTS.md b/.github/vouch/AGENTS.md index eb2b0e70c..248d4b599 100644 --- a/.github/vouch/AGENTS.md +++ b/.github/vouch/AGENTS.md @@ -5,9 +5,10 @@ A file for [guiding coding agents](https://agents.md/). - All commands must have a `--dry-run` option that is default on. - Commands that do not modify external state don't need a `--dry-run` option. - The order of definitions in Nu files should be: - (1) CLI commands (exported, sorted alphabetically) - (2) Helper commands (exported) - (3) Helper commands (non exported) + (1) General CLI commands (exported, sorted alphabetically) + (2) Platform-specific CLI commands like GitHub (exported, `gh-`) + (3) Helper commands (exported) + (4) Helper commands (non exported) - Verify help output using `use *; help `. Everything must have human-friendly help output. - See `VOUCHED.example` for an example vouch file. diff --git a/.github/vouch/vouch.nu b/.github/vouch/vouch.nu index 367770cbd..6dc67e73d 100755 --- a/.github/vouch/vouch.nu +++ b/.github/vouch/vouch.nu @@ -35,18 +35,14 @@ export def main [] { # ./vouch.nu add someuser --write # # # Add with platform prefix -# ./vouch.nu add someuser --platform github --write +# ./vouch.nu add github:someuser --write # export def "main add" [ - username: string, # Username to vouch for - --platform: string = "", # Platform prefix (e.g., "github") + username: string, # Username to vouch for (supports platform:user format) + --default-platform: string = "", # Assumed platform for entries without explicit platform --vouched-file: string, # Path to vouched contributors file (default: VOUCHED or .github/VOUCHED) --write (-w), # Write the file in-place (default: output to stdout) ] { - if ($username | str starts-with "-") and ($platform | is-empty) { - error make { msg: "platform is required when username starts with -" } - } - let file = if ($vouched_file | is-empty) { let default = default-vouched-file if ($default | is-empty) { @@ -63,13 +59,12 @@ export def "main add" [ let contributors = $lines | where { |line| not (($line | str starts-with "#") or ($line | str trim | is-empty)) } - let new_contributors = add-user $username $contributors --platform $platform + let new_contributors = add-user $username $contributors --default-platform $default_platform let new_content = ($comments | append $new_contributors | str join "\n") + "\n" if $write { $new_content | save -f $file - let entry = if ($platform | is-empty) { $username } else { $"($platform):($username)" } - print $"Added ($entry) to vouched contributors" + print $"Added ($username) to vouched contributors" } else { print -n $new_content } @@ -102,10 +97,8 @@ export def "main gh-manage-by-issue" [ --vouched-file: string, # Path to vouched contributors file (default: VOUCHED or .github/VOUCHED) --allow-vouch = true, # Enable "lgtm" handling to vouch for contributors --allow-denounce = true, # Enable "denounce" handling to denounce users - --explicit-platform = false, # Add platform prefix (github:) to entries --dry-run = true, # Print what would happen without making changes ] { - let platform = if $explicit_platform { "github" } else { "" } let file = if ($vouched_file | is-empty) { let default = default-vouched-file if ($default | is-empty) { @@ -159,7 +152,7 @@ export def "main gh-manage-by-issue" [ let lines = open-vouched-file $file if $is_lgtm { - let status = check-user $issue_author $lines --platform github --default-platform github + let status = check-user $issue_author $lines --default-platform github if $status == "vouched" { print $"($issue_author) is already vouched" @@ -175,18 +168,17 @@ export def "main gh-manage-by-issue" [ return } - let entry = if ($platform | is-empty) { $issue_author } else { $"($platform):($issue_author)" } if $dry_run { - print $"(dry-run) Would add ($entry) to ($file)" + print $"(dry-run) Would add ($issue_author) to ($file)" print "vouched" return } - let new_lines = add-user $issue_author $lines --platform $platform + let new_lines = add-user $issue_author $lines --default-platform github let new_content = ($new_lines | str join "\n") + "\n" $new_content | save -f $file - print $"Added ($entry) to vouched contributors" + print $"Added ($issue_author) to vouched contributors" print "vouched" return } @@ -200,22 +192,21 @@ export def "main gh-manage-by-issue" [ } let reason = $match.capture1? | default "" - let status = check-user $target_user $lines --platform github --default-platform github + let status = check-user $target_user $lines --default-platform github if $status == "denounced" { print $"($target_user) is already denounced" print "unchanged" return } - let handle = if ($platform | is-empty) { $target_user } else { $"($platform):($target_user)" } if $dry_run { - let entry = if ($reason | is-empty) { $"-($handle)" } else { $"-($handle) ($reason)" } + let entry = if ($reason | is-empty) { $"-($target_user)" } else { $"-($target_user) ($reason)" } print $"(dry-run) Would add ($entry) to ($file)" print "denounced" return } - let new_lines = denounce-user $target_user $reason $lines --platform $platform + let new_lines = denounce-user $target_user $reason $lines --default-platform github let new_content = ($new_lines | str join "\n") + "\n" $new_content | save -f $file @@ -242,19 +233,15 @@ export def "main gh-manage-by-issue" [ # ./vouch.nu denounce badactor --write # # # Denounce with platform prefix -# ./vouch.nu denounce badactor --platform github --write +# ./vouch.nu denounce github:badactor --write # export def "main denounce" [ - username: string, # Username to denounce + username: string, # Username to denounce (supports platform:user format) + --default-platform: string = "", # Assumed platform for entries without explicit platform --reason: string, # Optional reason for denouncement - --platform: string = "", # Platform prefix (e.g., "github") --vouched-file: string, # Path to vouched contributors file (default: VOUCHED or .github/VOUCHED) --write (-w), # Write the file in-place (default: output to stdout) ] { - if ($username | str starts-with "-") and ($platform | is-empty) { - error make { msg: "platform is required when username starts with -" } - } - let file = if ($vouched_file | is-empty) { let default = default-vouched-file if ($default | is-empty) { @@ -266,13 +253,12 @@ export def "main denounce" [ } let lines = open-vouched-file $file - let new_lines = denounce-user $username $reason $lines --platform $platform + let new_lines = denounce-user $username $reason $lines --default-platform $default_platform let new_content = ($new_lines | str join "\n") + "\n" if $write { $new_content | save -f $file - let handle = if ($platform | is-empty) { $username } else { $"($platform):($username)" } - print $"Denounced ($handle)" + print $"Denounced ($username)" } else { print -n $new_content } @@ -290,12 +276,12 @@ export def "main denounce" [ # Examples: # # ./vouch.nu check someuser +# ./vouch.nu check github:someuser # ./vouch.nu check someuser --vouched-file path/to/VOUCHED -# ./vouch.nu check someuser --platform github --default-platform github +# ./vouch.nu check someuser --default-platform github # export def "main check" [ - username: string, # Username to check - --platform: string = "", # Platform to match (e.g., "github"). Empty matches any. + username: string, # Username to check (supports platform:user format) --default-platform: string = "", # Assumed platform for entries without explicit platform --vouched-file: string, # Path to vouched contributors file (default: VOUCHED or .github/VOUCHED) ] { @@ -306,7 +292,7 @@ export def "main check" [ exit 1 } - let status = check-user $username $lines --platform $platform --default-platform $default_platform + let status = check-user $username $lines --default-platform $default_platform print $status match $status { "vouched" => { exit 0 } @@ -343,10 +329,8 @@ export def "main gh-check-pr" [ --vouched-file: string = ".github/VOUCHED", # Path to vouched contributors file --require-vouch = true, # Require users to be vouched; if false, only denounced users are blocked --auto-close = false, # Close unvouched PRs with a comment - --explicit-platform = false, # Require platform prefix (github:) when matching --dry-run = true, # Print what would happen without making changes ] { - let platform = if $explicit_platform { "github" } else { "" } let owner = ($repo | split row "/" | first) let repo_name = ($repo | split row "/" | last) @@ -379,7 +363,7 @@ export def "main gh-check-pr" [ let file_data = github api "get" $"/repos/($owner)/($repo_name)/contents/($vouched_file)?ref=($default_branch)" let content = $file_data.content | decode base64 | decode utf-8 let lines = $content | lines - let status = check-user $pr_author $lines --platform github --default-platform github + let status = check-user $pr_author $lines --default-platform github if $status == "vouched" { print $"($pr_author) is in the vouched contributors list" @@ -467,17 +451,18 @@ This PR will be closed automatically. See https://github.com/($owner)/($repo_nam # Supports platform:username format (e.g., github:mitchellh). # Returns "vouched", "denounced", or "unknown". export def check-user [ - username: string, # Username to check + username: string, # Username to check (supports platform:user format) lines: list, # Lines from the vouched file - --platform: string = "", # Platform to match (e.g., "github"). Empty matches any. --default-platform: string = "", # Assumed platform for entries without explicit platform ] { let contributors = $lines | where { |line| not (($line | str starts-with "#") or ($line | str trim | is-empty)) } - let username_lower = ($username | str downcase) - let platform_lower = ($platform | str downcase) + let parsed_input = parse-handle $username + let input_user = $parsed_input.username + let input_platform = $parsed_input.platform let default_platform_lower = ($default_platform | str downcase) + for line in $contributors { let handle = ($line | str trim | split row " " | first) @@ -489,10 +474,13 @@ export def check-user [ let entry_platform = if ($parsed.platform | is-empty) { $default_platform_lower } else { $parsed.platform } let entry_user = $parsed.username - # Match if usernames match and (no platform filter OR platforms match) - let platform_matches = ($platform_lower | is-empty) or ($entry_platform | is-empty) or ($entry_platform == $platform_lower) + # Determine platform to match against + let check_platform = if ($input_platform | is-empty) { $default_platform_lower } else { $input_platform } - if ($entry_user == $username_lower) and $platform_matches { + # Match if usernames match and platforms match (or either is empty) + let platform_matches = ($check_platform | is-empty) or ($entry_platform | is-empty) or ($entry_platform == $check_platform) + + if ($entry_user == $input_user) and $platform_matches { if $is_denounced { return "denounced" } else { @@ -505,17 +493,18 @@ export def check-user [ } # Add a user to the contributor lines, removing any existing entry first. +# Comments and blank lines are ignored and preserved. # # Supports platform:username format (e.g., github:mitchellh). +# # Returns the updated lines with the user added and sorted. export def add-user [ - username: string, # Username to add + username: string, # Username to add (supports platform:user format) lines: list, # Lines from the vouched file - --platform: string = "", # Platform prefix (e.g., "github") + --default-platform: string = "", # Assumed platform for entries without explicit platform ] { - let filtered = remove-user $username $lines --platform $platform - let entry = if ($platform | is-empty) { $username } else { $"($platform):($username)" } - $filtered | append $entry | sort -i + let filtered = remove-user $username $lines --default-platform $default_platform + $filtered | append $username | sort -i } # Denounce a user in the contributor lines, removing any existing entry first. @@ -523,14 +512,13 @@ export def add-user [ # Supports platform:username format (e.g., github:mitchellh). # Returns the updated lines with the user added as denounced and sorted. export def denounce-user [ - username: string, # Username to denounce + username: string, # Username to denounce (supports platform:user format) reason: string, # Reason for denouncement (can be empty) lines: list, # Lines from the vouched file - --platform: string = "", # Platform prefix (e.g., "github") + --default-platform: string = "", # Assumed platform for entries without explicit platform ] { - let filtered = remove-user $username $lines --platform $platform - let handle = if ($platform | is-empty) { $username } else { $"($platform):($username)" } - let entry = if ($reason | is-empty) { $"-($handle)" } else { $"-($handle) ($reason)" } + let filtered = remove-user $username $lines --default-platform $default_platform + let entry = if ($reason | is-empty) { $"-($username)" } else { $"-($username) ($reason)" } $filtered | append $entry | sort -i } @@ -540,12 +528,15 @@ export def denounce-user [ # Supports platform:username format (e.g., github:mitchellh). # Returns the filtered lines after removal. export def remove-user [ - username: string, # Username to remove + username: string, # Username to remove (supports platform:user format) lines: list, # Lines from the vouched file - --platform: string = "", # Platform to match (e.g., "github"). Empty matches any. + --default-platform: string = "", # Assumed platform for entries without explicit platform ] { - let username_lower = ($username | str downcase) - let platform_lower = ($platform | str downcase) + let parsed_input = parse-handle $username + let input_user = $parsed_input.username + let input_platform = $parsed_input.platform + let default_platform_lower = ($default_platform | str downcase) + $lines | where { |line| # Pass through comments and blank lines if ($line | str starts-with "#") or ($line | str trim | is-empty) { @@ -560,12 +551,15 @@ export def remove-user [ } let parsed = parse-handle $entry - let entry_platform = $parsed.platform + let entry_platform = if ($parsed.platform | is-empty) { $default_platform_lower } else { $parsed.platform } let entry_user = $parsed.username - # Keep if username doesn't match OR (platform filter set AND platforms don't match AND entry has platform) - let platform_matches = ($platform_lower | is-empty) or ($entry_platform | is-empty) or ($entry_platform == $platform_lower) - not (($entry_user == $username_lower) and $platform_matches) + # Determine platform to match against + let check_platform = if ($input_platform | is-empty) { $default_platform_lower } else { $input_platform } + + # Keep if username doesn't match OR platforms don't match (when both have platforms) + let platform_matches = ($check_platform | is-empty) or ($entry_platform | is-empty) or ($entry_platform == $check_platform) + not (($entry_user == $input_user) and $platform_matches) } }