@taprootio/trellis 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/SPEC.md +405 -0
- package/docs/import.md +273 -0
- package/package.json +59 -0
- package/profiles/taproot-ai-backlog.json +28 -0
- package/profiles/yaml-frontmatter.json +23 -0
- package/scripts/backlog-readme.mjs +84 -0
- package/scripts/pr-title-lint.mjs +69 -0
- package/scripts/trellis-history.mjs +127 -0
- package/scripts/trellis-import.mjs +141 -0
- package/scripts/trellis-init.mjs +287 -0
- package/scripts/trellis-mcp.mjs +222 -0
- package/scripts/trellis.mjs +62 -0
- package/src/backlog.mjs +667 -0
- package/src/cli.mjs +33 -0
- package/src/history.mjs +204 -0
- package/src/import.mjs +583 -0
- package/src/init.mjs +644 -0
- package/src/mcp.mjs +449 -0
- package/src/pr-title.mjs +47 -0
- package/src/profiles.mjs +68 -0
- package/src/prompts.mjs +189 -0
- package/templates/.github/pull_request_template.md +31 -0
- package/templates/trellis/branch-protection.md +116 -0
- package/templates/trellis/playbooks/code-review.md +64 -0
- package/templates/trellis/playbooks/conventions.md +56 -0
- package/templates/trellis/playbooks/pr-draft.md +39 -0
- package/templates/trellis/playbooks/work-task.md +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Taproot IO, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Trellis
|
|
2
|
+
|
|
3
|
+
A tool-agnostic toolkit for running a file-based backlog the same way in any git
|
|
4
|
+
repo. This repository is Trellis, and it dogfoods its own conventions to manage
|
|
5
|
+
its own backlog.
|
|
6
|
+
|
|
7
|
+
Start with [`AGENTS.md`](AGENTS.md) for the conventions and the milestone ethos,
|
|
8
|
+
then browse the live backlog in [`trellis/README.md`](trellis/README.md).
|
|
9
|
+
|
|
10
|
+
## Onboard a repo
|
|
11
|
+
|
|
12
|
+
`trellis init` scaffolds the Trellis layout into any repo — the config, a team
|
|
13
|
+
roster stub, the `trellis/` layout, the generated index, the CI check, an AGENTS.md
|
|
14
|
+
backlog section, and the process playbooks — idempotently, without clobbering
|
|
15
|
+
existing files:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npx @taprootio/trellis init <target> --prefix ABC # --dry-run to preview
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It does not vendor the generator; the onboarded repo runs Trellis via the
|
|
22
|
+
package (the scaffolded CI calls `npx @taprootio/trellis check`), which ships in TRL0010.
|
|
23
|
+
|
|
24
|
+
## Import an existing backlog
|
|
25
|
+
|
|
26
|
+
`trellis import` converts a backlog on a foreign schema into Trellis items in an
|
|
27
|
+
already-initialized repo, driven by a declarative mapping — either a built-in
|
|
28
|
+
**profile** (`--profile <name>`; run `--list-profiles`) or your own
|
|
29
|
+
`--mapping <file.json>`. It is **dry-run by default** — preview the plan and the id
|
|
30
|
+
map, then re-run with `--apply`:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
npx @taprootio/trellis import <source> --profile yaml-frontmatter --target . # add --apply to write
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Ids are assigned fresh-sequentially from the target's next id, colliding source
|
|
37
|
+
ids are deduped, and `depends_on` is rewritten through the id map; the source tree
|
|
38
|
+
is never modified and a real run leaves the backlog `--check`-green. To scaffold
|
|
39
|
+
and import in one step on a fresh repo, use
|
|
40
|
+
`trellis init --import <path> --profile <name>`. The mapping schema, the built-in
|
|
41
|
+
profiles, and the full getting-started guide are in
|
|
42
|
+
[`docs/import.md`](docs/import.md).
|
|
43
|
+
|
|
44
|
+
## Track task history
|
|
45
|
+
|
|
46
|
+
`trellis history` reconstructs a per-task change log from git — who changed an
|
|
47
|
+
item, when, and why — surviving the active→completed move via `git log --follow`:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
npx @taprootio/trellis history <id> # one task; omit <id> for the whole repo
|
|
51
|
+
npx @taprootio/trellis history --write # materialize trellis/history.json for a static viewer
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Entries are `{ id, commit, date, author, subject, reason }`, newest-first, where
|
|
55
|
+
`reason` is a `Trellis-Reason:` commit trailer when present, else the commit
|
|
56
|
+
subject. This is a **derived, non-gated report** (SPEC §8.4): volatile and
|
|
57
|
+
non-authoritative (git is the record), so it is **not** part of `backlog:check`,
|
|
58
|
+
and the materialized `history.json` is gitignored — regenerate it at build time.
|
|
59
|
+
|
|
60
|
+
## Operate over MCP
|
|
61
|
+
|
|
62
|
+
The backlog operations are also exposed as MCP tools, so any MCP-aware client
|
|
63
|
+
(Claude, Cursor, Windsurf, Codex, …) can list, read, create, move, validate,
|
|
64
|
+
regenerate, and read the history of tasks in a repo:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
npx @taprootio/trellis mcp --repo <path> # serves over stdio; defaults to cwd
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Tools: `list_tasks`, `get_task`, `next_id`, `create_task`, `move_task`,
|
|
71
|
+
`validate`, `regenerate`, `import`, `history` — each reuses the same core as the
|
|
72
|
+
CLI, so results carry the `backlog.json` shape (except `import`, which returns an
|
|
73
|
+
import summary, and `history`, which returns git-derived change entries). Mutating
|
|
74
|
+
tools regenerate and validate before returning, rolling back on failure; `import`
|
|
75
|
+
is dry-run unless `apply:true` (see [`docs/import.md`](docs/import.md)); `history`
|
|
76
|
+
is read-only. The process loops (work-a-task, review) ship separately as MCP
|
|
77
|
+
prompts in TRL0006.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# Trellis Backlog Spec
|
|
2
|
+
|
|
3
|
+
**Version:** 2.3.0 · **Status:** stable
|
|
4
|
+
|
|
5
|
+
Trellis is a tool-agnostic convention for running a software backlog as plain
|
|
6
|
+
files in a git repository. Work items are Markdown files with YAML front-matter;
|
|
7
|
+
a generator validates them and produces a human index and a machine-readable
|
|
8
|
+
`backlog.json`; CI gates the repo so the index can never drift. This document is
|
|
9
|
+
the canonical specification, intended to be vendored into any repository
|
|
10
|
+
unchanged.
|
|
11
|
+
|
|
12
|
+
This spec describes the backlog **format and artifacts** — the data, the
|
|
13
|
+
generated outputs, and the tooling contract. It does not prescribe a *process*
|
|
14
|
+
for working items (planning, review, branching); those layer on top and are
|
|
15
|
+
specified separately.
|
|
16
|
+
|
|
17
|
+
## 1. Concepts
|
|
18
|
+
|
|
19
|
+
- **Item** — one unit of tracked work, stored as a single Markdown file.
|
|
20
|
+
- **Status** — `active`, `completed`, or `removed`. An item has exactly one,
|
|
21
|
+
reflected by which directory it lives in.
|
|
22
|
+
- **Generator** — a program that validates items and regenerates the derived
|
|
23
|
+
artifacts (`README.md` tables and `backlog.json`).
|
|
24
|
+
- **Config** — `backlog.config.json`, the per-repo vocabulary (id prefix,
|
|
25
|
+
milestones, priorities, effort scale) that lets the same generator serve any
|
|
26
|
+
repo.
|
|
27
|
+
|
|
28
|
+
The per-item files are the single source of truth. The index and `backlog.json`
|
|
29
|
+
are derived and must never be hand-edited.
|
|
30
|
+
|
|
31
|
+
## 2. Repository layout
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
<repo>/
|
|
35
|
+
trellis/
|
|
36
|
+
backlog.config.json # per-repo configuration (§7)
|
|
37
|
+
team.json # optional team roster (§7.2)
|
|
38
|
+
active/<ID>.md # open items (§5)
|
|
39
|
+
completed/
|
|
40
|
+
tasks/<ID>.md # finished items, history preserved
|
|
41
|
+
index.md # GENERATED list of completed items (§8.1)
|
|
42
|
+
removed/<ID>.md # abandoned items, archived
|
|
43
|
+
removed/index.md # GENERATED list of removed items (§8.1)
|
|
44
|
+
README.md # GENERATED human index (§8.1)
|
|
45
|
+
backlog.json # GENERATED machine index (§8.2)
|
|
46
|
+
assets/effort/ # optional effort-scale images (§6.3)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The **backlog root** defaults to `trellis/` at the repo root and is configurable
|
|
50
|
+
per repo via the `tasksDir` key (§7) — a repo whose static-site generator
|
|
51
|
+
publishes one directory (e.g. Eleventy over `docs/`) can repoint it elsewhere.
|
|
52
|
+
The **config file** is always `trellis/backlog.config.json`, a fixed location
|
|
53
|
+
independent of `tasksDir`: tooling must be able to find the config before it
|
|
54
|
+
knows where the task tree lives. Above, `tasksDir` is its default (`trellis/`),
|
|
55
|
+
so the config and the task tree share one folder. Everything inside an item
|
|
56
|
+
file's body is free-form Markdown.
|
|
57
|
+
|
|
58
|
+
## 3. Identifiers
|
|
59
|
+
|
|
60
|
+
An id is a configured **prefix** followed by a zero-padded **number** of a
|
|
61
|
+
configured width — e.g. with prefix `AB` and width 4, `AB0042`.
|
|
62
|
+
|
|
63
|
+
- The id MUST match the item's filename (`AB0042` ⇄ `AB0042.md`).
|
|
64
|
+
- Ids are assigned monotonically from the `nextId` published in the generated
|
|
65
|
+
`backlog.json` (§8.2).
|
|
66
|
+
- An id is permanent and globally unique across all three directories. It MUST
|
|
67
|
+
NOT be reused, even after an item is removed.
|
|
68
|
+
|
|
69
|
+
## 4. Status lifecycle
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
create
|
|
73
|
+
│
|
|
74
|
+
▼
|
|
75
|
+
[ active ] ──── ship ────▶ [ completed ] → completed/tasks/
|
|
76
|
+
│
|
|
77
|
+
└──────── drop ───────▶ [ removed ] → removed/ (with reason)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- There is **no on-hold status.** Park low-priority work as a low-priority
|
|
81
|
+
`active` item, or remove it with a `removed_reason` that names the trigger to
|
|
82
|
+
revisit.
|
|
83
|
+
- Transitions are a file **move** plus front-matter edits (§5), made in the same
|
|
84
|
+
change that ships or drops the work, followed by a generator run.
|
|
85
|
+
- History is preserved: completing or removing an item moves the file (keeping
|
|
86
|
+
its git history); it is never deleted.
|
|
87
|
+
|
|
88
|
+
## 5. Item file format
|
|
89
|
+
|
|
90
|
+
An item file is YAML front-matter delimited by `---`, followed by a Markdown
|
|
91
|
+
body.
|
|
92
|
+
|
|
93
|
+
### 5.1 Front-matter schema
|
|
94
|
+
|
|
95
|
+
| field | active | completed | removed | rule |
|
|
96
|
+
| --- | :-: | :-: | :-: | --- |
|
|
97
|
+
| `id` | ✓ | ✓ | ✓ | matches the filename |
|
|
98
|
+
| `title` | ✓ | ✓ | ✓ | one line |
|
|
99
|
+
| `status` | ✓ | ✓ | ✓ | matches the directory |
|
|
100
|
+
| `summary` | ✓ | ✓ | ✓ | one sentence; feeds the README table |
|
|
101
|
+
| `milestone` | ✓ | ✓ | ✓ | a configured milestone (§7.1); historical on closed items |
|
|
102
|
+
| `priority` | ✓ | ✓ | ✓ | a configured priority (§7); historical on closed items |
|
|
103
|
+
| `effort` | ✓ | ✓ | ✓ | a configured effort value (§6); historical on closed items |
|
|
104
|
+
| `depends_on` | ✓ | ✓ | ✓ | list of existing ids (`[]` if none) |
|
|
105
|
+
| `owner` | ○ | ○ | ○ | a roster handle (§7.2); active → an active member; historical on closed |
|
|
106
|
+
| `collaborators` | ○ | ○ | ○ | list of roster handles; active → active members; historical on closed |
|
|
107
|
+
| `completed_on` | – | ✓ | – | ISO date (`YYYY-MM-DD`) |
|
|
108
|
+
| `removed_on` | – | – | ✓ | ISO date |
|
|
109
|
+
| `removed_reason` | – | – | ✓ | one line; why, and any trigger to revisit |
|
|
110
|
+
|
|
111
|
+
(○ = optional.) `owner` and `collaborators` are optional everywhere and reference
|
|
112
|
+
the team roster (§7.2); no item need carry them, and no backfill is required.
|
|
113
|
+
|
|
114
|
+
On close (completed or removed), the descriptive metadata — `milestone`,
|
|
115
|
+
`summary`, `priority`, `effort`, `depends_on` — is carried over from the active
|
|
116
|
+
item as a historical snapshot, and the close fields are added (`completed_on`,
|
|
117
|
+
or `removed_on`/`removed_reason`). These retained enum values are **historical**:
|
|
118
|
+
tooling records them as-was and does not re-validate them against the current
|
|
119
|
+
config (§8.3), so milestones and scales can evolve without breaking the archive.
|
|
120
|
+
`owner`/`collaborators` are likewise historical on closed items, so a member who
|
|
121
|
+
has since gone `inactive` (or left the roster) does not invalidate the archive —
|
|
122
|
+
though the stored value must still be a syntactically valid handle (§7.2).
|
|
123
|
+
|
|
124
|
+
### 5.2 Body
|
|
125
|
+
|
|
126
|
+
The body is free-form. Recommended sections are **Scope**, **Notes**, and
|
|
127
|
+
**Risks** for active items, plus a **Completed** section prepended on closeout
|
|
128
|
+
that summarizes what shipped and any follow-ups.
|
|
129
|
+
|
|
130
|
+
### 5.3 Dependencies
|
|
131
|
+
|
|
132
|
+
`depends_on` may reference `active` or `completed` ids (every referenced id MUST
|
|
133
|
+
exist somewhere), but an item SHOULD NOT be *worked* until its dependencies are
|
|
134
|
+
`completed`.
|
|
135
|
+
|
|
136
|
+
## 6. Effort
|
|
137
|
+
|
|
138
|
+
`effort` is **relative complexity, not time.** Canonical effort values are a
|
|
139
|
+
Fibonacci-like set (default `1, 2, 3, 5, 8, 13, 21`), configurable per repo. The
|
|
140
|
+
non-linear gaps are intentional: reaching the top of the scale is the signal to
|
|
141
|
+
split an item.
|
|
142
|
+
|
|
143
|
+
The number is always the stored, canonical value. Teams MAY skin it with a
|
|
144
|
+
custom **effort scale** for display.
|
|
145
|
+
|
|
146
|
+
### 6.1 Effort-scale config
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
"effort": {
|
|
150
|
+
"values": [1, 2, 3, 5, 8, 13, 21],
|
|
151
|
+
"scale": "fish",
|
|
152
|
+
"scales": {
|
|
153
|
+
"fish": {
|
|
154
|
+
"1": { "label": "Minnow", "emoji": "🐟" },
|
|
155
|
+
"2": { "label": "Goldfish", "emoji": "🐠" },
|
|
156
|
+
"3": { "label": "Trout", "emoji": "🐡" },
|
|
157
|
+
"5": { "label": "Tuna", "image": "assets/effort/tuna.svg" },
|
|
158
|
+
"8": { "label": "Swordfish" },
|
|
159
|
+
"13": { "label": "Shark", "emoji": "🦈" },
|
|
160
|
+
"21": { "label": "Whale", "emoji": "🐋" }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- `values` (required) — the canonical effort set.
|
|
167
|
+
- `scales` (optional) — named display scales. Each maps **every** value in
|
|
168
|
+
`values` (string keys) to an entry; a value missing from the active scale is an
|
|
169
|
+
error.
|
|
170
|
+
- `scale` (optional) — the active scale name. Absent or `"fibonacci"` selects the
|
|
171
|
+
identity scale (label = the number, no emoji/image).
|
|
172
|
+
- Each entry: `label` (required, unique within the scale), `emoji` (optional),
|
|
173
|
+
and `image` (optional, a path relative to the backlog root — `tasksDir`, default
|
|
174
|
+
`trellis/` — so it resolves the same way the artifacts do). The Tuna/Swordfish
|
|
175
|
+
entries above show the image-only and label-only cases.
|
|
176
|
+
|
|
177
|
+
### 6.2 Authoring and resolution
|
|
178
|
+
|
|
179
|
+
- In front-matter, `effort` MAY be the canonical number **or** a case-insensitive
|
|
180
|
+
`label` from the active scale (e.g. `effort: Goldfish`). The generator resolves
|
|
181
|
+
a label to its number; an unresolvable or ambiguous value is an error.
|
|
182
|
+
- `backlog.json` ALWAYS carries the resolved canonical number plus the resolved
|
|
183
|
+
`effortLabel`, and `effortEmoji`/`effortImage` when present — so consumers
|
|
184
|
+
render without reading the config.
|
|
185
|
+
|
|
186
|
+
### 6.3 Rendering
|
|
187
|
+
|
|
188
|
+
- The generated README shows `label · N` (label and number together) when a
|
|
189
|
+
non-identity scale is active, and just `N` otherwise — keeping the number
|
|
190
|
+
legible for velocity and rollup math.
|
|
191
|
+
- `label` doubles as the accessible text/alt for any `image`. SVG or emoji are
|
|
192
|
+
preferred; images are optional and live under `<tasksDir>/assets/effort/`
|
|
193
|
+
(default `trellis/assets/effort/`).
|
|
194
|
+
- The array form `effort: [1, 2, 3, 5, 8, 13, 21]` is shorthand for
|
|
195
|
+
`{ "values": [ … ], "scale": "fibonacci" }` and remains valid.
|
|
196
|
+
|
|
197
|
+
## 7. Configuration (`backlog.config.json`)
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"specVersion": "2.0",
|
|
202
|
+
"idPrefix": "AB",
|
|
203
|
+
"idWidth": 4,
|
|
204
|
+
"milestones": ["Alpha", "Beta", "v1", "Future"],
|
|
205
|
+
"priorities": ["High", "Medium", "Low"],
|
|
206
|
+
"effort": [1, 2, 3, 5, 8, 13, 21]
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| key | meaning |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| `specVersion` | the Trellis spec version this repo targets (§9) |
|
|
213
|
+
| `idPrefix` | id prefix (e.g. `AB`) |
|
|
214
|
+
| `idWidth` | zero-padded digit count |
|
|
215
|
+
| `milestones` | ordered milestone names (§7.1) |
|
|
216
|
+
| `priorities` | ordered priority names, highest first |
|
|
217
|
+
| `effort` | canonical values, or the effort-scale object (§6.1) |
|
|
218
|
+
| `tasksDir` | optional backlog-root path, repo-relative; defaults to `trellis/` |
|
|
219
|
+
|
|
220
|
+
`tasksDir` locates the task tree and the generated artifacts; omit it to accept
|
|
221
|
+
the `trellis/` default. The config file itself stays at `trellis/backlog.config.json`
|
|
222
|
+
regardless (§2) — its location is **not** governed by `tasksDir`, so the spec
|
|
223
|
+
example above omits the key.
|
|
224
|
+
|
|
225
|
+
**Configurable** per repo: everything in the table above, including the backlog
|
|
226
|
+
root via `tasksDir`. **Fixed** by the spec: the `trellis/backlog.config.json`
|
|
227
|
+
config location, the in-root layout (`active/`, `completed/tasks/`, `removed/`,
|
|
228
|
+
and the generated artifacts under `tasksDir`), the status lifecycle, the
|
|
229
|
+
front-matter schema, the generated-artifact contracts, and the meaning of each
|
|
230
|
+
field.
|
|
231
|
+
|
|
232
|
+
### 7.1 Milestones are a maturity axis
|
|
233
|
+
|
|
234
|
+
A milestone names the **release gate** an item must land in — and nothing else.
|
|
235
|
+
It is a single, ordered axis (e.g. `Alpha → Beta → v1 → Future`). A milestone is
|
|
236
|
+
**not** a feature area (that is the title), **not** a priority (its own field),
|
|
237
|
+
and **not** an on-hold state (§4). Milestone *names* are configured; the
|
|
238
|
+
single-axis, ordered semantics are fixed.
|
|
239
|
+
|
|
240
|
+
### 7.2 Team roster (`team.json`)
|
|
241
|
+
|
|
242
|
+
The optional **team roster** records who can own work. It is a separate authored
|
|
243
|
+
file, `team.json`, at the fixed config home (`trellis/team.json`, next to
|
|
244
|
+
`backlog.config.json` and independent of `tasksDir`) — kept apart from the config so
|
|
245
|
+
the core vocabulary stays stable while the roster (richer, faster-changing) evolves
|
|
246
|
+
on its own.
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"members": [
|
|
251
|
+
{ "handle": "ada", "name": "Ada Lovelace", "email": "ada@example.com", "status": "active" },
|
|
252
|
+
{ "handle": "alan", "name": "Alan Turing", "status": "inactive" }
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
- `members` (required) — the list of people. The object wrapper leaves room for
|
|
258
|
+
additive top-level keys in future minor versions.
|
|
259
|
+
- `handle` (required) — the stable key referenced by `owner`/`collaborators` in
|
|
260
|
+
front-matter (§5.1). Constrained to `[A-Za-z0-9._-]` (so it survives the inline
|
|
261
|
+
serialization of `collaborators`) and unique, case-insensitively.
|
|
262
|
+
- `name` (required) and `email` (optional) are **display only**.
|
|
263
|
+
- `status` — `active` or `inactive` (default `active`). Only **active** members may
|
|
264
|
+
own or collaborate on **active** items; closed items keep historical assignees
|
|
265
|
+
(§8.3), so a member can go `inactive` or leave without breaking the archive.
|
|
266
|
+
|
|
267
|
+
The roster is **optional**: an absent `team.json` is an empty roster, so a repo that
|
|
268
|
+
never assigns owners is unaffected. A present-but-malformed roster (bad JSON,
|
|
269
|
+
duplicate handle, bad `handle`/`status`, unknown member key) is a validation error
|
|
270
|
+
(§8.3). `handle` is the only identity contract — names/emails are not coupled to any
|
|
271
|
+
external identity provider; cross-repo identity is left to future work.
|
|
272
|
+
|
|
273
|
+
## 8. Generated artifacts
|
|
274
|
+
|
|
275
|
+
These are derived from the item files and config on every generator run. They
|
|
276
|
+
MUST be deterministic — identical inputs produce byte-identical output, with no
|
|
277
|
+
timestamps or other volatile fields — so that `--check` (§8.3) is stable in CI.
|
|
278
|
+
Reports derived from a source *outside* the item files that is inherently volatile
|
|
279
|
+
(e.g. git history) are a separate, **non-gated** class — see §8.4.
|
|
280
|
+
|
|
281
|
+
### 8.1 `README.md`
|
|
282
|
+
|
|
283
|
+
A human index. Item tables are emitted between the markers
|
|
284
|
+
`<!-- BEGIN GENERATED:MILESTONES -->` and `<!-- END GENERATED:MILESTONES -->`;
|
|
285
|
+
content outside the markers is author-owned and preserved. Active items are
|
|
286
|
+
grouped by milestone (config order) and sorted by priority then id. The next id
|
|
287
|
+
is not published here — `backlog.json` carries it (§8.2). Text between the markers
|
|
288
|
+
MUST NOT be hand-edited.
|
|
289
|
+
|
|
290
|
+
The completed and removed indexes (`completed/index.md`, `removed/index.md`) are
|
|
291
|
+
generated the same way, each between its own `BEGIN/END GENERATED` markers, as a
|
|
292
|
+
table that includes the item's **summary** — completed: id, title, summary, date;
|
|
293
|
+
removed: id, title, summary, date, reason. Closing or removing an item is a file
|
|
294
|
+
move plus a generator run; index rows are never hand-added.
|
|
295
|
+
|
|
296
|
+
### 8.2 `backlog.json`
|
|
297
|
+
|
|
298
|
+
The machine contract consumers build on:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"prefix": "AB",
|
|
303
|
+
"milestones": ["Alpha", "Beta", "v1", "Future"],
|
|
304
|
+
"nextId": "AB0016",
|
|
305
|
+
"counts": { "active": 14, "completed": 1, "removed": 0 },
|
|
306
|
+
"tasks": [
|
|
307
|
+
{
|
|
308
|
+
"id": "AB0042", "title": "…", "status": "active",
|
|
309
|
+
"milestone": "Beta", "priority": "High",
|
|
310
|
+
"effort": 5, "effortLabel": "Tuna", "effortImage": "assets/effort/tuna.svg",
|
|
311
|
+
"depends_on": ["AB0007"], "owner": "ada", "collaborators": ["alan"], "summary": "…"
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Every entry carries the descriptive metadata (`milestone`, `summary`,
|
|
318
|
+
`priority`, `effort`, `depends_on`) plus `owner` (a roster handle or `null`) and
|
|
319
|
+
`collaborators` (handles, `[]` if none) — historical on closed entries. Completed
|
|
320
|
+
entries add `completed_on`; removed entries add `removed_on` and `removed_reason`.
|
|
321
|
+
Effort label/emoji/image fields appear when a scale is active.
|
|
322
|
+
|
|
323
|
+
### 8.3 Tooling contract
|
|
324
|
+
|
|
325
|
+
A conforming generator MUST:
|
|
326
|
+
|
|
327
|
+
1. **Validate** every item against the schema (§5), config (§7), and roster (§7.2)
|
|
328
|
+
with actionable messages: id/filename match, required fields, enum membership,
|
|
329
|
+
effort resolution, unique ids, `depends_on` referential integrity, and
|
|
330
|
+
owner/collaborator roster membership. Enum membership (milestone/priority/effort)
|
|
331
|
+
and roster **membership** (owner/collaborators must be **active** members) are
|
|
332
|
+
enforced for **active** items only; on completed/removed items these values are
|
|
333
|
+
historical and MUST NOT fail validation if they are no longer in the current
|
|
334
|
+
config or roster (a mismatch MAY warn). `owner`/`collaborators` MUST nonetheless
|
|
335
|
+
be syntactically valid handles (§7.2) on **every** item — the closed-item
|
|
336
|
+
exemption is from membership, not from being a handle. A malformed `team.json` is
|
|
337
|
+
a validation error regardless.
|
|
338
|
+
2. **Regenerate** `README.md`, the completed/removed indexes (each between its
|
|
339
|
+
markers), and `backlog.json` deterministically.
|
|
340
|
+
3. Support a **`--check`** mode that validates and verifies the artifacts are
|
|
341
|
+
current **without writing**, exiting non-zero on any error or drift. This is
|
|
342
|
+
the CI gate.
|
|
343
|
+
4. **Warn** when `specVersion` is absent or its major version differs from the
|
|
344
|
+
spec version the generator implements (§9).
|
|
345
|
+
|
|
346
|
+
### 8.4 Derived, non-gated reports
|
|
347
|
+
|
|
348
|
+
Not every useful derivation belongs to the gated, deterministic set above. A
|
|
349
|
+
**derived report** is computed from a source *outside* the item files — typically
|
|
350
|
+
volatile (commit timestamps, author names) — so it cannot be byte-identical across
|
|
351
|
+
runs and is therefore handled separately from §8.1–§8.3. Such reports:
|
|
352
|
+
|
|
353
|
+
- are **regenerable** and **not authoritative** — the upstream source is (for a
|
|
354
|
+
git-derived report, git itself);
|
|
355
|
+
- are produced **on demand or at build time** (e.g. a CI step before a static-site
|
|
356
|
+
deploy), **not** by the generator on every edit;
|
|
357
|
+
- SHOULD NOT be committed; a repo that does commit one MUST mark it generated (e.g.
|
|
358
|
+
a top-level `"generated": true`) and keep it out of `--check`.
|
|
359
|
+
|
|
360
|
+
A conforming generator's **`--check` MUST NOT depend on git history** or any other
|
|
361
|
+
volatile source. Derived reports are **optional** and do not affect conformance
|
|
362
|
+
(§11).
|
|
363
|
+
|
|
364
|
+
**`history.json`** is the reference derived report: a per-repo change log keyed by
|
|
365
|
+
task id, materialized for a viewer with no git runtime at serve time. It lives under
|
|
366
|
+
the backlog root (`<tasksDir>/history.json`) when materialized. Each id maps to a
|
|
367
|
+
list of entries `{ id, commit, date, author, subject, reason }`, newest-first,
|
|
368
|
+
reconstructed with `git log --follow` over the task file so history survives the
|
|
369
|
+
active→completed move. `reason` is the value of a `Trellis-Reason:` commit trailer
|
|
370
|
+
when present, otherwise the commit subject. An item imported into a repo carries
|
|
371
|
+
history from its import commit forward; a single-commit or empty history is valid,
|
|
372
|
+
not an error.
|
|
373
|
+
|
|
374
|
+
## 9. Versioning and compatibility
|
|
375
|
+
|
|
376
|
+
This spec uses SemVer. A repo declares the version it targets via `specVersion`
|
|
377
|
+
(`major.minor`). Within a major version, changes are additive and backward
|
|
378
|
+
compatible; a major bump may change required fields or artifact shape. Tooling
|
|
379
|
+
warns on a major mismatch between `specVersion` and the implemented spec.
|
|
380
|
+
|
|
381
|
+
## 10. CI and branch protection
|
|
382
|
+
|
|
383
|
+
Conformant repositories MUST gate the default branch so the index cannot drift:
|
|
384
|
+
|
|
385
|
+
- The default branch is **protected**; changes land via pull/merge request.
|
|
386
|
+
- The generator's **`--check` is a required status check** that must pass before
|
|
387
|
+
merge.
|
|
388
|
+
|
|
389
|
+
This is forge-agnostic — GitHub branch protection, GitLab merge-request
|
|
390
|
+
pipelines, Bitbucket, or Azure DevOps all satisfy it. Setup specifics are left to
|
|
391
|
+
the consumer's onboarding tooling, which SHOULD provide a forge-appropriate setup
|
|
392
|
+
recipe.
|
|
393
|
+
|
|
394
|
+
Where a forge identifies the required check by name (e.g. a GitHub Actions job
|
|
395
|
+
name), keeping that name stable and pinned — and updating the protection rule in
|
|
396
|
+
lockstep if it ever changes — keeps a workflow or job rename from silently
|
|
397
|
+
dropping the gate. This is operational guidance for keeping the required check
|
|
398
|
+
effective, not an additional conformance requirement.
|
|
399
|
+
|
|
400
|
+
## 11. Conformance
|
|
401
|
+
|
|
402
|
+
A repository is **Trellis-conformant** at `specVersion` *X* if its items follow
|
|
403
|
+
§3–§6, its `backlog.config.json` follows §7, its generated artifacts follow §8,
|
|
404
|
+
and its default branch is gated per §10. A **tool** is conformant if it
|
|
405
|
+
implements the generator contract (§8.3) for that version.
|