Merge pull request #2 from LucasErcolano/codex/github-management-infra
Add GitHub management infrastructure
This commit is contained in:
commit
5c824b66a6
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.");
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue