Issue tracking

We use Gitea milestones + issues as a lightweight checklist + memory aid. No PR auto-linking, no Project boards, no status labels — just open vs. closed and “is this in the right milestone?” One source of truth, one URL, zero ceremony.

Where it lives

WhatWhere
Repohttps://git.rizoma.sa/rizoma/wiqaia
All milestoneshttps://git.rizoma.sa/rizoma/wiqaia/milestones
Open issueshttps://git.rizoma.sa/rizoma/wiqaia/issues?state=open
Critical-path filterhttps://git.rizoma.sa/rizoma/wiqaia/issues?state=open&labels=critical-path
Bootstrap scriptscripts/gitea-bootstrap-issues.py

Conventions

Milestones map 1:1 to the plan

Every plan milestone (M01–M10) is a Gitea milestone with the same name. When you open M01 — Foundations in Gitea, you see every open + closed issue that belongs to that plan milestone, plus a single progress bar — useful for “how close are we to M01 actually being done?”

If you find yourself wanting to create a Gitea milestone that doesn’t exist in apps/docs/src/routes/plan/, stop. Either:

  • the work belongs in an existing plan milestone (most likely — drop it there), or
  • the plan itself needs a new milestone (rare — edit the plan doc first, then add the matching Gitea milestone).

The Gitea side mirrors the plan; the plan is the canonical artifact.

Labels — minimal by design

Ten labels, no more without good reason:

LabelMeaning
cloudGCP / Terraform / GKE / networking / observability
backendNestJS / Postgres / API
aiTier-0/Tier-1/Tier-2 + eval + prompts
frontendReact / dashboards / 3D twin
edgeGo supervisor / Python edge apps
integrationFire panel / NVR drivers / 3D model
docsPlan docs / runbooks / handbooks
contractspackages/contracts (protos + DTOs)
critical-pathMust-have for the Hajj operational window
needs-humanPhysical / external / accountability — not AI-writable

Deliberately no status labels. Open issues are “to do”, closed issues are “done”. Anything in flight is implied by recent commits. We tried adding status:in-progress once; it became a thing-to-remember-to-update and got out of sync within a day.

needs-human items are decomposed

When an issue is gated on external input (fire-panel make/model, NVR access, a license purchase), the unblocked work gets its own issue separate from the gated piece. So we have:

  • #94 “Confirm fire-panel make/model at Arafat site” (needs-human, blocked on Saudi Airlines)
  • #95 “BACnet/IP fire-panel adapter (against bacpypes + recorded traces)” (NOT blocked — we can build the generic driver today)
  • #97 “Test fire-panel adapter against actual Saudi Airlines panel” (needs-human, blocked on physical access + #94)

The pattern matters: it stops us from saying “we can’t start fire panel work until we know the make/model” when actually 80% of the driver code is independent of the panel model.

Day-to-day usage

Pick something to work on

Open the milestone you’re on (M01 today). Skim open issues. Anything not labeled needs-human that you can start right now. Comment with what you’re starting if multiple people might pick the same one — otherwise just go.

Close an issue

When the work lands in master, close the issue manually in the Gitea UI (or via API). Add a one-comment summary if there’s anything load-bearing about how it was done that doesn’t belong in the commit message. Don’t close from a commit message — we deliberately don’t use the Closes #N auto-close magic.

Add a new issue

Two paths:

  1. One-off: create it in the Gitea UI. Pick the right milestone, slap the right track label on it, optionally critical-path.

  2. Bulk: edit scripts/gitea-bootstrap-issues.py, add issue(...) entries, re-run the script. It’s idempotent (skips by title), so you can append + re-run safely. This is how the initial 140 issues landed.

The bootstrap script reads its token from ~/.gitea-token (a Personal Access Token with write:issue scope, generated under Settings → Applications in Gitea).

Mark an old completed-before-tracking item as done

When seeding work that was already finished (like the 9 closed M01 issues for things we’d done before Gitea tracking existed), include a Completed YYYY-MM-DD line in the issue body. Gitea sets closed_at to when you closed the issue, not when the work happened — the body comment is how the actual completion date survives.

Why no PR auto-linking?

For solo work (me + Claude), it’s overhead with no payoff — there’s no “ready for review” stage between commit and merge. When the team grows and we have multiple humans reviewing each other’s PRs, we’ll revisit. The trigger to add Closes #N auto-linking is: “we’re now linking issues to specific branches anyway, and forgetting to close issues by hand.”

Why no project board?

Project boards (Kanban-style To Do / In Progress / Done) earn their keep at ~30+ active issues across multiple people. With one or two contributors, the milestone view is the project board — “open in this milestone” is the to-do column, “closed in this milestone” is the done column. Adding a Project on top would just be another thing to keep in sync with reality.

Resyncing the issue list with the plan

If a plan milestone gets significantly rescoped (an issue gets dropped, a new deliverable gets added), the Gitea side is allowed to drift briefly. Catch it up by:

  1. Editing scripts/gitea-bootstrap-issues.py so the ISSUES list reflects the new reality.
  2. Re-running the script — it creates anything new (since it’s idempotent by title).
  3. Closing or deleting any obsolete issues by hand in the UI.

The script is append-only-safe; it never modifies or deletes existing issues. Manual UI work is the escape hatch for that.

Related runbooks

WiqAIa+ Docs · Rizoma