diff --git a/docs/README.md b/docs/README.md index 0e0daf4..96c4693 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,8 @@ This directory contains shared documentation for the root Threadbase project. | [`plans/claude-pr-review-rollout.md`](plans/claude-pr-review-rollout.md) | Plan for automated Claude PR reviews across selected repositories | | [`contributing.md`](contributing.md) | Contribution areas and project participation guidance | | [`issue-tracker.md`](issue-tracker.md) | Canonical issue labels and format, shared by every component repo | +| [`triage-labels.md`](triage-labels.md) | Canonical triage-state labels, additive to the issue taxonomy | +| [`integration-branch-lifecycle.md`](integration-branch-lifecycle.md) | When an integration branch is deleted, and the audit that makes deleting it safe | | [`archive-policy.md`](archive-policy.md) | Rules for current docs, historical references, component docs, and generated artifacts | | [`marketing/article-writing-guide.md`](marketing/article-writing-guide.md) | Writing and launch-post guidance | | [`status/README.md`](status/README.md) | Status snapshot index and freshness warning | diff --git a/docs/integration-branch-lifecycle.md b/docs/integration-branch-lifecycle.md new file mode 100644 index 0000000..20073be --- /dev/null +++ b/docs/integration-branch-lifecycle.md @@ -0,0 +1,52 @@ +# Integration branches — lifecycle and stale-ref audit + +**Canonical for every Threadbase component repository.** +The `integration-branch` skill in each repo operates under this rule and cites this file. + +## An integration branch is a staging area with an expiry + +It exists to **test a set of pull requests together**, and it is deleted once `main` holds its content. +Deleting it costs nothing while a backup ref points at the same commit. + +Three consequences: + +- **Never develop on it.** The moment a fix is committed to the integration branch rather than to the pull request that needs it, it has stopped being a staging area and become a second trunk — one nobody reviews and nothing lands from. +- **A run is not finished when the branch is green; it is finished when the branch is gone.** Name the condition under which it is deleted, and who deletes it. +- **If the plan is to land pull requests one at a time onto `main`, an integration branch is the wrong tool.** That is a different procedure and needs no such branch, though it still deserves a log and a summary. + +`threadbase-mobile` retired its long-lived integration branch on 2026-08-12 and landed all 20 open pull requests onto `main` individually instead. +The decision, the refs deleted and the per-ref audit results are recorded in [`threadbase-mobile/docs/integration-branch-retirement-2026-08-12.md`](https://github.com/RonenMars/threadbase-mobile/blob/main/docs/integration-branch-retirement-2026-08-12.md). +That document is the origin of the rule above and stays in that repo, because the history is that repo's. + +## The audit that makes deletion safe + +The question for each ref is **not** "is it merged". +None of these refs are ancestors of `main`, because every pull request that fed them was squash-merged under a new SHA. + +The question is **does it hold a file that `main` has never had.** + +```bash +# for each candidate ref: files present there and absent from main, ignoring docs +git diff --diff-filter=A --name-only origin/main "$REF" | grep -v '^docs/' | +while read -r f; do + # empty result = main never deleted it = main never had it + [ -z "$(git log --diff-filter=D -1 --format=%h origin/main -- "$f")" ] && echo "NEVER on main: $f" +done +``` + +Across the 16 surviving integration-named refs in the 2026-08-12 audit, zero files were ever unlanded. + +## Two checks that return a confident wrong answer + +Both were hit during that audit, and neither fails loudly. + +**A branch-name glob is a filter, not an inventory.** +`git branch -a --list '*integration/*'` reported zero remaining branches, and that was reported as fact. +It matches only refs containing a literal `integration/`, so every hyphenated name — `integration-merge-…`, `integration-dev-…`, `land/integration-prep` — was invisible to it. +The true count was 23 refs. +Use `git branch -a | grep -i ` when the question is "what exists", and reserve globs for when the pattern *is* the question. + +**"Not an ancestor of `main`" does not mean "unlanded".** +Squash-merging gives the landed content a new SHA, so ancestry calls every merged pull request unlanded. +The file-level test above is what actually answers it. +The same trap applies to comparing a pull request against a branch by filename rather than by content. diff --git a/docs/issue-tracker.md b/docs/issue-tracker.md index 7ba3af1..00cd4c3 100644 --- a/docs/issue-tracker.md +++ b/docs/issue-tracker.md @@ -76,6 +76,13 @@ Zero or more. Where the work lands. Useful for filtering, never for priority. These get confused. `native` is about *code that compiles*; `platform` is about *the OS behaving differently*. A `better-sqlite3` ABI mismatch is `native`. Task Scheduler not redirecting stdout is `platform`. An issue can be both — a Windows-only `node-pty` build failure is `native` + `platform`. +## Triage state + +Priority, type and area say *what an issue is*. +A separate, additive set of labels says *what happens to it next* — `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`. + +Those are canonical in [`triage-labels.md`](./triage-labels.md), and applying one never means removing a priority, type or area. + ## Body Lead with what is wrong, in one or two sentences, with no heading. Then use whichever sections below carry real content, and omit the rest. **Do not pad an issue to fit the template.** diff --git a/docs/triage-labels.md b/docs/triage-labels.md new file mode 100644 index 0000000..306f235 --- /dev/null +++ b/docs/triage-labels.md @@ -0,0 +1,53 @@ +# Triage labels + +**Canonical for every Threadbase component repository.** +These are the *state* labels — what happens to an issue next — and they are additive to the priority, type and area taxonomy in [`issue-tracker.md`](./issue-tracker.md). + +Component repos link here; they do not keep their own copy of the vocabulary. +A convention that exists in two places becomes two conventions. + +Adopted by [`threadbase-mobile`](https://github.com/RonenMars/threadbase-mobile/issues) on 2026-08-14 and [`threadbase-streamer`](https://github.com/RonenMars/threadbase-streamer/issues) on 2026-08-20. +Adopting it in another component repo means creating the label set below — nothing else. + +## The labels + +| Label | Colour | Meaning | +|---|---|---| +| `needs-triage` | `E99695` | Maintainer needs to evaluate this issue. | +| `needs-info` | `F9D0C4` | Waiting on the reporter for more information. | +| `ready-for-agent` | `2E8B57` | Fully specified, ready for an unattended agent to pick up. | +| `ready-for-human` | `6F42C1` | Requires human implementation. | +| `wontfix` | `ffffff` | Will not be actioned. | + +`wontfix` predates this scheme in both repos and is left at GitHub's default colour. + +## Additive, never a substitute + +A triage label answers *what happens to this issue next*. +Priority, type and area answer *what this issue is*. + +They are orthogonal, so applying a triage label never means removing or substituting one of the others. +A well-formed issue carries a priority, a type, and — while in flight — a triage state. + +## Two near-collisions worth naming + +Reaching for the taxonomy label instead of the triage one loses information in both of these cases. + +`question` is a **type** — "further information is requested" as a permanent classification of what the issue is. +`needs-info` is a **state** — "blocked on the reporter right now". +An issue can be `question` + `ready-for-human`, or `bug` + `needs-info`. + +`wontfix` is the one label serving both roles at once. +Applying it is a terminal decision, not a state to move out of. + +## Checking compliance + +A triage state is optional, so there is nothing to assert about issues that carry none. +What is worth catching is an issue carrying two, which means the state was changed without the old one being removed. + +```sh +R=RonenMars/threadbase-streamer # or threadbase-mobile, or any adopting repo + +gh issue list --state open --limit 200 --json number,labels -R "$R" \ + -q '.[] | select((.labels|map(.name)|map(select(test("^(needs-triage|needs-info|ready-for-agent|ready-for-human)$")))|length) > 1) | .number' +```