tracerkit 1.9.5 → 1.10.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
@@ -13,25 +13,26 @@ Replace ad-hoc AI prompts with a repeatable spec-driven workflow: from idea to v
13
13
 
14
14
  Named after the tracer-bullet technique from _The Pragmatic Programmer_: **Tracer** + **Kit**.
15
15
 
16
- **Markdown skills, one CLI.** No build step, no project dependencies.
16
+ **Markdown skills, zero runtime deps.** No build step, no project dependencies.
17
17
 
18
18
  </div>
19
19
 
20
20
  ## Why TracerKit?
21
21
 
22
- Without specs, every AI session starts from scratch. Vague prompts, duplicated context, no way to confirm "done." Most planning tools produce flat task lists where nothing works until everything is done.
22
+ 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
- TracerKit takes a different approach: **tracer-bullet vertical slices**. Each phase cuts through every layer (schema service API UI tests), so every phase is demoable on its own. Integration problems surface early, context stays focused, and AI assistants get small, well-scoped phases instead of sprawling layers.
24
+ 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.
25
25
 
26
26
  ## Get Started
27
27
 
28
28
  ### Install
29
29
 
30
30
  ```bash
31
- npx tracerkit init
31
+ $ npm install -g tracerkit
32
+ $ tracerkit init
32
33
  ```
33
34
 
34
- Skills are installed globally to `~/.claude/skills/`, available in every project. Safe to re-run — adds missing skills without overwriting ones you've modified.
35
+ Skills are installed to `~/.claude/skills/`, available in every project. Safe to re-run — adds missing skills without overwriting ones you've modified.
35
36
 
36
37
  ### Workflow
37
38
 
@@ -72,16 +73,16 @@ See [Examples](docs/examples.md) for full walkthroughs.
72
73
  To scope skills to a single project (team members get them via git):
73
74
 
74
75
  ```bash
75
- npx tracerkit init . # install to .claude/skills/ in current dir
76
- npx tracerkit update . # update project-scoped skills
77
- npx tracerkit uninstall . # remove project-scoped skills
76
+ $ tracerkit init . # install to .claude/skills/ in current dir
77
+ $ tracerkit update . # update project-scoped skills
78
+ $ tracerkit uninstall . # remove project-scoped skills
78
79
  ```
79
80
 
80
81
  </details>
81
82
 
82
83
  ## Skills
83
84
 
84
- TracerKit ships three skills that take a feature from idea to verified archive.
85
+ TracerKit ships skills that take a feature from idea to verified archive.
85
86
 
86
87
  ### `/tk:prd <idea>`: Write a PRD
87
88
 
@@ -114,7 +115,7 @@ Without arguments, shows a feature dashboard with status and progress before ask
114
115
  | Document | Description |
115
116
  | ------------------------------------------------ | -------------------------------------------------- |
116
117
  | [Examples](docs/examples.md) | Walk through end-to-end usage scenarios |
117
- | [CLI Reference](docs/cli-reference.md) | Browse all CLI commands and flags |
118
+ | [CLI Reference](docs/cli-reference.md) | Lifecycle commands: init, update, uninstall |
118
119
  | [Configuration](docs/configuration.md) | Configure custom artifact paths via `config.json` |
119
120
  | [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions |
120
121
  | [Comparison](docs/comparison.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec |
package/dist/bin.js CHANGED
@@ -1,145 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { c as e, i as t, n, o as r, r as i, t as a } from "./uninstall-C4dWMK0k.js";
3
- import { existsSync as o, mkdirSync as s, readFileSync as c, readdirSync as l, rmSync as u, unlinkSync as d, writeFileSync as f } from "node:fs";
4
- import { dirname as p, join as m, resolve as h } from "node:path";
5
- import { fileURLToPath as g } from "node:url";
6
- import { homedir as _ } from "node:os";
7
- //#region src/frontmatter.ts
8
- var v = /^---\n([\s\S]*?)\n---(?:\n|$)/;
9
- function y(e) {
10
- return e.replace(/\r\n/g, "\n");
11
- }
12
- function b(e) {
13
- let t = y(e).match(v);
14
- if (!t) return {};
15
- let n = {};
16
- for (let e of t[1].split("\n")) {
17
- let t = e.indexOf(":");
18
- if (t === -1) continue;
19
- let r = e.slice(0, t).trim(), i = e.slice(t + 1).trim();
20
- r && (n[r] = i);
21
- }
22
- return n;
23
- }
24
- function x(e, t, n) {
25
- let r = y(e), i = r.match(v);
26
- if (!i) return `---\n${t}: ${n}\n---\n${r}`;
27
- let a = i[1].split("\n"), o = RegExp(`^${t}\\s*:`), s = a.findIndex((e) => o.test(e));
28
- s === -1 ? a.push(`${t}: ${n}`) : a[s] = `${t}: ${n}`;
29
- let c = r.slice(i[0].length);
30
- return `---\n${a.join("\n")}\n---\n${c}`;
31
- }
32
- //#endregion
33
- //#region src/commands/archive.ts
34
- function S(t, n) {
35
- let r = e(t), i = m(t, r.paths.prds, `${n}.md`), a = m(t, r.paths.plans, `${n}.md`), l = m(t, r.paths.archives, n), p = o(i);
36
- if (!o(a)) throw Error(`Plan "${n}" not found at ${a}`);
37
- if (o(l)) throw Error(`Archive "${n}" already exists at ${l}`);
38
- s(l, { recursive: !0 });
39
- try {
40
- let e = (/* @__PURE__ */ new Date()).toISOString(), t = [];
41
- if (p) {
42
- let t = c(i, "utf8");
43
- t = x(t, "status", "done"), t = x(t, "completed", e), f(m(l, "prd.md"), t);
44
- } else t.push(`Warning: PRD "${n}" missing, archiving plan only`);
45
- let o = c(a, "utf8");
46
- return o += `\n## Archived\n\nArchived on ${e.slice(0, 10)}.\n`, f(m(l, "plan.md"), o), p && d(i), d(a), t.push(`Archived "${n}" to ${r.paths.archives}/${n}/`), p && t.push(` prd.md — status: done, completed: ${e}`), t.push(" plan.md — archived block appended"), t;
47
- } catch (e) {
48
- throw u(l, {
49
- recursive: !0,
50
- force: !0
51
- }), e;
52
- }
53
- }
54
- //#endregion
55
- //#region src/plan.ts
56
- var C = /^## (Phase \d+\s*.*)$/, w = /^- \[x\] /i, T = /^- \[ \] /;
57
- function E(e) {
58
- let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = null;
59
- for (let e of t) {
60
- let t = e.trimStart(), i = t.match(C);
61
- if (t.startsWith("## ")) {
62
- i ? (r = {
63
- title: i[1].trim(),
64
- checked: 0,
65
- total: 0
66
- }, n.push(r)) : r = null;
67
- continue;
68
- }
69
- r && (w.test(t) ? (r.checked++, r.total++) : T.test(t) && r.total++);
70
- }
71
- return { phases: n };
72
- }
73
- //#endregion
74
- //#region src/commands/brief.ts
75
- var D = /^- \[ \] (.+)/;
76
- function O(e, t) {
77
- let n = new Date(e);
78
- if (isNaN(n.getTime())) return "";
79
- let r = Math.floor((t.getTime() - n.getTime()) / 864e5);
80
- return r < 0 ? "" : r < 7 ? `${r}d` : r < 30 ? `${Math.floor(r / 7)}w` : `${Math.floor(r / 30)}mo`;
81
- }
82
- function k(e) {
83
- for (let t of e.split("\n")) {
84
- let e = t.trimStart().match(D);
85
- if (e) return e[1].replace(/\s*\[.*?\]\s*$/, "").trim();
86
- }
87
- return "—";
88
- }
89
- function A(t, n = /* @__PURE__ */ new Date()) {
90
- let r = e(t), i = m(t, r.paths.prds);
91
- if (!o(i)) return ["No features found — run `/tk:prd` to start one."];
92
- let a = l(i).filter((e) => e.endsWith(".md")).sort();
93
- if (a.length === 0) return ["No features found — run `/tk:prd` to start one."];
94
- let s = m(t, r.paths.plans), u = [];
95
- for (let e of a) {
96
- let t = e.replace(/\.md$/, ""), n = b(c(m(i, e), "utf8"));
97
- if (n.status === "done") continue;
98
- let r = n.status || "unknown", a = n.created || "", l = a && !isNaN(new Date(a).getTime()) ? a : "", d = "—", f = "—", p = m(s, `${t}.md`);
99
- if (o(p)) {
100
- let e = c(p, "utf8"), { phases: t } = E(e), n = t.reduce((e, t) => e + t.checked, 0), r = t.reduce((e, t) => e + t.total, 0);
101
- r > 0 && (d = `${n}/${r}`), f = k(e);
102
- }
103
- u.push({
104
- slug: t,
105
- status: r,
106
- created: l,
107
- progress: d,
108
- next: f
109
- });
110
- }
111
- if (u.length === 0) return ["No features found — run `/tk:prd` to start one."];
112
- u.sort((e, t) => e.created && t.created ? new Date(e.created).getTime() - new Date(t.created).getTime() : e.created ? -1 : t.created ? 1 : 0);
113
- let d = u.map((e) => {
114
- let t = e.created ? O(e.created, n) : "";
115
- return `| ${e.slug} | ${e.status} | ${t} | ${e.progress} | ${e.next} |`;
116
- }), f = u.filter((e) => e.status === "in_progress"), p = f.length === 1 ? f[0] : f[0] ?? u[0];
117
- return [
118
- "| Feature | Status | Age | Progress | Next |",
119
- "|---------|--------|-----|----------|------|",
120
- ...d,
121
- "",
122
- `**Focus → ${p.slug}**`
123
- ];
124
- }
125
- //#endregion
126
- //#region src/commands/progress.ts
127
- function j(t, n) {
128
- let r = m(t, e(t).paths.plans, `${n}.md`);
129
- if (!o(r)) throw Error(`Plan "${n}" not found at ${r}`);
130
- let { phases: i } = E(c(r, "utf8"));
131
- if (i.length === 0) return ["No phases found in plan.", "Total: 0/0"];
132
- let a = [], s = 0, l = 0;
133
- for (let e of i) s += e.checked, l += e.total, a.push(` ${e.title}: ${e.checked}/${e.total}`);
134
- return a.push(""), a.push(`Total: ${s}/${l}`), a;
135
- }
136
- //#endregion
2
+ import { a as e, i as t, n, r, s as i, t as a } from "./uninstall-Bgs58wa2.js";
3
+ import { readFileSync as o } from "node:fs";
4
+ import { dirname as s, resolve as c } from "node:path";
5
+ import { fileURLToPath as l } from "node:url";
6
+ import { homedir as u } from "node:os";
137
7
  //#region src/cli.ts
138
- var { version: M } = JSON.parse(c(h(p(g(import.meta.url)), "..", "package.json"), "utf8")), N = Math.max(...t.map((e) => `${e.name} ${e.args}`.length)), P = [
8
+ var { version: d } = JSON.parse(o(c(s(l(import.meta.url)), "..", "package.json"), "utf8")), f = Math.max(...t.map((e) => `${e.name} ${e.args}`.length)), p = [
139
9
  "Usage: tracerkit <command> [path]",
140
10
  "",
141
11
  "Commands:",
142
- ...t.map((e) => ` ${`${e.name} ${e.args}`.padEnd(N + 2)}${e.desc}`),
12
+ ...t.map((e) => ` ${`${e.name} ${e.args}`.padEnd(f + 2)}${e.desc}`),
143
13
  "",
144
14
  "Options:",
145
15
  " --force Overwrite modified files during update",
@@ -148,43 +18,27 @@ var { version: M } = JSON.parse(c(h(p(g(import.meta.url)), "..", "package.json")
148
18
  "",
149
19
  "All commands default to the home directory when no path is given."
150
20
  ];
151
- function F(e, t = _()) {
21
+ function m(e, t = u()) {
152
22
  let n = e.find((e) => !e.startsWith("-"));
153
- return n ? h(n) : t;
154
- }
155
- function I(e, t) {
156
- let n = e.findIndex((e) => !e.startsWith("-"));
157
- if (n === -1) return [
158
- "Error: missing <slug> argument",
159
- "",
160
- ...P
161
- ];
162
- let r = e[n], i = e.filter((e, t) => t !== n);
163
- try {
164
- return t(F(i, process.cwd()), r);
165
- } catch (e) {
166
- return [`Error: ${e instanceof Error ? e.message : String(e)}`];
167
- }
168
- }
169
- function L(e) {
170
- if (e.includes(r.help) || e.includes("-h")) return P;
171
- if (e.includes(r.version) || e.includes("-v")) return [`tracerkit/${M}`];
172
- let t = e[0], o = e.slice(1);
173
- switch (t) {
174
- case "brief": return A(F(o, process.cwd()));
175
- case "init": return n(F(o));
23
+ return n ? c(n) : t;
24
+ }
25
+ function h(t) {
26
+ if (t.includes(i.help) || t.includes("-h")) return p;
27
+ if (t.includes(i.version) || t.includes("-v")) return [`tracerkit/${d}`];
28
+ let o = t[0], s = t.slice(1);
29
+ if (e.includes(o)) return [`"${o}" has been removed — skills handle this now.`, "Run `tracerkit update` to get the latest skills."];
30
+ switch (o) {
31
+ case "init": return n(m(s));
176
32
  case "update": {
177
- let e = o.includes(r.force), t = i(F(o.filter((e) => e !== r.force)), { force: e });
33
+ let e = s.includes(i.force), t = r(m(s.filter((e) => e !== i.force)), { force: e });
178
34
  return t.push("", "Updated to the latest TracerKit."), t.push("If using Claude Code, restart your session to load changes."), t;
179
35
  }
180
- case "uninstall": return a(F(o));
181
- case "progress": return I(o, j);
182
- case "archive": return I(o, S);
183
- default: return P;
36
+ case "uninstall": return a(m(s));
37
+ default: return p;
184
38
  }
185
39
  }
186
40
  //#endregion
187
41
  //#region src/bin.ts
188
- var R = L(process.argv.slice(2));
189
- for (let e of R) console.log(e);
42
+ var g = h(process.argv.slice(2));
43
+ for (let e of g) console.log(e);
190
44
  //#endregion
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, s as i, t as a } from "./uninstall-C4dWMK0k.js";
2
- export { t as COMMANDS, e as DEPRECATED_SKILLS, i as SKILL_NAMES, n as init, a as uninstall, r as update };
1
+ import { c as e, i as t, n, o as r, r as i, t as a } from "./uninstall-Bgs58wa2.js";
2
+ export { t as COMMANDS, r as DEPRECATED_SKILLS, e as SKILL_NAMES, n as init, a as uninstall, i as update };
@@ -34,6 +34,10 @@ var f = [
34
34
  help: "--help",
35
35
  version: "--version"
36
36
  }, h = [
37
+ "brief",
38
+ "progress",
39
+ "archive"
40
+ ], g = [
37
41
  {
38
42
  name: "init",
39
43
  args: "[path]",
@@ -48,53 +52,38 @@ var f = [
48
52
  name: "uninstall",
49
53
  args: "[path]",
50
54
  desc: "Remove TracerKit skill directories, keep .tracerkit/ artifacts"
51
- },
52
- {
53
- name: "brief",
54
- args: "[path]",
55
- desc: "Show active features, progress, and suggested focus"
56
- },
57
- {
58
- name: "progress",
59
- args: "<slug>",
60
- desc: "Show per-phase checkbox progress for a plan"
61
- },
62
- {
63
- name: "archive",
64
- args: "<slug>",
65
- desc: "Archive a completed feature (PRD + plan)"
66
55
  }
67
- ], g = s(o(l(import.meta.url)), "..", "templates");
68
- function _(e, t = "") {
56
+ ], _ = s(o(l(import.meta.url)), "..", "templates");
57
+ function v(e, t = "") {
69
58
  let n = r(e, { withFileTypes: !0 }), i = [];
70
59
  for (let r of n) {
71
60
  let n = t ? `${t}/${r.name}` : r.name;
72
- r.isDirectory() ? i.push(..._(s(e, r.name), n)) : i.push(n);
61
+ r.isDirectory() ? i.push(...v(s(e, r.name), n)) : i.push(n);
73
62
  }
74
63
  return i.sort();
75
64
  }
76
- function v(e, t) {
65
+ function y(e, t) {
77
66
  return e.replaceAll("{{paths.prds}}", t.paths.prds).replaceAll("{{paths.plans}}", t.paths.plans).replaceAll("{{paths.archives}}", t.paths.archives);
78
67
  }
79
- function y(e, r, i) {
80
- let c = i ?? _(g);
68
+ function b(e, r, i) {
69
+ let c = i ?? v(_);
81
70
  for (let i of c) {
82
- let c = s(g, i), l = s(e, i);
83
- t(o(l), { recursive: !0 }), a(l, v(n(c, "utf8"), r));
71
+ let c = s(_, i), l = s(e, i);
72
+ t(o(l), { recursive: !0 }), a(l, y(n(c, "utf8"), r));
84
73
  }
85
74
  return { copied: c };
86
75
  }
87
- function b(e) {
76
+ function x(e) {
88
77
  return c("sha256").update(e).digest("hex");
89
78
  }
90
- function x(t, r) {
91
- let i = _(g), a = [], o = [], c = [];
79
+ function S(t, r) {
80
+ let i = v(_), a = [], o = [], c = [];
92
81
  for (let l of i) {
93
82
  let i = s(t, l);
94
83
  if (!e(i)) c.push(l);
95
84
  else {
96
- let e = v(n(s(g, l), "utf8"), r);
97
- b(Buffer.from(e)) === b(n(i)) ? a.push(l) : o.push(l);
85
+ let e = y(n(s(_, l), "utf8"), r);
86
+ x(Buffer.from(e)) === x(n(i)) ? a.push(l) : o.push(l);
98
87
  }
99
88
  }
100
89
  return {
@@ -105,9 +94,9 @@ function x(t, r) {
105
94
  }
106
95
  //#endregion
107
96
  //#region src/commands/update.ts
108
- function S(t, n) {
97
+ function C(t, n) {
109
98
  if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — run `tracerkit init` first");
110
- let r = d(t), { unchanged: a, modified: o, missing: c } = x(t, r), l = [];
99
+ let r = d(t), { unchanged: a, modified: o, missing: c } = S(t, r), l = [];
111
100
  for (let n of p) {
112
101
  let r = s(t, ".claude", "skills", n);
113
102
  e(r) && (i(r, {
@@ -121,7 +110,7 @@ function S(t, n) {
121
110
  ...u ? o : []
122
111
  ];
123
112
  if (m.length > 0) {
124
- y(t, r, m);
113
+ b(t, r, m);
125
114
  for (let e of a) l.push(`✓ ${e}`);
126
115
  for (let e of c) l.push(`✓ ${e} (added)`);
127
116
  if (u) for (let e of o) l.push(`✓ ${e} (replaced)`);
@@ -134,14 +123,14 @@ function S(t, n) {
134
123
  }
135
124
  //#endregion
136
125
  //#region src/commands/init.ts
137
- function C(t) {
138
- if (f.some((n) => e(s(t, ".claude", "skills", n)))) return S(t);
139
- let { copied: n } = y(t, d(t));
126
+ function w(t) {
127
+ if (f.some((n) => e(s(t, ".claude", "skills", n)))) return C(t);
128
+ let { copied: n } = b(t, d(t));
140
129
  return n.map((e) => `✓ ${e}`);
141
130
  }
142
131
  //#endregion
143
132
  //#region src/commands/uninstall.ts
144
- function w(t) {
133
+ function T(t) {
145
134
  if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
146
135
  let n = [];
147
136
  for (let r of f) {
@@ -154,4 +143,4 @@ function w(t) {
154
143
  return n;
155
144
  }
156
145
  //#endregion
157
- export { p as a, d as c, h as i, C as n, m as o, S as r, f as s, w as t };
146
+ export { h as a, f as c, g as i, w as n, p as o, C as r, m as s, T as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracerkit",
3
- "version": "1.9.5",
3
+ "version": "1.10.0",
4
4
  "description": "Spec-driven workflow for Claude Code: replace ad-hoc prompts with PRD → plan → verify.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -8,30 +8,69 @@ Get a quick overview of all active features, their progress, and what to focus o
8
8
 
9
9
  ## Pre-loaded context
10
10
 
11
- - Briefing: !`npx tracerkit brief 2>/dev/null || echo "no {{paths.prds}}/ directory found"`
11
+ - Available PRDs: !`ls {{paths.prds}}/ 2>&1`
12
12
 
13
- ## Workflow
13
+ ## Algorithm
14
14
 
15
- ### 1. Present the briefing
15
+ Follow these steps exactly to build the briefing table:
16
16
 
17
- Display the output above as-is. The table includes:
17
+ ### 1. Discover features
18
18
 
19
- - **Feature**: slug (PRD filename without `.md`)
20
- - **Status**: from PRD frontmatter (`created`, `in_progress`) — `unknown` if no frontmatter
21
- - **Age**: time since `created` date
22
- - **Progress**: checked/total from plan checkboxes, or `—` if no plan
23
- - **Next**: first unchecked item from the plan, or `—`
19
+ For each `.md` file in `{{paths.prds}}/`:
24
20
 
25
- Features with `status: done` are excluded (already archived).
21
+ 1. Read the file
22
+ 2. Parse YAML frontmatter (the block between `---` fences at the top)
23
+ 3. Extract `status` and `created` fields
24
+ 4. Skip files where `status: done`
25
+ 5. The slug is the filename without `.md`
26
26
 
27
- ### 2. Focus recommendation
27
+ ### 2. Count progress from plans
28
28
 
29
- The **Focus** line at the bottom suggests which feature to work on:
29
+ For each slug, check if `{{paths.plans}}/<slug>.md` exists. If it does:
30
30
 
31
- - If exactly 1 feature is `in_progress`, it's auto-selected
32
- - Otherwise, the oldest feature by `created` date is selected
31
+ 1. Read the plan file
32
+ 2. Find every `## Phase N` heading (regex: `^## Phase \d+`)
33
+ 3. Within each phase section (until the next `## ` heading), count:
34
+ - Checked items: lines matching `^- \[x\] ` (case-insensitive)
35
+ - Unchecked items: lines matching `^- \[ \] `
36
+ 4. Sum checked and total across all phases → `checked/total`
37
+ 5. Find the first unchecked item (`^- \[ \] (.+)`) in the entire plan — that's the "Next" value. Strip any trailing `[tag]` markers.
33
38
 
34
- ### 3. Offer next steps
39
+ If no plan exists, progress is `—` and next is `—`.
40
+
41
+ ### 3. Build the table
42
+
43
+ Sort features by `created` date ascending (no-date entries last). Calculate age from `created`:
44
+
45
+ - < 7 days → `Nd` (e.g. `3d`)
46
+ - < 30 days → `Nw` (e.g. `2w`)
47
+ - 30+ days → `Nmo` (e.g. `1mo`)
48
+
49
+ Output this exact table format:
50
+
51
+ ```
52
+ | Feature | Status | Age | Progress | Next |
53
+ |---------|--------|-----|----------|------|
54
+ | <slug> | <status> | <age> | <checked>/<total> | <next item> |
55
+ ```
56
+
57
+ If no features found, output: `No features found — run /tk:prd to start one.`
58
+
59
+ ### 4. Determine focus
60
+
61
+ Apply these rules in order:
62
+
63
+ 1. Exactly 1 feature with `status: in_progress` → auto-select it
64
+ 2. Multiple `in_progress` features → select the oldest by `created`
65
+ 3. Zero `in_progress` features → select the oldest overall
66
+
67
+ Append below the table:
68
+
69
+ ```
70
+ **Focus → <slug>**
71
+ ```
72
+
73
+ ### 5. Offer next steps
35
74
 
36
75
  Ask the user what they'd like to do:
37
76
 
@@ -9,7 +9,7 @@ Check implementation against a plan. Update checks, stamp findings, transition s
9
9
 
10
10
  ## Pre-loaded context
11
11
 
12
- - Available plans: !`ls {{paths.plans}}/ 2>/dev/null || echo "no {{paths.plans}}/ directory found"`
12
+ - Available plans: !`ls {{paths.plans}}/ 2>&1`
13
13
 
14
14
  ## Input
15
15
 
@@ -17,20 +17,33 @@ The argument (if provided) is: $ARGUMENTS
17
17
 
18
18
  Use the argument as `<slug>` if given.
19
19
 
20
- If no argument is provided, scan `{{paths.prds}}/` and `{{paths.plans}}/` and show a summary table before asking which one to check:
20
+ If no argument is provided, build a summary table before asking which one to check:
21
21
 
22
- ```
22
+ ```markdown
23
23
  | Feature | Status | Progress |
24
- |---------|--------|----------|
24
+ | ------- | ------ | -------- |
25
25
  | <slug> | ... | 3/7 |
26
26
  ```
27
27
 
28
- - **Feature**: slug (filename without `.md`)
29
- - **Status**: from PRD frontmatter (`created`, `in_progress`, `done`) — `unknown` if no frontmatter
30
- - **Progress**: run `npx tracerkit progress <slug>` for each feature with a plan — use the Total line (e.g. "3/7"). Show `—` if no plan.
28
+ For each `.md` file in `{{paths.prds}}/`:
29
+
30
+ 1. Read the file, parse YAML frontmatter (block between `---` fences)
31
+ 2. Extract `status` — use `unknown` if missing
32
+ 3. If `{{paths.plans}}/<slug>.md` exists, count progress (see Progress Algorithm below). Show `—` if no plan.
31
33
 
32
34
  After the table, ask which feature to verify.
33
35
 
36
+ ## Progress Algorithm
37
+
38
+ To count progress for a plan file:
39
+
40
+ 1. Find every `## Phase N` heading (regex: `^## Phase \d+`)
41
+ 2. Within each phase section (until the next `## ` heading), count:
42
+ - Checked: lines matching `^- \[x\] ` (case-insensitive)
43
+ - Unchecked: lines matching `^- \[ \] `
44
+ 3. Per-phase output: ` Phase N — title: checked/total`
45
+ 4. Sum across all phases → `Total: checked/total`
46
+
34
47
  ## Workflow
35
48
 
36
49
  ### 1. Load the plan
@@ -75,20 +88,25 @@ Based on checks and findings, decide the status transition:
75
88
 
76
89
  ### 5. Report to user
77
90
 
78
- Run `npx tracerkit progress <slug>` to get exact per-phase progress, then print the verdict report:
91
+ Count progress per phase using the Progress Algorithm above, then print the verdict report:
79
92
 
80
- ```
93
+ ```markdown
81
94
  ## Verification: <slug>
82
95
 
83
96
  ### Status: created | in_progress | done
84
97
 
85
98
  ### Progress
86
- <output from tracerkit progress>
99
+
100
+ Phase 1 — title: checked/total
101
+ Phase 2 — title: checked/total
102
+ Total: checked/total
87
103
 
88
104
  ### BLOCKERS
105
+
89
106
  - (list or "None")
90
107
 
91
108
  ### SUGGESTIONS
109
+
92
110
  - (list or "None")
93
111
  ```
94
112
 
@@ -111,13 +129,19 @@ If a previous verdict block exists, replace it with the new one.
111
129
 
112
130
  ### 7. On `done` — archive
113
131
 
114
- If all checks pass and zero BLOCKERS, run:
115
-
116
- ```
117
- npx tracerkit archive <slug>
118
- ```
132
+ If all checks pass and zero BLOCKERS, perform these steps in order:
119
133
 
120
- This handles: PRD frontmatter update (`status: done`, `completed` timestamp), file moves to `{{paths.archives}}/<slug>/`, and archived block on the plan.
134
+ 1. Create directory `{{paths.archives}}/<slug>/`
135
+ 2. If `{{paths.prds}}/<slug>.md` exists:
136
+ - Read the PRD file
137
+ - In the YAML frontmatter (between `---` fences), find the `status:` line and replace its value with `done`. If no `status:` line exists, add `status: done` as a new line inside the frontmatter block.
138
+ - Add a `completed: <current UTC ISO 8601 timestamp>` line inside the frontmatter block (e.g. `completed: 2025-06-15T14:30:00Z`)
139
+ - Write the updated content to `{{paths.archives}}/<slug>/prd.md`
140
+ 3. Read `{{paths.plans}}/<slug>.md`
141
+ - Append to the end: `\n## Archived\n\nArchived on YYYY-MM-DD.\n`
142
+ - Write the result to `{{paths.archives}}/<slug>/plan.md`
143
+ 4. Delete `{{paths.prds}}/<slug>.md` (if it exists)
144
+ 5. Delete `{{paths.plans}}/<slug>.md`
121
145
 
122
146
  Tell the user: archived to `{{paths.archives}}/<slug>/`, one-line summary of the feature.
123
147
 
@@ -132,14 +156,13 @@ List the blockers to fix, then re-run `/tk:check <slug>`.
132
156
  ## Rules
133
157
 
134
158
  - The review subagent must be **read-only** — it must not create, edit, or delete any files
135
- - The only file writes this skill makes are: checkboxes + verdict block in the plan, and the archive command on `done`
136
- - Never modify the source PRD manually — `tracerkit archive` handles frontmatter updates
159
+ - The only file writes this skill makes are: checkboxes + verdict block in the plan, and the archive steps on `done`
137
160
  - Never modify implementation code — only observe and report
138
- - If the PRD file is missing but all checks pass, warn and proceed — `tracerkit archive` supports plan-only archiving
161
+ - If the PRD file is missing but all checks pass, warn and proceed — archive the plan only (skip PRD steps in archive)
139
162
 
140
163
  ## Error Handling
141
164
 
142
165
  - Plan not found — list available plans and ask
143
166
  - PRD referenced in plan not found — warn and continue with plan checks only
144
167
  - `{{paths.plans}}/` missing — tell user to run `/tk:plan` first
145
- - `{{paths.archives}}/<slug>/` already exists — `tracerkit archive` will error; warn and ask whether to remove it first
168
+ - `{{paths.archives}}/<slug>/` already exists — warn and ask whether to remove it first
@@ -9,7 +9,7 @@ Break a PRD into phased vertical slices (tracer bullets). Output: `{{paths.plans
9
9
 
10
10
  ## Pre-loaded context
11
11
 
12
- - Available PRDs: !`ls {{paths.prds}}/ 2>/dev/null || echo "no {{paths.prds}}/ directory found"`
12
+ - Available PRDs: !`ls {{paths.prds}}/ 2>&1`
13
13
 
14
14
  ## Input
15
15
 
@@ -67,11 +67,17 @@ Each phase is a thin **tracer bullet** — a narrow but complete path through ev
67
67
 
68
68
  **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.
69
69
 
70
- **Done when:** write as a checkbox list of atomic, testable conditions — not prose. Each item should be independently verifiable. The agent marks `[x]` during implementation to track progress.
70
+ **Done when:** write as a checkbox list of atomic, verifiable conditions — not prose. Each item must pass this test: _"Can an agent verify this by reading files, running a command, or checking a test result — with no subjective judgment?"_ Bad: "API is clean". Good: "`GET /api/revenue` returns `{ total: number }`". The agent marks `[x]` during implementation to track progress.
71
71
 
72
72
  **When to use layer-by-layer instead:** If the PRD has complex schema changes that all modules depend on and no single user story can stand alone without the full schema, build the data foundation first, then slice the rest vertically.
73
73
 
74
- **Phase count:** 2–3 for single-module, 3–5 for multi-module, 5+ means consider splitting the PRD.
74
+ **Phase count thresholds:**
75
+
76
+ - 1 module touched → 2–3 phases max
77
+ - 2–3 modules touched → 3–5 phases max
78
+ - 4+ modules or 6+ phases → stop and ask the user to split the PRD
79
+
80
+ Count "modules touched" by scanning the PRD's New Modules and Schema Changes sections.
75
81
 
76
82
  Assign an agent tag to tasks where appropriate:
77
83
 
@@ -9,7 +9,7 @@ Skip steps already satisfied. If user provided a description via arguments, skip
9
9
 
10
10
  ## Pre-loaded context
11
11
 
12
- - Existing PRDs: !`ls {{paths.prds}}/ 2>/dev/null || echo "none"`
12
+ - Existing PRDs: !`ls {{paths.prds}}/ 2>&1`
13
13
 
14
14
  ## Input
15
15
 
@@ -17,7 +17,15 @@ The argument is: $ARGUMENTS
17
17
 
18
18
  If the argument is empty, go straight to Step 1 (gather problem description). After gathering the idea, derive the slug from the idea.
19
19
 
20
- If the argument is provided, derive a slug: use only the text before `—` or `–` (if present), take at most 3–4 keywords, strip filler words (a, the, for, etc.), then convert to kebab-case (lowercase, hyphens). This is `<slug>`. The output file is `{{paths.prds}}/<slug>.md`.
20
+ If the argument is provided, derive a slug using this exact algorithm:
21
+
22
+ 1. Take only the text before the first `—` or `–` (if present)
23
+ 2. Lowercase the text
24
+ 3. Remove filler words: a, an, the, for, of, to, in, on, with, and, or, but, is, be
25
+ 4. Take the first 4 remaining words (or fewer if less exist)
26
+ 5. Join with hyphens → `<slug>`
27
+
28
+ The output file is `{{paths.prds}}/<slug>.md`.
21
29
 
22
30
  If `{{paths.prds}}/<slug>.md` already exists, tell the user and ask whether to overwrite or pick a new name.
23
31
 
@@ -37,19 +45,26 @@ Verify assertions and map current state: data models, services, API routes, fron
37
45
 
38
46
  Interview relentlessly, one question at a time. Lead with your recommended answer; let the user confirm or correct. If a question can be answered by exploring code, explore instead of asking. For terse answers, offer concrete options (A/B/C).
39
47
 
40
- Walk these branches (skip any already resolved or irrelevant to the project type e.g., for CLI tools and libraries, skip UI-specific branches like Display, Access, Navigation unless the idea involves them):
48
+ Walk these branches. **Skip rule**: skip a branch when the project type makes it irrelevant (e.g., skip Display/Access/Navigation for CLIs and libraries) AND the user's idea does not mention it.
41
49
 
42
- - **Scope & Surface** — Where does this live? New page/view or integrated? Which user roles?
43
- - **Data & Concepts** — Precise definitions for each new concept. What data exists, what's missing?
44
- - **Behavior & Interaction** — How does the user interact? Sorting, filtering, search, time ranges?
45
- - **Display & Output** — Numbers, tables, charts, forms? Exportable? URL-driven state?
46
- - **Access & Privacy** — Who sees what? Role-based restrictions? Sensitive data concerns?
47
- - **Boundaries** — What is explicitly out of scope? Adjacent features to defer?
48
- - **Integration** — Schema changes? New or extended services? External dependencies?
50
+ - **Scope & Surface** — Where does this live? New page/view or integrated? Which user roles? _Skip if_: single-surface project (CLI, library) with no new entry points.
51
+ - **Data & Concepts** — Precise definitions for each new concept. What data exists, what's missing? _Never skip_ — every feature has data.
52
+ - **Behavior & Interaction** — How does the user interact? Sorting, filtering, search, time ranges? _Skip if_: feature is purely internal/backend with no user-facing behavior change.
53
+ - **Display & Output** — Numbers, tables, charts, forms? Exportable? URL-driven state? _Skip if_: no UI or formatted output involved.
54
+ - **Access & Privacy** — Who sees what? Role-based restrictions? Sensitive data concerns? _Skip if_: single-user project with no auth layer.
55
+ - **Boundaries** — What is explicitly out of scope? Adjacent features to defer? _Never skip_ — scope control prevents creep.
56
+ - **Integration** — Schema changes? New or extended services? External dependencies? _Skip if_: self-contained change touching no external systems or storage.
49
57
 
50
58
  ### 3b. Gray area checkpoint
51
59
 
52
- Before continuing, list any unresolved ambiguities from the interview vague answers, contradictions, assumptions you made without confirmation. Present them as a numbered list:
60
+ Before continuing, scan the interview for gray areas. Something is a gray area if any of these are true:
61
+
62
+ - **Vague answer**: user said "maybe", "probably", "I think", or gave a one-word answer to a multi-part question
63
+ - **Contradiction**: two answers conflict (e.g., "no auth needed" but "only admins can access")
64
+ - **Unstated assumption**: you filled in a detail the user never confirmed
65
+ - **Ambiguous scope**: a feature boundary is unclear (could be in or out of scope)
66
+
67
+ Present as a numbered list:
53
68
 
54
69
  ```
55
70
  Gray areas found:
@@ -57,7 +72,7 @@ Gray areas found:
57
72
  2. <ambiguity> — two options: A or B
58
73
  ```
59
74
 
60
- If the list is empty, say so and move on. Otherwise, resolve each item with the user before proceeding.
75
+ If the list is empty, say "No gray areas found" and move on. Otherwise, resolve each item with the user before proceeding.
61
76
 
62
77
  ### 4. Design modules
63
78