From 8d447bea0483c70240cbe44f750cdcfb02051471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20Cadeiras?= Date: Tue, 14 Apr 2026 19:54:05 -0300 Subject: [PATCH] Add GitHub project management infrastructure --- .github/ISSUE_TEMPLATE/bug.yml | 53 +++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 +++ .github/ISSUE_TEMPLATE/epic.yml | 60 ++++++++++++++++ .github/ISSUE_TEMPLATE/spike.yml | 50 ++++++++++++++ .github/ISSUE_TEMPLATE/task.yml | 52 ++++++++++++++ .github/labels.yml | 37 ++++++++++ .github/pull_request_template.md | 15 ++++ .github/workflows/pr-hygiene.yml | 57 ++++++++++++++++ .github/workflows/sync-labels.yml | 109 ++++++++++++++++++++++++++++++ 9 files changed, 441 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/epic.yml create mode 100644 .github/ISSUE_TEMPLATE/spike.yml create mode 100644 .github/ISSUE_TEMPLATE/task.yml create mode 100644 .github/labels.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/pr-hygiene.yml create mode 100644 .github/workflows/sync-labels.yml diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 00000000..c5c99e0e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,53 @@ +name: Bug +description: Report a defect that needs investigation or a fix +title: "[Bug] " +labels: + - "type:bug" +body: + - type: markdown + attributes: + value: | + Use this template for broken or incorrect behavior. Include enough detail to reproduce the issue. + - type: textarea + id: current_behavior + attributes: + label: Current behavior + description: What is happening now? + placeholder: Describe the observed behavior. + validations: + required: true + - type: textarea + id: expected_behavior + attributes: + label: Expected behavior + description: What should happen instead? + placeholder: Describe the correct or intended behavior. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: How can someone reproduce the issue? + placeholder: | + 1. Go to ... + 2. Run ... + 3. Observe ... + validations: + required: true + - type: textarea + id: impact + attributes: + label: Impact + description: Who or what is affected, and how severe is it? + placeholder: Explain user impact, system impact, or urgency. + validations: + required: true + - type: textarea + id: done_criteria + attributes: + label: Done criteria + description: What confirms the bug is fixed? + placeholder: Define the validation or acceptance conditions. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..00def678 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Questions about setup or usage + url: https://github.com/LucasErcolano/MiroFish/blob/main/README.md + about: Check the project README before opening a workflow issue for general usage questions. + - name: Community chat on Discord + url: http://discord.gg/ePf5aPaHnA + about: Use Discord for broad questions, discussions, or community support. diff --git a/.github/ISSUE_TEMPLATE/epic.yml b/.github/ISSUE_TEMPLATE/epic.yml new file mode 100644 index 00000000..26063434 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/epic.yml @@ -0,0 +1,60 @@ +name: Epic +description: Coordinating issue for a larger body of work +title: "[Epic] " +labels: + - "type:epic" +body: + - type: markdown + attributes: + value: | + Use this template for a larger initiative that will be split into smaller issues. + - type: textarea + id: objective + attributes: + label: Objective + description: What outcome should this epic deliver? + placeholder: Describe the main goal and the value this epic should create. + validations: + required: true + - type: textarea + id: scope + attributes: + label: Scope + description: What is included in this epic? + placeholder: List the work this epic should cover. + validations: + required: true + - type: textarea + id: out_of_scope + attributes: + label: Out of scope + description: What is explicitly not part of this epic? + placeholder: Capture boundaries to keep the work focused. + validations: + required: true + - type: textarea + id: dependencies + attributes: + label: Dependencies + description: What work, systems, or decisions does this depend on? + placeholder: Reference related issues, external dependencies, or blockers. + validations: + required: false + - type: textarea + id: done_criteria + attributes: + label: Done criteria + description: How will we know this epic is complete? + placeholder: Define the conditions required to close this epic. + validations: + required: true + - type: textarea + id: subissues + attributes: + label: Sub-issues checklist + description: List the issues that should roll up into this epic. + placeholder: | + - [ ] #123 + - [ ] Create issue for ... + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/spike.yml b/.github/ISSUE_TEMPLATE/spike.yml new file mode 100644 index 00000000..3d7a2290 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/spike.yml @@ -0,0 +1,50 @@ +name: Spike +description: Time-boxed investigation or research task +title: "[Spike] " +labels: + - "type:spike" +body: + - type: markdown + attributes: + value: | + Use this template for research or technical exploration that should end with a recommendation. + - type: textarea + id: question + attributes: + label: Question to answer + description: What decision or uncertainty is this spike meant to resolve? + placeholder: State the main question as clearly as possible. + validations: + required: true + - type: textarea + id: options + attributes: + label: Options to evaluate + description: Which alternatives should be compared? + placeholder: List the candidate approaches or tools to investigate. + validations: + required: true + - type: textarea + id: deliverable + attributes: + label: Expected deliverable + description: What output should this spike produce? + placeholder: "Example: recommendation, benchmark, decision memo, or migration notes." + validations: + required: true + - type: textarea + id: done_criteria + attributes: + label: Done criteria + description: What needs to happen before this spike can be closed? + placeholder: Define the minimum evidence or conclusion expected. + validations: + required: true + - type: input + id: deadline + attributes: + label: Target date + description: Optional deadline for the spike + placeholder: YYYY-MM-DD + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/task.yml b/.github/ISSUE_TEMPLATE/task.yml new file mode 100644 index 00000000..3ce29cfb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task.yml @@ -0,0 +1,52 @@ +name: Task +description: Actionable implementation task +title: "[Task] " +labels: + - "type:task" +body: + - type: markdown + attributes: + value: | + Use this template for a focused piece of work that should usually land in a single pull request. + - type: textarea + id: context + attributes: + label: Context + description: What problem or opportunity is this task addressing? + placeholder: Summarize the relevant context and why this task matters. + validations: + required: true + - type: textarea + id: expected_result + attributes: + label: Expected result + description: What should be true once this task is complete? + placeholder: Describe the intended outcome in concrete terms. + validations: + required: true + - type: textarea + id: acceptance_criteria + attributes: + label: Acceptance criteria + description: What conditions must be met before this can be closed? + placeholder: | + - [ ] ... + - [ ] ... + validations: + required: true + - type: textarea + id: dependencies + attributes: + label: Dependencies + description: What related issues, decisions, or blockers affect this task? + placeholder: Reference anything this task needs before it can start or finish. + validations: + required: false + - type: textarea + id: technical_notes + attributes: + label: Technical notes + description: Optional implementation notes, caveats, or pointers + placeholder: Include technical details that will help the implementer. + validations: + required: false diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 00000000..98153e66 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,37 @@ +labels: + - name: "type:epic" + color: "5319e7" + description: "Large coordinating issue that groups related work" + - name: "type:task" + color: "0e8a16" + description: "Actionable implementation task" + - name: "type:spike" + color: "fbca04" + description: "Time-boxed research or technical investigation" + - name: "type:bug" + color: "d73a4a" + description: "Defect or incorrect behavior that requires a fix" + - name: "area:llm" + color: "1d76db" + description: "Work related to LLM providers, routing, or inference" + - name: "area:memory" + color: "0052cc" + description: "Work related to conversation memory or persistence" + - name: "area:rag" + color: "0e4e8a" + description: "Work related to retrieval, indexing, or knowledge access" + - name: "area:planning" + color: "5319e7" + description: "Work related to planning, orchestration, or agent coordination" + - name: "area:infra" + color: "c2e0c6" + description: "Repository, tooling, CI, or operational infrastructure work" + - name: "area:eval" + color: "bfdadc" + description: "Benchmarks, experiments, or evaluation work" + - name: "blocked" + color: "b60205" + description: "Cannot progress until a dependency or blocker is resolved" + - name: "good first issue" + color: "7057ff" + description: "Good candidate for a newcomer to the project" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..d9a7bb10 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## Linked issue + +Closes # + +## What changed + +Describe the changes in this pull request. + +## How to test + +Describe the verification steps. If no runtime test applies, explain why. + +## Risks + +List known risks, follow-up work, or rollout considerations. diff --git a/.github/workflows/pr-hygiene.yml b/.github/workflows/pr-hygiene.yml new file mode 100644 index 00000000..f4a3d845 --- /dev/null +++ b/.github/workflows/pr-hygiene.yml @@ -0,0 +1,57 @@ +name: PR Hygiene + +on: + pull_request: + types: + - opened + - edited + - reopened + - synchronize + +permissions: + contents: read + pull-requests: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Validate pull request body + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.pull_request.body || ""; + + function getSection(title) { + const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const regex = new RegExp(`##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s+|$)`, "i"); + const match = body.match(regex); + return match ? match[1].trim() : ""; + } + + const linkedIssue = getSection("Linked issue"); + const howToTest = getSection("How to test"); + + const hasIssueReference = + /#\d+/.test(linkedIssue) || + /https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/\d+/i.test(linkedIssue); + + const sanitizedHowToTest = howToTest + .replace(/Describe the verification steps\. If no runtime test applies, explain why\./i, "") + .trim(); + + const failures = []; + + if (!hasIssueReference) { + failures.push("Add an issue reference under `## Linked issue` (for example `Closes #123`)."); + } + + if (!sanitizedHowToTest) { + failures.push("Fill in `## How to test` with verification steps or explain why no runtime test applies."); + } + + if (failures.length > 0) { + core.setFailed(failures.join("\n")); + } else { + core.info("Pull request body passed hygiene checks."); + } diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 00000000..9e80d047 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,109 @@ +name: Sync Labels + +on: + workflow_dispatch: + push: + paths: + - ".github/labels.yml" + +permissions: + contents: read + issues: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: python -m pip install --disable-pip-version-check pyyaml + + - name: Sync labels + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + python - <<'PY' + import json + import os + import urllib.error + import urllib.parse + import urllib.request + + import yaml + + token = os.environ["GITHUB_TOKEN"] + owner_repo = os.environ["GITHUB_REPOSITORY"] + base_url = f"https://api.github.com/repos/{owner_repo}" + + with open(".github/labels.yml", "r", encoding="utf-8") as fh: + config = yaml.safe_load(fh) or {} + + desired_labels = config.get("labels", []) + desired_by_name = {label["name"]: label for label in desired_labels} + + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": "mirofish-label-sync", + } + + def request(method, url, payload=None): + data = None + if payload is not None: + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers=headers, method=method) + with urllib.request.urlopen(req) as response: + raw = response.read() + return json.loads(raw.decode("utf-8")) if raw else None + + existing = [] + page = 1 + while True: + url = f"{base_url}/labels?per_page=100&page={page}" + batch = request("GET", url) + if not batch: + break + existing.extend(batch) + if len(batch) < 100: + break + page += 1 + + existing_by_name = {label["name"]: label for label in existing} + + for name, desired in desired_by_name.items(): + payload = { + "new_name": desired["name"], + "color": desired["color"], + "description": desired["description"], + } + if name in existing_by_name: + url = f"{base_url}/labels/{urllib.parse.quote(name, safe='')}" + request("PATCH", url, payload) + print(f"updated {name}") + else: + url = f"{base_url}/labels" + request("POST", url, { + "name": desired["name"], + "color": desired["color"], + "description": desired["description"], + }) + print(f"created {name}") + + for name in sorted(existing_by_name): + if name not in desired_by_name: + url = f"{base_url}/labels/{urllib.parse.quote(name, safe='')}" + try: + request("DELETE", url) + print(f"deleted {name}") + except urllib.error.HTTPError as exc: + raise RuntimeError(f"Failed to delete label '{name}': {exc.read().decode('utf-8')}") from exc + PY