bacon-tracker 1.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/LICENSE +21 -0
- data/README.md +430 -0
- data/bin/tracker-dashboard +47 -0
- data/bin/tracker-init +267 -0
- data/lib/bacon_tracker/assets/app.js +1258 -0
- data/lib/bacon_tracker/assets/decisions.js +382 -0
- data/lib/bacon_tracker/assets/docs.js +456 -0
- data/lib/bacon_tracker/assets/logic.js +85 -0
- data/lib/bacon_tracker/assets/theme.js +33 -0
- data/lib/bacon_tracker/commands/tracker.md +269 -0
- data/lib/bacon_tracker/dashboard.rb +168 -0
- data/lib/bacon_tracker/launcher.rb +64 -0
- data/lib/bacon_tracker/server.rb +476 -0
- data/lib/bacon_tracker/tasks.rb +465 -0
- data/lib/bacon_tracker/version.rb +3 -0
- data/lib/bacon_tracker/views/_board_css.erb +122 -0
- data/lib/bacon_tracker/views/_detail_css.erb +109 -0
- data/lib/bacon_tracker/views/_header_css.erb +82 -0
- data/lib/bacon_tracker/views/_markdown_css.erb +81 -0
- data/lib/bacon_tracker/views/_root_css.erb +41 -0
- data/lib/bacon_tracker/views/_theme_boot.erb +1 -0
- data/lib/bacon_tracker/views/dashboard.erb +281 -0
- data/lib/bacon_tracker/views/decisions.erb +211 -0
- data/lib/bacon_tracker/views/docs.erb +234 -0
- data/lib/bacon_tracker/views/index.erb +1065 -0
- data/lib/bacon_tracker.rb +1993 -0
- metadata +213 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 69adddc52e57d700e447f7090e300ff58cf74c3ca9881eb54c19b488f39222d7
|
|
4
|
+
data.tar.gz: 5efcc62978f6d2aeb3689649298956be52ab3fba0bef0ac3ae55603c2afc584f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 202a6ab1a3974e6a303d6571500c230a968114e85003db38c9206908701a51472566f01523637ee032507a1d060df5c2b1272dec001d0a4b76275d64c374628a
|
|
7
|
+
data.tar.gz: 9c33747da78d15551c351a884c81ff3a1ef1284079fc6dd627d0284ec6fada9f5ba3f4ec3586f06bf6a56ac958e87a63b066d0a303b7445d60a94f8572b88487
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented here.
|
|
4
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [1.0.0] - 2026-09-25
|
|
7
|
+
|
|
8
|
+
First public release.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Stories, bugs and chores as Markdown and Gherkin files in four stage directories: icebox, backlog, started, done. `backlog.md` holds the priority order.
|
|
13
|
+
- Rake tasks to create, commit, start, finish, edit and lint stories, with sequential ids issued under a file lock.
|
|
14
|
+
- A local web board with drag and drop, inline create and edit, subtask checklists, relationship badges and a detail view. Stories can also be moved from the edit form and operated by keyboard.
|
|
15
|
+
- A dashboard across several projects, driven by one `dashboard.md`.
|
|
16
|
+
- Decision records (ADRs) with their own ids, a status board, `decision:*` Rake tasks and a lint.
|
|
17
|
+
- A docs browser for a project's `docs/` tree, with search, backlinks and a README/CHANGELOG front page.
|
|
18
|
+
- `tracker-init` to scaffold a project, and an optional `/tracker` command for Claude Code.
|
|
19
|
+
- `story:lint` output as GitHub Actions annotations with `LINT_FORMAT=github`.
|
|
20
|
+
- Reveal and open-in-editor on macOS, Windows and Linux (`xdg-open`), with `BACON_EDITOR` for a custom editor.
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
|
|
24
|
+
- Markdown rendered by the docs and decisions surfaces is sanitised: raw HTML, event handlers and non-http(s)/mailto links are removed.
|
|
25
|
+
- State-changing requests from another origin are rejected, the Host header is pinned to localhost, pages cannot be framed, and request bodies are capped.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Berger
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# bacon-tracker
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bybacon/bacon-tracker/actions/workflows/specs.yml)
|
|
4
|
+
[](https://rubygems.org/gems/bacon-tracker)
|
|
5
|
+
[](https://github.com/bybacon/bacon-tracker/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
A Ruby gem for managing XP-style stories, bugs, and chores as plain Markdown and Gherkin files in a git repo, with a local tracker board.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
> The local drag-and-drop tracker board, served at `http://localhost:4567`. Want to poke at it right now? Boot the bundled [demo tracker](https://github.com/bybacon/bacon-tracker/tree/main/examples/demo/): `cd examples/demo && rake story:server`.
|
|
12
|
+
|
|
13
|
+
No database. No third-party service. Stories are files. The backlog is a text file. Everything lives in your repo and moves through four stages: **icebox → backlog → started → done**.
|
|
14
|
+
|
|
15
|
+
Every interface - the rake CLI, the tracker board, the [`/tracker` Claude Code command](#claude-code-tracker), or your text editor - is a view over the same files. Use whichever fits; the state doesn't care.
|
|
16
|
+
|
|
17
|
+
## Why a tracker in your repo
|
|
18
|
+
|
|
19
|
+
Hosted issue trackers keep your project's plan somewhere else. bacon-tracker keeps it next to the code, which changes two things.
|
|
20
|
+
|
|
21
|
+
**The plan and the code share one history.** A story, the change that delivers it, and the decision record behind it land in the same pull request and are reviewed together. `git log` on a story file shows every move it ever made, and a commit that names its story id links a line of code straight back to the reason for it. Months later you can still see what was decided, when, and why, without searching a separate system. A merge that leaves the tracker inconsistent is caught by `story:lint` in CI.
|
|
22
|
+
|
|
23
|
+
**Agents work with it directly, and it is yours.** A coding agent reads and edits stories the way it reads and edits code: as files. There is no MCP server to run, no tool definitions taking up the agent's context, and no API token to issue or rotate. The agent picks up a story, works it, and moves it to done in the same diff you review. Your plan never leaves your repository, so it sits with your code under your access rules and your backups, and it moves wherever the repository goes. And there is nothing to pay for: no seats, no plan tiers, no subscription. It is an MIT-licensed gem.
|
|
24
|
+
|
|
25
|
+
The [personas](https://github.com/bybacon/bacon-tracker/tree/main/docs/personas) the project is built for show who it serves: Ingo, a developer who owns several products; Leonor, a remote teammate who reviews the tracker alongside the code; and Jean, the AI agent working with both.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quickstart
|
|
30
|
+
|
|
31
|
+
Five minutes from nothing to a story on a board. Requires Ruby >= 3.3.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gem install bacon-tracker
|
|
35
|
+
|
|
36
|
+
# Non-interactive setup: writes ./dashboard.md and ./Rakefile in the current
|
|
37
|
+
# directory, plus my-app/tracker/ and my-app/docs/decisions/.
|
|
38
|
+
# (Run `tracker-init` with no flags to be prompted instead.)
|
|
39
|
+
tracker-init --path ./my-app --namespace APP --title 'My App' --yes
|
|
40
|
+
|
|
41
|
+
# Run rake from the directory that holds dashboard.md and the Rakefile.
|
|
42
|
+
NS=APP rake "story:feature[My first story]" # creates APP-001 in the icebox
|
|
43
|
+
NS=APP rake "story:commit[APP-001]" # commit to it: icebox → backlog
|
|
44
|
+
|
|
45
|
+
tracker-dashboard --dashboard dashboard.md # start the web UI
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The namespace you pass to `tracker-init` (`APP` here) is the one you pass as `NS=` - pick your own, but keep the two in sync. Open `http://localhost:4567`, click your project card, and drag `APP-001` across the board. Everything you just did - and everything the board does - is file moves inside `tracker/`, so `git diff` shows your project management history like any other change.
|
|
49
|
+
|
|
50
|
+
**Why the quotes?** zsh (the macOS default shell) treats `[ ]` as a glob pattern, so an unquoted `rake story:feature[My first story]` fails with `zsh: no matches found`. Quote the task, as every example in these docs does (`noglob rake ...` also works, for arguments without spaces). Other shells are happy with the quotes too.
|
|
51
|
+
|
|
52
|
+
The rest of this README is the detail: the working model, the two setup modes, the interfaces, and the file formats.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Working model
|
|
57
|
+
|
|
58
|
+
bacon-tracker is designed for **ongoing product development**. There are no sprints, releases, or finish lines. Work accumulates and ships continuously. (This section is the summary - the full way of working is written up in [docs/flow.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/flow.md), the bacon flow, and the terms it uses are defined in [docs/vocabulary.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/vocabulary.md).)
|
|
59
|
+
|
|
60
|
+
| Stage | Directory | Meaning |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| Icebox | `1_icebox/` | Ideas and future work - not yet committed |
|
|
63
|
+
| Backlog | `2_backlog/` | Committed and prioritised - ready to work on next |
|
|
64
|
+
| Started | `3_started/` | Actively in progress |
|
|
65
|
+
| Done | `4_done/` | Complete - permanent record, never deleted |
|
|
66
|
+
|
|
67
|
+
Stories move forward with three commands:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
icebox ──story:commit──▶ backlog ──story:start──▶ started ──story:done──▶ done
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The verbs mean what they say: **`commit`** moves a story into the backlog - the act of committing to do it (icebox → backlog) - and **`start`** begins the work (backlog → started), landing it in `3_started/`.
|
|
74
|
+
|
|
75
|
+
**Icebox vs Backlog:** Icebox holds everything you *might* do; backlog holds only what you *will* do next, in priority order. Moving a story from icebox to backlog is an act of commitment. Keep the backlog short (3–7 items).
|
|
76
|
+
|
|
77
|
+
**Started:** Pull from the top of the backlog when you begin work. A large started pile signals over-commitment.
|
|
78
|
+
|
|
79
|
+
### Progress bar & health colors
|
|
80
|
+
|
|
81
|
+
Each dashboard card shows a progress bar measuring completion:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
progress = done / total × 100
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Workload health is signalled separately, by color:
|
|
88
|
+
|
|
89
|
+
- **Card left border** - green (actively working, 1–2 started), amber (overloaded, 3+ started), red (idle - backlog waiting but nothing started)
|
|
90
|
+
- **Story counts** - started and backlog counts turn from green to amber past the healthy thresholds (2 started, 5 backlog)
|
|
91
|
+
|
|
92
|
+
Sweet spot: **1 started** (2 at most) and **3–4 in backlog**.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Setup
|
|
97
|
+
|
|
98
|
+
### Two ways to run it
|
|
99
|
+
|
|
100
|
+
Decide which shape fits before you initialize - the rest of this section sets up the first:
|
|
101
|
+
|
|
102
|
+
- **Central tracker with dashboard (recommended)** - one **tracker home** directory manages every project. Each project keeps its story files in its own repo, but a single `dashboard.md` + `Rakefile` in the tracker home drives them all, and you select a project with `NS=<namespace>`. Best when you juggle more than one codebase. `tracker-init` sets this up. See [Central tracker with dashboard](#central-tracker-with-dashboard-recommended).
|
|
103
|
+
- **Per-project (standalone)** - the tracker lives inside a single project directory with a local `Rakefile`; no `NS=` and no central dashboard. Best for a single repo. See [Per-project (standalone)](#per-project-standalone).
|
|
104
|
+
|
|
105
|
+
**Requirements:** Ruby >= 3.3. Nothing else - no database, no service to sign up for, no daemon to keep running. (Bundler only comes in if you add the gem to a project's `Gemfile` for the [per-project setup](#per-project-standalone).)
|
|
106
|
+
|
|
107
|
+
Install the gem and run the interactive setup:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
gem install bacon-tracker
|
|
111
|
+
tracker-init
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`tracker-init` asks for a project directory, a namespace (e.g. `APP`), a title, and where to keep `dashboard.md` (default: `./dashboard.md`), then writes:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
. # your tracker home (where dashboard.md lives)
|
|
118
|
+
├── dashboard.md # the projects this tracker manages
|
|
119
|
+
└── Rakefile # loads bacon-tracker, defines the story:* and decision:* tasks
|
|
120
|
+
|
|
121
|
+
my-app/ # the project directory
|
|
122
|
+
├── tracker/
|
|
123
|
+
│ ├── .gitignore # ignores .lock, the machine-local write lock
|
|
124
|
+
│ ├── .next-id # next story number
|
|
125
|
+
│ ├── backlog.md # the ordered backlog - position is priority
|
|
126
|
+
│ ├── features/ # .feature files (Gherkin)
|
|
127
|
+
│ │ ├── _template.feature
|
|
128
|
+
│ │ ├── 1_icebox/ # a stage is a directory
|
|
129
|
+
│ │ ├── 2_backlog/
|
|
130
|
+
│ │ ├── 3_started/
|
|
131
|
+
│ │ └── 4_done/
|
|
132
|
+
│ ├── bugs/ # .md files - same four stages, plus _template.md
|
|
133
|
+
│ └── chores/ # .md files - same four stages, plus _template.md
|
|
134
|
+
└── docs/
|
|
135
|
+
└── decisions/ # decision records - see "Decision records" below
|
|
136
|
+
├── .gitignore
|
|
137
|
+
├── .next-id # next decision number
|
|
138
|
+
├── _template.md
|
|
139
|
+
├── proposed.md # the ordered list of decisions still to make
|
|
140
|
+
├── proposed/ # a status is a directory
|
|
141
|
+
├── accepted/
|
|
142
|
+
├── rejected/
|
|
143
|
+
├── deprecated/
|
|
144
|
+
└── superseded/
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
That's the whole system. A story is a file in one of those stage directories;
|
|
148
|
+
moving work forward moves the file. Nothing else is keeping state.
|
|
149
|
+
|
|
150
|
+
The project is registered in your `dashboard.md`; if there is no `dashboard.md` yet, one is created, along with a central `Rakefile` beside it if there isn't one already. Every stage and status directory gets a `.gitkeep` so the empty structure survives your first commit. An existing `docs/decisions/` is left alone.
|
|
151
|
+
|
|
152
|
+
The namespace is 2-8 characters, a letter followed by letters or digits (`APP`, `OPS`, `B2B`); it is upcased for you, and anything else is refused rather than guessed at. It must be unique within a `dashboard.md`.
|
|
153
|
+
|
|
154
|
+
Pass `--command` to also install the `/tracker` Claude Code command at `.claude/commands/tracker.md` next to the dashboard (skipped if the file already exists, so your edits are kept). Without it, no Claude files are written.
|
|
155
|
+
|
|
156
|
+
You can also pass everything non-interactively:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
tracker-init --path ~/projects/my-app --namespace APP --title "My App" --dashboard ~/tracker/dashboard.md --yes
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`--yes` means no prompts at all, so the command can run unattended: `--path` and `--namespace` are required (missing either is an error), the title defaults to one made from the directory name, and the dashboard defaults to `./dashboard.md`. Add `--command` if you also want the `/tracker` Claude command. `tracker-init --help` lists every flag.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Central tracker with dashboard (recommended)
|
|
167
|
+
|
|
168
|
+
The recommended setup is a single **tracker home** directory that manages all your projects. Run all commands from there using `NS=` to select the project.
|
|
169
|
+
|
|
170
|
+
### dashboard.md
|
|
171
|
+
|
|
172
|
+
`tracker-init` creates and registers projects in `dashboard.md`:
|
|
173
|
+
|
|
174
|
+
```markdown
|
|
175
|
+
## My App
|
|
176
|
+
path: ~/projects/my-app
|
|
177
|
+
namespace: APP
|
|
178
|
+
|
|
179
|
+
## Ops
|
|
180
|
+
path: ~/projects/ops
|
|
181
|
+
namespace: OPS
|
|
182
|
+
tracker: planning
|
|
183
|
+
docs: handbook
|
|
184
|
+
version: lib/VERSION
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Each `## Heading` is a project and its display name. `path:` is the **project directory**; relative paths are resolved against the directory holding `dashboard.md`. By default the stories are in `<path>/tracker`, the docs in `<path>/docs` (with decision records in `<path>/docs/decisions`), and the version shown on the docs front page comes from a `VERSION` file in the project. Three optional keys, each relative to `path:`, override those defaults: `tracker:`, `docs:` and `version:`. A project without a `docs/` directory simply has no docs surface.
|
|
188
|
+
|
|
189
|
+
An older registry whose `path:` points straight at a tracker directory (one holding `backlog.md` or `.next-id`) is still recognised and works as before. The dashboard server re-reads `dashboard.md` when it changes, so there's no need to restart it.
|
|
190
|
+
|
|
191
|
+
### Rake commands
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
cd ~/tracker
|
|
195
|
+
|
|
196
|
+
# Create stories (optional trailing field=value list - same fields as edit)
|
|
197
|
+
NS=APP rake "story:feature[Add login page]"
|
|
198
|
+
NS=APP rake "story:bug[Crash on startup,size=S,assignee=AB]"
|
|
199
|
+
NS=APP rake "story:chore[Upgrade Ruby]"
|
|
200
|
+
|
|
201
|
+
# Move through stages
|
|
202
|
+
NS=APP rake "story:commit[APP-001]" # icebox → backlog (commit to it)
|
|
203
|
+
NS=APP rake "story:start[APP-001]" # backlog → started (work on it)
|
|
204
|
+
NS=APP rake "story:done[APP-001]" # → done
|
|
205
|
+
|
|
206
|
+
# Set fields (size/assignee/blocked_by/linked_to/title/body)
|
|
207
|
+
NS=APP rake "story:edit[APP-001,size=M,assignee=AB]"
|
|
208
|
+
NS=APP rake "story:edit[APP-001,blocked_by=APP-002,APP-005]" # comma list is fine
|
|
209
|
+
NS=APP rake "story:edit[APP-001,linked_to=APP-012]" # a "see also" sibling
|
|
210
|
+
NS=APP rake "story:edit[APP-001,size=]" # empty value clears it
|
|
211
|
+
|
|
212
|
+
# Inspect
|
|
213
|
+
NS=APP rake story:next # print top backlog item
|
|
214
|
+
NS=APP rake story:lint # check backlog.md, IDs, and blocked_by/linked_to
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`NS` selects the project (`NAMESPACE` works too). Without it, only `rake story:dashboard_server` is available - the same dashboard server as `tracker-dashboard`, reading `$DASHBOARD` or else `./dashboard.md`, on `$PORT` (default 4567). `rake -T` lists what is installed.
|
|
218
|
+
|
|
219
|
+
Every field can be set from any interface - at create time or later - via `rake story:edit` / a trailing `field=value` list on `story:feature|bug|chore`, `/tracker edit`, the web UI's edit mode, or by hand in the frontmatter. All of them go through the same validation (`size` is S/M/L; an empty value clears a field; changing `title` renames the file). The one practical limit: a rake `body=` is a single shell line, so for multi-line bodies and [subtask](https://github.com/bybacon/bacon-tracker/blob/main/docs/story-format.md#subtasks) checklists, reach for `/tracker`, the web UI, or your editor.
|
|
220
|
+
|
|
221
|
+
### Migrating an existing repo
|
|
222
|
+
|
|
223
|
+
Adopting the tracker in a repo with pre-existing stories? `story:migrate` assigns namespace IDs to all existing story files, ordered by their first git commit:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
NS=APP rake story:migrate
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Per-project (standalone)
|
|
232
|
+
|
|
233
|
+
If you prefer to work from inside a project directory instead of a central tracker, add the gem to that project's `Gemfile` and use a local `Rakefile`:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
require "bacon_tracker"
|
|
237
|
+
require "bacon_tracker/tasks"
|
|
238
|
+
|
|
239
|
+
BaconTracker.configure do |c|
|
|
240
|
+
c.namespace = "APP"
|
|
241
|
+
c.tracker_root = File.expand_path("tracker", __dir__)
|
|
242
|
+
# Optional - leave these out and the docs and decisions surfaces are off.
|
|
243
|
+
c.docs_root = File.expand_path("docs", __dir__) # the docs browser at /docs
|
|
244
|
+
c.decisions_root = File.expand_path("docs/decisions", __dir__) # decision records and decision:* tasks
|
|
245
|
+
c.project_root = __dir__ # README, CHANGELOG and VERSION on the docs front page
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
BaconTracker::Tasks.install
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Roots may be relative or start with `~`; they are expanded for you. `c.version_path` (relative to `project_root`) points at a `VERSION` file the default search can't find. Stage and status directories are created as they are first needed, and stories use built-in templates when there's no `_template` file, so an empty `tracker/` is enough to start. Run rake from that directory without `NS=`:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
rake "story:feature[Add login page]"
|
|
255
|
+
rake "story:commit[APP-001]"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Start a single-project server:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
rake story:server
|
|
262
|
+
PORT=4000 rake story:server
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Web UI
|
|
268
|
+
|
|
269
|
+
Start the dashboard server from the tracker home:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
tracker-dashboard --dashboard ~/tracker/dashboard.md --port 4567
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Visit `http://localhost:4567`. Click a project card to open its board.
|
|
276
|
+
|
|
277
|
+
`--dashboard` defaults to `$DASHBOARD`, then `./dashboard.md`; `--port` defaults to 4567. The server binds to localhost only.
|
|
278
|
+
|
|
279
|
+
On macOS you can also let [BaconTrackerMenu](https://github.com/bybacon/bacon-tracker-menu), a companion menu bar app, start and stop it for you, with a shortcut straight to each project's board.
|
|
280
|
+
|
|
281
|
+
Either way the server is an ordinary process: start it when you want the board, Ctrl-C when you don't. Nothing breaks while it's off - the files are the state.
|
|
282
|
+
|
|
283
|
+
The pages load one web font (Source Code Pro) from Google Fonts - the only request that leaves your machine. Offline, they fall back to your system's monospace font.
|
|
284
|
+
|
|
285
|
+
### Board features
|
|
286
|
+
|
|
287
|
+
- **Four columns:** Icebox → Backlog → Started → Done
|
|
288
|
+
- **Drag** cards between columns to change stage
|
|
289
|
+
- **Drag** within Backlog to reprioritize (syncs `backlog.md`)
|
|
290
|
+
- **Click a card body** to expand - shows rendered Markdown or syntax-highlighted Gherkin
|
|
291
|
+
- **Maximize (⤢)** - open a story in a detail modal: full body and metadata at reading width, with interactive subtasks; closes on ✕ / `Esc` / click-outside
|
|
292
|
+
- **Subtasks** - Markdown checklist lines (`- [ ] task`) in any story body render as clickable checkboxes; the card header shows `done/total` progress, and toggling writes straight back to the story file
|
|
293
|
+
- **Edit mode** - update title, T-shirt size, assignee initials, blocked-by / linked-to IDs, stage, and body; a failed save keeps your edits open
|
|
294
|
+
- **Keyboard** - Tab to a card, Enter/Space to expand it or tick a subtask; edit mode's **stage** picker moves a story without dragging (done stories stay done)
|
|
295
|
+
- **Reveal (↗)** - show a story file in your file manager (macOS Finder, Windows Explorer, or the containing folder via `xdg-open` on Linux)
|
|
296
|
+
- **Deep links** - `/#APP-042` scrolls to that story and flashes it
|
|
297
|
+
- **Relationships** - `⛔` a story is blocked by another; the derived amber `⛔ blocks` badge shows the reverse (this story is holding one up); `🔗` a symmetric "see also" link. Badges that point at a known story are clickable and jump to it
|
|
298
|
+
- **"+" button** in Icebox and Backlog column headers - inline create form
|
|
299
|
+
- Done column is paginated (20 per page)
|
|
300
|
+
- **§ badges** - a story cited by a decision record links to it on the decisions board
|
|
301
|
+
|
|
302
|
+

|
|
303
|
+
|
|
304
|
+
> A story open in the detail modal - full metadata, syntax-highlighted Gherkin, and interactive subtasks.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Docs browser
|
|
309
|
+
|
|
310
|
+
When a project has a `docs/` directory (in dashboard mode) or a `docs_root` (standalone), the server also serves its documentation at `/docs` - `/projects/<slug>/docs` in dashboard mode, linked from the project card as **open docs**. It renders the Markdown tree as a column browser with full-text search, a "recently changed" list from git history, and backlinks ("what links here"). The front page shows the project's README, CHANGELOG and version. Each page has **open in editor** and **reveal** buttons.
|
|
311
|
+
|
|
312
|
+
Markdown is sanitised on the server before it's shown: raw HTML, `{::nomarkdown}` blocks, event-handler and style attributes, and links or images whose scheme isn't http, https or mailto are dropped, and task lists render as disabled checkboxes.
|
|
313
|
+
|
|
314
|
+
"Open in editor" uses `$BACON_EDITOR` when set (split like a shell command, so `BACON_EDITOR="code -w"` works), otherwise the system default: `open` on macOS, `start` on Windows, `xdg-open` on Linux. Reveal works as on the board. Where no launcher exists, the page says so instead of pretending it worked.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Decision records
|
|
319
|
+
|
|
320
|
+
A decision record (an ADR) is a short Markdown file capturing one design decision: its context, what was decided, and the consequences. They live in `docs/decisions/`, and as with stories the directory is the state - one directory per status. IDs are `<NS>-ADR-NNNN`, a sequence separate from story IDs.
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
NS=APP rake "decision:new[Use Postgres for the queue]" # APP-ADR-0001 in proposed/
|
|
324
|
+
NS=APP rake "decision:accept[APP-ADR-0001]" # or decision:reject
|
|
325
|
+
NS=APP rake "decision:deprecate[APP-ADR-0001]" # accepted → deprecated
|
|
326
|
+
NS=APP rake "decision:supersede[APP-ADR-0001,APP-ADR-0002]" # accepted → superseded by 0002
|
|
327
|
+
NS=APP rake decision:lint # frontmatter, status vs directory, IDs, proposed.md
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Decisions only move forward: proposed → accepted or rejected, accepted → deprecated or superseded. Nothing returns to proposed; to reverse a decision, supersede it with a new one. `proposed.md` is the ordered list of decisions still to make. `decision:lint` exits non-zero on integrity problems, so it works as a CI gate like `story:lint`.
|
|
331
|
+
|
|
332
|
+
The decisions board at `/docs/decisions` (**open adrs** on a dashboard card, once the project has a record) shows the records by status. Drag to reorder the proposed list or to move a record to a new status. A record's `stories:` frontmatter key cites stories, which then show a § badge on the tracker board.
|
|
333
|
+
|
|
334
|
+
The HTTP endpoints behind the docs browser and the decisions board are in [docs/api.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/api.md).
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Claude Code: /tracker
|
|
339
|
+
|
|
340
|
+
`tracker-init --command` installs a `/tracker` command for [Claude Code](https://claude.com/claude-code) at `.claude/commands/tracker.md` next to your dashboard - a natural-language interface over the same files:
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
/tracker # status overview: counts + ordered backlog
|
|
344
|
+
/tracker new bug crash on empty cart # create a story in the icebox
|
|
345
|
+
/tracker commit APP-007 # icebox → backlog
|
|
346
|
+
/tracker start APP-007 # backlog → started
|
|
347
|
+
/tracker done APP-007 # → done
|
|
348
|
+
/tracker next # show the top backlog item
|
|
349
|
+
/tracker show APP-003 # full story content
|
|
350
|
+
/tracker edit APP-003 size=M assignee=AB # update fields (title renames the file)
|
|
351
|
+
/tracker delete APP-012 # remove a story (asks first; refuses done)
|
|
352
|
+
/tracker list started # list stories, optionally by stage
|
|
353
|
+
/tracker lint # consistency checks
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Because stories are plain files in the repo, the agent needs no API tokens or integrations - it reads and moves files, exactly like the CLI and the board do. And anything beyond the listed verbs you can just ask for in plain language: the files are right there.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Story format
|
|
361
|
+
|
|
362
|
+
A story is a plain file with a few frontmatter fields. Gherkin for features,
|
|
363
|
+
Markdown for bugs and chores:
|
|
364
|
+
|
|
365
|
+
```gherkin
|
|
366
|
+
# id: APP-001
|
|
367
|
+
# type: feature
|
|
368
|
+
# status: backlog
|
|
369
|
+
# size: M
|
|
370
|
+
# blocked_by: APP-005
|
|
371
|
+
|
|
372
|
+
Feature: Implement user auth
|
|
373
|
+
Scenario: A returning user signs in
|
|
374
|
+
Given ...
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The stage is the directory the file sits in, the backlog order is a line per
|
|
378
|
+
story in `backlog.md`, and a `- [ ]` line in any body is a subtask the board
|
|
379
|
+
counts and ticks off.
|
|
380
|
+
|
|
381
|
+
Full contract - both frontmatter dialects, every field and its valid values,
|
|
382
|
+
subtask fence rules, `backlog.md` and `.next-id`: **[docs/story-format.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/story-format.md)**.
|
|
383
|
+
|
|
384
|
+
## Linting
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
NS=APP rake story:lint
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Because the tracker is files, a merge can leave it inconsistent - a backlog
|
|
391
|
+
line whose file moved, an ID issued twice, a `blocked_by` whose blocker already
|
|
392
|
+
shipped. `story:lint` catches all of it and exits non-zero when something is
|
|
393
|
+
genuinely broken, so it works as a CI gate as-is. With `LINT_FORMAT=github`
|
|
394
|
+
each finding becomes an annotation on the story file and line that caused it.
|
|
395
|
+
|
|
396
|
+
Every check, what it means and what to do about it:
|
|
397
|
+
**[docs/linting.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/linting.md)**.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## HTTP API
|
|
402
|
+
|
|
403
|
+
The server exposes a localhost JSON API consumed by the web UI - stories grouped by stage, create/move/edit/delete, backlog reordering, subtask toggling, project stats, and the docs and decisions endpoints. Full reference with request/response shapes: [docs/api.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/api.md).
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Development
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
bundle install
|
|
411
|
+
bundle exec rspec # Ruby suite
|
|
412
|
+
node --test spec/js/*.test.js # JavaScript unit tests (board client)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
- The words this project uses - story, stage, card, commit - and the ones it doesn't: [docs/vocabulary.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/vocabulary.md).
|
|
416
|
+
- Something not working: [docs/troubleshooting.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/troubleshooting.md).
|
|
417
|
+
- Cutting a release (maintainers): [docs/releasing.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/releasing.md).
|
|
418
|
+
- How the project is tested and where to add coverage: [docs/testing.md](https://github.com/bybacon/bacon-tracker/blob/main/docs/testing.md).
|
|
419
|
+
- Contributing guide: [CONTRIBUTING.md](https://github.com/bybacon/bacon-tracker/blob/main/CONTRIBUTING.md).
|
|
420
|
+
- This project's own design decisions are recorded in [docs/decisions/](https://github.com/bybacon/bacon-tracker/tree/main/docs/decisions/), one
|
|
421
|
+
directory per status - [accepted/](https://github.com/bybacon/bacon-tracker/tree/main/docs/decisions/accepted/) is the set in
|
|
422
|
+
force. The record format itself is defined in
|
|
423
|
+
[BT-ADR-0014](https://github.com/bybacon/bacon-tracker/blob/main/docs/decisions/accepted/BT-ADR-0014-adr-contract-ids-statuses-frontmatter-and-proposed-list.md),
|
|
424
|
+
and `rake decision:lint` enforces it.
|
|
425
|
+
- What changed in each release: [CHANGELOG.md](https://github.com/bybacon/bacon-tracker/blob/main/CHANGELOG.md).
|
|
426
|
+
- A ready-to-run board with sample data lives in [examples/demo/](https://github.com/bybacon/bacon-tracker/tree/main/examples/demo/).
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
[MIT](https://github.com/bybacon/bacon-tracker/blob/main/LICENSE).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
|
|
6
|
+
options = { port: 4567 }
|
|
7
|
+
OptionParser.new do |opts|
|
|
8
|
+
opts.banner = "Usage: tracker-dashboard [options]"
|
|
9
|
+
opts.on("--path PATH", "--dashboard PATH", "Path to dashboard.md") { |v| options[:dashboard] = v }
|
|
10
|
+
opts.on("--port PORT", Integer, "Port (default: 4567)") { |v| options[:port] = v }
|
|
11
|
+
opts.on("-h", "--help", "Show this help") { puts opts; exit }
|
|
12
|
+
end.parse!
|
|
13
|
+
|
|
14
|
+
dashboard_path = options[:dashboard] ||
|
|
15
|
+
ENV.fetch("DASHBOARD", nil) ||
|
|
16
|
+
File.join(Dir.pwd, "dashboard.md")
|
|
17
|
+
|
|
18
|
+
unless File.exist?(dashboard_path)
|
|
19
|
+
warn "dashboard.md not found at #{dashboard_path}"
|
|
20
|
+
warn ""
|
|
21
|
+
warn "Create it like this:"
|
|
22
|
+
warn ""
|
|
23
|
+
warn " ## My Project"
|
|
24
|
+
warn " path: /path/to/my-project"
|
|
25
|
+
warn " namespace: MYP"
|
|
26
|
+
warn ""
|
|
27
|
+
warn "or run tracker-init, which writes it for you."
|
|
28
|
+
exit 1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
require "bacon_tracker"
|
|
32
|
+
require "bacon_tracker/dashboard"
|
|
33
|
+
require "bacon_tracker/server"
|
|
34
|
+
|
|
35
|
+
dashboard = BaconTracker::Dashboard.new(dashboard_path)
|
|
36
|
+
|
|
37
|
+
if dashboard.projects.empty?
|
|
38
|
+
warn "No projects found in #{dashboard_path}"
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
port = options[:port]
|
|
43
|
+
$stdout.sync = true
|
|
44
|
+
puts "Bacon Dashboard running at http://localhost:#{port}"
|
|
45
|
+
puts "Projects: #{dashboard.projects.map(&:name).join(", ")}"
|
|
46
|
+
puts "Ctrl-C to stop."
|
|
47
|
+
BaconTracker::Server.boot_dashboard(dashboard).run!(port: port, bind: "localhost", quiet: true)
|