tracerkit 1.7.0 → 1.8.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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  Replace ad-hoc AI prompts with a repeatable spec-driven workflow: from idea to verified, archived code.
12
12
 
13
- Named after the tracer-bullet technique from _The Pragmatic Programmer_ **Tracer** + **Kit**.
13
+ Named after the tracer-bullet technique from _The Pragmatic Programmer_: **Tracer** + **Kit**.
14
14
 
15
15
  **Zero runtime dependencies.** Pure Markdown skills, no build step.
16
16
 
@@ -18,7 +18,7 @@ Named after the tracer-bullet technique from _The Pragmatic Programmer_ — **Tr
18
18
 
19
19
  ## Why TracerKit?
20
20
 
21
- Without specs, every AI session starts from scratch vague prompts, duplicated context, no way to confirm "done." Most planning tools produce flat task lists where nothing works until everything is done.
21
+ Without specs, every AI session starts from scratch. Vague prompts, duplicated context, no way to confirm "done." Most planning tools produce flat task lists where nothing works until everything is done.
22
22
 
23
23
  TracerKit takes a different approach: **tracer-bullet vertical slices**. Each phase cuts through every layer (schema → service → API → UI → tests), so every phase is demoable on its own. Integration problems surface early, context stays focused, and AI assistants get small, well-scoped phases instead of sprawling layers.
24
24
 
@@ -36,17 +36,19 @@ Skills are installed globally to `~/.claude/skills/`, available in every project
36
36
 
37
37
  ```
38
38
  You: /tk:prd add dark mode support
39
- AI: Written .tracerkit/prds/dark-mode-support.md
39
+ AI: Written .tracerkit/prds/dark-mode-support.md
40
+ Run `/tk:plan dark-mode-support` next?
40
41
 
41
42
  You: /tk:plan dark-mode-support
42
- AI: Phase 1 — CSS variables + ThemeProvider
43
- Phase 2 — Toggle component + localStorage
44
- Written .tracerkit/plans/dark-mode-support.md
43
+ AI: Phase 1 — Theme visible end-to-end
44
+ Phase 2 — User can toggle and persist preference
45
+ Written .tracerkit/plans/dark-mode-support.md
46
+ Run `/tk:check dark-mode-support` when ready?
45
47
 
46
- You: # implement each phase...
48
+ You: # open the plan, implement each phase, write tests...
47
49
 
48
50
  You: /tk:check dark-mode-support
49
- AI: All checks verified — status: done
51
+ AI: Status: done | Total: 5/5
50
52
  Archived to .tracerkit/archives/dark-mode-support/
51
53
  ```
52
54
 
@@ -97,7 +99,7 @@ Without arguments, shows a feature dashboard with status and progress before ask
97
99
  | [CLI Reference](docs/cli-reference.md) | Browse all CLI commands and flags |
98
100
  | [Configuration](docs/configuration.md) | Configure custom artifact paths via `config.json` |
99
101
  | [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions |
100
- | [Compared to](docs/compared-to.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec |
102
+ | [Comparison](docs/comparison.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec |
101
103
 
102
104
  ## Contributing
103
105
 
package/dist/bin.js CHANGED
@@ -1,16 +1,28 @@
1
1
  #!/usr/bin/env node
2
- import { n as e, o as t, r as n, t as r } from "./uninstall-Dri3LFXP.js";
3
- import { existsSync as i, mkdirSync as a, readFileSync as o, rmSync as s, unlinkSync as c, writeFileSync as l } from "node:fs";
4
- import { dirname as u, join as d, resolve as f } from "node:path";
5
- import { fileURLToPath as p } from "node:url";
6
- import { homedir as m } from "node:os";
2
+ import { i as e, n as t, r as n, s as r, t as i } from "./uninstall-DO2YpTSz.js";
3
+ import { existsSync as a, mkdirSync as o, readFileSync as s, readdirSync as c, rmSync as l, unlinkSync as u, writeFileSync as d } from "node:fs";
4
+ import { dirname as f, join as p, resolve as m } from "node:path";
5
+ import { fileURLToPath as h } from "node:url";
6
+ import { homedir as g } from "node:os";
7
7
  //#region src/frontmatter.ts
8
- var h = /^---\n([\s\S]*?)\n---(?:\n|$)/;
9
- function g(e) {
8
+ var _ = /^---\n([\s\S]*?)\n---(?:\n|$)/;
9
+ function v(e) {
10
10
  return e.replace(/\r\n/g, "\n");
11
11
  }
12
- function _(e, t, n) {
13
- let r = g(e), i = r.match(h);
12
+ function y(e) {
13
+ let t = v(e).match(_);
14
+ if (!t) return {};
15
+ let n = {};
16
+ for (let e of t[1].split("\n")) {
17
+ let t = e.indexOf(":");
18
+ if (t === -1) continue;
19
+ let r = e.slice(0, t).trim(), i = e.slice(t + 1).trim();
20
+ r && (n[r] = i);
21
+ }
22
+ return n;
23
+ }
24
+ function b(e, t, n) {
25
+ let r = v(e), i = r.match(_);
14
26
  if (!i) return `---\n${t}: ${n}\n---\n${r}`;
15
27
  let a = i[1].split("\n"), o = RegExp(`^${t}\\s*:`), s = a.findIndex((e) => o.test(e));
16
28
  s === -1 ? a.push(`${t}: ${n}`) : a[s] = `${t}: ${n}`;
@@ -19,21 +31,21 @@ function _(e, t, n) {
19
31
  }
20
32
  //#endregion
21
33
  //#region src/commands/archive.ts
22
- function v(e, n) {
23
- let r = t(e), u = d(e, r.paths.prds, `${n}.md`), f = d(e, r.paths.plans, `${n}.md`), p = d(e, r.paths.archives, n), m = i(u);
24
- if (!i(f)) throw Error(`Plan "${n}" not found at ${f}`);
25
- if (i(p)) throw Error(`Archive "${n}" already exists at ${p}`);
26
- a(p, { recursive: !0 });
34
+ function x(e, t) {
35
+ let n = r(e), i = p(e, n.paths.prds, `${t}.md`), c = p(e, n.paths.plans, `${t}.md`), f = p(e, n.paths.archives, t), m = a(i);
36
+ if (!a(c)) throw Error(`Plan "${t}" not found at ${c}`);
37
+ if (a(f)) throw Error(`Archive "${t}" already exists at ${f}`);
38
+ o(f, { recursive: !0 });
27
39
  try {
28
- let e = (/* @__PURE__ */ new Date()).toISOString(), t = [];
40
+ let e = (/* @__PURE__ */ new Date()).toISOString(), r = [];
29
41
  if (m) {
30
- let t = o(u, "utf8");
31
- t = _(t, "status", "done"), t = _(t, "completed", e), l(d(p, "prd.md"), t);
32
- } else t.push(`Warning: PRD "${n}" missing, archiving plan only`);
33
- let i = o(f, "utf8");
34
- return i += `\n## Archived\n\nArchived on ${e.slice(0, 10)}.\n`, l(d(p, "plan.md"), i), m && c(u), c(f), t.push(`Archived "${n}" to ${r.paths.archives}/${n}/`), m && t.push(` prd.md — status: done, completed: ${e}`), t.push(" plan.md — archived block appended"), t;
42
+ let t = s(i, "utf8");
43
+ t = b(t, "status", "done"), t = b(t, "completed", e), d(p(f, "prd.md"), t);
44
+ } else r.push(`Warning: PRD "${t}" missing, archiving plan only`);
45
+ let a = s(c, "utf8");
46
+ return a += `\n## Archived\n\nArchived on ${e.slice(0, 10)}.\n`, d(p(f, "plan.md"), a), m && u(i), u(c), r.push(`Archived "${t}" to ${n.paths.archives}/${t}/`), m && r.push(` prd.md — status: done, completed: ${e}`), r.push(" plan.md — archived block appended"), r;
35
47
  } catch (e) {
36
- throw s(p, {
48
+ throw l(f, {
37
49
  recursive: !0,
38
50
  force: !0
39
51
  }), e;
@@ -41,11 +53,11 @@ function v(e, n) {
41
53
  }
42
54
  //#endregion
43
55
  //#region src/plan.ts
44
- var y = /^## (Phase \d+\s*.*)$/, b = /^- \[x\] /i, x = /^- \[ \] /;
45
- function S(e) {
56
+ var S = /^## (Phase \d+\s*.*)$/, C = /^- \[x\] /i, w = /^- \[ \] /;
57
+ function T(e) {
46
58
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = null;
47
59
  for (let e of t) {
48
- let t = e.trimStart(), i = t.match(y);
60
+ let t = e.trimStart(), i = t.match(S);
49
61
  if (t.startsWith("## ")) {
50
62
  i ? (r = {
51
63
  title: i[1].trim(),
@@ -54,32 +66,80 @@ function S(e) {
54
66
  }, n.push(r)) : r = null;
55
67
  continue;
56
68
  }
57
- r && (b.test(t) ? (r.checked++, r.total++) : x.test(t) && r.total++);
69
+ r && (C.test(t) ? (r.checked++, r.total++) : w.test(t) && r.total++);
58
70
  }
59
71
  return { phases: n };
60
72
  }
61
73
  //#endregion
74
+ //#region src/commands/brief.ts
75
+ var E = /^- \[ \] (.+)/;
76
+ function D(e, t) {
77
+ let n = new Date(e);
78
+ if (isNaN(n.getTime())) return "";
79
+ let r = Math.floor((t.getTime() - n.getTime()) / 864e5);
80
+ return r < 0 ? "" : r < 7 ? `${r}d` : r < 30 ? `${Math.floor(r / 7)}w` : `${Math.floor(r / 30)}mo`;
81
+ }
82
+ function O(e) {
83
+ for (let t of e.split("\n")) {
84
+ let e = t.trimStart().match(E);
85
+ if (e) return e[1].replace(/\s*\[.*?\]\s*$/, "").trim();
86
+ }
87
+ return "—";
88
+ }
89
+ function k(e, t = /* @__PURE__ */ new Date()) {
90
+ let n = r(e), i = p(e, n.paths.prds);
91
+ if (!a(i)) return ["No features found — run `/tk:prd` to start one."];
92
+ let o = c(i).filter((e) => e.endsWith(".md")).sort();
93
+ if (o.length === 0) return ["No features found — run `/tk:prd` to start one."];
94
+ let l = p(e, n.paths.plans), u = [];
95
+ for (let e of o) {
96
+ let t = e.replace(/\.md$/, ""), n = y(s(p(i, e), "utf8"));
97
+ if (n.status === "done") continue;
98
+ let r = n.status || "unknown", o = n.created || "", c = o && !isNaN(new Date(o).getTime()) ? o : "", d = "—", f = "—", m = p(l, `${t}.md`);
99
+ if (a(m)) {
100
+ let e = s(m, "utf8"), { phases: t } = T(e), n = t.reduce((e, t) => e + t.checked, 0), r = t.reduce((e, t) => e + t.total, 0);
101
+ r > 0 && (d = `${n}/${r}`), f = O(e);
102
+ }
103
+ u.push({
104
+ slug: t,
105
+ status: r,
106
+ created: c,
107
+ progress: d,
108
+ next: f
109
+ });
110
+ }
111
+ if (u.length === 0) return ["No features found — run `/tk:prd` to start one."];
112
+ u.sort((e, t) => e.created && t.created ? new Date(e.created).getTime() - new Date(t.created).getTime() : e.created ? -1 : t.created ? 1 : 0);
113
+ let d = u.map((e) => {
114
+ let n = e.created ? D(e.created, t) : "";
115
+ return `| ${e.slug} | ${e.status} | ${n} | ${e.progress} | ${e.next} |`;
116
+ }), f = u.filter((e) => e.status === "in_progress"), m = f.length === 1 ? f[0] : f[0] ?? u[0];
117
+ return [
118
+ "| Feature | Status | Age | Progress | Next |",
119
+ "|---------|--------|-----|----------|------|",
120
+ ...d,
121
+ "",
122
+ `**Focus → ${m.slug}**`
123
+ ];
124
+ }
125
+ //#endregion
62
126
  //#region src/commands/progress.ts
63
- function C(e, n) {
64
- let r = d(e, t(e).paths.plans, `${n}.md`);
65
- if (!i(r)) throw Error(`Plan "${n}" not found at ${r}`);
66
- let { phases: a } = S(o(r, "utf8"));
67
- if (a.length === 0) return ["No phases found in plan.", "Total: 0/0"];
68
- let s = [], c = 0, l = 0;
69
- for (let e of a) c += e.checked, l += e.total, s.push(` ${e.title}: ${e.checked}/${e.total}`);
70
- return s.push(""), s.push(`Total: ${c}/${l}`), s;
127
+ function A(e, t) {
128
+ let n = p(e, r(e).paths.plans, `${t}.md`);
129
+ if (!a(n)) throw Error(`Plan "${t}" not found at ${n}`);
130
+ let { phases: i } = T(s(n, "utf8"));
131
+ if (i.length === 0) return ["No phases found in plan.", "Total: 0/0"];
132
+ let o = [], c = 0, l = 0;
133
+ for (let e of i) c += e.checked, l += e.total, o.push(` ${e.title}: ${e.checked}/${e.total}`);
134
+ return o.push(""), o.push(`Total: ${c}/${l}`), o;
71
135
  }
72
136
  //#endregion
73
137
  //#region src/cli.ts
74
- var { version: w } = JSON.parse(o(f(u(p(import.meta.url)), "..", "package.json"), "utf8")), T = [
138
+ var { version: j } = JSON.parse(s(m(f(h(import.meta.url)), "..", "package.json"), "utf8")), M = Math.max(...e.map((e) => `${e.name} ${e.args}`.length)), N = [
75
139
  "Usage: tracerkit <command> [path]",
76
140
  "",
77
141
  "Commands:",
78
- " init [path] Install skills to ~/.claude/skills/ (or [path] if given)",
79
- " update [path] Refresh unchanged files from latest version, skip modified",
80
- " uninstall [path] Remove TracerKit skill directories, keep .tracerkit/ artifacts",
81
- " progress <slug> Show per-phase checkbox progress for a plan",
82
- " archive <slug> Archive a completed feature (PRD + plan)",
142
+ ...e.map((e) => ` ${`${e.name} ${e.args}`.padEnd(M + 2)}${e.desc}`),
83
143
  "",
84
144
  "Options:",
85
145
  " --force Overwrite modified files during update",
@@ -88,42 +148,43 @@ var { version: w } = JSON.parse(o(f(u(p(import.meta.url)), "..", "package.json")
88
148
  "",
89
149
  "All commands default to the home directory when no path is given."
90
150
  ];
91
- function E(e, t = m()) {
151
+ function P(e, t = g()) {
92
152
  let n = e.find((e) => !e.startsWith("-"));
93
- return n ? f(n) : t;
153
+ return n ? m(n) : t;
94
154
  }
95
- function D(e, t) {
155
+ function F(e, t) {
96
156
  let n = e.findIndex((e) => !e.startsWith("-"));
97
157
  if (n === -1) return [
98
158
  "Error: missing <slug> argument",
99
159
  "",
100
- ...T
160
+ ...N
101
161
  ];
102
162
  let r = e[n], i = e.filter((e, t) => t !== n);
103
163
  try {
104
- return t(E(i, process.cwd()), r);
164
+ return t(P(i, process.cwd()), r);
105
165
  } catch (e) {
106
166
  return [`Error: ${e instanceof Error ? e.message : String(e)}`];
107
167
  }
108
168
  }
109
- function O(t) {
110
- if (t.includes("--help") || t.includes("-h")) return T;
111
- if (t.includes("--version") || t.includes("-v")) return [`tracerkit/${w}`];
112
- let i = t[0], a = t.slice(1);
113
- switch (i) {
114
- case "init": return n(E(a));
169
+ function I(e) {
170
+ if (e.includes("--help") || e.includes("-h")) return N;
171
+ if (e.includes("--version") || e.includes("-v")) return [`tracerkit/${j}`];
172
+ let r = e[0], a = e.slice(1);
173
+ switch (r) {
174
+ case "brief": return k(P(a, process.cwd()));
175
+ case "init": return n(P(a));
115
176
  case "update": {
116
- let t = a.includes("--force"), n = e(E(a.filter((e) => e !== "--force")), { force: t });
177
+ let e = a.includes("--force"), n = t(P(a.filter((e) => e !== "--force")), { force: e });
117
178
  return n.push("", "Updated to the latest TracerKit."), n.push("If using Claude Code, restart your session to load changes."), n;
118
179
  }
119
- case "uninstall": return r(E(a));
120
- case "progress": return D(a, C);
121
- case "archive": return D(a, v);
122
- default: return T;
180
+ case "uninstall": return i(P(a));
181
+ case "progress": return F(a, A);
182
+ case "archive": return F(a, x);
183
+ default: return N;
123
184
  }
124
185
  }
125
186
  //#endregion
126
187
  //#region src/bin.ts
127
- var k = O(process.argv.slice(2));
128
- for (let e of k) console.log(e);
188
+ var L = I(process.argv.slice(2));
189
+ for (let e of L) console.log(e);
129
190
  //#endregion
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./uninstall-Dri3LFXP.js";
2
- export { t as DEPRECATED_SKILLS, e as SKILL_NAMES, r as init, i as uninstall, n as update };
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "./uninstall-DO2YpTSz.js";
2
+ export { t as COMMANDS, e as DEPRECATED_SKILLS, r as SKILL_NAMES, i as init, a as uninstall, n as update };
@@ -25,40 +25,72 @@ function d(t) {
25
25
  } };
26
26
  }
27
27
  var f = [
28
+ "tk:brief",
28
29
  "tk:prd",
29
30
  "tk:plan",
30
31
  "tk:check"
31
- ], p = ["tk:verify"], m = s(o(l(import.meta.url)), "..", "templates");
32
- function h(e, t = "") {
32
+ ], p = ["tk:verify"], m = [
33
+ {
34
+ name: "init",
35
+ args: "[path]",
36
+ desc: "Install skills to ~/.claude/skills/ (or [path] if given)"
37
+ },
38
+ {
39
+ name: "update",
40
+ args: "[path]",
41
+ desc: "Refresh unchanged files from latest version, skip modified"
42
+ },
43
+ {
44
+ name: "uninstall",
45
+ args: "[path]",
46
+ desc: "Remove TracerKit skill directories, keep .tracerkit/ artifacts"
47
+ },
48
+ {
49
+ name: "brief",
50
+ args: "[path]",
51
+ desc: "Show active features, progress, and suggested focus"
52
+ },
53
+ {
54
+ name: "progress",
55
+ args: "<slug>",
56
+ desc: "Show per-phase checkbox progress for a plan"
57
+ },
58
+ {
59
+ name: "archive",
60
+ args: "<slug>",
61
+ desc: "Archive a completed feature (PRD + plan)"
62
+ }
63
+ ], h = s(o(l(import.meta.url)), "..", "templates");
64
+ function g(e, t = "") {
33
65
  let n = r(e, { withFileTypes: !0 }), i = [];
34
66
  for (let r of n) {
35
67
  let n = t ? `${t}/${r.name}` : r.name;
36
- r.isDirectory() ? i.push(...h(s(e, r.name), n)) : i.push(n);
68
+ r.isDirectory() ? i.push(...g(s(e, r.name), n)) : i.push(n);
37
69
  }
38
70
  return i.sort();
39
71
  }
40
- function g(e, t) {
72
+ function _(e, t) {
41
73
  return e.replaceAll("{{paths.prds}}", t.paths.prds).replaceAll("{{paths.plans}}", t.paths.plans).replaceAll("{{paths.archives}}", t.paths.archives);
42
74
  }
43
- function _(e, r, i) {
44
- let c = i ?? h(m);
75
+ function v(e, r, i) {
76
+ let c = i ?? g(h);
45
77
  for (let i of c) {
46
- let c = s(m, i), l = s(e, i);
47
- t(o(l), { recursive: !0 }), a(l, g(n(c, "utf8"), r));
78
+ let c = s(h, i), l = s(e, i);
79
+ t(o(l), { recursive: !0 }), a(l, _(n(c, "utf8"), r));
48
80
  }
49
81
  return { copied: c };
50
82
  }
51
- function v(e) {
83
+ function y(e) {
52
84
  return c("sha256").update(e).digest("hex");
53
85
  }
54
- function y(t, r) {
55
- let i = h(m), a = [], o = [], c = [];
86
+ function b(t, r) {
87
+ let i = g(h), a = [], o = [], c = [];
56
88
  for (let l of i) {
57
89
  let i = s(t, l);
58
90
  if (!e(i)) c.push(l);
59
91
  else {
60
- let e = g(n(s(m, l), "utf8"), r);
61
- v(Buffer.from(e)) === v(n(i)) ? a.push(l) : o.push(l);
92
+ let e = _(n(s(h, l), "utf8"), r);
93
+ y(Buffer.from(e)) === y(n(i)) ? a.push(l) : o.push(l);
62
94
  }
63
95
  }
64
96
  return {
@@ -69,16 +101,16 @@ function y(t, r) {
69
101
  }
70
102
  //#endregion
71
103
  //#region src/commands/init.ts
72
- function b(t) {
104
+ function x(t) {
73
105
  for (let n of f) if (e(s(t, ".claude", "skills", n))) throw Error(`.claude/skills/${n}/ already exists — aborting`);
74
- let { copied: n } = _(t, d(t));
106
+ let { copied: n } = v(t, d(t));
75
107
  return n.map((e) => `✓ ${e}`);
76
108
  }
77
109
  //#endregion
78
110
  //#region src/commands/update.ts
79
- function x(t, n) {
111
+ function S(t, n) {
80
112
  if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — run `tracerkit init` first");
81
- let r = d(t), { unchanged: a, modified: o, missing: c } = y(t, r), l = [];
113
+ let r = d(t), { unchanged: a, modified: o, missing: c } = b(t, r), l = [];
82
114
  for (let n of p) {
83
115
  let r = s(t, ".claude", "skills", n);
84
116
  e(r) && (i(r, {
@@ -92,7 +124,7 @@ function x(t, n) {
92
124
  ...u ? o : []
93
125
  ];
94
126
  if (m.length > 0) {
95
- _(t, r, m);
127
+ v(t, r, m);
96
128
  for (let e of a) l.push(`✓ ${e}`);
97
129
  for (let e of c) l.push(`✓ ${e} (added)`);
98
130
  if (u) for (let e of o) l.push(`✓ ${e} (replaced)`);
@@ -105,7 +137,7 @@ function x(t, n) {
105
137
  }
106
138
  //#endregion
107
139
  //#region src/commands/uninstall.ts
108
- function S(t) {
140
+ function C(t) {
109
141
  if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
110
142
  let n = [];
111
143
  for (let r of f) {
@@ -118,4 +150,4 @@ function S(t) {
118
150
  return n;
119
151
  }
120
152
  //#endregion
121
- export { f as a, p as i, x as n, d as o, b as r, S as t };
153
+ export { p as a, m as i, S as n, f as o, x as r, d as s, C as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracerkit",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Spec-driven workflow for Claude Code: replace ad-hoc prompts with PRD → plan → verify.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Session briefing — shows active features, progress, and suggested focus. Use at the start of a session to orient.
3
+ ---
4
+
5
+ # Session Briefing
6
+
7
+ Get a quick overview of all active features, their progress, and what to focus on next.
8
+
9
+ ## Pre-loaded context
10
+
11
+ - Briefing: !`npx tracerkit brief 2>/dev/null || echo "no {{paths.prds}}/ directory found"`
12
+
13
+ ## Workflow
14
+
15
+ ### 1. Present the briefing
16
+
17
+ Display the output above as-is. The table includes:
18
+
19
+ - **Feature**: slug (PRD filename without `.md`)
20
+ - **Status**: from PRD frontmatter (`created`, `in_progress`) — `unknown` if no frontmatter
21
+ - **Age**: time since `created` date
22
+ - **Progress**: checked/total from plan checkboxes, or `—` if no plan
23
+ - **Next**: first unchecked item from the plan, or `—`
24
+
25
+ Features with `status: done` are excluded (already archived).
26
+
27
+ ### 2. Focus recommendation
28
+
29
+ The **Focus** line at the bottom suggests which feature to work on:
30
+
31
+ - If exactly 1 feature is `in_progress`, it's auto-selected
32
+ - Otherwise, the oldest feature by `created` date is selected
33
+
34
+ ### 3. Offer next steps
35
+
36
+ Ask the user what they'd like to do:
37
+
38
+ - Continue the focused feature (read its plan at `{{paths.plans}}/<slug>.md`)
39
+ - Start a new feature with `/tk:prd`
40
+ - Check progress on a feature with `/tk:check <slug>`
@@ -47,7 +47,7 @@ Before launching a subagent, check whether the primary module file(s) from Phase
47
47
 
48
48
  ### 3b. Launch read-only review
49
49
 
50
- Use a **read-only subagent** (no file writes, no edits) to:
50
+ Use a **general-purpose subagent** (not `code-review` — that agent is for PR reviews). The subagent must be **read-only** (no file writes, no edits). It should:
51
51
 
52
52
  1. Read every section of the plan — architectural decisions, each phase, done-when checkboxes
53
53
  2. For each phase, check every `- [ ]` / `- [x]` item against the codebase