← back to Ticket System
ticket ids: 5-digit zero-padded + title slug (TK-00025-style); resolveId accepts bare number, TK-n, or full id; legacy ids resolve
a54785050f068509e8f60076692cb2d64f862056 · 2026-07-26 15:34:39 -0700 · Steve Abrams
Files touched
Diff
commit a54785050f068509e8f60076692cb2d64f862056
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 15:34:39 2026 -0700
ticket ids: 5-digit zero-padded + title slug (TK-00025-style); resolveId accepts bare number, TK-n, or full id; legacy ids resolve
---
lib.js | 32 ++++++++++++++++++++++++++++----
tk | 10 +++++++---
2 files changed, 35 insertions(+), 7 deletions(-)
diff --git a/lib.js b/lib.js
index 9367cd4..e10e777 100644
--- a/lib.js
+++ b/lib.js
@@ -53,10 +53,34 @@ function tickets() {
return map;
}
-function nextId() {
+// Ticket ids are 5-digit zero-padded + a slug of the title (Steve, 2026-07-26):
+// TK-00025-ga4-attribution-prep
+// The numeric part is the tracking key; legacy short ids (TK-3, TK-17) remain
+// valid in the log and resolvable by number.
+function slugify(title) {
+ return String(title || '').toLowerCase()
+ .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40).replace(/-+$/, '');
+}
+function idNum(id) { return parseInt(String(id).replace(/^TK-/i, ''), 10); } // parseInt stops at the slug dash
+function nextId(title) {
let max = 0;
- for (const ev of readEvents()) if (ev.type === 'create') { const n = parseInt(String(ev.id).replace('TK-', ''), 10); if (n > max) max = n; }
- return 'TK-' + (max + 1);
+ for (const ev of readEvents()) if (ev.type === 'create') { const n = idNum(ev.id); if (n > max) max = n; }
+ const num = String(max + 1).padStart(5, '0');
+ const slug = slugify(title);
+ return 'TK-' + num + (slug ? '-' + slug : '');
+}
+// Resolve any reference — full id, TK-24, 24, 00024, or 00024-partial-slug —
+// to the canonical stored id (or null if nothing matches).
+function resolveId(ref, map) {
+ if (!ref) return null;
+ const m = map || tickets();
+ let r = String(ref).toUpperCase();
+ if (!r.startsWith('TK-')) r = 'TK-' + r;
+ for (const id of m.keys()) if (id.toUpperCase() === r) return id;
+ const n = idNum(r);
+ if (!Number.isFinite(n)) return null;
+ for (const id of m.keys()) if (idNum(id) === n) return id;
+ return null;
}
-module.exports = { withLock, append, tickets, nextId, STATUSES, EVENTS };
+module.exports = { withLock, append, tickets, nextId, resolveId, idNum, slugify, STATUSES, EVENTS };
diff --git a/tk b/tk
index 89e3015..8d87d53 100755
--- a/tk
+++ b/tk
@@ -1,5 +1,7 @@
#!/opt/homebrew/bin/node
// tk — fleet ticket CLI. Every agent action ties to a ticket; comments/notes are the exchange channel.
+// Ids are TK-<5-digit>-<name-slug> (e.g. TK-00025-ga4-attribution-prep); reference
+// tickets by any form — 25, TK-25, 00025, or the full id. Legacy TK-3-style ids resolve too.
// Usage:
// tk new "title" [-p project] [-a agent] [-b "opening comment"]
// tk comment TK-3 "text" [-a agent] # discussion comment
@@ -14,7 +16,7 @@
// tk list [--status s] [--project p] [--agent a] [--all]
// tk show TK-3
// Agent identity defaults to $TK_AGENT, else "claude@<tty-or-pid>".
-const { withLock, append, tickets, nextId, STATUSES } = require(require('path').join(__dirname, 'lib.js'));
+const { withLock, append, tickets, nextId, resolveId, STATUSES } = require(require('path').join(__dirname, 'lib.js'));
const argv = process.argv.slice(2);
const cmd = argv.shift();
@@ -24,14 +26,16 @@ const agentOpt = opt('-a') || opt('--agent-id');
const agent = agentOpt || process.env.TK_AGENT || ('claude@' + (process.env.TERM_SESSION_ID || process.pid));
const ts = new Date().toISOString();
const die = m => { console.error(m); process.exit(1); };
-const norm = id => { if (!id) die('missing ticket id'); id = id.toUpperCase(); return id.startsWith('TK-') ? id : 'TK-' + id; };
+// Resolve any reference (24, TK-24, 00024, or the full 5-digit+slug id) to the
+// canonical stored id — legacy short ids and new TK-00024-slug ids both work.
+const norm = ref => { if (!ref) die('missing ticket id'); const id = resolveId(ref); if (!id) die('no such ticket ' + ref); return id; };
const fmt = t => `${t.id} [${t.status}] (${t.assignee || 'unassigned'})${t.project ? ' {' + t.project + '}' : ''} ${t.title}`;
if (cmd === 'new') {
const project = opt('-p') || opt('--project') || '';
const body = opt('-b') || opt('--body') || '';
const title = argv.join(' ').trim(); if (!title) die('usage: tk new "title" [-p project] [-a agent]');
- const ev = withLock(() => append({ ts, type: 'create', id: nextId(), title, project, agent, body }));
+ const ev = withLock(() => append({ ts, type: 'create', id: nextId(title), title, project, agent, body }));
console.log(ev.id);
} else if (cmd === 'comment' || cmd === 'note' || cmd === 'win' || cmd === 'challenge' || cmd === 'cody') {
const id = norm(argv.shift()); const text = argv.join(' ').trim(); if (!text) die('missing text');
← 0744d30 auto-save: 2026-07-26T14:44:02 (2 files) — server.js tk
·
back to Ticket System
·
agent-to-agent DMs: tk dm/inbox/reply/thread + @mention auto eac02d5 →