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 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
+ [![Specs](https://github.com/bybacon/bacon-tracker/actions/workflows/specs.yml/badge.svg)](https://github.com/bybacon/bacon-tracker/actions/workflows/specs.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/bacon-tracker.svg)](https://rubygems.org/gems/bacon-tracker)
5
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](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
+ ![The bacon-tracker board](https://raw.githubusercontent.com/bybacon/bacon-tracker/main/docs/images/board.png)
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
+ ![A story open in the detail view](https://raw.githubusercontent.com/bybacon/bacon-tracker/main/docs/images/board-detail.png)
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)