From f2147a26015f91f2c437d2004bba32759cecaaa0 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Thu, 20 Aug 2026 13:34:52 +0300 Subject: [PATCH] docs: make the umbrella canonical for triage labels and integration-branch lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two vocabularies were canonical by accident in threadbase-mobile, which is the failure this repo's issue-tracker doc exists to prevent. Adds triage-labels.md for the state labels — needs-triage, needs-info, ready-for-agent, ready-for-human — with their colours and the rule that they are additive to priority, type and area rather than a substitute. Both threadbase-mobile and threadbase-streamer now carry the identical five-label set. Adds integration-branch-lifecycle.md for the rule the integration-branch skill in both repos operates under: an integration branch is a staging area with an expiry, a run ends when the branch is gone, and each ref is audited by whether it holds a file main has never had rather than by ancestry. Squash-merging gives landed content a new SHA, so ancestry reports every merged pull request as unlanded. The mobile retirement document is not moved here: its refs, file counts and misnamed commit are that repo's history, and three of its relative links do not resolve outside it. This file states the reusable rule and links to that record as its origin. Indexes both in docs/README.md and cross-links the triage set from issue-tracker.md. --- docs/README.md | 2 ++ docs/integration-branch-lifecycle.md | 52 +++++++++++++++++++++++++++ docs/issue-tracker.md | 7 ++++ docs/triage-labels.md | 53 ++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 docs/integration-branch-lifecycle.md create mode 100644 docs/triage-labels.md 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' +```