Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -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.
55 changes: 55 additions & 0 deletions .github/labels.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
35 changes: 35 additions & 0 deletions .github/workflows/labels.yml
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<kind>:<state>` 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
`<kind>/<n>`, 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
Expand All @@ -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
Expand Down
150 changes: 150 additions & 0 deletions docs/internal/DELIVERY.md
Original file line number Diff line number Diff line change
@@ -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 `<kind>:<state>` 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/<n>-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 `<kind>/<n>`, 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`
8 changes: 8 additions & 0 deletions docs/internal/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Loading