tracerkit 1.15.0 → 1.17.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
@@ -9,7 +9,7 @@
9
9
  [![npm downloads](https://img.shields.io/npm/dm/tracerkit)](https://www.npmjs.com/package/tracerkit)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
11
 
12
- Replace ad-hoc AI prompts with a repeatable spec-driven workflow: from idea to verified, archived spec.
12
+ Replace ad-hoc AI prompts with a repeatable spec-driven workflow: from idea to verified spec.
13
13
 
14
14
  Named after the tracer-bullet technique from _The Pragmatic Programmer_: **Tracer** + **Kit**.
15
15
 
@@ -19,9 +19,9 @@ Named after the tracer-bullet technique from _The Pragmatic Programmer_: **Trace
19
19
 
20
20
  ## Why TracerKit?
21
21
 
22
- AI assistants work best with small, well-scoped tasks, not sprawling layers or flat task lists. TracerKit structures every feature as **tracer-bullet vertical slices**: each phase cuts through every layer (schema → service → API → UI → tests) and is demoable on its own. Integration problems surface early, not at the end.
22
+ AI assistants work best with small, well-scoped tasks not sprawling layers or flat task lists. TracerKit structures every feature as **tracer-bullet vertical slices**: each phase cuts through every layer (schema → service → API → UI → tests) and is demoable on its own. Integration problems surface early, not at the end.
23
23
 
24
- The workflow is three skills: **define** (`/tk:prd`), **plan** (`/tk:plan`), **verify** (`/tk:check`). Skills are pure Markdown with zero runtime deps. The AI reads your specs directly, counts progress, and archives completed work. No build step, no CLI at runtime.
24
+ Three skills drive the workflow: **define** (`/tk:prd`), **plan** (`/tk:plan`), **verify** (`/tk:check`). The AI reads your specs directly, counts progress, and marks completed work done. Pure Markdown, zero runtime deps.
25
25
 
26
26
  ## Get Started
27
27
 
@@ -75,10 +75,10 @@ You: # open the plan, implement each phase, write tests...
75
75
 
76
76
  You: /tk:check dark-mode-support
77
77
  AI: Status: done | Total: 5/5
78
- Archived to .tracerkit/archives/dark-mode-support/
78
+ Marked complete in .tracerkit/prds/dark-mode-support.md
79
79
  ```
80
80
 
81
- Use `/tk:brief` at the start of any session to see active features and pick up where you left off:
81
+ Between sessions, `/tk:brief` shows active features and picks up where you left off:
82
82
 
83
83
  ```
84
84
  You: /tk:brief
@@ -101,7 +101,7 @@ tracerkit config storage github # set current project to use GitHub
101
101
  tracerkit config github.repo org/repo # set target repo
102
102
  ```
103
103
 
104
- PRDs and plans become GitHub Issues with `tk:prd` and `tk:plan` labels. On `/tk:check` pass, issues are closed instead of archived locally. Each project can use a different backend; local is the default. See [Configuration](docs/configuration.md) for details.
104
+ PRDs and plans become GitHub Issues with `tk:prd` and `tk:plan` labels. On `/tk:check` pass, issues are closed with `completed` reason and any related PRs are linked automatically. Each project can use a different backend; local is the default. See [Configuration](docs/configuration.md) for details.
105
105
 
106
106
  To migrate existing artifacts between backends:
107
107
 
@@ -109,39 +109,18 @@ To migrate existing artifacts between backends:
109
109
  tracerkit migrate-storage # local→github or github→local (auto-detected)
110
110
  ```
111
111
 
112
- Direction is inferred from the current `storage` config. All artifacts are migrated, existing duplicates are skipped, and the config is flipped to the target backend. Source artifacts are left intact as backup. For archived features migrating to GitHub, merged PRs matching the slug are linked automatically.
112
+ Direction is inferred from the current `storage` config. All artifacts are migrated, existing duplicates are skipped, and the config is flipped to the target backend. Source artifacts are left intact as backup.
113
113
 
114
114
  </details>
115
115
 
116
116
  ## Skills
117
117
 
118
- TracerKit ships skills that take a feature from idea to verified archive.
119
-
120
- ### `/tk:prd <idea>`: Write a PRD
121
-
122
- Interactive interview that explores your codebase, asks scoping questions one at a time, designs deep modules, and writes a structured PRD.
123
-
124
- **Output:** `.tracerkit/prds/<slug>.md` (local) or GitHub Issue with `tk:prd` label
125
-
126
- ### `/tk:plan <slug>`: Create an implementation plan
127
-
128
- Reads a PRD and breaks it into phased **tracer-bullet vertical slices**. Each phase is a thin but complete path through every layer (schema, service, API, UI, tests), demoable on its own.
129
-
130
- **Output:** `.tracerkit/plans/<slug>.md` (local) or GitHub Issue with `tk:plan` label
131
-
132
- ### `/tk:brief`: Session briefing
133
-
134
- Shows active features, their progress, and suggested focus. Use at the start of a session to orient.
135
-
136
- **Output:** Feature dashboard in the terminal. No files written.
137
-
138
- ### `/tk:check [slug]`: Verify and archive
139
-
140
- Verifies the codebase against the plan's done-when checkboxes. Runs tests, validates user stories, updates phase progress, and transitions the PRD status. On `done`, archives the PRD and plan to `.tracerkit/archives/<slug>/` automatically.
141
-
142
- Without arguments, shows a feature dashboard with status and progress before asking which feature to check.
143
-
144
- **Output:** Verdict block appended to the plan. On `done`: archives to `.tracerkit/archives/<slug>/` (local) or closes both issues (GitHub).
118
+ | Skill | What it does | Output |
119
+ | ------------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
120
+ | `/tk:prd <idea>` | Interview codebase scan → structured PRD | `.tracerkit/prds/<slug>.md` or GitHub Issue |
121
+ | `/tk:plan <slug>` | PRD → phased vertical slices, each demoable on its own | `.tracerkit/plans/<slug>.md` or GitHub Issue |
122
+ | `/tk:brief` | Feature dashboard with progress and suggested focus | Terminal only, no files |
123
+ | `/tk:check [slug]` | Verify done-when checkboxes against codebase and tests | Verdict block in plan. On `done`: status updated (local) or issues closed + PRs linked (GitHub) |
145
124
 
146
125
  ## Docs
147
126
 
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as e, f as t, i as n, l as r, n as i, o as a, p as o, r as s, s as c, t as l, u } from "./uninstall-k_ZGY-Mk.js";
2
+ import { a as e, f as t, i as n, l as r, n as i, o as a, p as o, r as s, s as c, t as l, u } from "./uninstall-Ba_zfPOx.js";
3
3
  import { existsSync as d, readFileSync as f, statSync as p } from "node:fs";
4
4
  import { dirname as m, join as h, resolve as g } from "node:path";
5
5
  import { fileURLToPath as _ } from "node:url";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { c as e, i as t, n, o as r, r as i, t as a, u as o } from "./uninstall-k_ZGY-Mk.js";
1
+ import { c as e, i as t, n, o as r, r as i, t as a, u as o } from "./uninstall-Ba_zfPOx.js";
2
2
  export { r as COMMANDS, e as DEPRECATED_SKILLS, o as SKILL_NAMES, i as init, n as migrateStorage, a as uninstall, t as update };
@@ -6,8 +6,7 @@ import { execSync as d } from "node:child_process";
6
6
  //#region src/config.ts
7
7
  var f = "local", p = "github", m = [f, p], h = {
8
8
  prds: ".tracerkit/prds",
9
- plans: ".tracerkit/plans",
10
- archives: ".tracerkit/archives"
9
+ plans: ".tracerkit/plans"
11
10
  }, g = { labels: {
12
11
  prd: "tk:prd",
13
12
  plan: "tk:plan"
@@ -26,24 +25,23 @@ function _(t) {
26
25
  throw Error("Invalid .tracerkit/config.json — expected valid JSON");
27
26
  }
28
27
  return {
29
- storage: ee(i.storage),
30
- paths: v(i.paths),
31
- github: y(i.github)
28
+ storage: v(i.storage),
29
+ paths: y(i.paths),
30
+ github: b(i.github)
32
31
  };
33
32
  }
34
- function ee(e) {
33
+ function v(e) {
35
34
  return typeof e == "string" && m.includes(e) ? e : f;
36
35
  }
37
- function v(e) {
38
- let t = S(e) ? e : {};
36
+ function y(e) {
37
+ let t = C(e) ? e : {};
39
38
  return {
40
39
  prds: typeof t.prds == "string" ? t.prds : h.prds,
41
- plans: typeof t.plans == "string" ? t.plans : h.plans,
42
- archives: typeof t.archives == "string" ? t.archives : h.archives
40
+ plans: typeof t.plans == "string" ? t.plans : h.plans
43
41
  };
44
42
  }
45
- function y(e) {
46
- let t = S(e) ? e : {}, n = S(t.labels) ? t.labels : {};
43
+ function b(e) {
44
+ let t = C(e) ? e : {}, n = C(t.labels) ? t.labels : {};
47
45
  return {
48
46
  ...typeof t.repo == "string" ? { repo: t.repo } : {},
49
47
  labels: {
@@ -52,7 +50,7 @@ function y(e) {
52
50
  }
53
51
  };
54
52
  }
55
- function b(r, i) {
53
+ function x(r, i) {
56
54
  let o = c(r, ".tracerkit", "config.json");
57
55
  t(s(o), { recursive: !0 });
58
56
  let l = {};
@@ -61,31 +59,31 @@ function b(r, i) {
61
59
  } catch {
62
60
  l = {};
63
61
  }
64
- let u = x(l, i);
62
+ let u = S(l, i);
65
63
  a(o, JSON.stringify(u, null, 2) + "\n");
66
64
  }
67
- function x(e, t) {
65
+ function S(e, t) {
68
66
  let n = { ...e };
69
- for (let e of Object.keys(t)) S(n[e]) && S(t[e]) ? n[e] = x(n[e], t[e]) : n[e] = t[e];
67
+ for (let e of Object.keys(t)) C(n[e]) && C(t[e]) ? n[e] = S(n[e], t[e]) : n[e] = t[e];
70
68
  return n;
71
69
  }
72
- function S(e) {
70
+ function C(e) {
73
71
  return typeof e == "object" && !!e && !Array.isArray(e);
74
72
  }
75
- var C = [
73
+ var w = [
76
74
  "tk:brief",
77
75
  "tk:prd",
78
76
  "tk:plan",
79
77
  "tk:check"
80
- ], w = ["tk:verify"], T = {
78
+ ], T = ["tk:verify"], E = {
81
79
  force: "--force",
82
80
  help: "--help",
83
81
  version: "--version"
84
- }, E = [
82
+ }, D = [
85
83
  "brief",
86
84
  "progress",
87
85
  "archive"
88
- ], D = [
86
+ ], O = [
89
87
  {
90
88
  name: "init",
91
89
  args: "[path]",
@@ -111,44 +109,44 @@ var C = [
111
109
  args: "[path]",
112
110
  desc: "Migrate artifacts between local and GitHub storage backends"
113
111
  }
114
- ], O = c(s(u(import.meta.url)), "..", "skills");
115
- function k(e, t = "") {
112
+ ], k = c(s(u(import.meta.url)), "..", "skills");
113
+ function A(e, t = "") {
116
114
  let n = r(e, { withFileTypes: !0 }), i = [];
117
115
  for (let r of n) {
118
116
  let n = t ? `${t}/${r.name}` : r.name;
119
- r.isDirectory() ? i.push(...k(c(e, r.name), n)) : i.push(n);
117
+ r.isDirectory() ? i.push(...A(c(e, r.name), n)) : i.push(n);
120
118
  }
121
119
  return i.sort();
122
120
  }
123
- function A(e) {
121
+ function j(e) {
124
122
  return `.claude/skills/tk:${e}`;
125
123
  }
126
- function j(e) {
124
+ function M(e) {
127
125
  return e.slice(18);
128
126
  }
129
- function M(e, t) {
127
+ function N(e, t) {
130
128
  let n = e;
131
- return t.paths.prds !== h.prds && (n = n.replaceAll(h.prds, t.paths.prds)), t.paths.plans !== h.plans && (n = n.replaceAll(h.plans, t.paths.plans)), t.paths.archives !== h.archives && (n = n.replaceAll(h.archives, t.paths.archives)), t.github?.repo && (n = n.replaceAll("{{github.repo}}", t.github.repo)), t.github?.labels?.prd && (n = n.replaceAll("{{github.labels.prd}}", t.github.labels.prd)), t.github?.labels?.plan && (n = n.replaceAll("{{github.labels.plan}}", t.github.labels.plan)), n;
129
+ return t.paths.prds !== h.prds && (n = n.replaceAll(h.prds, t.paths.prds)), t.paths.plans !== h.plans && (n = n.replaceAll(h.plans, t.paths.plans)), t.github?.repo && (n = n.replaceAll("{{github.repo}}", t.github.repo)), t.github?.labels?.prd && (n = n.replaceAll("{{github.labels.prd}}", t.github.labels.prd)), t.github?.labels?.plan && (n = n.replaceAll("{{github.labels.plan}}", t.github.labels.plan)), n;
132
130
  }
133
- function N(e, r, i) {
134
- let o = i ?? k(O).map(A);
131
+ function P(e, r, i) {
132
+ let o = i ?? A(k).map(j);
135
133
  for (let i of o) {
136
- let o = c(O, j(i)), l = c(e, i);
137
- t(s(l), { recursive: !0 }), a(l, M(n(o, "utf8"), r));
134
+ let o = c(k, M(i)), l = c(e, i);
135
+ t(s(l), { recursive: !0 }), a(l, N(n(o, "utf8"), r));
138
136
  }
139
137
  return { copied: o };
140
138
  }
141
- function P(e) {
139
+ function F(e) {
142
140
  return l("sha256").update(e).digest("hex");
143
141
  }
144
- function F(t, r) {
145
- let i = k(O).map(A), a = [], o = [], s = [];
142
+ function I(t, r) {
143
+ let i = A(k).map(j), a = [], o = [], s = [];
146
144
  for (let l of i) {
147
145
  let i = c(t, l);
148
146
  if (!e(i)) s.push(l);
149
147
  else {
150
- let e = M(n(c(O, j(l)), "utf8"), r);
151
- P(Buffer.from(e)) === P(n(i)) ? a.push(l) : o.push(l);
148
+ let e = N(n(c(k, M(l)), "utf8"), r);
149
+ F(Buffer.from(e)) === F(n(i)) ? a.push(l) : o.push(l);
152
150
  }
153
151
  }
154
152
  return {
@@ -159,10 +157,10 @@ function F(t, r) {
159
157
  }
160
158
  //#endregion
161
159
  //#region src/commands/update.ts
162
- function I(t, n) {
163
- if (!C.some((n) => e(c(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — run `tracerkit init` first");
164
- let r = _(t), { unchanged: a, modified: o, missing: s } = F(t, r), l = [];
165
- for (let n of w) {
160
+ function L(t, n) {
161
+ if (!w.some((n) => e(c(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — run `tracerkit init` first");
162
+ let r = _(t), { unchanged: a, modified: o, missing: s } = I(t, r), l = [];
163
+ for (let n of T) {
166
164
  let r = c(t, ".claude", "skills", n);
167
165
  e(r) && (i(r, {
168
166
  recursive: !0,
@@ -175,7 +173,7 @@ function I(t, n) {
175
173
  ...u ? o : []
176
174
  ];
177
175
  if (d.length > 0) {
178
- N(t, r, d);
176
+ P(t, r, d);
179
177
  for (let e of a) l.push(`✓ ${e}`);
180
178
  for (let e of s) l.push(`✓ ${e} (added)`);
181
179
  if (u) for (let e of o) l.push(`✓ ${e} (replaced)`);
@@ -188,14 +186,14 @@ function I(t, n) {
188
186
  }
189
187
  //#endregion
190
188
  //#region src/commands/init.ts
191
- function te(t) {
192
- if (C.some((n) => e(c(t, ".claude", "skills", n)))) return I(t, { force: !1 });
193
- let { copied: n } = N(t, _(t));
189
+ function ee(t) {
190
+ if (w.some((n) => e(c(t, ".claude", "skills", n)))) return L(t, { force: !1 });
191
+ let { copied: n } = P(t, _(t));
194
192
  return n.map((e) => `✓ ${e}`);
195
193
  }
196
194
  //#endregion
197
195
  //#region src/commands/migrate-storage.ts
198
- var L = {
196
+ var te = {
199
197
  created: "tk:created",
200
198
  in_progress: "tk:in-progress",
201
199
  done: "tk:done"
@@ -227,7 +225,7 @@ function B(e) {
227
225
  return t.length === 0 ? "<!-- tk:metadata\n-->" : `<!-- tk:metadata\n${t.map(([e, t]) => `${e}: ${t}`).join("\n")}\n-->`;
228
226
  }
229
227
  function V(e) {
230
- return L[e] ?? "tk:created";
228
+ return te[e] ?? "tk:created";
231
229
  }
232
230
  function H(e) {
233
231
  let t = e.match(/<!--\s*tk:metadata\n([\s\S]*?)-->\n*([\s\S]*)$/);
@@ -269,66 +267,44 @@ function K(t, i) {
269
267
  type: "prd",
270
268
  metadata: r,
271
269
  body: i,
272
- title: G(i),
273
- archived: !1
270
+ title: G(i)
274
271
  });
275
272
  }
276
273
  let l = c(t, i.paths.plans);
277
274
  if (e(l)) for (let e of r(l)) {
278
275
  if (!e.endsWith(".md")) continue;
279
- let t = o(e, ".md"), r = n(c(l, e), "utf8");
276
+ let t = o(e, ".md"), r = n(c(l, e), "utf8"), { metadata: i, body: s } = z(r);
280
277
  a.push({
281
278
  slug: t,
282
279
  type: "plan",
283
- metadata: {},
284
- body: r,
285
- title: G(r),
286
- archived: !1
280
+ metadata: i,
281
+ body: s,
282
+ title: G(s || r)
287
283
  });
288
284
  }
289
- let u = c(t, i.paths.archives);
290
- if (e(u)) for (let t of r(u, { withFileTypes: !0 })) {
291
- if (!t.isDirectory()) continue;
292
- let r = t.name, i = c(u, r, "prd.md"), o = c(u, r, "plan.md");
293
- if (e(i)) {
294
- let { metadata: e, body: t } = z(n(i, "utf8"));
295
- a.push({
296
- slug: r,
297
- type: "prd",
298
- metadata: {
299
- ...e,
300
- status: "done"
301
- },
302
- body: t,
303
- title: G(t),
304
- archived: !0
305
- });
306
- }
307
- if (e(o)) {
308
- let e = n(o, "utf8");
309
- a.push({
310
- slug: r,
311
- type: "plan",
312
- metadata: { status: "done" },
313
- body: e,
314
- title: G(e),
315
- archived: !0
316
- });
317
- }
318
- }
319
285
  return a;
320
286
  }
321
287
  function q(e) {
322
- return d(`gh ${e.map((e) => `'${e.replace(/'/g, "'\\''")}'`).join(" ")}`, {
323
- encoding: "utf8",
324
- stdio: [
325
- "pipe",
326
- "pipe",
327
- "pipe"
328
- ]
329
- }).trim();
330
- }
331
- function J(e, t) {
288
+ let t = e;
289
+ if (t.code === "ENOENT") return /* @__PURE__ */ Error("gh CLI not found — install it: https://cli.github.com");
290
+ let n = (t.stderr ?? t.message ?? "").toLowerCase();
291
+ return n.includes("not logged in") || n.includes("authentication") ? /* @__PURE__ */ Error("Not authenticated with GitHub. Run: gh auth login") : n.includes("rate limit") || n.includes("403") ? /* @__PURE__ */ Error("GitHub rate limit exceeded. Wait and retry.") : n.includes("not found") || n.includes("404") ? /* @__PURE__ */ Error("Repository not found. Check github.repo in .tracerkit/config.json") : e instanceof Error ? e : Error(String(e));
292
+ }
293
+ function J(e) {
294
+ try {
295
+ return d(`gh ${e.map((e) => `'${e.replace(/'/g, "'\\''")}'`).join(" ")}`, {
296
+ encoding: "utf8",
297
+ stdio: [
298
+ "pipe",
299
+ "pipe",
300
+ "pipe"
301
+ ]
302
+ }).trim();
303
+ } catch (e) {
304
+ throw q(e);
305
+ }
306
+ }
307
+ function Y(e, t) {
332
308
  return e.github.repo ? e.github.repo : t([
333
309
  "repo",
334
310
  "view",
@@ -338,7 +314,7 @@ function J(e, t) {
338
314
  ".nameWithOwner"
339
315
  ]);
340
316
  }
341
- function Y(e, t, n) {
317
+ function X(e, t, n) {
342
318
  let r = n([
343
319
  "issue",
344
320
  "list",
@@ -355,10 +331,16 @@ function Y(e, t, n) {
355
331
  ]);
356
332
  return JSON.parse(r || "[]");
357
333
  }
358
- function X(e, t) {
359
- return t.some((t) => W(t.title) === e);
334
+ function Z(e, t) {
335
+ return t.some((t) => {
336
+ if (t.body) {
337
+ let { metadata: n } = H(t.body);
338
+ if (n.slug) return n.slug === e;
339
+ }
340
+ return W(t.title) === e;
341
+ });
360
342
  }
361
- function Z(e, t, n) {
343
+ function ne(e, t, n) {
362
344
  for (let r of t) n([
363
345
  "label",
364
346
  "create",
@@ -368,7 +350,7 @@ function Z(e, t, n) {
368
350
  "--force"
369
351
  ]);
370
352
  }
371
- function ne(e, t, n) {
353
+ function re(e, t, n) {
372
354
  let r = [
373
355
  "issue",
374
356
  "create",
@@ -383,7 +365,7 @@ function ne(e, t, n) {
383
365
  let i = n(r).match(/\/(\d+)\s*$/);
384
366
  return i ? parseInt(i[1], 10) : 0;
385
367
  }
386
- function re(e, t, n) {
368
+ function ie(e, t, n) {
387
369
  n([
388
370
  "issue",
389
371
  "close",
@@ -392,7 +374,7 @@ function re(e, t, n) {
392
374
  e
393
375
  ]);
394
376
  }
395
- function ie(e, t, n) {
377
+ function ae(e, t, n) {
396
378
  let r = n([
397
379
  "pr",
398
380
  "list",
@@ -409,7 +391,7 @@ function ie(e, t, n) {
409
391
  ]);
410
392
  return JSON.parse(r || "[]");
411
393
  }
412
- function ae(e, t, n, r) {
394
+ function oe(e, t, n, r) {
413
395
  r([
414
396
  "issue",
415
397
  "comment",
@@ -420,7 +402,7 @@ function ae(e, t, n, r) {
420
402
  n
421
403
  ]);
422
404
  }
423
- function oe(e) {
405
+ function se(e) {
424
406
  for (let t of e) {
425
407
  let e = R[t];
426
408
  if (e) return e;
@@ -430,82 +412,74 @@ function oe(e) {
430
412
  function Q(e, n) {
431
413
  t(s(e), { recursive: !0 }), a(e, n);
432
414
  }
433
- function $(e, t) {
434
- let n = t?.runGh ?? q, r = _(e);
435
- return r.storage === "local" ? se(e, r, n) : le(e, r, n);
415
+ function ce(e, t) {
416
+ let n = t?.runGh ?? J, r = _(e);
417
+ return r.storage === "local" ? le(e, r, n) : ue(e, r, n);
436
418
  }
437
- function se(e, t, n) {
438
- let r = [], i = J(t, n), a = K(e, t);
439
- if (a.length === 0) return b(e, { storage: p }), r.push("No artifacts found — nothing to migrate."), r.push(`✓ Storage switched to "${p}".`), r;
419
+ function le(e, t, n) {
420
+ let r = [], i = Y(t, n), a = K(e, t);
421
+ if (a.length === 0) return x(e, { storage: p }), r.push("No artifacts found — nothing to migrate."), r.push(`✓ Storage switched to "${p}".`), r;
440
422
  let o = t.github.labels?.prd ?? "tk:prd", s = t.github.labels?.plan ?? "tk:plan";
441
- Z(i, [
423
+ ne(i, [
442
424
  o,
443
425
  s,
444
426
  ...[...new Set(a.map((e) => V(e.metadata.status ?? "created")))]
445
427
  ], n);
446
- let c = Y(i, o, n), l = Y(i, s, n);
428
+ let c = X(i, o, n), l = X(i, s, n);
447
429
  for (let e of a) {
448
430
  let t = e.type === "prd" ? o : s, a = e.type === "prd" ? c : l;
449
- if (X(e.slug, a)) {
431
+ if (Z(e.slug, a)) {
450
432
  r.push(`⚠ skip ${e.type} "${e.slug}" — already exists on GitHub`);
451
433
  continue;
452
434
  }
453
- let u = V(e.metadata.status ?? "created"), d = `${B(e.metadata)}\n\n${e.body.replace(/^\n/, "")}`, f = ne(i, {
435
+ let u = e.metadata.status ?? "created", d = V(u), f = `${B(e.metadata)}\n\n${e.body.replace(/^\n/, "")}`, p = re(i, {
454
436
  title: `[${t}] ${e.slug}: ${e.title}`,
455
- body: d,
456
- labels: [t, u]
437
+ body: f,
438
+ labels: [t, d]
457
439
  }, n);
458
- e.archived ? (re(i, f, n), ce(i, e.slug, f, n), r.push(`✓ ${e.type} "${e.slug}" → issue #${f} (closed)`)) : r.push(`✓ ${e.type} "${e.slug}" → issue #${f}`);
440
+ u === "done" ? (ie(i, p, n), $(i, e.slug, p, n), r.push(`✓ ${e.type} "${e.slug}" → issue #${p} (closed)`)) : r.push(`✓ ${e.type} "${e.slug}" → issue #${p}`);
459
441
  }
460
- return b(e, { storage: p }), r.push(`✓ Storage switched to "${p}".`), r;
442
+ return x(e, { storage: p }), r.push(`✓ Storage switched to "${p}".`), r;
461
443
  }
462
- function ce(e, t, n, r) {
463
- let i = ie(e, t, r);
464
- i.length !== 0 && ae(e, n, `Linked PR: ${i.map((e) => `#${e.number}`).join(", ")}`, r);
444
+ function $(e, t, n, r) {
445
+ let i = ae(e, t, r);
446
+ i.length !== 0 && oe(e, n, `Linked PR: ${i.map((e) => `#${e.number}`).join(", ")}`, r);
465
447
  }
466
- function le(t, n, r) {
467
- let i = [], a = J(n, r), o = n.github.labels?.prd ?? "tk:prd", s = n.github.labels?.plan ?? "tk:plan", l = Y(a, o, r), u = Y(a, s, r), d = [...l.map((e) => ({
448
+ function ue(t, n, r) {
449
+ let i = [], a = Y(n, r), o = n.github.labels?.prd ?? "tk:prd", s = n.github.labels?.plan ?? "tk:plan", l = X(a, o, r), u = X(a, s, r), d = [...l.map((e) => ({
468
450
  ...e,
469
451
  type: "prd"
470
452
  })), ...u.map((e) => ({
471
453
  ...e,
472
454
  type: "plan"
473
455
  }))];
474
- if (d.length === 0) return b(t, { storage: f }), i.push("No GitHub issues found — nothing to migrate."), i.push(`✓ Storage switched to "${f}".`), i;
456
+ if (d.length === 0) return x(t, { storage: f }), i.push("No GitHub issues found — nothing to migrate."), i.push(`✓ Storage switched to "${f}".`), i;
475
457
  for (let r of d) {
476
- let a = W(r.title);
477
- if (!a) continue;
478
- let o = r.labels.map((e) => e.name), s = o.includes("tk:done") && r.state === "CLOSED", { metadata: l, body: u } = H(r.body ?? ""), d = l.status ?? oe(o);
479
- if (s) {
480
- let o = c(t, n.paths.archives, a), s = c(o, "prd.md"), f = c(o, "plan.md"), p = r.type === "prd" ? s : f;
481
- if (e(p)) {
482
- i.push(`⚠ skip ${r.type} "${a}" — local file already exists`);
483
- continue;
484
- }
485
- r.type === "prd" ? Q(p, `${U({
486
- ...l,
487
- status: d
488
- })}\n${u}`) : Q(p, u), i.push(`✓ ${r.type} "${a}" → ${p} (archived)`);
489
- } else {
490
- let o = c(t, r.type === "prd" ? n.paths.prds : n.paths.plans, `${a}.md`);
491
- if (e(o)) {
492
- i.push(`⚠ skip ${r.type} "${a}" — local file already exists`);
493
- continue;
494
- }
495
- r.type === "prd" ? Q(o, `${U({
496
- ...l,
497
- status: d
498
- })}\n${u}`) : Q(o, u), i.push(`✓ ${r.type} "${a}" → ${o}`);
458
+ let { metadata: a, body: o } = H(r.body ?? ""), s = a.slug ?? W(r.title);
459
+ if (!s) continue;
460
+ let l = r.labels.map((e) => e.name), u = a.status ?? se(l), d = c(t, r.type === "prd" ? n.paths.prds : n.paths.plans, `${s}.md`);
461
+ if (e(d)) {
462
+ i.push(`⚠ skip ${r.type} "${s}" local file already exists`);
463
+ continue;
464
+ }
465
+ if (r.type === "prd") Q(d, `${U({
466
+ ...a,
467
+ status: u
468
+ })}\n${o}`);
469
+ else {
470
+ let e = {};
471
+ a.source_prd && (e.source_prd = a.source_prd), (a.slug || s) && (e.slug = a.slug ?? s), u && (e.status = u), a.completed && (e.completed = a.completed), Q(d, `${U(e)}\n${o}`);
499
472
  }
473
+ i.push(`✓ ${r.type} "${s}" → ${d}`);
500
474
  }
501
- return b(t, { storage: f }), i.push(`✓ Storage switched to "${f}".`), i;
475
+ return x(t, { storage: f }), i.push(`✓ Storage switched to "${f}".`), i;
502
476
  }
503
477
  //#endregion
504
478
  //#region src/commands/uninstall.ts
505
- function ue(t) {
506
- if (!C.some((n) => e(c(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
479
+ function de(t) {
480
+ if (!w.some((n) => e(c(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
507
481
  let n = [];
508
- for (let r of C) {
482
+ for (let r of w) {
509
483
  let a = c(t, ".claude", "skills", r);
510
484
  e(a) && (i(a, {
511
485
  recursive: !0,
@@ -515,4 +489,4 @@ function ue(t) {
515
489
  return n;
516
490
  }
517
491
  //#endregion
518
- export { N as a, w as c, f as d, _ as f, I as i, T as l, $ as n, D as o, b as p, te as r, E as s, ue as t, C as u };
492
+ export { P as a, T as c, f as d, _ as f, L as i, E as l, ce as n, O as o, x as p, ee as r, D as s, de as t, w as u };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tracerkit",
3
- "version": "1.15.0",
4
- "description": "Spec-driven workflow for Claude Code: replace ad-hoc prompts with PRD → plan → verify.",
3
+ "version": "1.17.0",
4
+ "description": "Spec-driven workflow for AI coding agents PRD → plan → verify. Pure Markdown skills, zero runtime deps.",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "helderberto",
@@ -12,7 +12,7 @@ Overview of active features, progress, and suggested focus.
12
12
 
13
13
  <!-- if:local -->
14
14
 
15
- - Available PRDs: !`ls .tracerkit/prds/ 2>&1`
15
+ - Available PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
16
16
  <!-- end:local -->
17
17
  <!-- if:github -->
18
18
  - Available PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
@@ -31,11 +31,11 @@ For each `.md` file in `.tracerkit/prds/`: parse frontmatter, extract `status` a
31
31
 
32
32
  List open GitHub Issues with label `{{github.labels.prd}}`:
33
33
 
34
- 6. For each issue, parse the `<!-- tk:metadata -->` comment in the body
35
- 7. Extract `status` and `created` fields from the metadata
36
- 8. Also check labels: `tk:created`, `tk:in-progress`
37
- 9. Skip issues with `tk:done` label
38
- 10. The slug is extracted from the title: `[{{github.labels.prd}}] <slug>: ...`
34
+ 1. For each issue, parse the `<!-- tk:metadata -->` comment in the body
35
+ 2. Extract `status` and `created` fields from the metadata
36
+ 3. Also check labels: `tk:created`, `tk:in-progress`
37
+ 4. Skip issues with `tk:done` label
38
+ 5. The slug is extracted from the title: `[{{github.labels.prd}}] <slug>: ...`
39
39
  <!-- end:github -->
40
40
 
41
41
  ### 2. Count progress from plans
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Verify implementation against plan. Shows progress, finds blockers, and archives when done. Use after implementing a plan, or without arguments to see a feature dashboard.
2
+ description: Verify implementation against plan. Shows progress, finds blockers, and marks complete when done. Use after implementing a plan, or without arguments to see a feature dashboard.
3
3
  argument-hint: '[slug]'
4
4
  ---
5
5
 
@@ -7,13 +7,15 @@ argument-hint: '[slug]'
7
7
 
8
8
  # Check Implementation
9
9
 
10
- Check implementation against a plan. Update checks, stamp findings, transition status, and archive when done.
10
+ Check implementation against a plan. Update checks, stamp findings, transition status, and mark complete when done.
11
+
12
+ **Interactive prompts**: use `AskUserQuestion` for all user-facing questions — selections and confirmations.
11
13
 
12
14
  ## Pre-loaded context
13
15
 
14
16
  <!-- if:local -->
15
17
 
16
- - Available plans: !`ls .tracerkit/plans/ 2>&1`
18
+ - Available plans: !`ls .tracerkit/plans/*.md 2>/dev/null || echo "(none)"`
17
19
  <!-- end:local -->
18
20
  <!-- if:github -->
19
21
  - Available plans: list open GitHub Issues with label `{{github.labels.plan}}`
@@ -50,7 +52,7 @@ For each `.md` file in `.tracerkit/prds/`:
50
52
  6. If plan issue exists, count progress from checkboxes in its body (see Progress Algorithm below). Show `—` if no plan.
51
53
  <!-- end:github -->
52
54
 
53
- Ask which feature to verify.
55
+ Use `AskUserQuestion` with each feature as an option to let the user pick which to verify.
54
56
 
55
57
  ## Progress Algorithm
56
58
 
@@ -62,12 +64,12 @@ Count `- [x]` and `- [ ]` lines under each `## Phase N` heading. Per-phase: `Pha
62
64
 
63
65
  <!-- if:local -->
64
66
 
65
- Read `.tracerkit/plans/<slug>.md`. If missing, list plans and ask.
67
+ Read `.tracerkit/plans/<slug>.md`. If missing, list plans and use `AskUserQuestion` to select one.
66
68
 
67
69
  <!-- end:local -->
68
70
  <!-- if:github -->
69
71
 
70
- Find plan issue: open issue with label `{{github.labels.plan}}`, title matching `[{{github.labels.plan}}] <slug>:`. If missing, list plans and ask.
72
+ Find plan issue: open issue with label `{{github.labels.plan}}`, title matching `[{{github.labels.plan}}] <slug>:`. If missing, list plans and use `AskUserQuestion` to select one.
71
73
 
72
74
  <!-- end:github -->
73
75
 
@@ -175,25 +177,25 @@ Append a verdict block at the bottom of the plan issue body by editing the issue
175
177
 
176
178
  If a previous verdict block exists, replace it with the new one.
177
179
 
178
- ### 7. On `done` — archive
180
+ ### 7. On `done` — mark complete
179
181
 
180
182
  If all checks pass and zero BLOCKERS:
181
183
 
182
184
  <!-- if:local -->
183
185
 
184
- Archive to `.tracerkit/archives/<slug>/`:
185
-
186
- 1. Copy PRD → `prd.md` (set `status: done`, add `completed` timestamp in frontmatter)
187
- 2. Copy plan → `plan.md` (append `## Archived` with date)
188
- 3. Delete originals
186
+ 1. Update PRD frontmatter: `status: done`, add `completed: <UTC ISO 8601>`
187
+ 2. Update plan frontmatter: `status: done`, add `completed: <UTC ISO 8601>`
189
188
 
190
189
  <!-- end:local -->
191
190
  <!-- if:github -->
192
191
 
193
- 1. PRD issue: add `tk:done`, remove `tk:in-progress`, set metadata `status: done` + `completed` timestamp
194
- 2. Close PRD issue (reason: `completed`)
195
- 3. Close plan issue (reason: `completed`)
196
- 4. If current PR exists, reference it in closing comment on PRD issue
192
+ 1. If on a feature branch with commits ahead of default:
193
+ a. Push branch if not pushed
194
+ b. Open PR with `Closes #<prd-number>, Closes #<plan-number>` (or update existing PR body)
195
+ 2. Search merged PRs matching slug: `gh pr list --search <slug> --state merged` — add comment on PRD issue linking found PRs
196
+ 3. PRD issue: add `tk:done`, remove `tk:in-progress`, update metadata `status: done` + `completed` timestamp
197
+ 4. Close PRD issue (reason: `completed`)
198
+ 5. Close plan issue (reason: `completed`)
197
199
 
198
200
  <!-- end:github -->
199
201
 
@@ -208,6 +210,6 @@ List the blockers to fix, then re-run `/tk:check <slug>`.
208
210
  ## Rules
209
211
 
210
212
  - The review subagent must be **read-only** — it must not create, edit, or delete any files
211
- - The only file writes this skill makes are: checkboxes + verdict block in the plan, and the archive steps on `done`
213
+ - The only file writes this skill makes are: checkboxes + verdict block in the plan, and the status updates on `done`
212
214
  - Never modify implementation code — only observe and report
213
- - If the PRD file is missing but all checks pass, warn and proceed — archive the plan only (skip PRD steps in archive)
215
+ - If the PRD file is missing but all checks pass, warn and proceed — mark the plan complete only (skip PRD status update)
@@ -9,6 +9,8 @@ argument-hint: '[slug]'
9
9
 
10
10
  Break a PRD into phased vertical slices (tracer bullets).
11
11
 
12
+ **Interactive prompts**: use `AskUserQuestion` for all user-facing questions — selections, confirmations, and approval steps.
13
+
12
14
  <!-- if:local -->
13
15
 
14
16
  Output: `.tracerkit/plans/<slug>.md`.
@@ -24,7 +26,7 @@ Output: a GitHub Issue with label `{{github.labels.plan}}`.
24
26
 
25
27
  <!-- if:local -->
26
28
 
27
- - Available PRDs: !`ls .tracerkit/prds/ 2>&1`
29
+ - Available PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
28
30
  <!-- end:local -->
29
31
  <!-- if:github -->
30
32
  - Available PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
@@ -34,7 +36,7 @@ Output: a GitHub Issue with label `{{github.labels.plan}}`.
34
36
 
35
37
  The argument (if provided) is: $ARGUMENTS
36
38
 
37
- Use argument as `<slug>`. If empty, list available PRDs and ask.
39
+ Use argument as `<slug>`. If empty, list available PRDs and use `AskUserQuestion` with each PRD as an option.
38
40
 
39
41
  ## Workflow
40
42
 
@@ -42,12 +44,12 @@ Use argument as `<slug>`. If empty, list available PRDs and ask.
42
44
 
43
45
  <!-- if:local -->
44
46
 
45
- Read `.tracerkit/prds/<slug>.md`. If missing, list PRDs and ask. If `.tracerkit/plans/<slug>.md` exists, ask: overwrite or new name?
47
+ Read `.tracerkit/prds/<slug>.md`. If missing, list PRDs and use `AskUserQuestion` to select one. If `.tracerkit/plans/<slug>.md` exists, use `AskUserQuestion` with options: "Overwrite existing" / "Pick a new name".
46
48
 
47
49
  <!-- end:local -->
48
50
  <!-- if:github -->
49
51
 
50
- Find PRD issue: open issue with label `{{github.labels.prd}}`, title matching `[{{github.labels.prd}}] <slug>:`. If missing, list PRDs and ask. If plan issue with label `{{github.labels.plan}}` and matching title exists, ask: update or new name?
52
+ Find PRD issue: open issue with label `{{github.labels.prd}}`, title matching `[{{github.labels.prd}}] <slug>:`. If missing, list PRDs and use `AskUserQuestion` to select one. If plan issue with label `{{github.labels.plan}}` and matching title exists, use `AskUserQuestion` with options: "Update existing plan" / "Use a new name".
51
53
 
52
54
  <!-- end:github -->
53
55
 
@@ -102,7 +104,7 @@ Each phase: thin vertical slice through all layers (schema → service → API
102
104
 
103
105
  **Phase naming:** use a goal phrase answering "what can we demo when this is done?" (e.g., "Phase 1 — Revenue visible end-to-end"), not a layer name.
104
106
 
105
- **Done when:** checkbox list of atomic, verifiable conditions (not prose). Test: "Can an agent verify by reading files, running a command, or checking a test?" Agent marks `[x]` during implementation.
107
+ **Done when:** checkbox list of atomic, verifiable conditions. Each must name a test file/name, a shell command, or a file+content to verify. No prose-only conditions. Test: "Can an agent verify by reading files, running a command, or checking a test?"
106
108
 
107
109
  **Layer-by-layer exception:** if complex schema changes underpin all modules and no story stands alone, build data foundation first, then slice vertically.
108
110
 
@@ -110,7 +112,7 @@ Each phase: thin vertical slice through all layers (schema → service → API
110
112
 
111
113
  - 1 module touched → 2–3 phases max
112
114
  - 2–3 modules touched → 3–5 phases max
113
- - 4+ modules or 6+ phases → stop and ask the user to split the PRD
115
+ - 4+ modules or 6+ phases → stop and use `AskUserQuestion`: "PRD touches 4+ modules. Split before planning?" with options: "Split the PRD" (Recommended) / "Continue anyway"
114
116
 
115
117
  Count "modules touched" by scanning the PRD's New Modules and Schema Changes sections.
116
118
 
@@ -122,7 +124,7 @@ Assign an agent tag to tasks where appropriate:
122
124
 
123
125
  ### 5. Quiz the user
124
126
 
125
- Present breakdown (title, user stories covered, done-when per phase). Ask: granularity right? Merge or split? Iterate until approved.
127
+ Present breakdown (title, user stories covered, done-when per phase). Use `AskUserQuestion`: "How's the granularity?" with options: "Looks good, proceed" (Recommended) / "Merge some phases" / "Split a phase". Iterate until approved.
126
128
 
127
129
  ### 6. Save plan
128
130
 
@@ -131,15 +133,23 @@ Present breakdown (title, user stories covered, done-when per phase). Ask: granu
131
133
  Save to `.tracerkit/plans/<slug>.md` (create dir if missing).
132
134
 
133
135
  ```markdown
136
+ ---
137
+ source_prd: .tracerkit/prds/<slug>.md
138
+ slug: <slug>
139
+ status: in_progress
140
+ ---
141
+
134
142
  # Plan: <Feature Name>
135
143
 
136
144
  > Source PRD: `.tracerkit/prds/<slug>.md`
137
145
  ```
138
146
 
147
+ Then update PRD frontmatter: add `plan: .tracerkit/plans/<slug>.md` field.
148
+
139
149
  <!-- end:local -->
140
150
  <!-- if:github -->
141
151
 
142
- Ensure labels exist: `{{github.labels.plan}}`, `tk:in-progress` (create if missing).
152
+ Ensure labels exist: `gh label create {{github.labels.plan}} --repo {{github.repo}} --force`, `gh label create tk:in-progress --repo {{github.repo}} --force`.
143
153
 
144
154
  Create GitHub Issue — title: `[{{github.labels.plan}}] <slug>: Plan: <Feature Title>`, labels: `{{github.labels.plan}}`, `tk:in-progress`.
145
155
 
@@ -147,6 +157,7 @@ Create GitHub Issue — title: `[{{github.labels.plan}}] <slug>: Plan: <Feature
147
157
  <!-- tk:metadata
148
158
  source_prd: #<PRD issue number>
149
159
  slug: <slug>
160
+ status: in_progress
150
161
  -->
151
162
 
152
163
  # Plan: <Feature Name>
@@ -156,6 +167,19 @@ slug: <slug>
156
167
 
157
168
  <!-- end:github -->
158
169
 
170
+ ### 6b. Backlink PRD
171
+
172
+ <!-- if:local -->
173
+
174
+ Already linked via PRD frontmatter `plan:` field (set in step 6).
175
+
176
+ <!-- end:local -->
177
+ <!-- if:github -->
178
+
179
+ Add comment on PRD issue: "Plan: #<plan-issue-number>" (creates cross-reference).
180
+
181
+ <!-- end:github -->
182
+
159
183
  Use this structure for the plan body:
160
184
 
161
185
  ```markdown
@@ -193,7 +217,25 @@ Carried forward from PRD verbatim.
193
217
  Gaps found in the PRD needing resolution. Blank if none.
194
218
  ```
195
219
 
196
- Print one line per phase: `Phase N — <title> (<condition summary>)`. Then ask: "Run `/tk:check <slug>` when ready?"
220
+ Print one line per phase: `Phase N — <title> (<condition summary>)`. Then use `AskUserQuestion`: "What's next?" with options: "Start implementing" (Recommended) / "Run `/tk:check <slug>`" / "Done for now".
221
+
222
+ ## Execution guidance
223
+
224
+ When implementing this plan, **always offer to create a feature branch** before writing any code. Use `AskUserQuestion`: "Create branch `feat/<slug>`?" with options: "Yes, create branch" (Recommended) / "No, stay on current branch". If accepted, create the branch from the default branch.
225
+
226
+ ### During implementation
227
+
228
+ Mark each "Done when" checkbox `[x]` **immediately after verifying** the condition.
229
+
230
+ Always update the local plan file (`.tracerkit/plans/<slug>.md`): change `- [ ]` → `- [x]`. This file is the working copy for both local and GitHub modes.
231
+
232
+ <!-- if:github -->
233
+
234
+ **Sync to GitHub at phase boundaries**: after completing all items in a phase, update the plan issue body with `gh issue edit` to reflect the local state. This avoids per-item API calls.
235
+
236
+ After all phases, open a PR with body containing `Closes #<prd-issue>, Closes #<plan-issue>`.
237
+
238
+ <!-- end:github -->
197
239
 
198
240
  ## Rules
199
241
 
@@ -9,11 +9,13 @@ argument-hint: <idea>
9
9
 
10
10
  Skip satisfied steps. If argument provided, skip to Step 2.
11
11
 
12
+ **Interactive prompts**: use `AskUserQuestion` for all user-facing questions — choices, confirmations, and interview questions. This provides structured selection UI instead of plain text prompts.
13
+
12
14
  ## Pre-loaded context
13
15
 
14
16
  <!-- if:local -->
15
17
 
16
- - Existing PRDs: !`ls .tracerkit/prds/ 2>&1`
18
+ - Existing PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
17
19
  <!-- end:local -->
18
20
  <!-- if:github -->
19
21
  - Existing PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
@@ -26,19 +28,20 @@ The argument is: $ARGUMENTS
26
28
  If empty, go to Step 1; derive slug after gathering the idea. If provided, derive slug:
27
29
 
28
30
  1. Take only the text before the first `—` or `–` (if present)
29
- 2. Lowercase the text
30
- 3. Remove filler words: a, an, the, for, of, to, in, on, with, and, or, but, is, be
31
- 4. Take the first 4 remaining words (or fewer if less exist)
32
- 5. Join with hyphens `<slug>`
31
+ 2. Strip leading command verbs: create, build, implement, add, update, fix, make, write, plan, get, show, support
32
+ 3. Lowercase the text
33
+ 4. Remove filler words: a, an, the, for, of, to, in, on, with, and, or, but, is, be
34
+ 5. Take the first 4 remaining words (or fewer if less exist)
35
+ 6. Join with hyphens → `<slug>`
33
36
 
34
37
  <!-- if:local -->
35
38
 
36
- Output: `.tracerkit/prds/<slug>.md`. If exists, ask: overwrite or new name?
39
+ Output: `.tracerkit/prds/<slug>.md`. If exists, use `AskUserQuestion` with options: "Overwrite existing" / "Pick a new name".
37
40
 
38
41
  <!-- end:local -->
39
42
  <!-- if:github -->
40
43
 
41
- Output: GitHub Issue with label `{{github.labels.prd}}`, title `[{{github.labels.prd}}] <slug>: <Feature Title>`. If matching issue exists, ask: update or new slug?
44
+ Output: GitHub Issue with label `{{github.labels.prd}}`, title `[{{github.labels.prd}}] <slug>: <Feature Title>`. If matching issue exists, use `AskUserQuestion` with options: "Update existing issue" / "Use a new slug".
42
45
 
43
46
  <!-- end:github -->
44
47
 
@@ -56,7 +59,7 @@ Map current state: data models, services, API routes, frontend, tests. Note exis
56
59
 
57
60
  ### 3. Interview
58
61
 
59
- One question at a time. Lead with your recommended answer. Explore code instead of asking when possible. Offer A/B/C for terse answers.
62
+ One question at a time. Lead with your recommended answer (mark it `(Recommended)` and list first). Explore code instead of asking when possible. Use `AskUserQuestion` with 2–4 options for each interview question — structured choices are faster than free-text.
60
63
 
61
64
  | Branch | Key questions | Skip when |
62
65
  | ---------------- | --------------------------------------- | -------------------------------- |
@@ -70,7 +73,7 @@ One question at a time. Lead with your recommended answer. Explore code instead
70
73
 
71
74
  ### 3b. Gray areas
72
75
 
73
- Surface ambiguities, contradictions, unstated assumptions. Present numbered list. Resolve all before continuing.
76
+ Surface ambiguities, contradictions, unstated assumptions. For each gray area, use `AskUserQuestion` with proposed resolution options. Resolve all before continuing.
74
77
 
75
78
  ### 4. Design modules
76
79
 
@@ -78,7 +81,7 @@ Sketch modules. Favor **deep modules** — simple interface (1-3 entry points) h
78
81
 
79
82
  Shallow signals: many small 1:1 functions, callers compose multiple calls, feature changes require interface changes.
80
83
 
81
- Present modules. Confirm which need tests.
84
+ Present modules. Use `AskUserQuestion` (multiSelect) to confirm which modules need tests.
82
85
 
83
86
  ### 5. Write PRD
84
87
 
@@ -98,12 +101,13 @@ status: created
98
101
  <!-- end:local -->
99
102
  <!-- if:github -->
100
103
 
101
- Ensure labels exist: `{{github.labels.prd}}`, `tk:created` (create if missing).
104
+ Ensure labels exist: `gh label create {{github.labels.prd}} --repo {{github.repo}} --force`, `gh label create tk:created --repo {{github.repo}} --force`.
102
105
 
103
106
  Create GitHub Issue — title: `[{{github.labels.prd}}] <slug>: <Feature Title>`, labels: `{{github.labels.prd}}`, `tk:created`.
104
107
 
105
108
  ```markdown
106
109
  <!-- tk:metadata
110
+ slug: <slug>
107
111
  created: <UTC ISO 8601>
108
112
  status: created
109
113
  -->
@@ -132,4 +136,4 @@ PRD body structure (same for local file and issue body). Omit empty sections. No
132
136
 
133
137
  ---
134
138
 
135
- Then ask: "Run `/tk:plan <slug>` next?"
139
+ Then use `AskUserQuestion`: "What's next?" with options: "Run `/tk:plan <slug>`" (Recommended) / "Done for now".