diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..7d811e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,40 @@ +name: Bug report +description: Something Simlock does that it should not, or does not do that it should. +labels: ["bug:new"] +body: + - type: textarea + id: observed + attributes: + label: What happened + description: What Simlock did, as precisely as you can. Paste the exact command and its output where there is one. + validations: + required: true + - type: textarea + id: expected + attributes: + label: What you expected + validations: + required: true + - type: textarea + id: steps + attributes: + label: How to reproduce + description: Steps from a fresh state. If it only happens sometimes, say how often. + placeholder: | + 1. simlock daemon start + 2. simlock lease --platform ios + 3. ... + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: Simlock version, platform (iOS, Android, or both), OS version, and the output of `simlock doctor` if it runs. + validations: + required: true + - type: textarea + id: evidence + attributes: + label: Logs and evidence + description: Daemon logs, screenshots, or anything else that shows the problem. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3a61e75 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,3 @@ +# Blank issues stay enabled: feature and task specs are authored by the +# maintainer from docs/internal/templates/, not through a form. +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..65d229e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,32 @@ +name: Feature request +description: Something you want Simlock to do that it does not do today. +labels: ["request:new"] +body: + - type: markdown + attributes: + value: | + Describe the problem, not the solution. If the request is taken up, a maintainer writes a separate spec issue and links it here; this issue stays yours and stays open until that feature ships. + - type: textarea + id: problem + attributes: + label: The problem + description: What you are trying to do and what gets in the way. + validations: + required: true + - type: textarea + id: who + attributes: + label: Who runs into this + description: You, a team, a coding agent, a CI job? What does that situation look like? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: What you would like to happen + description: The behaviour you would want, from the outside. Implementation ideas are welcome but optional. + - type: textarea + id: context + attributes: + label: Anything else + description: Workarounds you use today, related issues, links. diff --git a/.github/labels.json b/.github/labels.json new file mode 100644 index 0000000..18526bc --- /dev/null +++ b/.github/labels.json @@ -0,0 +1,55 @@ +[ + { + "name": "request:new", + "color": "bfdadc", + "description": "Inbox. Never picked up, body never edited." + }, + { "name": "bug:new", "color": "d73a4a", "description": "Reported. Nobody has looked yet." }, + { + "name": "bug:triage", + "color": "d73a4a", + "description": "An agent may reproduce it and write the triage report." + }, + { + "name": "bug:needs-info", + "color": "d73a4a", + "description": "Could not reproduce. Waiting on the reporter." + }, + { "name": "bug:ready", "color": "d73a4a", "description": "An agent may fix it." }, + { + "name": "feature:spec", + "color": "0e8a16", + "description": "Business or technical spec in progress." + }, + { + "name": "feature:ready", + "color": "0e8a16", + "description": "No sub-issues; one PR delivers the whole feature." + }, + { + "name": "feature:planned", + "color": "0e8a16", + "description": "Split into tasks. Never picked up itself." + }, + { + "name": "task:draft", + "color": "5319e7", + "description": "Scope written; technical spec, approval, or deps missing." + }, + { "name": "task:ready", "color": "5319e7", "description": "An agent may implement it." }, + { + "name": "documentation", + "color": "0075ca", + "description": "Improvements or additions to documentation" + }, + { + "name": "dependencies", + "color": "0366d6", + "description": "Pull requests that update a dependency file" + }, + { + "name": "github_actions", + "color": "000000", + "description": "Pull requests that update GitHub Actions code" + } +] diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 0000000..fcd6e5f --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,35 @@ +name: Labels + +# Keeps the repo's labels equal to .github/labels.json. Creates and updates +# only; a label that is no longer listed is deleted by hand, on purpose. + +on: + push: + branches: [main] + paths: [.github/labels.json] + workflow_dispatch: + +permissions: {} + +jobs: + sync: + name: Sync labels + runs-on: ubuntu-24.04 + permissions: + issues: write + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Create or update every listed label + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + jq -c '.[]' .github/labels.json | while read -r label; do + name=$(jq -r .name <<<"$label") + color=$(jq -r .color <<<"$label") + description=$(jq -r .description <<<"$label") + gh label create "$name" --color "$color" --description "$description" --force + done diff --git a/AGENTS.md b/AGENTS.md index 5a66de7..7eeea3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,11 @@ changes in this repo: docs (`docs/`) vs. maintainer/agent docs (`docs/internal/`); no ADR links or internal-doc links from end-user docs; nothing the tool prints names a file path in this repo. +- [delivery.md](docs/internal/agent-rules/delivery.md) — GitHub Issues as + spec and queue: one `:` label per issue, agents act only on + `*:ready` and `bug:triage`, the body is the spec and comments are + discussion, reporters' issues are never rewritten, branches are + `/`, handoffs are one `## Handoff` comment per stop. So are the accepted records in [docs/internal/adr/](docs/internal/adr/). An ADR marked _Accepted — not yet implemented_ means the documentation already describes the @@ -49,6 +54,10 @@ End-user docs live directly under [docs/](docs/) and must stay self-contained Maintainer/agent docs live under [docs/internal/](docs/internal/): - [ARCHITECTURE.md](docs/internal/ARCHITECTURE.md) — high-level architecture overview +- [DELIVERY.md](docs/internal/DELIVERY.md) — how work flows through GitHub + Issues: the three walkthroughs, handoffs, what is automated, where ADRs fit +- [templates/](docs/internal/templates/) — the feature and task spec bodies a + spec session writes - [EVENTS.md](docs/internal/EVENTS.md) — the same catalog with rationale and ADR references - [IDEAS.md](docs/internal/IDEAS.md) — post-v1 ideas; don't implement these unless asked - [KNOWN-PITFALLS.md](docs/internal/KNOWN-PITFALLS.md) — accepted gaps and their planned fixes diff --git a/docs/internal/DELIVERY.md b/docs/internal/DELIVERY.md new file mode 100644 index 0000000..fd1d0b7 --- /dev/null +++ b/docs/internal/DELIVERY.md @@ -0,0 +1,150 @@ +# How work flows + +GitHub Issues is the specification and the queue. An issue's body is what an +agent builds against; its label is the only thing that tells an agent whether +it may act. This page is the maintainer's manual for that model. The binding +rules are in [agent-rules/delivery.md](agent-rules/delivery.md); this page +explains how they fit together in practice. + +## The model in five sentences + +Every issue carries exactly one `:` label, and the prefix is the +kind. Agents act only on `bug:triage`, `bug:ready`, `feature:ready` and +`task:ready`; everything else is either an unread claim from outside or an +unfinished spec. The body is the spec and comments are discussion, so a +comment changes nothing until a spec session folds it into the body. A +reporter's issue is never rewritten: bugs are triaged in a comment, and a +feature that came from a request is a new issue the maintainer owns. What +happens after `ready` is read from GitHub itself — assignee, linked pull +request, closed — not from a label. + +| Label | Meaning | +| ----------------- | -------------------------------------------------------- | +| `request:new` | Inbox. Never picked up, body never edited. | +| `bug:new` | Reported. Nobody has looked yet. | +| `bug:triage` | An agent may reproduce it and write the triage report. | +| `bug:needs-info` | Could not reproduce. Waiting on the reporter. | +| `bug:ready` | An agent may fix it. | +| `feature:spec` | Business or technical spec in progress. | +| `feature:ready` | No sub-issues; one PR delivers the whole feature. | +| `feature:planned` | Split into tasks. Never picked up itself. | +| `task:draft` | Scope written; technical spec, approval or deps missing. | +| `task:ready` | An agent may implement it. | + +## A bug, from report to fix + +1. Someone opens a bug through the form. It arrives as `bug:new`. Nothing + happens. +2. The maintainer reads it. If it sounds real, they add `bug:triage`. That is + the whole delegation. +3. An agent running `triage-bug` assigns itself, reproduces the bug as a + failing test whose title states the claim, finds the root cause, pushes + only the test to `bug/-repro`, and posts one comment with five + sections: Reproduction, Root cause, Simplest fix, Alternatives rejected, + Risk. It opens no pull request, and it unassigns itself when done. +4. If it could not reproduce, it swaps the label to `bug:needs-info` and says + exactly what is missing. A reply from the reporter moves the issue back to + `bug:triage` on its own; two weeks of silence closes it. +5. The maintainer reads the report. Agree: add `bug:ready`. Disagree: reply + with what is wrong and re-add `bug:triage`; the next agent starts from + that reply. +6. An agent running `deliver` claims the `bug:ready` issue, branches from the + repro branch, and opens a PR that closes the issue. The triage test is now + the regression test. Merge closes the bug. + +## A feature, delivered as one PR + +1. The maintainer creates a `feature:spec` issue, or an outside request + arrives as `request:new` and the maintainer decides to take it up. Nothing + happens to a request by default. +2. First spec session. The agent interviews the maintainer and writes the + business half of [templates/feature.md](templates/feature.md): Problem, + Who it is for, Outcome, Non-goals, Completion conditions, Open questions. + It says what a user can do afterwards, never how. If the feature came from + a request, the session creates the feature issue, links it to the request, + and comments on the request so the reporter knows where to look. The + request stays open until the feature ships. +3. Discussion happens, on the feature or on the request, in comments. Nobody + edits the body by hand. +4. Second spec session. The agent first lists every comment posted since the + body was last edited, on both threads, as a set of proposed changes; the + maintainer accepts or rejects each. Then it interviews for the Technical + spec section. Any decision that constrains more than one future change or + would be expensive to reverse becomes an ADR at _Proposed_, listed under + Decisions. The session ends by editing the body and leaving a one-line + "Spec updated" comment. +5. Once Open questions is empty and every linked ADR is accepted, the + maintainer adds `feature:ready`. The ADRs move to _Accepted — not yet + implemented_. +6. An agent running `deliver` claims it and opens a PR whose body walks every + completion condition. Merge closes the feature, and its ADRs flip to + _Accepted_. + +## A feature, split into tasks + +Steps 1 to 4 are the same, but the second session ends differently: instead +of a Technical spec section on the feature, it produces sub-issues. + +5. Each task is a native sub-issue of the feature, labelled `task:draft`, with + a body from [templates/task.md](templates/task.md): Scope, Technical spec, + Done when, Out of scope, Depends on, and an approval checkbox. The feature + becomes `feature:planned` and keeps only its business spec, Decisions and + the task list. +6. The maintainer ticks the approval box on each task, once, at planning + time. From then on the automation promotes a task to `task:ready` the + moment its box is ticked, its Technical spec has content, and every issue + under Depends on is closed. Nobody re-reads the dependency graph by hand. +7. Agents claim `task:ready` issues one PR each. As tasks close, the ones they + unblocked become ready on their own. +8. When the last sub-issue closes, the automation comments on the feature + that completion conditions are due. An agent proves them against `main`, + usually with an end-to-end run, and reports in a comment. The maintainer + closes the feature. + +## Handoffs between agents + +An agent that stops before its PR is merged — out of context, blocked, or +told to stop — unassigns itself and leaves one comment headed `## Handoff` +with four sections: Done, Not done, Findings, Blocked on. The branch is the +other half of the handoff: because it is always `/`, the next agent +knows where the commits are without being told. A handoff records the state +of the work, never a change to the spec; if the work showed the spec is +wrong, the handoff says so and the maintainer runs a revise spec session. +Agents do not post progress updates, only handoffs, so the one comment that +matters is easy to find. + +## What is automated and what is not + +The workflow in `.github/workflows/issue-state.yml` handles the transitions +that are mechanical: adding a state label removes the previous one, so every +transition is a single add; `task:draft` becomes `task:ready` when approved, +specified and unblocked; a reporter's reply moves `bug:needs-info` back to +`bug:triage`; a silent `bug:needs-info` closes after two weeks; a feature +whose last sub-issue closed gets the completion-conditions note. The repo's +skills — `spec-session`, `triage-bug`, `deliver` — handle the transitions an +agent makes as part of its own procedure. + +Four transitions are judgments and stay manual on purpose: `bug:new` to +`bug:triage`, `bug:triage` to `bug:ready`, `feature:spec` to +`feature:ready`, and the approval box on each task. Each is one click. + +## Where ADRs fit + +A feature answers _what_ and _why_ in business terms. An ADR answers _how_, +but only for choices that constrain more than one task or would be expensive +to reverse. A task answers _how_ for exactly one PR. Most features never need +an ADR; the ones that fix a protocol shape or an ownership model do. An ADR +is born in a spec session, is _Proposed_ while the feature is `feature:spec`, +_Accepted — not yet implemented_ while the feature is open, and _Accepted_ +when it closes. See [adr/README.md](adr/README.md). + +## Pointers + +- Rules: [agent-rules/delivery.md](agent-rules/delivery.md) +- Templates: [templates/feature.md](templates/feature.md), + [templates/task.md](templates/task.md) +- Reporter forms: `.github/ISSUE_TEMPLATE/` +- Labels: `.github/labels.json`, synced by `.github/workflows/labels.yml` +- Automation: `.github/workflows/issue-state.yml` +- Skills: `.claude/skills/spec-session`, `.claude/skills/triage-bug`, + `.claude/skills/deliver` diff --git a/docs/internal/adr/README.md b/docs/internal/adr/README.md index 16d0ea8..79c909c 100644 --- a/docs/internal/adr/README.md +++ b/docs/internal/adr/README.md @@ -18,6 +18,14 @@ Status values: toward it should treat the documentation as the specification. - **Superseded by NNNN** — no longer binding; read the replacement. +An ADR's status follows the feature that produced it, per +[agent-rules/delivery.md](../agent-rules/delivery.md): it is *Proposed* +while the feature is still `feature:spec`, becomes *Accepted — not yet +implemented* when the feature leaves that state, and becomes *Accepted* +when the feature closes. An ADR that is *Accepted — not yet implemented* +with no open feature behind it is a gap: either the feature is missing or +the status is stale. + | ADR | Title | Status | |---|---|---| | [0001](0001-simlock-owned-device-roots.md) | Simlock-owned device roots | Accepted | diff --git a/docs/internal/agent-rules/delivery.md b/docs/internal/agent-rules/delivery.md new file mode 100644 index 0000000..cc55c4a --- /dev/null +++ b/docs/internal/agent-rules/delivery.md @@ -0,0 +1,157 @@ +# Agent rules: delivery + +Rules for how work enters and leaves this repo. GitHub Issues is both the +specification and the queue: an issue's body is the spec an agent builds +against, and its label is the only signal that tells an agent whether it may +act. These rules are binding in the same way the other files here are — an +agent that picks up an issue it was not entitled to, or that implements from +a comment thread instead of the body, has made an error even if the code is +good. + +## Kinds and labels + +Every issue carries exactly one label of the form `:`. The +prefix is the kind; there is no separate kind label. An issue with zero or +two such labels is in an invalid state and must be corrected before anything +else happens to it. + +| Label | Set by | Meaning | +| ------------------ | ----------------------------------- | --------------------------------------------------------- | +| `request:new` | feature request form | Inbox. Never picked up, body never edited. | +| `bug:new` | bug report form | Reported. Nobody has looked yet. | +| `bug:triage` | maintainer | An agent may reproduce it and write the triage report. | +| `bug:needs-info` | triage agent | Could not reproduce. Waiting on the reporter. | +| `bug:ready` | maintainer, after the triage report | An agent may fix it. | +| `feature:spec` | spec session, on creation | Business or technical spec in progress. | +| `feature:ready` | maintainer | No sub-issues; one PR delivers the whole feature. | +| `feature:planned` | spec session, on split | Split into tasks. Never picked up itself. | +| `task:draft` | spec session, on creation | Scope written; technical spec, approval, or deps missing. | +| `task:ready` | automation, or maintainer | An agent may implement it. | + +Transitions per kind: + +- **request**: `new` until closed. Closed as not planned, or as completed with + a comment naming the feature it became. +- **bug**: `new` → `triage` → `ready`, with `needs-info` as a side-trip that + returns to `triage` when the reporter answers. +- **feature**: `spec` → `ready` or `planned`. Both end at closed. +- **task**: `draft` → `ready`. + +Everything after `ready` is read from GitHub itself, not from a label: +in progress means an assignee is set, in review means a linked pull request +is open, done means closed as completed. + +## Rules + +1. **An agent acts only on `bug:triage`, `bug:ready`, `feature:ready`, and + `task:ready`.** Nothing else is work. A `request:new` or `bug:new` issue + is a claim from outside that a maintainer has not yet looked at; a + `feature:spec` or `task:draft` issue is a spec that is not finished. Agents + do not label their way into work: only a maintainer, or the automation a + maintainer configured, moves an issue to a state an agent may act on. + +2. **The assignee is the claim, and a handoff is how a claim is released.** + Before doing anything on an issue an agent assigns itself, and it never + touches an issue that already has an assignee. This is the one + cross-agent lock in the model; there is no other. An agent that stops + before the PR is merged unassigns itself and leaves exactly one comment + headed `## Handoff` with four sections: *Done* (what is on the branch and + how it was verified), *Not done* (what remains, in the spec's own terms), + *Findings* (what was learned that is not in the spec and the next agent + would otherwise rediscover), *Blocked on* (who or what, or "nothing"). + A handoff is state, never spec: if the work revealed that the body is + wrong or incomplete, the handoff says "spec needs: ..." and the agent + stops; it does not amend the spec in the comment. One handoff per stop, + no progress log — an agent comments when it stops, not while it works. + +3. **The body is the spec. Comments are discussion.** Whoever implements an + issue reads its body, the documents it links, the latest `## Handoff` + comment if there is one, and for a bug the triage report — never the rest + of the comment thread. + A comment changes nothing until a spec session folds it into the body. The + body is cumulative: sections are added and amended in place, never + restated in comments, and GitHub's edit history is the record of what + changed. + +4. **Every spec session starts by reconciling.** Before writing anything, the + session fetches every comment created after the body's `lastEditedAt`, on + the issue itself and on its linked request if it has one, and lists what + those comments change about the spec. The maintainer accepts or rejects + each item. Only then does the session write the section it was started + for. It ends by editing the body and leaving one short comment saying + which sections moved, so a reader of the thread can see where the spec + last caught up. + +5. **A reporter's issue is never rewritten.** Bug reports and feature requests + arrive through the issue forms and stay in the reporter's words. Triage + is a comment on the bug. A feature that came from a request is a new + issue authored by the maintainer, linked from and to the request; the + request stays open, and closes with the feature. + +6. **A feature spec describes outcomes, not implementation.** The business + sections of `docs/internal/templates/feature.md` say what a user can do afterwards + that they could not before, and how anyone would check it. An + implementation detail belongs there only when it *is* a requirement — "no + inbound port on the worker" is an outcome; "use a WebSocket" is not. + Implementation lives in the technical section, in an ADR, or in a task. + +7. **A task carries both halves.** A task's Scope is the slice of the parent's + outcome it delivers; its Technical spec is what a delivery agent needs to + build it without asking: modules touched, contract and event changes, + which rules in this directory are in play, and the tests to write phrased + as claims. A task with an empty technical section is `task:draft` no + matter what anyone says in a comment. + +8. **Triage produces a report, not a fix.** An agent working `bug:triage` + reproduces the bug as a failing test whose title states the claim, + finds the root cause, and posts one comment with exactly these sections: + *Reproduction*, *Root cause* (with file and line), *Simplest fix*, + *Alternatives rejected*, *Risk*. It pushes the failing test to a + `bug/-repro` branch and opens no pull request. It then unassigns + itself. If it cannot reproduce, it moves the issue to `bug:needs-info`, + says exactly what is missing, and unassigns itself. + +9. **Done is defined per kind.** A task or bug is done when the pull request + that closes it is merged; for a bug the failing test from triage is the + regression test and must be in that PR. A `feature:ready` issue is done + when its PR is merged and the PR body walks every completion condition. + A `feature:planned` issue is done when every sub-issue is closed *and* an + agent has proven the completion conditions against main, usually by an + end-to-end run reported in a comment; the maintainer closes it. + +10. **ADR status follows the feature.** A decision made during a spec session + that constrains more than one task, or would be expensive to reverse, + becomes an ADR. It is *Accepted — not yet implemented* from the moment + the feature leaves `feature:spec`, and flips to *Accepted* when the + feature closes. A feature does not leave `feature:spec` while any ADR it + links is still *Proposed*. + +## Procedures + +**Claiming work.** Find it, claim it, branch, and finish with a PR that +closes the issue: + +```bash +gh issue list --search 'label:bug:ready,feature:ready,task:ready no:assignee' +gh issue edit --add-assignee @me +git switch -c task/- # or bug/-, feature/- +``` + +The PR body contains `Closes #`. For a bug, branch from `bug/-repro` +when it exists so the triage test is carried forward. + +**Finding triage work.** + +```bash +gh issue list --label bug:triage --search 'no:assignee' +``` + +**Moving a label.** Add the new one and remove the old one in the same +command, so the one-label invariant never breaks in between: + +```bash +gh issue edit --add-label bug:needs-info --remove-label bug:triage +``` + +The repo's own skills (`spec-session`, `triage-bug`, `deliver`) encode these +procedures; use them rather than retyping the steps. diff --git a/docs/internal/templates/feature.md b/docs/internal/templates/feature.md new file mode 100644 index 0000000..e78de90 --- /dev/null +++ b/docs/internal/templates/feature.md @@ -0,0 +1,64 @@ + + +Request: #NNN + +## Problem + + + +## Who it is for + + + +## Outcome + + + +## Non-goals + + + +## Completion conditions + + + +- ... + +## Open questions + + + +## Decisions + + + +- ADR NNNN — : <one line on what it fixes for this feature> + +## Technical spec + +<!-- Only for a feature delivered as one PR. Delete this section when the +feature is split into tasks. --> + +### Modules touched + +### Contract and event changes + +<!-- Every new or changed event needs its EVENTS.md entry named here. --> + +### Rules in play + +<!-- Which files in docs/internal/agent-rules/ the implementer must reread, and why. --> + +### Tests + +<!-- Each line is a test title: a claim the body must prove. --> + +- ... diff --git a/docs/internal/templates/task.md b/docs/internal/templates/task.md new file mode 100644 index 0000000..5dd546c --- /dev/null +++ b/docs/internal/templates/task.md @@ -0,0 +1,48 @@ +<!-- +Task spec. A spec session writes this as a sub-issue of a feature. +A task is task:draft until the Technical spec is filled in, the approval box +is ticked, and every issue under "Depends on" is closed. +--> + +Part of #NNN. + +## Scope + +<!-- The slice of the parent's outcome this task delivers: what a user can do +after this PR merges that they could not after the previous one. --> + +## Technical spec + +### Modules touched + +### Contract and event changes + +<!-- Every new or changed event needs its EVENTS.md entry named here. --> + +### Rules in play + +<!-- Which files in docs/internal/agent-rules/ the implementer must reread, and why. --> + +### Tests + +<!-- Each line is a test title: a claim the body must prove. --> + +- ... + +## Done when + +<!-- Observable checks a reviewer runs before merging. --> + +- ... + +## Out of scope + +## Depends on + +<!-- One issue per line. Leave empty if none. --> + +- #NNN + +## Approval + +- [ ] Approved for delivery