trackerctl 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -90,7 +90,9 @@ Read commands serve from a local sqlite cache that auto-syncs when older than
90
90
  | `tracker close <id> [--reason <text>]` | Close (clears assignee + in-progress label) |
91
91
  | `tracker comment <id> <text>` | Post a comment on an item |
92
92
  | `tracker attach <id> <file...> [-m <msg>]` | Upload files, reference them from one comment; prints markdown |
93
+ | `tracker label <id> [--add a,b] [--remove c,d]` | Add/remove labels without clobbering the rest |
93
94
  | `tracker spend <id> <duration>` | Add time spent (`1h30m`, `2d`; `-30m` subtracts) |
95
+ | `tracker spend <id> --since-claim` | Log wall-clock elapsed since the latest 🔒 claim note (claim-to-done timing) |
94
96
  | `tracker estimate <id> <duration>` | Set the time estimate (`0` clears it) |
95
97
  | `tracker dep <id> --blocked-by <o> \| --blocks <o>` | Add a dependency edge |
96
98
  | `tracker parent <child> <parent>` | Re-parent an item |
@@ -175,8 +177,9 @@ domain refusal (e.g. lost a claim race) — pick different work, don't retry.
175
177
  - Land it: `tracker pr merge <id> --close-issues` — also closes the `-i` issues
176
178
  with a comment explaining why; never rely on provider auto-close
177
179
 
178
- - Track time: `tracker spend <id> 1h30m` after finishing work (estimate vs spent
179
- feeds later evaluations; durations use 1d=8h, -30m subtracts)
180
+ - Track time: `tracker spend <id> --since-claim` after landing work logs the
181
+ elapsed claim-to-done wall clock from server timestamps (or an
182
+ explicit `tracker spend <id> 1h30m`; durations use 1d=8h, -30m subtracts)
180
183
  - Project memory: `tracker remember <key> "<fact>"`, `tracker memories --json`,
181
184
  `tracker forget <key>`
182
185
 
package/dist/tracker.js CHANGED
@@ -1003,6 +1003,17 @@ function electWinner(comments, nowMs, ttlMs) {
1003
1003
  live.sort((a, b) => a.ts - b.ts || compareCommentIds(a.commentId, b.commentId));
1004
1004
  return live[0] ?? null;
1005
1005
  }
1006
+ function secondsSinceClaim(comments, nowMs) {
1007
+ let latest = null;
1008
+ for (const c of comments) {
1009
+ if (!c.body.startsWith(CLAIM_MARK))
1010
+ continue;
1011
+ const ts = Date.parse(c.createdAt);
1012
+ if (Number.isFinite(ts) && (latest === null || ts > latest))
1013
+ latest = ts;
1014
+ }
1015
+ return latest === null ? null : Math.max(0, Math.round((nowMs - latest) / 1000));
1016
+ }
1006
1017
  var realClaimDeps = {
1007
1018
  now: () => Date.now(),
1008
1019
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -1354,9 +1365,12 @@ write commands:
1354
1365
  release <id> clear assignee/label, tombstone live claim tokens
1355
1366
  close <id> [--reason <text>]
1356
1367
  comment <id> <text> post a comment on an item
1368
+ label <id> [--add a,b] [--remove c,d]
1369
+ add/remove labels without touching the others
1357
1370
  attach <id> <file...> [-m <message>]
1358
1371
  upload files and attach them via a comment
1359
1372
  spend <id> <duration> add time spent (1h30m, 45m, 2d; -30m subtracts)
1373
+ spend <id> --since-claim log elapsed time since the latest claim note
1360
1374
  estimate <id> <duration> set the time estimate (0 clears it)
1361
1375
  dep <id> --blocked-by <other> | --blocks <other>
1362
1376
  parent <child-id> <parent-id>
@@ -1430,6 +1444,14 @@ Posts a comment on the item. Everything after the id is joined into one
1430
1444
  comment body, so quoting multi-word text is optional.
1431
1445
 
1432
1446
  example: tracker comment 42 "blocked on the design review, see thread"`,
1447
+ label: `usage: tracker label <id> [--add <a,b>] [--remove <c,d>]
1448
+
1449
+ Adds and/or removes labels (comma-separated) without clobbering the rest.
1450
+ At least one of --add/--remove is required.
1451
+
1452
+ examples:
1453
+ tracker label 42 --add status::agent-blocked
1454
+ tracker label 42 --remove status::agent-blocked --add meta::human-only`,
1433
1455
  attach: `usage: tracker attach <id> <file...> [-m <message>] [--json]
1434
1456
 
1435
1457
  Uploads each file to the provider and posts ONE comment on the item containing
@@ -1472,14 +1494,19 @@ Lists the item's comments oldest-first (system notes are filtered out). Claim
1472
1494
  notes (\uD83D\uDD12/\uD83D\uDD13) and memory entries (\uD83D\uDCCC) appear here too \u2014 useful for debugging.
1473
1495
 
1474
1496
  example: tracker comments 42 --json`,
1475
- spend: `usage: tracker spend <id> <duration>
1497
+ spend: `usage: tracker spend <id> <duration> | tracker spend <id> --since-claim
1476
1498
 
1477
1499
  Adds to the item's time spent. Durations use GitLab conventions:
1478
1500
  units w/d/h/m/s with 1d = 8h and 1w = 5d. A leading "-" subtracts.
1479
1501
 
1502
+ --since-claim logs the wall-clock elapsed since the latest \uD83D\uDD12 claim note
1503
+ (rounded to whole minutes, minimum 1m) \u2014 claim-to-done ticket timing with
1504
+ no client-side clock needed. Exit 2 if the item was never claimed.
1505
+
1480
1506
  examples:
1481
1507
  tracker spend 42 1h30m
1482
- tracker spend 42 -30m # logged too much, take 30m back`,
1508
+ tracker spend 42 -30m # logged too much, take 30m back
1509
+ tracker spend 42 --since-claim`,
1483
1510
  estimate: `usage: tracker estimate <id> <duration>
1484
1511
 
1485
1512
  Sets the item's time estimate (same duration format as spend). 0 clears it.
@@ -1794,9 +1821,21 @@ async function cmdSpend(ctx, args) {
1794
1821
  requireTimeTracking(ctx);
1795
1822
  const [idArg, durationArg] = args.positionals;
1796
1823
  const id = normalizeId(idArg);
1797
- if (!durationArg)
1824
+ const sinceClaim = args.flags.get("--since-claim") === true;
1825
+ if (sinceClaim && durationArg) {
1826
+ throw new UsageError("pass either a duration or --since-claim, not both");
1827
+ }
1828
+ if (!sinceClaim && !durationArg)
1798
1829
  throw new UsageError(commandHelp("spend"));
1799
- const seconds = parseDuration(durationArg);
1830
+ let seconds;
1831
+ if (sinceClaim) {
1832
+ const elapsed = secondsSinceClaim(await ctx.adapter.listComments(id), Date.now());
1833
+ if (elapsed === null)
1834
+ throw new DomainError(`#${id} has no claim note to measure from`);
1835
+ seconds = Math.max(60, Math.round(elapsed / 60) * 60);
1836
+ } else {
1837
+ seconds = parseDuration(durationArg);
1838
+ }
1800
1839
  if (seconds === 0)
1801
1840
  throw new UsageError("spend needs a non-zero duration (e.g. 1h30m or -30m)");
1802
1841
  await ctx.adapter.addTimeSpent(id, seconds);
@@ -1826,6 +1865,25 @@ async function cmdComment(ctx, args) {
1826
1865
  await ctx.adapter.comment(id, body);
1827
1866
  console.log(`commented on #${id}`);
1828
1867
  }
1868
+ async function cmdLabel(ctx, args) {
1869
+ const id = normalizeId(args.positionals[0]);
1870
+ const csv = (name) => (str(args, name) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
1871
+ const addLabels = csv("--add");
1872
+ const removeLabels = csv("--remove");
1873
+ if (addLabels.length === 0 && removeLabels.length === 0) {
1874
+ throw new UsageError(commandHelp("label"));
1875
+ }
1876
+ await ctx.adapter.update(id, {
1877
+ ...addLabels.length ? { addLabels } : {},
1878
+ ...removeLabels.length ? { removeLabels } : {}
1879
+ });
1880
+ invalidate(ctx);
1881
+ const parts = [
1882
+ ...addLabels.length ? [`+${addLabels.join(" +")}`] : [],
1883
+ ...removeLabels.length ? [`-${removeLabels.join(" -")}`] : []
1884
+ ];
1885
+ console.log(`#${id} labels: ${parts.join(" ")}`);
1886
+ }
1829
1887
  async function cmdAttach(ctx, args) {
1830
1888
  const [idArg, ...paths] = args.positionals;
1831
1889
  const id = normalizeId(idArg);
@@ -2164,6 +2222,7 @@ var VALUE_FLAGS = {
2164
2222
  comment: {},
2165
2223
  comments: { "--json": "bool" },
2166
2224
  attach: { "--message": "value", "--json": "bool" },
2225
+ label: { "--add": "value", "--remove": "value" },
2167
2226
  pr: {
2168
2227
  "--title": "value",
2169
2228
  "--description": "value",
@@ -2175,7 +2234,7 @@ var VALUE_FLAGS = {
2175
2234
  "--message": "value",
2176
2235
  "--json": "bool"
2177
2236
  },
2178
- spend: {},
2237
+ spend: { "--since-claim": "bool" },
2179
2238
  estimate: {},
2180
2239
  sync: {},
2181
2240
  claim: {},
@@ -2240,6 +2299,7 @@ ${HELP}`);
2240
2299
  comment: cmdComment,
2241
2300
  comments: cmdComments,
2242
2301
  attach: cmdAttach,
2302
+ label: cmdLabel,
2243
2303
  pr: cmdPr,
2244
2304
  spend: cmdSpend,
2245
2305
  estimate: cmdEstimate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackerctl",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Multi-backend issue-tracking CLI for humans and AI agents — claim races, dependencies, hierarchy, local FTS search, project memory. GitLab adapter first.",
5
5
  "license": "MIT",
6
6
  "author": "refo",