tp maturity
task-plus.yml declares the project's level on the manual's two
maturity ladders: code flow
0–2 and deployment 0–4. maturity.code alone picks how tp release
works (ADR 0006):
| Code level | Name | A release is |
|---|---|---|
| 0 | Trunk | a tag on the base branch, committed and pushed there |
| 1 | Batch | stories, each carrying a changes/<n>.<kind>.md fragment. One story ships by its own pull request into the base branch, a lone release; two or more go through release, opened by tp release batch or by the second story's run, and ship by one release pull request (ADR 0007). The version follows from the fragments |
| 2 | Gated | a batch, with CI on every pull request (#51) |
A batch only holds if the base branch refuses direct pushes on the forge. tp maturity is the write side of
the code ladder (#84): it declares the level and makes the forge agree.
| Command | Does | Prints |
|---|---|---|
tp maturity |
reads the declared levels and asks the forge about the base branch, the merge style and what a release in flight has left | declared: code N, deploy M, then main on origin: protected or not protected, then merge commits on origin: the default or not the default, then at rest or in flight: … |
tp maturity code N |
declares code level N (0, 1 or 2) in task-plus.yml once the repository is at rest; from 1, first protects the base branch on the release remote and makes merge commits its default merge style |
the level set, the branch's protection, the merge style, the token used |
tp maturity code N -dry-run |
the same, writing nothing | (dry-run) Would ... for the file and the API call |
tp --init writes maturity: {code: 0} (#109), so a new project
starts on trunk; a missing maturity.code is 0 too. At level 1 and up
tp check errors on a hand-edited CHANGELOG [Unreleased] or a
.tp-release-comment, which a batch does not read, and warns on the
obsolete fragments: key at any level.
The base branch is the release remote's default branch (main, or
master when there is no main). Protection means no direct pushes:
on Forgejo a branch protection rule named for the branch with pushes
disabled; on GitLab push access level 0 and merge access level 40
(maintainers). Pull requests still merge.
code N |
Release remote | Token | Base branch | Then |
|---|---|---|---|---|
| 1 or 2 | Forgejo or GitLab | yes | not protected | protected on the forge, confirmed, then the level written: main on origin: protected now |
| 1 or 2 | Forgejo or GitLab | yes | protected | level written; already protected |
| 1 or 2 | Forgejo or GitLab | yes | either, merge commits not the default | merge commits allowed and made the default (Forgejo allow_merge_commits, default_merge_style: merge; GitLab merge_method: merge), then the level written: merge commits on origin: the default now (#129) |
| 1 or 2 | Forgejo or GitLab | yes | merge style refused (403) | error naming the right to grant; nothing written |
| 1 or 2 | Forgejo or GitLab | yes | forge answers 403 | error naming the right to grant (repository admin); nothing written |
| 1 or 2 | Forgejo or GitLab | none | — | error: set token_env on the release remote; nothing written |
| 1 or 2 | GitHub or none | — | — | error: a batch needs a Forgejo or GitLab release remote; nothing written |
| 0 | any | — | protected | level written; still protected; tp release refuses a trunk release until the protection is removed on the forge |
| 0 | any | — | not protected | level written |
| same as declared | — | — | — | maturity.code: N (unchanged); the protection is still checked and made |
| 3 or more, negative | — | — | — | error: the ladder is 0, 1 or 2 |
At rest
The level alone picks the release process, so it changes only when no
release is in flight (#181, story 029): what the old process left
behind would otherwise be stranded, and tp has no migration from one
process to the other. tp maturity code N with N different from the
declared level reads the in-flight state first and refuses with one
error listing every item and its remedy; nothing is written and the
forge is not touched. The same level is a repair, not a change, and
runs with a release in flight. -dry-run gives the same refusal under
the (dry-run) prefix.
| In flight | Listed as | Clear it by |
|---|---|---|
release on the release remote |
release exists on origin |
finishing the batch (tp release on main) or deleting the branch |
.tp-release.yml in the checkout |
.tp-release.yml exists |
finishing the batch or deleting the file |
an open pull request into release (a story's) |
pull request #12 (story/12-x → release) is open |
merging or closing it |
an open pull request from release (the release's) |
pull request #13 (release → main) is open |
merging it and tagging, or closing it |
an open pull request from a story/<n>-<slug> branch into main |
pull request #14 (story/14-y → main) is open |
merging or closing it |
Any other open pull request does not count. Without a forge API or
token (allowed when lowering to 0) the branch and the file are still
read through git; the pull requests are not, and the output says
pull requests on origin were not checked (no API). tp maturity prints
the same items without their remedies, in flight: release on origin; pull request #12 (story/12-x → release), or at rest.
Merge commits are needed because a batch's stories are all cut from the
same base: once the first is merged into release, the rest are no longer
descendants of it, so a forge that only fast-forwards refuses them, and a
rebase would rewrite the commits whose ancestry tp tracks. Fast-forward
stays allowed. At level 1 and up tp check warns when merge commits are
not the forge's default, naming tp maturity code N.
The protection is made before the level is written, so a forge that
refuses leaves task-plus.yml as it was and the two never disagree.
The token is the release remote's token_env (through the secrets
provider when one is configured), or the default chain tp release
uses. Deployment levels are declared by hand for now; tp check
reporting each ladder is #50, and code level 2's CI check is #51.