tracerkit 1.15.0 → 1.16.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.16.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,13 @@ 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
11
 
12
12
  ## Pre-loaded context
13
13
 
14
14
  <!-- if:local -->
15
15
 
16
- - Available plans: !`ls .tracerkit/plans/ 2>&1`
16
+ - Available plans: !`ls .tracerkit/plans/*.md 2>/dev/null || echo "(none)"`
17
17
  <!-- end:local -->
18
18
  <!-- if:github -->
19
19
  - Available plans: list open GitHub Issues with label `{{github.labels.plan}}`
@@ -175,25 +175,25 @@ Append a verdict block at the bottom of the plan issue body by editing the issue
175
175
 
176
176
  If a previous verdict block exists, replace it with the new one.
177
177
 
178
- ### 7. On `done` — archive
178
+ ### 7. On `done` — mark complete
179
179
 
180
180
  If all checks pass and zero BLOCKERS:
181
181
 
182
182
  <!-- if:local -->
183
183
 
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
184
+ 1. Update PRD frontmatter: `status: done`, add `completed: <UTC ISO 8601>`
185
+ 2. Update plan frontmatter: `status: done`, add `completed: <UTC ISO 8601>`
189
186
 
190
187
  <!-- end:local -->
191
188
  <!-- if:github -->
192
189
 
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
190
+ 1. If on a feature branch with commits ahead of default:
191
+ a. Push branch if not pushed
192
+ b. Open PR with `Closes #<prd-number>, Closes #<plan-number>` (or update existing PR body)
193
+ 2. Search merged PRs matching slug: `gh pr list --search <slug> --state merged` — add comment on PRD issue linking found PRs
194
+ 3. PRD issue: add `tk:done`, remove `tk:in-progress`, update metadata `status: done` + `completed` timestamp
195
+ 4. Close PRD issue (reason: `completed`)
196
+ 5. Close plan issue (reason: `completed`)
197
197
 
198
198
  <!-- end:github -->
199
199
 
@@ -208,6 +208,6 @@ List the blockers to fix, then re-run `/tk:check <slug>`.
208
208
  ## Rules
209
209
 
210
210
  - 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`
211
+ - The only file writes this skill makes are: checkboxes + verdict block in the plan, and the status updates on `done`
212
212
  - 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)
213
+ - If the PRD file is missing but all checks pass, warn and proceed — mark the plan complete only (skip PRD status update)
@@ -24,7 +24,7 @@ Output: a GitHub Issue with label `{{github.labels.plan}}`.
24
24
 
25
25
  <!-- if:local -->
26
26
 
27
- - Available PRDs: !`ls .tracerkit/prds/ 2>&1`
27
+ - Available PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
28
28
  <!-- end:local -->
29
29
  <!-- if:github -->
30
30
  - Available PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
@@ -102,7 +102,7 @@ Each phase: thin vertical slice through all layers (schema → service → API
102
102
 
103
103
  **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
104
 
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.
105
+ **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
106
 
107
107
  **Layer-by-layer exception:** if complex schema changes underpin all modules and no story stands alone, build data foundation first, then slice vertically.
108
108
 
@@ -131,15 +131,23 @@ Present breakdown (title, user stories covered, done-when per phase). Ask: granu
131
131
  Save to `.tracerkit/plans/<slug>.md` (create dir if missing).
132
132
 
133
133
  ```markdown
134
+ ---
135
+ source_prd: .tracerkit/prds/<slug>.md
136
+ slug: <slug>
137
+ status: in_progress
138
+ ---
139
+
134
140
  # Plan: <Feature Name>
135
141
 
136
142
  > Source PRD: `.tracerkit/prds/<slug>.md`
137
143
  ```
138
144
 
145
+ Then update PRD frontmatter: add `plan: .tracerkit/plans/<slug>.md` field.
146
+
139
147
  <!-- end:local -->
140
148
  <!-- if:github -->
141
149
 
142
- Ensure labels exist: `{{github.labels.plan}}`, `tk:in-progress` (create if missing).
150
+ Ensure labels exist: `gh label create {{github.labels.plan}} --repo {{github.repo}} --force`, `gh label create tk:in-progress --repo {{github.repo}} --force`.
143
151
 
144
152
  Create GitHub Issue — title: `[{{github.labels.plan}}] <slug>: Plan: <Feature Title>`, labels: `{{github.labels.plan}}`, `tk:in-progress`.
145
153
 
@@ -147,6 +155,7 @@ Create GitHub Issue — title: `[{{github.labels.plan}}] <slug>: Plan: <Feature
147
155
  <!-- tk:metadata
148
156
  source_prd: #<PRD issue number>
149
157
  slug: <slug>
158
+ status: in_progress
150
159
  -->
151
160
 
152
161
  # Plan: <Feature Name>
@@ -156,6 +165,19 @@ slug: <slug>
156
165
 
157
166
  <!-- end:github -->
158
167
 
168
+ ### 6b. Backlink PRD
169
+
170
+ <!-- if:local -->
171
+
172
+ Already linked via PRD frontmatter `plan:` field (set in step 6).
173
+
174
+ <!-- end:local -->
175
+ <!-- if:github -->
176
+
177
+ Add comment on PRD issue: "Plan: #<plan-issue-number>" (creates cross-reference).
178
+
179
+ <!-- end:github -->
180
+
159
181
  Use this structure for the plan body:
160
182
 
161
183
  ```markdown
@@ -195,6 +217,28 @@ Gaps found in the PRD needing resolution. Blank if none.
195
217
 
196
218
  Print one line per phase: `Phase N — <title> (<condition summary>)`. Then ask: "Run `/tk:check <slug>` when ready?"
197
219
 
220
+ ## Execution guidance
221
+
222
+ When implementing this plan, **always offer to create a feature branch** before writing any code:
223
+
224
+ > "Create branch `feat/<slug>` for this work? (Y/n)"
225
+
226
+ If accepted, create the branch from the default branch.
227
+
228
+ ### During implementation
229
+
230
+ Mark each "Done when" checkbox `[x]` **immediately after verifying** the condition.
231
+
232
+ Always update the local plan file (`.tracerkit/plans/<slug>.md`): change `- [ ]` → `- [x]`. This file is the working copy for both local and GitHub modes.
233
+
234
+ <!-- if:github -->
235
+
236
+ **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.
237
+
238
+ After all phases, open a PR with body containing `Closes #<prd-issue>, Closes #<plan-issue>`.
239
+
240
+ <!-- end:github -->
241
+
198
242
  ## Rules
199
243
 
200
244
  - Phases derive from PRD user stories — never invented
@@ -13,7 +13,7 @@ Skip satisfied steps. If argument provided, skip to Step 2.
13
13
 
14
14
  <!-- if:local -->
15
15
 
16
- - Existing PRDs: !`ls .tracerkit/prds/ 2>&1`
16
+ - Existing PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
17
17
  <!-- end:local -->
18
18
  <!-- if:github -->
19
19
  - Existing PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
@@ -26,10 +26,11 @@ The argument is: $ARGUMENTS
26
26
  If empty, go to Step 1; derive slug after gathering the idea. If provided, derive slug:
27
27
 
28
28
  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>`
29
+ 2. Strip leading command verbs: create, build, implement, add, update, fix, make, write, plan, get, show, support
30
+ 3. Lowercase the text
31
+ 4. Remove filler words: a, an, the, for, of, to, in, on, with, and, or, but, is, be
32
+ 5. Take the first 4 remaining words (or fewer if less exist)
33
+ 6. Join with hyphens → `<slug>`
33
34
 
34
35
  <!-- if:local -->
35
36
 
@@ -98,12 +99,13 @@ status: created
98
99
  <!-- end:local -->
99
100
  <!-- if:github -->
100
101
 
101
- Ensure labels exist: `{{github.labels.prd}}`, `tk:created` (create if missing).
102
+ Ensure labels exist: `gh label create {{github.labels.prd}} --repo {{github.repo}} --force`, `gh label create tk:created --repo {{github.repo}} --force`.
102
103
 
103
104
  Create GitHub Issue — title: `[{{github.labels.prd}}] <slug>: <Feature Title>`, labels: `{{github.labels.prd}}`, `tk:created`.
104
105
 
105
106
  ```markdown
106
107
  <!-- tk:metadata
108
+ slug: <slug>
107
109
  created: <UTC ISO 8601>
108
110
  status: created
109
111
  -->