tracerkit 1.9.6 → 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,29 +13,25 @@ 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.
25
-
26
- **Deterministic CLI, intelligent skills.** The CLI produces structured, repeatable output — feature tables, progress counts, archive operations — while the skills layer AI reasoning on top. The foundation is deterministic, so every session starts from the same ground truth. AI adds the judgment: interviewing you for scope, designing modules, verifying implementation against specs.
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.
27
25
 
28
26
  ## Get Started
29
27
 
30
28
  ### Install
31
29
 
32
30
  ```bash
33
- npm install -g tracerkit
34
- tracerkit init
31
+ $ npm install -g tracerkit
32
+ $ tracerkit init
35
33
  ```
36
34
 
37
- The global install is by design — skills call `tracerkit brief`, `tracerkit progress`, and `tracerkit archive` directly to get deterministic output before the AI acts on it. Having the CLI in your PATH keeps this seamless.
38
-
39
35
  Skills are installed to `~/.claude/skills/`, available in every project. Safe to re-run — adds missing skills without overwriting ones you've modified.
40
36
 
41
37
  ### Workflow
@@ -77,9 +73,9 @@ See [Examples](docs/examples.md) for full walkthroughs.
77
73
  To scope skills to a single project (team members get them via git):
78
74
 
79
75
  ```bash
80
- npx tracerkit init . # install to .claude/skills/ in current dir
81
- npx tracerkit update . # update project-scoped skills
82
- 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
83
79
  ```
84
80
 
85
81
  </details>
@@ -119,7 +115,7 @@ Without arguments, shows a feature dashboard with status and progress before ask
119
115
  | Document | Description |
120
116
  | ------------------------------------------------ | -------------------------------------------------- |
121
117
  | [Examples](docs/examples.md) | Walk through end-to-end usage scenarios |
122
- | [CLI Reference](docs/cli-reference.md) | Browse all CLI commands and flags |
118
+ | [CLI Reference](docs/cli-reference.md) | Lifecycle commands: init, update, uninstall |
123
119
  | [Configuration](docs/configuration.md) | Configure custom artifact paths via `config.json` |
124
120
  | [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions |
125
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.6",
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,22 +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: !`tracerkit brief 2>&1`
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. Features with `status: done` are excluded (already archived).
17
+ ### 1. Discover features
18
18
 
19
- ### 3. Focus recommendation
19
+ For each `.md` file in `{{paths.prds}}/`:
20
20
 
21
- The **Focus** line at the bottom suggests which feature to work on:
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`
22
26
 
23
- - If exactly 1 feature is `in_progress`, it's auto-selected
24
- - Otherwise, the oldest feature by `created` date is selected
27
+ ### 2. Count progress from plans
25
28
 
26
- ### 4. Offer next steps
29
+ For each slug, check if `{{paths.plans}}/<slug>.md` exists. If it does:
30
+
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.
38
+
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
27
74
 
28
75
  Ask the user what they'd like to do:
29
76
 
@@ -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 `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 `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
- 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
@@ -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
 
@@ -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