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
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
52 changes: 52 additions & 0 deletions docs/integration-branch-lifecycle.md
Original file line number Diff line number Diff line change
@@ -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 <word>` 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.
7 changes: 7 additions & 0 deletions docs/issue-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.**
Expand Down
53 changes: 53 additions & 0 deletions docs/triage-labels.md
Original file line number Diff line number Diff line change
@@ -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'
```