tracerkit 1.14.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,49 +101,36 @@ 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
- </details>
107
-
108
- ## Skills
109
-
110
- TracerKit ships skills that take a feature from idea to verified archive.
111
-
112
- ### `/tk:prd <idea>`: Write a PRD
113
-
114
- Interactive interview that explores your codebase, asks scoping questions one at a time, designs deep modules, and writes a structured PRD.
115
-
116
- **Output:** `.tracerkit/prds/<slug>.md` (local) or GitHub Issue with `tk:prd` label
117
-
118
- ### `/tk:plan <slug>`: Create an implementation plan
106
+ To migrate existing artifacts between backends:
119
107
 
120
- 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.
121
-
122
- **Output:** `.tracerkit/plans/<slug>.md` (local) or GitHub Issue with `tk:plan` label
123
-
124
- ### `/tk:brief`: Session briefing
125
-
126
- Shows active features, their progress, and suggested focus. Use at the start of a session to orient.
127
-
128
- **Output:** Feature dashboard in the terminal. No files written.
108
+ ```bash
109
+ tracerkit migrate-storage # local→github or github→local (auto-detected)
110
+ ```
129
111
 
130
- ### `/tk:check [slug]`: Verify and archive
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.
131
113
 
132
- 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.
114
+ </details>
133
115
 
134
- Without arguments, shows a feature dashboard with status and progress before asking which feature to check.
116
+ ## Skills
135
117
 
136
- **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) |
137
124
 
138
125
  ## Docs
139
126
 
140
- | Document | Description |
141
- | ------------------------------------------------ | -------------------------------------------------- |
142
- | [Examples](docs/examples.md) | Walk through end-to-end usage scenarios |
143
- | [CLI Reference](docs/cli-reference.md) | Commands: init, update, config, uninstall |
144
- | [Configuration](docs/configuration.md) | Storage backends, GitHub options, custom paths |
145
- | [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions |
146
- | [Comparison](docs/comparison.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec |
127
+ | Document | Description |
128
+ | ------------------------------------------------ | ---------------------------------------------------------- |
129
+ | [Examples](docs/examples.md) | Walk through end-to-end usage scenarios |
130
+ | [CLI Reference](docs/cli-reference.md) | Commands: init, update, config, migrate-storage, uninstall |
131
+ | [Configuration](docs/configuration.md) | Storage backends, GitHub options, custom paths |
132
+ | [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions |
133
+ | [Comparison](docs/comparison.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec |
147
134
 
148
135
  ## Contributing
149
136
 
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-C4K7aNpf.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-C4K7aNpf.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"
@@ -38,8 +37,7 @@ function y(e) {
38
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
43
  function b(e) {
@@ -128,7 +126,7 @@ function M(e) {
128
126
  }
129
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
131
  function P(e, r, i) {
134
132
  let o = i ?? A(k).map(j);
@@ -188,19 +186,23 @@ function L(t, n) {
188
186
  }
189
187
  //#endregion
190
188
  //#region src/commands/init.ts
191
- function R(t) {
189
+ function ee(t) {
192
190
  if (w.some((n) => e(c(t, ".claude", "skills", n)))) return L(t, { force: !1 });
193
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 z = {
196
+ var te = {
199
197
  created: "tk:created",
200
198
  in_progress: "tk:in-progress",
201
199
  done: "tk:done"
200
+ }, R = {
201
+ "tk:created": "created",
202
+ "tk:in-progress": "in_progress",
203
+ "tk:done": "done"
202
204
  };
203
- function B(e) {
205
+ function z(e) {
204
206
  let t = e.match(/^---\n([\s\S]*?)---\n([\s\S]*)$/);
205
207
  if (!t) return {
206
208
  metadata: {},
@@ -218,91 +220,91 @@ function B(e) {
218
220
  body: t[2]
219
221
  };
220
222
  }
221
- function V(e) {
223
+ function B(e) {
222
224
  let t = Object.entries(e);
223
225
  return t.length === 0 ? "<!-- tk:metadata\n-->" : `<!-- tk:metadata\n${t.map(([e, t]) => `${e}: ${t}`).join("\n")}\n-->`;
224
226
  }
227
+ function V(e) {
228
+ return te[e] ?? "tk:created";
229
+ }
225
230
  function H(e) {
226
- return z[e] ?? "tk:created";
231
+ let t = e.match(/<!--\s*tk:metadata\n([\s\S]*?)-->\n*([\s\S]*)$/);
232
+ if (!t) return {
233
+ metadata: {},
234
+ body: e
235
+ };
236
+ let n = {};
237
+ for (let e of t[1].split("\n")) {
238
+ let t = e.indexOf(":");
239
+ if (t === -1) continue;
240
+ let r = e.slice(0, t).trim(), i = e.slice(t + 1).trim();
241
+ r && (n[r] = i);
242
+ }
243
+ return {
244
+ metadata: n,
245
+ body: t[2]
246
+ };
227
247
  }
228
248
  function U(e) {
249
+ let t = Object.entries(e);
250
+ return t.length === 0 ? "---\n---\n" : `---\n${t.map(([e, t]) => `${e}: ${t}`).join("\n")}\n---\n`;
251
+ }
252
+ function W(e) {
229
253
  let t = e.match(/\[[^\]]+\]\s+([^:]+):/);
230
254
  return t ? t[1].trim() : null;
231
255
  }
232
- function W(e) {
256
+ function G(e) {
233
257
  let t = e.match(/^#\s+(.+)$/m);
234
258
  return t ? t[1].trim() : "Untitled";
235
259
  }
236
- function G(t, i) {
260
+ function K(t, i) {
237
261
  let a = [], s = c(t, i.paths.prds);
238
262
  if (e(s)) for (let e of r(s)) {
239
263
  if (!e.endsWith(".md")) continue;
240
- let t = o(e, ".md"), { metadata: r, body: i } = B(n(c(s, e), "utf8"));
264
+ let t = o(e, ".md"), { metadata: r, body: i } = z(n(c(s, e), "utf8"));
241
265
  a.push({
242
266
  slug: t,
243
267
  type: "prd",
244
268
  metadata: r,
245
269
  body: i,
246
- title: W(i),
247
- archived: !1
270
+ title: G(i)
248
271
  });
249
272
  }
250
273
  let l = c(t, i.paths.plans);
251
274
  if (e(l)) for (let e of r(l)) {
252
275
  if (!e.endsWith(".md")) continue;
253
- 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);
254
277
  a.push({
255
278
  slug: t,
256
279
  type: "plan",
257
- metadata: {},
258
- body: r,
259
- title: W(r),
260
- archived: !1
280
+ metadata: i,
281
+ body: s,
282
+ title: G(s || r)
261
283
  });
262
284
  }
263
- let u = c(t, i.paths.archives);
264
- if (e(u)) for (let t of r(u, { withFileTypes: !0 })) {
265
- if (!t.isDirectory()) continue;
266
- let r = t.name, i = c(u, r, "prd.md"), o = c(u, r, "plan.md");
267
- if (e(i)) {
268
- let { metadata: e, body: t } = B(n(i, "utf8"));
269
- a.push({
270
- slug: r,
271
- type: "prd",
272
- metadata: {
273
- ...e,
274
- status: "done"
275
- },
276
- body: t,
277
- title: W(t),
278
- archived: !0
279
- });
280
- }
281
- if (e(o)) {
282
- let e = n(o, "utf8");
283
- a.push({
284
- slug: r,
285
- type: "plan",
286
- metadata: { status: "done" },
287
- body: e,
288
- title: W(e),
289
- archived: !0
290
- });
291
- }
292
- }
293
285
  return a;
294
286
  }
295
- function K(e) {
296
- return d(`gh ${e.map((e) => `'${e.replace(/'/g, "'\\''")}'`).join(" ")}`, {
297
- encoding: "utf8",
298
- stdio: [
299
- "pipe",
300
- "pipe",
301
- "pipe"
302
- ]
303
- }).trim();
304
- }
305
- function q(e, t) {
287
+ function q(e) {
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) {
306
308
  return e.github.repo ? e.github.repo : t([
307
309
  "repo",
308
310
  "view",
@@ -312,7 +314,7 @@ function q(e, t) {
312
314
  ".nameWithOwner"
313
315
  ]);
314
316
  }
315
- function J(e, t, n) {
317
+ function X(e, t, n) {
316
318
  let r = n([
317
319
  "issue",
318
320
  "list",
@@ -323,16 +325,22 @@ function J(e, t, n) {
323
325
  "--state",
324
326
  "all",
325
327
  "--json",
326
- "number,title,labels,state",
328
+ "number,title,body,labels,state",
327
329
  "--limit",
328
330
  "1000"
329
331
  ]);
330
332
  return JSON.parse(r || "[]");
331
333
  }
332
- function Y(e, t) {
333
- return t.some((t) => U(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
+ });
334
342
  }
335
- function X(e, t, n) {
343
+ function ne(e, t, n) {
336
344
  for (let r of t) n([
337
345
  "label",
338
346
  "create",
@@ -342,7 +350,7 @@ function X(e, t, n) {
342
350
  "--force"
343
351
  ]);
344
352
  }
345
- function Z(e, t, n) {
353
+ function re(e, t, n) {
346
354
  let r = [
347
355
  "issue",
348
356
  "create",
@@ -357,7 +365,7 @@ function Z(e, t, n) {
357
365
  let i = n(r).match(/\/(\d+)\s*$/);
358
366
  return i ? parseInt(i[1], 10) : 0;
359
367
  }
360
- function Q(e, t, n) {
368
+ function ie(e, t, n) {
361
369
  n([
362
370
  "issue",
363
371
  "close",
@@ -366,36 +374,109 @@ function Q(e, t, n) {
366
374
  e
367
375
  ]);
368
376
  }
369
- function $(e, t) {
370
- let n = t?.runGh ?? K, r = _(e), i = [];
371
- if (r.storage !== "local") return [`Storage is already "${r.storage}". Nothing to migrate.`];
372
- let a = q(r, n), o = G(e, r);
373
- if (o.length === 0) return x(e, { storage: p }), i.push("No artifacts found — nothing to migrate."), i.push(`✓ Storage switched to "${p}".`), i;
374
- let s = r.github.labels?.prd ?? "tk:prd", c = r.github.labels?.plan ?? "tk:plan";
375
- X(a, [
377
+ function ae(e, t, n) {
378
+ let r = n([
379
+ "pr",
380
+ "list",
381
+ "--repo",
382
+ e,
383
+ "--search",
384
+ t,
385
+ "--state",
386
+ "merged",
387
+ "--json",
388
+ "number,title",
389
+ "--limit",
390
+ "5"
391
+ ]);
392
+ return JSON.parse(r || "[]");
393
+ }
394
+ function oe(e, t, n, r) {
395
+ r([
396
+ "issue",
397
+ "comment",
398
+ String(t),
399
+ "--repo",
400
+ e,
401
+ "--body",
402
+ n
403
+ ]);
404
+ }
405
+ function se(e) {
406
+ for (let t of e) {
407
+ let e = R[t];
408
+ if (e) return e;
409
+ }
410
+ return "created";
411
+ }
412
+ function Q(e, n) {
413
+ t(s(e), { recursive: !0 }), a(e, n);
414
+ }
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);
418
+ }
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;
422
+ let o = t.github.labels?.prd ?? "tk:prd", s = t.github.labels?.plan ?? "tk:plan";
423
+ ne(i, [
424
+ o,
376
425
  s,
377
- c,
378
- ...[...new Set(o.map((e) => H(e.metadata.status ?? "created")))]
426
+ ...[...new Set(a.map((e) => V(e.metadata.status ?? "created")))]
379
427
  ], n);
380
- let l = J(a, s, n), u = J(a, c, n);
381
- for (let e of o) {
382
- let t = e.type === "prd" ? s : c, r = e.type === "prd" ? l : u;
383
- if (Y(e.slug, r)) {
384
- i.push(`⚠ skip ${e.type} "${e.slug}" — already exists on GitHub`);
428
+ let c = X(i, o, n), l = X(i, s, n);
429
+ for (let e of a) {
430
+ let t = e.type === "prd" ? o : s, a = e.type === "prd" ? c : l;
431
+ if (Z(e.slug, a)) {
432
+ r.push(`⚠ skip ${e.type} "${e.slug}" — already exists on GitHub`);
385
433
  continue;
386
434
  }
387
- let o = H(e.metadata.status ?? "created"), d = `${V(e.metadata)}\n\n${e.body.replace(/^\n/, "")}`, f = Z(a, {
435
+ let u = e.metadata.status ?? "created", d = V(u), f = `${B(e.metadata)}\n\n${e.body.replace(/^\n/, "")}`, p = re(i, {
388
436
  title: `[${t}] ${e.slug}: ${e.title}`,
389
- body: d,
390
- labels: [t, o]
437
+ body: f,
438
+ labels: [t, d]
391
439
  }, n);
392
- e.archived ? (Q(a, f, n), i.push(`✓ ${e.type} "${e.slug}" → issue #${f} (closed)`)) : i.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}`);
393
441
  }
394
- return x(e, { storage: p }), i.push(`✓ Storage switched to "${p}".`), i;
442
+ return x(e, { storage: p }), r.push(`✓ Storage switched to "${p}".`), r;
443
+ }
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);
447
+ }
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) => ({
450
+ ...e,
451
+ type: "prd"
452
+ })), ...u.map((e) => ({
453
+ ...e,
454
+ type: "plan"
455
+ }))];
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;
457
+ for (let r of d) {
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}`);
472
+ }
473
+ i.push(`✓ ${r.type} "${s}" → ${d}`);
474
+ }
475
+ return x(t, { storage: f }), i.push(`✓ Storage switched to "${f}".`), i;
395
476
  }
396
477
  //#endregion
397
478
  //#region src/commands/uninstall.ts
398
- function ee(t) {
479
+ function de(t) {
399
480
  if (!w.some((n) => e(c(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
400
481
  let n = [];
401
482
  for (let r of w) {
@@ -408,4 +489,4 @@ function ee(t) {
408
489
  return n;
409
490
  }
410
491
  //#endregion
411
- export { P as a, T as c, f as d, _ as f, L as i, E as l, $ as n, O as o, x as p, R as r, D as s, ee as t, w 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.14.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
  -->