diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 124bde53..bfa1de66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,16 @@ jobs: git fetch origin "${{ github.base_ref }}" --depth=1 git diff --check "origin/${{ github.base_ref }}"...HEAD + issue-template-label-validation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Validate issue template labels + run: python scripts/validate_issue_template_labels.py + backend-lint: needs: detect-changes if: needs.detect-changes.outputs.run_backend == 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c3526b9..073eaa74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,24 @@ SecuScan is built for learning, defensive security workflows, and ethical testin When issue labels are available, look for tags such as `good first issue`, `documentation`, `frontend`, `backend`, `plugin`, `help wanted`, or `gssoc`. +## Issue Template Label Maintenance + +Issue templates in `.github/ISSUE_TEMPLATE/` must only reference labels from the active repository taxonomy. + +When adding or updating issue template labels: + +- Use active label groups such as `type:*`, `area:*`, `priority:*`, and `level:*`. +- Avoid deprecated labels such as `bug`, `feature`, `documentation`, and `help wanted`. +- Keep template labels aligned with the labels used by maintainers and CI. + +Before opening a pull request that changes issue templates, run: + +```bash +python scripts/validate_issue_template_labels.py +``` + +The CI workflow also runs this validation and will fail if an issue template references a label that is not included in the approved label taxonomy. + ## Local Setup ### Prerequisites diff --git a/scripts/validate_issue_template_labels.py b/scripts/validate_issue_template_labels.py new file mode 100644 index 00000000..39e8a85f --- /dev/null +++ b/scripts/validate_issue_template_labels.py @@ -0,0 +1,101 @@ +from pathlib import Path +import re +import sys + +VALID_LABELS = { + "type:bug", + "type:feature", + "type:docs", + "type:devops", + "type:security", + "type:testing", + "type:performance", + "type:refactor", + "area:ci", + "area:docs", + "area:backend", + "area:frontend", + "priority:low", + "priority:medium", + "priority:high", + "level:beginner", + "level:intermediate", + "level:advanced", +} + +REPO_ROOT = Path(__file__).resolve().parents[1] +TEMPLATE_DIR = REPO_ROOT / ".github" / "ISSUE_TEMPLATE" + +errors = [] + + +def extract_front_matter(content): + if not content.startswith("---"): + return "" + + parts = content.split("---", 2) + if len(parts) < 3: + return "" + + return parts[1] + + +def parse_labels(raw_value): + raw_value = raw_value.strip().strip("\"'") + + if raw_value.startswith("[") and raw_value.endswith("]"): + raw_value = raw_value[1:-1] + + return [ + label.strip().strip("\"'") + for label in raw_value.split(",") + if label.strip() + ] + + +def extract_labels_from_front_matter(front_matter): + labels = [] + lines = front_matter.splitlines() + + for index, line in enumerate(lines): + match = re.match(r"^labels:\s*(.*)$", line) + + if not match: + continue + + value = match.group(1).strip() + + if value: + labels.extend(parse_labels(value)) + continue + + for next_line in lines[index + 1:]: + stripped = next_line.strip() + + if not stripped: + continue + + if not stripped.startswith("-"): + break + + labels.append(stripped[1:].strip().strip("\"'")) + + return labels + + +for template in list(TEMPLATE_DIR.glob("*.md")) + list(TEMPLATE_DIR.glob("*.yml")) + list(TEMPLATE_DIR.glob("*.yaml")): + content = template.read_text(encoding="utf-8") + front_matter = extract_front_matter(content) + labels = extract_labels_from_front_matter(front_matter) + + for label in labels: + if label not in VALID_LABELS: + errors.append("{}: invalid label '{}'".format(template.relative_to(REPO_ROOT), label)) + +if errors: + print("Invalid issue template labels found:") + for error in errors: + print("- {}".format(error)) + sys.exit(1) + +print("All issue template labels are valid.") \ No newline at end of file