From 0cac5a4a7b4110876a5e5874ea7476e1aa43b38a Mon Sep 17 00:00:00 2001 From: Obayne Date: Fri, 12 Sep 2025 19:18:28 -0500 Subject: [PATCH 1/7] chore(dev): normalize warning punctuation in setup_dev.ps1 --- setup_dev.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup_dev.ps1 b/setup_dev.ps1 index c452186..19bd73f 100644 --- a/setup_dev.ps1 +++ b/setup_dev.ps1 @@ -26,7 +26,7 @@ if (Test-Path "requirements.txt") { Write-Host "[dev-setup] Installing project requirements" pip install -r requirements.txt } else { - Write-Warning "requirements.txt not found — skipping." + Write-Warning "requirements.txt not found - skipping." } if (Test-Path "requirements-dev.txt") { @@ -35,8 +35,9 @@ if (Test-Path "requirements-dev.txt") { Write-Host "[dev-setup] Installing pre-commit hooks" pre-commit install } else { - Write-Warning "requirements-dev.txt not found — skipping dev tools." + Write-Warning "requirements-dev.txt not found - skipping dev tools." } Write-Host "[dev-setup] Done. To activate later: . .venv/Scripts/Activate.ps1" + From 6c96a18548ebbe591d05ee947b0a914127b80111 Mon Sep 17 00:00:00 2001 From: Obayne Date: Fri, 12 Sep 2025 19:22:14 -0500 Subject: [PATCH 2/7] docs: add Sprint 01 workplan --- docs/SPRINT-01.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/SPRINT-01.md diff --git a/docs/SPRINT-01.md b/docs/SPRINT-01.md new file mode 100644 index 0000000..8131c2d --- /dev/null +++ b/docs/SPRINT-01.md @@ -0,0 +1,74 @@ +Sprint 01 Workplan — AutoFire + +Goals +- Keep `main` green; all work via short-lived PRs. +- Establish a thin vertical slice: open a project, view/edit simple geometry, persist, and test. +- Ensure Black/Ruff/pre-commit and CI pass on every PR. + +Branches (create as needed) +- feat/frontend-model-space +- feat/backend-settings-and-store +- feat/cad-core-geometry-basics +- fix/ci-lint-orchestrations (only if CI requires tweaks) + +Definition of Done +- Branch follows naming rules and has ≤300 changed LOC per PR. +- Tests cover new logic; `tests/` updated accordingly. +- Black (L100) and Ruff pass; no unused imports. +- No implicit module side effects; clear boundaries (frontend/backend/cad_core). + +Workstreams and Tasks + +Frontend (Qt) +- Model Space view shell + - Implement a minimal Model Space widget and route for opening a project. + - Hide Sheets dock by default; add a toggle in View menu. + - Add Space selector + lock UI (non-functional toggle wired to backend stub). + - Command bar widget stub with signal emission on Enter. + - Acceptance: launching the app shows Model Space; commands emit signals; no crashes. + +- Input handling foundation + - Centralize key/mouse events in a small handler class. + - Acceptance: events logged via a signal; unit test for key mapping. + +Backend +- Settings service + - Define a typed settings object (e.g., pydantic/dataclass) with load/save to disk. + - Acceptance: round-trip test ensures persistence and defaults. + +- Catalog store (SQLite) + - Wrap SQLite access for seed/types/devices/specs with simple CRUD. + - Provide an interface consumed by frontend to list/search items. + - Acceptance: fixture DB created in tests; CRUD covered with pytest. + +cad_core +- Geometry primitives and units + - Implement basic entities (Point, Vector, LineSegment) and unit helpers. + - Pure functions for transform (translate/scale/rotate) with tests. + - Acceptance: deterministic outputs; no UI dependencies; 100% covered by unit tests. + +Tests +- Add small fixtures for temp project directory and in-memory SQLite. +- Add pytest markers for slow/db if needed; keep default run fast (<10s). + +CI and Tooling +- Ensure `pyproject.toml` configures Black/Ruff; wire pre-commit (local) and validate in CI. +- Add `pytest -q` step in CI if not present; keep cache usage minimal. + +Suggested PR Sequence (vertical slices) +1) cad_core: geometry primitives + tests. +2) backend: settings service + tests. +3) backend: catalog store + tests (SQLite fixtures). +4) frontend: model space shell + command bar stub wired to backend stubs. +5) frontend: input handling + simple command execution that logs. + +Owner Handoff Notes +- Run `. .venv/Scripts/Activate.ps1` then `pip install -r requirements-dev.txt`. +- Pre-commit: `pre-commit install`; run `pre-commit run --all-files` before pushing. +- Test quickly: `pytest -q` (skip if not installed locally; rely on CI). + +Open Questions +- Confirm UI framework pin (PySide6 vs PyQt6) and minimum versions. +- Confirm DB file location and schema migration approach (alembic vs hand-rolled). +- Confirm command architecture (text commands vs palette-style actions). + From 0bb1e793818a20e9547f7fb694937873cd2c6000 Mon Sep 17 00:00:00 2001 From: Obayne Date: Fri, 12 Sep 2025 19:25:19 -0500 Subject: [PATCH 3/7] chore(ci): add issue templates for frontend/backend/cad_core --- .github/ISSUE_TEMPLATE/backend_task.yml | 28 ++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/cad_core_task.yml | 28 ++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 6 +++++ .github/ISSUE_TEMPLATE/frontend_task.yml | 28 ++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/backend_task.yml create mode 100644 .github/ISSUE_TEMPLATE/cad_core_task.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/frontend_task.yml diff --git a/.github/ISSUE_TEMPLATE/backend_task.yml b/.github/ISSUE_TEMPLATE/backend_task.yml new file mode 100644 index 0000000..2269192 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/backend_task.yml @@ -0,0 +1,28 @@ +name: Backend Task +description: Non-UI logic under backend/ (services, persistence, loaders) +labels: [area:backend, type:feature] +body: + - type: textarea + id: summary + attributes: + label: Summary + description: Describe the service/storage/settings change. + placeholder: e.g., Typed settings service with load/save + validations: + required: true + - type: textarea + id: scope + attributes: + label: Scope + description: Files/modules to change; avoid touching UI. + placeholder: backend/settings/service.py, backend/store/catalog.py + - type: checkboxes + id: dod + attributes: + label: Definition of Done + options: + - label: Black/Ruff clean + - label: Tests added (round-trip, CRUD) + - label: No module side effects + - label: ≤300 LOC changed + diff --git a/.github/ISSUE_TEMPLATE/cad_core_task.yml b/.github/ISSUE_TEMPLATE/cad_core_task.yml new file mode 100644 index 0000000..aea2c1e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/cad_core_task.yml @@ -0,0 +1,28 @@ +name: CAD Core Task +description: Geometry kernels, tools, algorithms, and units (cad_core/) +labels: [area:cad_core, type:feature] +body: + - type: textarea + id: summary + attributes: + label: Summary + description: Describe the geometry/algorithm addition. + placeholder: e.g., Point/Vector/LineSegment + transforms + validations: + required: true + - type: textarea + id: scope + attributes: + label: Scope + description: Modules/functions to add/edit; pure code only. + placeholder: cad_core/geom/primitives.py, cad_core/geom/transform.py + - type: checkboxes + id: dod + attributes: + label: Definition of Done + options: + - label: Black/Ruff clean + - label: Unit tests cover new functions + - label: No UI imports or side effects + - label: ≤300 LOC changed + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..64c2bad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false +contact_links: + - name: Sprint Plan + url: https://github.com/OWNER/REPO/blob/main/docs/SPRINT-01.md + about: Review the sprint plan for goals, tasks, and acceptance. + diff --git a/.github/ISSUE_TEMPLATE/frontend_task.yml b/.github/ISSUE_TEMPLATE/frontend_task.yml new file mode 100644 index 0000000..714045c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/frontend_task.yml @@ -0,0 +1,28 @@ +name: Frontend Task +description: UI work under frontend/ (Qt widgets, views, input) +labels: [area:frontend, type:feature] +body: + - type: textarea + id: summary + attributes: + label: Summary + description: Briefly describe the UI change. + placeholder: e.g., Model Space shell with space selector and lock + validations: + required: true + - type: textarea + id: scope + attributes: + label: Scope + description: Files/components to touch; avoid unrelated edits. + placeholder: frontend/widgets/ModelSpace.py, frontend/menus/ViewMenu.py + - type: checkboxes + id: dod + attributes: + label: Definition of Done + options: + - label: Black/Ruff clean + - label: Tests added/updated (signals, handlers) + - label: No side effects in imports + - label: ≤300 LOC changed + From dc892e42c81f974de726c12a2f276cf00feba733 Mon Sep 17 00:00:00 2001 From: Obayne Date: Fri, 12 Sep 2025 19:33:48 -0500 Subject: [PATCH 4/7] chore(issues): seed Sprint 01 issues via GitHub Actions workflow --- .github/ISSUE_TEMPLATE/config.yml | 3 +- .github/seed_issues.json | 28 ++++++++++++ .github/workflows/seed-issues.yml | 74 +++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 .github/seed_issues.json create mode 100644 .github/workflows/seed-issues.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 64c2bad..81c472a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,6 +1,5 @@ blank_issues_enabled: false contact_links: - name: Sprint Plan - url: https://github.com/OWNER/REPO/blob/main/docs/SPRINT-01.md + url: https://github.com/Obayne/AutoFireBase/blob/main/docs/SPRINT-01.md about: Review the sprint plan for goals, tasks, and acceptance. - diff --git a/.github/seed_issues.json b/.github/seed_issues.json new file mode 100644 index 0000000..cae007b --- /dev/null +++ b/.github/seed_issues.json @@ -0,0 +1,28 @@ +[ + { + "title": "Sprint 01: CAD Core — primitives + transforms", + "labels": ["area:cad_core", "type:feature", "sprint:01"], + "body": "Implement Point, Vector, LineSegment and pure transform functions (translate/scale/rotate). Add unit tests with deterministic outputs. See docs/SPRINT-01.md (cad_core section). DoD: Black/Ruff clean; no UI deps; tests added." + }, + { + "title": "Sprint 01: Backend — settings service", + "labels": ["area:backend", "type:feature", "sprint:01"], + "body": "Typed settings object with load/save to disk and sensible defaults. Round-trip tests to ensure persistence. See docs/SPRINT-01.md (Backend / Settings service). DoD: Black/Ruff clean; tests; no module side-effects." + }, + { + "title": "Sprint 01: Backend — catalog store (SQLite)", + "labels": ["area:backend", "type:feature", "sprint:01"], + "body": "Wrap SQLite access for seed/types/devices/specs with simple CRUD and list/search. Use in-memory fixtures for tests. See docs/SPRINT-01.md (Backend / Catalog store). DoD: Black/Ruff clean; CRUD covered by tests." + }, + { + "title": "Sprint 01: Frontend — Model Space shell + command bar", + "labels": ["area:frontend", "type:feature", "sprint:01"], + "body": "Minimal Model Space widget shell. Hide Sheets dock by default with a View menu toggle. Space selector + lock (non-functional toggle wired to backend stub). Command bar emits signal on Enter. See docs/SPRINT-01.md (Frontend / Model Space). DoD: Black/Ruff; signals tested; no crashes." + }, + { + "title": "Sprint 01: Frontend — Input handling foundation", + "labels": ["area:frontend", "type:feature", "sprint:01"], + "body": "Centralize key/mouse events in a small handler class and log via signal. Unit test for key mapping. See docs/SPRINT-01.md (Frontend / Input handling). DoD: Black/Ruff; tests; no side-effects." + } +] + diff --git a/.github/workflows/seed-issues.yml b/.github/workflows/seed-issues.yml new file mode 100644 index 0000000..bdee957 --- /dev/null +++ b/.github/workflows/seed-issues.yml @@ -0,0 +1,74 @@ +name: Seed Sprint Issues + +on: + workflow_dispatch: + push: + branches: + - chore/dev-setup-warnings + paths: + - .github/seed_issues.json + - .github/workflows/seed-issues.yml + +jobs: + seed: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create labels and issues from seed file + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = '.github/seed_issues.json'; + const payload = JSON.parse(fs.readFileSync(path, 'utf8')); + + // Ensure labels exist + const existingLabels = await github.paginate(github.rest.issues.listLabelsForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + }); + const existing = new Set(existingLabels.map(l => l.name)); + const needed = new Set(payload.flatMap(i => i.labels || [])); + for (const name of needed) { + if (!existing.has(name)) { + core.info(`Creating missing label: ${name}`); + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name, + color: '0e8a16', + description: 'Auto-created by seed-issues workflow', + }).catch(e => core.warning(`Failed to create label ${name}: ${e.message}`)); + } + } + + // Fetch existing open issues to avoid duplicates by title + const allOpenIssues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100, + }); + const openTitles = new Set(allOpenIssues.map(i => i.title)); + + for (const item of payload) { + if (openTitles.has(item.title)) { + core.info(`Issue already exists: ${item.title}`); + continue; + } + core.info(`Creating issue: ${item.title}`); + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: item.title, + body: item.body, + labels: item.labels, + }); + } + From 4fd2a7cda7f347451f2cc84cda53d736139e37e8 Mon Sep 17 00:00:00 2001 From: Obayne Date: Fri, 12 Sep 2025 19:36:56 -0500 Subject: [PATCH 5/7] chore(ci): auto-open PR for chore/dev-setup-warnings via workflow --- .../open-pr-chore-dev-setup-warnings.yml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/open-pr-chore-dev-setup-warnings.yml diff --git a/.github/workflows/open-pr-chore-dev-setup-warnings.yml b/.github/workflows/open-pr-chore-dev-setup-warnings.yml new file mode 100644 index 0000000..ad9197f --- /dev/null +++ b/.github/workflows/open-pr-chore-dev-setup-warnings.yml @@ -0,0 +1,56 @@ +name: Open PR for chore/dev-setup-warnings + +on: + workflow_dispatch: + push: + branches: + - chore/dev-setup-warnings + +jobs: + open-pr: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create or update PR + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const head = 'chore/dev-setup-warnings'; + const base = 'main'; + + // Check if PR already exists + const prs = await github.paginate(github.rest.pulls.list, { owner, repo, state: 'open', head: `${owner}:${head}` }); + if (prs.length > 0) { + core.info(`PR already open: #${prs[0].number}`); + core.setOutput('pr_number', prs[0].number); + return; + } + + const title = 'chore: dev setup warnings + Sprint 01 plan + issue templates'; + const body = [ + 'This PR keeps main green and unblocks the team by:', + '', + '- Normalizing dev setup warnings punctuation in `setup_dev.ps1`', + '- Adding Sprint 01 plan at `docs/SPRINT-01.md`', + '- Adding issue templates under `.github/ISSUE_TEMPLATE/`', + '- Adding workflows to seed Sprint issues and auto-open this PR', + '', + 'Review focus:', + '- Repo hygiene only; no runtime logic changed beyond script messages', + '- Validate sprint plan structure and labels/templates', + '', + 'Links:', + '- Sprint Plan: docs/SPRINT-01.md' + ].join('\n'); + + const pr = await github.rest.pulls.create({ owner, repo, title, head, base, body, draft: false }); + core.info(`Opened PR #${pr.data.number}`); + core.setOutput('pr_number', pr.data.number); + From 6fe316282cefc4cf4ddc89039ad450c265f33000 Mon Sep 17 00:00:00 2001 From: Obayne Date: Fri, 12 Sep 2025 20:24:45 -0500 Subject: [PATCH 6/7] chore(agents): add Agent Orchestrator + labeler workflows to scaffold branches from issues --- .github/workflows/agent-orchestrator.yml | 65 +++++++++++ .github/workflows/label-sprint-issues.yml | 34 ++++++ tools/agents/orchestrator.py | 135 ++++++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 .github/workflows/agent-orchestrator.yml create mode 100644 .github/workflows/label-sprint-issues.yml create mode 100644 tools/agents/orchestrator.py diff --git a/.github/workflows/agent-orchestrator.yml b/.github/workflows/agent-orchestrator.yml new file mode 100644 index 0000000..6cc9046 --- /dev/null +++ b/.github/workflows/agent-orchestrator.yml @@ -0,0 +1,65 @@ +name: Agent Orchestrator + +on: + workflow_dispatch: + issues: + types: [labeled] + +jobs: + scaffold: + if: github.event_name == 'workflow_dispatch' || contains(github.event.label.name, 'agent:auto') + runs-on: ubuntu-latest + permissions: + contents: write + issues: read + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Run orchestrator + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_ACTOR: ${{ github.actor }} + run: | + python tools/agents/orchestrator.py "$GITHUB_EVENT_PATH" + + - name: Open PR for new branch (if any) + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const cp = require('child_process'); + // Attempt to detect the most recent branch created by this run + const out = cp.execSync('git branch --show-current').toString().trim(); + const head = out && out !== '' ? out : null; + if (!head || head === 'main' || head.startsWith('chore/')) { + core.info('No feature branch detected for PR.'); + return; + } + const owner = context.repo.owner; + const repo = context.repo.repo; + const base = 'main'; + const title = `feat: ${head} (agent scaffold)`; + const body = [ + 'Automated scaffold for this issue by Agent Orchestrator.', + '', + 'Includes minimal stub files and skipped tests to keep CI green.', + 'Please replace stubs with real implementations and enable tests.', + ].join('\n'); + // Check if a PR from this head already exists + const prs = await github.paginate(github.rest.pulls.list, { owner, repo, state: 'open', head: `${owner}:${head}` }); + if (prs.length === 0) { + await github.rest.pulls.create({ owner, repo, head, base, title, body }); + } else { + core.info(`PR already open: #${prs[0].number}`); + } + diff --git a/.github/workflows/label-sprint-issues.yml b/.github/workflows/label-sprint-issues.yml new file mode 100644 index 0000000..ae1e388 --- /dev/null +++ b/.github/workflows/label-sprint-issues.yml @@ -0,0 +1,34 @@ +name: Label Sprint Issues for Agents + +on: + workflow_dispatch: + push: + branches: + - chore/dev-setup-warnings + paths: + - .github/workflows/label-sprint-issues.yml + +jobs: + label: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + - name: Add agent:auto label to Sprint 01 issues + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner, repo, state: 'open', per_page: 100, labels: 'sprint:01' + }); + for (const i of issues) { + const has = (i.labels || []).some(l => l.name === 'agent:auto'); + if (!has) { + core.info(`Labeling issue #${i.number}: ${i.title}`); + await github.rest.issues.addLabels({ owner, repo, issue_number: i.number, labels: ['agent:auto'] }); + } + } + diff --git a/tools/agents/orchestrator.py b/tools/agents/orchestrator.py new file mode 100644 index 0000000..376dc3c --- /dev/null +++ b/tools/agents/orchestrator.py @@ -0,0 +1,135 @@ +import json +import os +import re +import subprocess +import sys +from pathlib import Path + + +def slugify(text: str) -> str: + s = re.sub(r"[^a-zA-Z0-9]+", "-", text.strip().lower()).strip("-") + return re.sub(r"-+", "-", s) + + +def run(cmd, cwd=None): + subprocess.check_call(cmd, cwd=cwd) + + +def write(path: Path, content: str): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + +def create_branch(branch: str): + run(["git", "fetch", "origin", "main"]) + run(["git", "checkout", "-B", branch, "origin/main"]) + + +def git_commit_push(branch: str, message: str, repo: str, token: str, actor: str): + run(["git", "add", "-A"]) + run(["git", "-c", f"user.name={actor}", "-c", f"user.email={actor}@users.noreply.github.com", "commit", "-m", message]) + run(["git", "remote", "set-url", "origin", f"https://x-access-token:{token}@github.com/{repo}.git"]) + run(["git", "push", "-u", "origin", branch]) + + +def scaffold_skip_test(path: Path, reason: str): + write( + path, + """ +import pytest +pytest.skip(%r, allow_module_level=True) +""".lstrip() + % reason, + ) + + +def scaffold_for_issue(title: str, labels: list[str]): + lowered = title.lower() + + if any(l.startswith("area:cad_core") for l in labels): + # cad_core primitives + transforms + write(Path("cad_core/geom/__init__.py"), "\n") + write(Path("cad_core/geom/primitives.py"), '"""Geometry primitives scaffold (agent)."""\n') + write(Path("cad_core/geom/transform.py"), '"""Transform functions scaffold (agent)."""\n') + scaffold_skip_test(Path("tests/cad_core/test_primitives.py"), "scaffold: cad_core primitives") + return { + "branch": f"feat/agent-{slugify(title)}", + "message": "chore(agent): scaffold cad_core primitives + transforms", + } + + if any(l.startswith("area:backend") for l in labels): + if "settings" in lowered: + write(Path("backend/settings/__init__.py"), "\n") + write(Path("backend/settings/service.py"), '"""Settings service scaffold (agent)."""\n') + scaffold_skip_test(Path("tests/backend/test_settings_service.py"), "scaffold: backend settings service") + return { + "branch": f"feat/agent-{slugify(title)}", + "message": "chore(agent): scaffold backend settings service", + } + # default to catalog store + write(Path("backend/store/__init__.py"), "\n") + write(Path("backend/store/catalog.py"), '"""Catalog store scaffold (agent)."""\n') + scaffold_skip_test(Path("tests/backend/test_catalog_store.py"), "scaffold: backend catalog store") + return { + "branch": f"feat/agent-{slugify(title)}", + "message": "chore(agent): scaffold backend catalog store", + } + + if any(l.startswith("area:frontend") for l in labels): + if "input" in lowered: + write(Path("frontend/input/__init__.py"), "\n") + write(Path("frontend/input/handler.py"), '"""Input handler scaffold (agent)."""\n') + scaffold_skip_test(Path("tests/frontend/test_input_handler.py"), "scaffold: frontend input handler") + return { + "branch": f"feat/agent-{slugify(title)}", + "message": "chore(agent): scaffold frontend input handler", + } + # default to model space shell + write(Path("frontend/widgets/__init__.py"), "\n") + write(Path("frontend/widgets/model_space.py"), '"""Model Space widget scaffold (agent)."""\n') + write(Path("frontend/widgets/command_bar.py"), '"""Command bar scaffold (agent)."""\n') + scaffold_skip_test(Path("tests/frontend/test_model_space.py"), "scaffold: frontend model space shell") + return { + "branch": f"feat/agent-{slugify(title)}", + "message": "chore(agent): scaffold frontend model space shell", + } + + # Unknown area: no-op + return None + + +def main(): + if len(sys.argv) < 2: + print("Usage: orchestrator.py ") + return 1 + event_path = sys.argv[1] + with open(event_path, "r", encoding="utf-8") as f: + event = json.load(f) + + issue = event.get("issue", {}) + title = issue.get("title", "") + labels = [l["name"] for l in issue.get("labels", [])] + + plan = scaffold_for_issue(title, labels) + if not plan: + print("No scaffold rule matched; exiting.") + return 0 + + branch = plan["branch"] + message = plan["message"] + + token = os.environ.get("GITHUB_TOKEN") + repo = os.environ.get("GITHUB_REPOSITORY") + actor = os.environ.get("GITHUB_ACTOR", "agent-bot") + if not token or not repo: + print("Missing GITHUB_TOKEN or GITHUB_REPOSITORY") + return 2 + + create_branch(branch) + git_commit_push(branch, message, repo, token, actor) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) + From 1f9f530ad116e0277b2443b1aebca26731edca8a Mon Sep 17 00:00:00 2001 From: Obayne Date: Fri, 12 Sep 2025 20:45:35 -0500 Subject: [PATCH 7/7] chore(owners): auto-assign Sprint issues/PRs to repo owner with optional per-area overrides --- .github/workflows/assign-owners.yml | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/assign-owners.yml diff --git a/.github/workflows/assign-owners.yml b/.github/workflows/assign-owners.yml new file mode 100644 index 0000000..02f710e --- /dev/null +++ b/.github/workflows/assign-owners.yml @@ -0,0 +1,75 @@ +name: Assign Owners + +on: + workflow_dispatch: + issues: + types: [opened, labeled] + pull_request: + types: [opened, labeled, reopened] + +jobs: + assign: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + env: + AREA_FRONTEND_OWNER: ${{ vars.AREA_FRONTEND_OWNER }} + AREA_BACKEND_OWNER: ${{ vars.AREA_BACKEND_OWNER }} + AREA_CAD_CORE_OWNER: ${{ vars.AREA_CAD_CORE_OWNER }} + DEFAULT_PR_OWNER: ${{ vars.DEFAULT_PR_OWNER }} + steps: + - name: Assign new/updated issues + if: github.event_name == 'issues' + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue = context.payload.issue; + const labels = (issue.labels || []).map(l => l.name); + function pickAssignee(labels){ + if (labels.includes('area:frontend')) return process.env.AREA_FRONTEND_OWNER || owner; + if (labels.includes('area:backend')) return process.env.AREA_BACKEND_OWNER || owner; + if (labels.includes('area:cad_core')) return process.env.AREA_CAD_CORE_OWNER || owner; + return owner; + } + const login = pickAssignee(labels); + core.info(`Assigning @${login} to issue #${issue.number}`); + await github.rest.issues.addAssignees({ owner, repo, issue_number: issue.number, assignees: [login] }); + + - name: Batch-assign Sprint 01 issues (manual run) + if: github.event_name == 'workflow_dispatch' + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const issues = await github.paginate(github.rest.issues.listForRepo, { owner, repo, state: 'open', per_page: 100, labels: 'sprint:01' }); + for (const i of issues) { + if ((i.assignees || []).length) { continue; } + const labels = (i.labels || []).map(l => l.name); + function pickAssignee(labels){ + if (labels.includes('area:frontend')) return process.env.AREA_FRONTEND_OWNER || owner; + if (labels.includes('area:backend')) return process.env.AREA_BACKEND_OWNER || owner; + if (labels.includes('area:cad_core')) return process.env.AREA_CAD_CORE_OWNER || owner; + return owner; + } + const login = pickAssignee(labels); + core.info(`Assigning @${login} to issue #${i.number}`); + await github.rest.issues.addAssignees({ owner, repo, issue_number: i.number, assignees: [login] }); + } + + - name: Assign PRs + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pr = context.payload.pull_request; + const login = process.env.DEFAULT_PR_OWNER || owner; + core.info(`Assigning @${login} to PR #${pr.number}`); + await github.rest.issues.addAssignees({ owner, repo, issue_number: pr.number, assignees: [login] }); +