trackerctl 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 refo
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,222 @@
1
+ # tracker
2
+
3
+ One CLI for all issue-tracking needs — create, claim, dependencies, hierarchy, search,
4
+ project memory — built for both humans and AI agents. Provider-agnostic core with a
5
+ **GitLab adapter** (self-managed instances supported); GitHub/Jira/Azure DevOps adapters
6
+ can be added behind the same interface without touching the core.
7
+
8
+ ## Install
9
+
10
+ Requires [Bun](https://bun.sh) ≥ 1.1 (the cache uses `bun:sqlite`). The npm package is
11
+ **`trackerctl`**; the command it installs is `tracker`.
12
+
13
+ ```sh
14
+ bunx trackerctl help # one-off, no install
15
+ npx trackerctl help # also works (re-executes via bun; tells you if bun is missing)
16
+
17
+ bun add -d trackerctl # per project → `bunx tracker <cmd>` / npm scripts
18
+ bun add -g trackerctl # global → `tracker <cmd>`
19
+ ```
20
+
21
+ From a checkout of this repo:
22
+
23
+ ```sh
24
+ bun install # dev deps only (typescript, biome); zero runtime deps
25
+ bun run tracker help # run from source
26
+ bun run build # bundle to dist/tracker.js (what the npm package ships)
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ Copy `tracker.config.example.json` to `tracker.config.json` and fill in your instance:
32
+
33
+ ```sh
34
+ cp tracker.config.example.json tracker.config.json
35
+ ```
36
+
37
+ `tracker.config.json` is **gitignored** (it carries instance/project identifiers); the
38
+ committed example holds the shape. The token is also never committed (env var or gitignored
39
+ `.env`). Config discovery walks up from the current directory, so any subdirectory of the
40
+ project works.
41
+
42
+ ```json
43
+ {
44
+ "provider": "gitlab",
45
+ "gitlab": {
46
+ "base_url": "https://gitlab.example.com",
47
+ "project": "group/project",
48
+ "token_env": ["TRACKER_GITLAB_TOKEN", "GITLAB_PERSONAL_ACCESS_TOKEN"],
49
+ "native_blocking": true
50
+ },
51
+ "labels": { "in_progress": "status::in-progress" },
52
+ "memory": { "enabled": true, "title": "📌 Project Memory", "label": "meta::memory" },
53
+ "cache": { "path": ".tracker/cache.sqlite", "stale_minutes": 15 }
54
+ }
55
+ ```
56
+
57
+ - `gitlab.project` — path (`group/repo`) or numeric id.
58
+ - `gitlab.token_env` — env var names tried in order, first in the environment, then in a
59
+ `.env` next to the config (see `.env.example`). The token needs the `api` scope. It is
60
+ never put in URLs, argv, or error messages.
61
+ - `gitlab.native_blocking` — `true` on Premium (native blocking links). With `false`,
62
+ dependencies are stored as a `Tracker-Blocked-By: #1, #2` trailer line in the blocked
63
+ issue's description — same semantics, works on any tier, zero extra API calls.
64
+
65
+ Verify a setup with `tracker doctor`.
66
+
67
+ ## Commands
68
+
69
+ Read commands serve from a local sqlite cache that auto-syncs when older than
70
+ `stale_minutes`; every read command accepts `--json` (stable shapes, below).
71
+
72
+ | Command | Description |
73
+ | --- | --- |
74
+ | `tracker sync` | Full cache refresh (issues + hierarchy + dependency links, batched — no per-issue calls) |
75
+ | `tracker ready [--parent <id>]` | Items that are open, unblocked, unassigned, not in-progress |
76
+ | `tracker show <id>` | Full detail, including blocked-by/blocks |
77
+ | `tracker children <id>` | Direct children (work-item hierarchy) |
78
+ | `tracker epic-status <id>` | `closed/total` progress over children |
79
+ | `tracker search [text] [filters]` | Local FTS5 + filters; `--remote` for server-side |
80
+ | `tracker comments <id>` | List an item's comments, oldest first (system notes hidden) |
81
+ | `tracker users <query>` | Resolve usernames/names to user ids (project members) |
82
+ | `tracker whoami` | Authenticated user |
83
+ | `tracker doctor` | Config/token/connectivity/capability checks with fixes |
84
+ | `tracker create -t <title> …` | Create; `--parent` builds hierarchy, `--blocked-by` adds deps |
85
+ | `tracker claim <id>` | Race-safe claim (see protocol below) |
86
+ | `tracker release <id>` | Clear assignee + label, tombstone claim tokens |
87
+ | `tracker close <id> [--reason <text>]` | Close (clears assignee + in-progress label) |
88
+ | `tracker comment <id> <text>` | Post a comment on an item |
89
+ | `tracker dep <id> --blocked-by <o> \| --blocks <o>` | Add a dependency edge |
90
+ | `tracker parent <child> <parent>` | Re-parent an item |
91
+ | `tracker remember <key> <text>` | Store a project memory |
92
+ | `tracker forget <key>` | Hide a memory key |
93
+ | `tracker memories [filter]` | List memories (latest per key wins) |
94
+
95
+ Examples:
96
+
97
+ ```sh
98
+ tracker ready --parent 12 --json
99
+ tracker search --assignee mehmet # filters work with no text query
100
+ tracker search --state closed # state alone is a valid filter
101
+ tracker search "payment timeout" --label backend --state open
102
+ tracker comment 42 "blocked on design review"
103
+ tracker comments 42 --json
104
+ tracker search checkout --remote --json # fresher, server-side
105
+ tracker create -t "Ship login" -d "OAuth" --parent 12 --blocked-by 7,9 -l auth,backend
106
+ tracker claim 42 && do-the-work || echo "someone else got it"
107
+ tracker close 42 --reason "fixed in MR !17"
108
+ tracker remember deploy-cmd "bun run deploy:prod"
109
+ ```
110
+
111
+ Exit codes: **0** success · **2** domain failure (lost claim race, refused claim, not
112
+ found) · **1** usage/config/network error.
113
+
114
+ ## The claim protocol
115
+
116
+ Multiple agents can race for the same issue safely with nothing but issue notes:
117
+
118
+ 1. Refuse up-front if the issue is closed, assigned, already labeled in-progress, or the memory issue.
119
+ 2. Post a claim note: `🔒 tracker-claim agent=<user> token=<ts-rand> at=<iso>`.
120
+ 3. Wait a 2-second settle window, then re-read **all** notes.
121
+ 4. Drop claims whose token has a release note (`🔓 tracker-release token=…`) and claims older than 5 minutes (crashed claimers expire).
122
+ 5. **Oldest live claim wins** — by timestamp, then note id.
123
+ 6. Loser posts a release note for its own token and exits `2`. Winner assigns themself and adds the in-progress label.
124
+
125
+ `tracker release` clears assignee + label and posts release marks for every live token, so
126
+ stale claims can never win a later election.
127
+
128
+ ## Agent usage (paste into CLAUDE.md)
129
+
130
+ ```markdown
131
+ ## Issue tracking (tracker CLI)
132
+
133
+ Use `bun run tracker <cmd>` from the repo (or `tracker` if linked). Exit code 2 means a
134
+ domain refusal (e.g. lost a claim race) — pick different work, don't retry.
135
+
136
+ - Find work: `tracker ready --json` (optionally `--parent <epic-id>`)
137
+ - Take work: `tracker claim <id>` — only proceed if exit code is 0
138
+ - Finish work: `tracker close <id> --reason "<what was done>"`
139
+ - Abandon work: `tracker release <id>`
140
+ - Add a task: `tracker create -t "<title>" -d "<details>" [--parent <epic>] [--blocked-by <ids>] --json`
141
+ - Dependencies: `tracker dep <id> --blocked-by <other>`
142
+ - Find an issue: `tracker search "<text>" --json`, or by person with no text:
143
+ `tracker search --assignee <user> --json`
144
+ - Inspect: `tracker show <id> --json`, `tracker children <id> --json`,
145
+ `tracker epic-status <id> --json`, `tracker comments <id> --json`
146
+ - Discuss: `tracker comment <id> "<note for humans or other agents>"`
147
+ - Project memory: `tracker remember <key> "<fact>"`, `tracker memories --json`,
148
+ `tracker forget <key>`
149
+
150
+ Always pass `--json` on read commands and parse stdout; progress notes go to stderr.
151
+ Never edit the `📌 Project Memory` issue or `🔒/🔓` notes by hand.
152
+ ```
153
+
154
+ ## JSON output shapes
155
+
156
+ `WorkItem` (returned by `ready`, `show`, `children`, `search`, `create`):
157
+
158
+ ```jsonc
159
+ {
160
+ "id": "42", // string everywhere (Jira-ready)
161
+ "kind": "task", // "epic" | "task"
162
+ "title": "Ship login",
163
+ "state": "open", // "open" | "closed"
164
+ "labels": ["auth"],
165
+ "assignees": [{ "id": "6377", "username": "alice", "name": "…" }],
166
+ "author": { "id": "6377", "username": "alice" }, // or null
167
+ "parent": "12", // or null
168
+ "blockedBy": ["7", "9"], // ids of open OR closed blockers; `ready` checks openness
169
+ "url": "https://gitlab…/issues/42",
170
+ "description": "…",
171
+ "updatedAt": "2026-06-10T17:21:33.000Z"
172
+ }
173
+ ```
174
+
175
+ Notes: `show --json` adds `"blocks": ["50"]` (reverse edges from the cache). `--remote`
176
+ search results have `parent: null` and only trailer-derived `blockedBy` (the REST search
177
+ endpoint returns neither).
178
+
179
+ Other shapes:
180
+
181
+ ```jsonc
182
+ // epic-status { "parent": "12", "total": 5, "open": 2, "closed": 3, "pctClosed": 60 }
183
+ // memories [{ "key": "deploy-cmd", "text": "bun run deploy:prod", "ts": "2026-…" }]
184
+ // comments [{ "id": "991", "body": "…", "author": { "id": "1", "username": "alice" }, "createdAt": "2026-…" }]
185
+ // users/whoami [{ "id": "6377", "username": "alice", "name": "…" }]
186
+ // doctor { "ok": true, "checks": [{ "name": "auth", "status": "ok", "detail": "…", "fix": "…" }] }
187
+ ```
188
+
189
+ ## Architecture
190
+
191
+ ```
192
+ src/
193
+ model/ canonical types (WorkItem, Comment, User…) — no provider words here
194
+ adapters/
195
+ types.ts TrackerAdapter port + AdapterCapabilities
196
+ gitlab/ the only concrete adapter: fetch client (REST /api/v4 + GraphQL),
197
+ wire↔canonical mapping, batched hierarchy+links sync, trailer fallback
198
+ core/ provider-neutral policies: claim/release, ready, epic-status, memory,
199
+ local search, sync — built ONLY on the port + cache
200
+ cache/ sqlite (bun:sqlite) canonical snapshot + FTS5 index + meta/staleness
201
+ cli/ command parsing, human + --json output, exit codes
202
+ ```
203
+
204
+ The core never imports provider code. The contract test suite
205
+ (`test/contract/suite.ts`) runs identically against a `FakeAdapter` and the GitLab
206
+ adapter over mocked HTTP — that is the proof a new backend only needs to implement
207
+ `TrackerAdapter` to get `ready`/`claim`/`search`/memory for free.
208
+
209
+ ## Development
210
+
211
+ ```sh
212
+ bun test # 100 tests: unit, adapter contract ×2, claim races, CLI
213
+ bun run gate # typecheck + lint (biome) + tests
214
+ bun run smoke # LIVE end-to-end against the configured project (creates+closes
215
+ # clearly marked issues) — sandbox projects only
216
+ ```
217
+
218
+ ## Publishing
219
+
220
+ `npm publish` runs the full gate and the build automatically (`prepublishOnly`). The
221
+ package ships only `bin/` (Node-safe launcher), `dist/tracker.js` (bundled CLI), the
222
+ example config, README, and LICENSE — no sources, tests, or local config.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ // Launcher: tracker runs on Bun (bun:sqlite). `bunx trackerctl` lands here
3
+ // already inside Bun; `npx trackerctl` lands here inside Node and re-executes
4
+ // via the bun binary, with a friendly error if Bun is not installed.
5
+ import { spawnSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const cli = fileURLToPath(new URL("../dist/tracker.js", import.meta.url));
9
+ const runtime = typeof Bun !== "undefined" ? process.execPath : "bun";
10
+ const result = spawnSync(runtime, [cli, ...process.argv.slice(2)], { stdio: "inherit" });
11
+
12
+ if (result.error && result.error.code === "ENOENT") {
13
+ console.error(
14
+ "tracker requires the Bun runtime, and `bun` was not found on your PATH.\n" +
15
+ "Install it from https://bun.sh (curl -fsSL https://bun.sh/install | bash),\n" +
16
+ "then re-run, or use `bunx trackerctl` directly.",
17
+ );
18
+ process.exit(1);
19
+ }
20
+ process.exit(result.status ?? 1);