From 00856a116caf1502dece6d28e3f234ec6abe6aa3 Mon Sep 17 00:00:00 2001 From: Sebastien Taggart Date: Thu, 11 Jun 2026 18:20:24 -0400 Subject: [PATCH] Audit configured labels at /setup time and offer to create any missing ones --- .agents/skills/setup/SKILL.md | 19 ++- .claude/commands/setup.md | 19 ++- .cursor/rules/setup.mdc | 19 ++- .gemini/skills/setup/SKILL.md | 19 ++- skills/github-agile/scripts/label-audit.py | 130 ++++++++++++++++++++ skills/github-agile/scripts/label-create.py | 68 ++++++++++ skills/github-agile/setup.md | 17 ++- 7 files changed, 282 insertions(+), 9 deletions(-) create mode 100755 skills/github-agile/scripts/label-audit.py create mode 100755 skills/github-agile/scripts/label-create.py diff --git a/.agents/skills/setup/SKILL.md b/.agents/skills/setup/SKILL.md index 01d78c4..4a0dc49 100644 --- a/.agents/skills/setup/SKILL.md +++ b/.agents/skills/setup/SKILL.md @@ -384,9 +384,24 @@ Ask: **"Create any missing labels from this baseline now? (yes/no)"** Wait for response. -- **yes** → create missing labels only (do not recreate existing labels). Use sensible colors and short descriptions. +- **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py bug enhancement chore documentation ready-for-qa qa-passed qa-failed`. The script applies sensible color/description defaults from a baked-in table and warns-and-continues on any name that already exists. - **no / skip / anything else** → continue without creating labels. +#### Configured-label audit + +Whether or not the greenfield baseline ran, audit the labels that configured skills will try to apply at runtime against what actually exists in the repo: + +```bash +python3 CodeCannon/skills/github-agile/scripts/label-audit.py +``` + +The script reads `.codecannon.yaml`, collects the names referenced by `TICKET_LABELS` plus `QA_READY_LABEL` / `QA_PASSED_LABEL` / `QA_FAILED_LABEL` (skipping any that are unset), diffs against `gh label list`, and prints missing names — one per line — on stdout. A one-line diagnostic goes to stderr. + +- **No stdout output** → all configured labels exist. Continue silently to the "Display the results" step below. +- **One or more names on stdout** → show them as a combined list and ask once: **"These labels are referenced by your configured skills but don't exist in the repo: \. Create them now? (yes/no)"** + - **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py ...` with the missing names from the audit output. + - **no / skip** → continue, but at the end of Phase 4 print a one-line summary: "Skipped creating: \. `/submit-for-review` will warn and continue if it needs to apply a missing label; `/qa` and `/start` may degrade similarly." + After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again. If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects." @@ -470,4 +485,4 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri - Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling. - Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation. - If the user skips a config value, do not ask again. Move on. - + diff --git a/.claude/commands/setup.md b/.claude/commands/setup.md index 4227f0e..c06c340 100644 --- a/.claude/commands/setup.md +++ b/.claude/commands/setup.md @@ -379,9 +379,24 @@ Ask: **"Create any missing labels from this baseline now? (yes/no)"** Wait for response. -- **yes** → create missing labels only (do not recreate existing labels). Use sensible colors and short descriptions. +- **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py bug enhancement chore documentation ready-for-qa qa-passed qa-failed`. The script applies sensible color/description defaults from a baked-in table and warns-and-continues on any name that already exists. - **no / skip / anything else** → continue without creating labels. +#### Configured-label audit + +Whether or not the greenfield baseline ran, audit the labels that configured skills will try to apply at runtime against what actually exists in the repo: + +```bash +python3 CodeCannon/skills/github-agile/scripts/label-audit.py +``` + +The script reads `.codecannon.yaml`, collects the names referenced by `TICKET_LABELS` plus `QA_READY_LABEL` / `QA_PASSED_LABEL` / `QA_FAILED_LABEL` (skipping any that are unset), diffs against `gh label list`, and prints missing names — one per line — on stdout. A one-line diagnostic goes to stderr. + +- **No stdout output** → all configured labels exist. Continue silently to the "Display the results" step below. +- **One or more names on stdout** → show them as a combined list and ask once: **"These labels are referenced by your configured skills but don't exist in the repo: \. Create them now? (yes/no)"** + - **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py ...` with the missing names from the audit output. + - **no / skip** → continue, but at the end of Phase 4 print a one-line summary: "Skipped creating: \. `/submit-for-review` will warn and continue if it needs to apply a missing label; `/qa` and `/start` may degrade similarly." + After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again. If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects." @@ -465,4 +480,4 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri - Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling. - Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation. - If the user skips a config value, do not ask again. Move on. - + diff --git a/.cursor/rules/setup.mdc b/.cursor/rules/setup.mdc index c8fa0ac..98c8e98 100644 --- a/.cursor/rules/setup.mdc +++ b/.cursor/rules/setup.mdc @@ -385,9 +385,24 @@ Ask: **"Create any missing labels from this baseline now? (yes/no)"** Wait for response. -- **yes** → create missing labels only (do not recreate existing labels). Use sensible colors and short descriptions. +- **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py bug enhancement chore documentation ready-for-qa qa-passed qa-failed`. The script applies sensible color/description defaults from a baked-in table and warns-and-continues on any name that already exists. - **no / skip / anything else** → continue without creating labels. +#### Configured-label audit + +Whether or not the greenfield baseline ran, audit the labels that configured skills will try to apply at runtime against what actually exists in the repo: + +```bash +python3 CodeCannon/skills/github-agile/scripts/label-audit.py +``` + +The script reads `.codecannon.yaml`, collects the names referenced by `TICKET_LABELS` plus `QA_READY_LABEL` / `QA_PASSED_LABEL` / `QA_FAILED_LABEL` (skipping any that are unset), diffs against `gh label list`, and prints missing names — one per line — on stdout. A one-line diagnostic goes to stderr. + +- **No stdout output** → all configured labels exist. Continue silently to the "Display the results" step below. +- **One or more names on stdout** → show them as a combined list and ask once: **"These labels are referenced by your configured skills but don't exist in the repo: \. Create them now? (yes/no)"** + - **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py ...` with the missing names from the audit output. + - **no / skip** → continue, but at the end of Phase 4 print a one-line summary: "Skipped creating: \. `/submit-for-review` will warn and continue if it needs to apply a missing label; `/qa` and `/start` may degrade similarly." + After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again. If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects." @@ -471,4 +486,4 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri - Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling. - Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation. - If the user skips a config value, do not ask again. Move on. - + diff --git a/.gemini/skills/setup/SKILL.md b/.gemini/skills/setup/SKILL.md index 0399fcc..2dc6ee9 100644 --- a/.gemini/skills/setup/SKILL.md +++ b/.gemini/skills/setup/SKILL.md @@ -384,9 +384,24 @@ Ask: **"Create any missing labels from this baseline now? (yes/no)"** Wait for response. -- **yes** → create missing labels only (do not recreate existing labels). Use sensible colors and short descriptions. +- **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py bug enhancement chore documentation ready-for-qa qa-passed qa-failed`. The script applies sensible color/description defaults from a baked-in table and warns-and-continues on any name that already exists. - **no / skip / anything else** → continue without creating labels. +#### Configured-label audit + +Whether or not the greenfield baseline ran, audit the labels that configured skills will try to apply at runtime against what actually exists in the repo: + +```bash +python3 CodeCannon/skills/github-agile/scripts/label-audit.py +``` + +The script reads `.codecannon.yaml`, collects the names referenced by `TICKET_LABELS` plus `QA_READY_LABEL` / `QA_PASSED_LABEL` / `QA_FAILED_LABEL` (skipping any that are unset), diffs against `gh label list`, and prints missing names — one per line — on stdout. A one-line diagnostic goes to stderr. + +- **No stdout output** → all configured labels exist. Continue silently to the "Display the results" step below. +- **One or more names on stdout** → show them as a combined list and ask once: **"These labels are referenced by your configured skills but don't exist in the repo: \. Create them now? (yes/no)"** + - **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py ...` with the missing names from the audit output. + - **no / skip** → continue, but at the end of Phase 4 print a one-line summary: "Skipped creating: \. `/submit-for-review` will warn and continue if it needs to apply a missing label; `/qa` and `/start` may degrade similarly." + After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again. If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects." @@ -470,4 +485,4 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri - Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling. - Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation. - If the user skips a config value, do not ask again. Move on. - + diff --git a/skills/github-agile/scripts/label-audit.py b/skills/github-agile/scripts/label-audit.py new file mode 100755 index 0000000..45a7c1a --- /dev/null +++ b/skills/github-agile/scripts/label-audit.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""label-audit — list configured labels that don't exist in the GitHub repo. + +Reads `.codecannon.yaml` from the current working directory, collects the +labels that skills will try to apply at runtime (TICKET_LABELS pool plus the +three QA labels), then queries `gh label list` and emits the missing names — +one per line — on stdout. Used by /setup Phase 4 to catch the failure mode +where a configured label doesn't exist in the repo and a downstream skill +(/submit-for-review, /qa, /start) only finds out at the worst moment. + +Stdout is intentionally minimal so the calling skill can do `if stdout is +non-empty: prompt the operator`. A one-line diagnostic goes to stderr for +transparency. + +Usage: + python3 CodeCannon/skills/github-agile/scripts/label-audit.py + +Exit codes: + 0 audit completed (stdout may be empty or list missing labels) + 1 could not read .codecannon.yaml + 2 gh subcommand failed +""" + +import json +import subprocess +import sys +from pathlib import Path + + +def _dequote(value): + if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"): + return value[1:-1] + return value + + +def read_config_values(config_path): + """Return the {key: value} pairs nested under the top-level `config:` key. + + Minimal flat-YAML reader — only covers what /setup writes: a `config:` + block of `KEY: value` lines, indent 2, optional quotes. Block scalars + (`|`) and other keys are ignored — the audit only needs string values. + """ + text = config_path.read_text() + values = {} + in_config = False + for raw in text.splitlines(): + line = raw.rstrip() + if not line.strip() or line.lstrip().startswith('#'): + continue + indent = len(line) - len(line.lstrip()) + stripped = line.strip() + if indent == 0: + in_config = stripped == 'config:' + continue + if not in_config or indent != 2 or ':' not in stripped: + continue + key, _, value = stripped.partition(':') + value = value.strip() + if value == '|' or not value: + continue + values[key.strip()] = _dequote(value) + return values + + +def collect_configured_labels(values): + """Return the unique set of label names referenced by configured skills.""" + labels = set() + pool = values.get('TICKET_LABELS', '') + for name in pool.split(','): + name = name.strip() + if name: + labels.add(name) + for key in ('QA_READY_LABEL', 'QA_PASSED_LABEL', 'QA_FAILED_LABEL'): + name = values.get(key, '').strip() + if name: + labels.add(name) + return labels + + +def fetch_existing_labels(): + """Return the set of label names that exist in the current GitHub repo.""" + result = subprocess.run( + ['gh', 'label', 'list', '--limit', '100', '--json', 'name'], + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(f"Error: gh label list failed: {result.stderr.strip()}", file=sys.stderr) + return None + try: + data = json.loads(result.stdout) + except json.JSONDecodeError as exc: + print(f"Error: could not parse gh label list output: {exc}", file=sys.stderr) + return None + return {entry['name'] for entry in data} + + +def main(argv): + config_path = Path('.codecannon.yaml') + if not config_path.is_file(): + print(f"Error: {config_path} not found in current directory.", file=sys.stderr) + return 1 + + try: + values = read_config_values(config_path) + except OSError as exc: + print(f"Error: could not read {config_path}: {exc}", file=sys.stderr) + return 1 + + configured = collect_configured_labels(values) + if not configured: + print("audited 0 configured labels (none set in .codecannon.yaml)", file=sys.stderr) + return 0 + + existing = fetch_existing_labels() + if existing is None: + return 2 + + missing = sorted(configured - existing) + print( + f"audited {len(configured)} configured label(s); {len(missing)} missing", + file=sys.stderr, + ) + for name in missing: + print(name) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/skills/github-agile/scripts/label-create.py b/skills/github-agile/scripts/label-create.py new file mode 100755 index 0000000..c007d3f --- /dev/null +++ b/skills/github-agile/scripts/label-create.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""label-create — create GitHub labels with sensible defaults. + +Wraps `gh label create` so call-sites don't have to improvise colors and +descriptions. A baked-in table covers the names skills commonly reference +(bug, enhancement, documentation, chore, ready-for-qa, qa-passed, +qa-failed). For any other name — including operator-renamed QA labels +(e.g. QA_PASSED_LABEL: "tested") — falls back to neutral gray with no +description; the operator can refine later in the GitHub UI. + +Used by /setup Phase 4 in two places: the greenfield baseline create when +the repo has zero labels, and the configured-label audit when one or more +labels referenced by .codecannon.yaml don't yet exist in the repo. + +Per the warn-and-continue posture established in #182, a single label +that fails to create (typically because it already exists) prints a +one-line warning and the script moves on to the next name. Exit is 0 as +long as the loop completed; only argument errors abort. + +Usage: + python3 CodeCannon/skills/github-agile/scripts/label-create.py ... + +Exit codes: + 0 loop completed (individual labels may have warned to stderr) + 3 no label names provided +""" + +import subprocess +import sys + +# Default color and description for well-known label names. Colors are the +# GitHub defaults where they exist (bug, enhancement, documentation) and +# conventional traffic-light choices for the QA labels. +DEFAULTS = { + 'bug': ('d73a4a', "Something isn't working"), + 'enhancement': ('a2eeef', 'New feature or request'), + 'documentation': ('0075ca', 'Improvements or additions to documentation'), + 'chore': ('cccccc', 'Routine maintenance'), + 'ready-for-qa': ('fbca04', 'Ready for QA review'), + 'qa-passed': ('0e8a16', 'Passed QA review'), + 'qa-failed': ('e11d21', 'Failed QA review'), +} + +FALLBACK_COLOR = 'cccccc' + + +def main(argv): + if len(argv) < 2: + print(f"Usage: {argv[0]} ...", file=sys.stderr) + return 3 + + for name in argv[1:]: + color, description = DEFAULTS.get(name, (FALLBACK_COLOR, '')) + cmd = ['gh', 'label', 'create', name, '--color', color] + if description: + cmd += ['--description', description] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + stderr = result.stderr.strip() or '(no stderr)' + print(f"Warning: failed to create label '{name}': {stderr}", file=sys.stderr) + continue + print(f"Created label: {name}") + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/skills/github-agile/setup.md b/skills/github-agile/setup.md index 98f160a..330713e 100644 --- a/skills/github-agile/setup.md +++ b/skills/github-agile/setup.md @@ -382,9 +382,24 @@ Ask: **"Create any missing labels from this baseline now? (yes/no)"** Wait for response. -- **yes** → create missing labels only (do not recreate existing labels). Use sensible colors and short descriptions. +- **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py bug enhancement chore documentation ready-for-qa qa-passed qa-failed`. The script applies sensible color/description defaults from a baked-in table and warns-and-continues on any name that already exists. - **no / skip / anything else** → continue without creating labels. +#### Configured-label audit + +Whether or not the greenfield baseline ran, audit the labels that configured skills will try to apply at runtime against what actually exists in the repo: + +```bash +python3 CodeCannon/skills/github-agile/scripts/label-audit.py +``` + +The script reads `.codecannon.yaml`, collects the names referenced by `TICKET_LABELS` plus `QA_READY_LABEL` / `QA_PASSED_LABEL` / `QA_FAILED_LABEL` (skipping any that are unset), diffs against `gh label list`, and prints missing names — one per line — on stdout. A one-line diagnostic goes to stderr. + +- **No stdout output** → all configured labels exist. Continue silently to the "Display the results" step below. +- **One or more names on stdout** → show them as a combined list and ask once: **"These labels are referenced by your configured skills but don't exist in the repo: \. Create them now? (yes/no)"** + - **yes** → run `python3 CodeCannon/skills/github-agile/scripts/label-create.py ...` with the missing names from the audit output. + - **no / skip** → continue, but at the end of Phase 4 print a one-line summary: "Skipped creating: \. `/submit-for-review` will warn and continue if it needs to apply a missing label; `/qa` and `/start` may degrade similarly." + After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again. If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects."