tracerkit 1.11.2 → 1.12.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
@@ -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
+ 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
 
@@ -32,7 +32,7 @@ npm install -g tracerkit
32
32
  tracerkit init
33
33
  ```
34
34
 
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
+ Skills are installed to `~/.claude/skills/`, available in every project. Safe to re-run: adds missing skills without overwriting ones you've modified.
36
36
 
37
37
  <details>
38
38
  <summary>Per-project install (team members get skills via git)</summary>
@@ -54,7 +54,7 @@ Inside Claude Code, run:
54
54
  /plugin install tk@claude-plugins-official
55
55
  ```
56
56
 
57
- Run `/reload-plugins` if needed. Skills are available immediately no build step, no config.
57
+ Run `/reload-plugins` if needed. Skills are available immediately, no build step, no config.
58
58
 
59
59
  </details>
60
60
 
@@ -91,6 +91,20 @@ AI: | Feature | Status | Age | Progress | Next
91
91
 
92
92
  See [Examples](docs/examples.md) for full walkthroughs.
93
93
 
94
+ <details>
95
+ <summary>GitHub Issues as storage backend</summary>
96
+
97
+ Same skills, same workflow. Artifacts live in GitHub Issues instead of local files:
98
+
99
+ ```bash
100
+ tracerkit init --storage github # first install
101
+ tracerkit config github.repo org/repo # set target repo
102
+ ```
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. See [Configuration](docs/configuration.md) for details.
105
+
106
+ </details>
107
+
94
108
  ## Skills
95
109
 
96
110
  TracerKit ships skills that take a feature from idea to verified archive.
@@ -99,19 +113,19 @@ TracerKit ships skills that take a feature from idea to verified archive.
99
113
 
100
114
  Interactive interview that explores your codebase, asks scoping questions one at a time, designs deep modules, and writes a structured PRD.
101
115
 
102
- **Output:** `.tracerkit/prds/<slug>.md`
116
+ **Output:** `.tracerkit/prds/<slug>.md` (local) or GitHub Issue with `tk:prd` label
103
117
 
104
118
  ### `/tk:plan <slug>`: Create an implementation plan
105
119
 
106
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.
107
121
 
108
- **Output:** `.tracerkit/plans/<slug>.md`
122
+ **Output:** `.tracerkit/plans/<slug>.md` (local) or GitHub Issue with `tk:plan` label
109
123
 
110
124
  ### `/tk:brief`: Session briefing
111
125
 
112
126
  Shows active features, their progress, and suggested focus. Use at the start of a session to orient.
113
127
 
114
- **Output:** Feature dashboard in the terminal no files written.
128
+ **Output:** Feature dashboard in the terminal. No files written.
115
129
 
116
130
  ### `/tk:check [slug]`: Verify and archive
117
131
 
@@ -119,7 +133,7 @@ Verifies the codebase against the plan's done-when checkboxes. Runs tests, valid
119
133
 
120
134
  Without arguments, shows a feature dashboard with status and progress before asking which feature to check.
121
135
 
122
- **Output:** Verdict block in `.tracerkit/plans/<slug>.md`. On `done`: `.tracerkit/archives/<slug>/prd.md` + `.tracerkit/archives/<slug>/plan.md`
136
+ **Output:** Verdict block appended to the plan. On `done`: archives to `.tracerkit/archives/<slug>/` (local) or closes both issues (GitHub).
123
137
 
124
138
  ## Docs
125
139
 
@@ -127,7 +141,7 @@ Without arguments, shows a feature dashboard with status and progress before ask
127
141
  | ------------------------------------------------ | -------------------------------------------------- |
128
142
  | [Examples](docs/examples.md) | Walk through end-to-end usage scenarios |
129
143
  | [CLI Reference](docs/cli-reference.md) | Lifecycle commands: init, update, uninstall |
130
- | [Configuration](docs/configuration.md) | Configure custom artifact paths via `config.json` |
144
+ | [Configuration](docs/configuration.md) | Storage backends, GitHub options, custom paths |
131
145
  | [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions |
132
146
  | [Comparison](docs/comparison.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec |
133
147
 
package/dist/bin.js CHANGED
@@ -1,44 +1,101 @@
1
1
  #!/usr/bin/env node
2
- import { a as e, i as t, n, r, s as i, t as a } from "./uninstall-BhTMOfMb.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";
2
+ import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, t as l } from "./uninstall-BkaWoWd0.js";
3
+ import { existsSync as u, readFileSync as d, statSync 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/commands/config.ts
8
+ function v(e, t) {
9
+ let [n, r] = t;
10
+ return n ? r ? x(e, n, r) : b(e, n) : y(e);
11
+ }
12
+ function y(e) {
13
+ let t = n(e), r = t.storage === "local" ? {
14
+ storage: t.storage,
15
+ paths: t.paths
16
+ } : t;
17
+ return JSON.stringify(r, null, 2).split("\n");
18
+ }
19
+ function b(e, t) {
20
+ let r = C(n(e), t);
21
+ return r === void 0 ? [`Unknown key: ${t}`] : typeof r == "object" ? [JSON.stringify(r, null, 2)] : [String(r)];
22
+ }
23
+ function x(e, t, n) {
24
+ r(e, w(t, n));
25
+ let i = [`✓ Set ${t} = ${n}`];
26
+ return S(e, i), i;
27
+ }
28
+ function S(e, t) {
29
+ a.some((t) => u(m(e, ".claude", "skills", t))) && (i(e, n(e)), t.push("✓ Skills re-rendered"));
30
+ }
31
+ function C(e, t) {
32
+ let n = t.split("."), r = e;
33
+ for (let e of n) {
34
+ if (typeof r != "object" || !r) return;
35
+ r = r[e];
36
+ }
37
+ return r;
38
+ }
39
+ function w(e, t) {
40
+ let n = e.split("."), r = {}, i = r;
41
+ for (let e = 0; e < n.length - 1; e++) {
42
+ let t = {};
43
+ i[n[e]] = t, i = t;
44
+ }
45
+ return i[n[n.length - 1]] = t, r;
46
+ }
47
+ //#endregion
7
48
  //#region src/cli.ts
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 = [
49
+ var { version: T } = JSON.parse(d(h(p(g(import.meta.url)), "..", "package.json"), "utf8")), E = Math.max(...e.map((e) => `${e.name} ${e.args}`.length)), D = [
9
50
  "Usage: tracerkit <command> [path]",
10
51
  "",
11
52
  "Commands:",
12
- ...t.map((e) => ` ${`${e.name} ${e.args}`.padEnd(f + 2)}${e.desc}`),
53
+ ...e.map((e) => ` ${`${e.name} ${e.args}`.padEnd(E + 2)}${e.desc}`),
13
54
  "",
14
55
  "Options:",
15
56
  " --force Overwrite modified files during update",
57
+ " --storage <type> Set storage (local, github) during init",
16
58
  " --help, -h Show this help message",
17
59
  " --version, -v Print version",
18
60
  "",
19
61
  "All commands default to the home directory when no path is given."
20
62
  ];
21
- function m(e, t = u()) {
63
+ function O(e) {
64
+ if (!e) return !1;
65
+ try {
66
+ return f(h(e)).isDirectory();
67
+ } catch {
68
+ return !1;
69
+ }
70
+ }
71
+ function k(e, t = _()) {
22
72
  let n = e.find((e) => !e.startsWith("-"));
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));
73
+ return n ? h(n) : t;
74
+ }
75
+ function A(e) {
76
+ if (e.includes(t.help) || e.includes("-h")) return D;
77
+ if (e.includes(t.version) || e.includes("-v")) return [`tracerkit/${T}`];
78
+ let n = e[0], r = e.slice(1);
79
+ if (s.includes(n)) return [`"${n}" has been removed — skills handle this now.`, "Run `tracerkit update` to get the latest skills."];
80
+ switch (n) {
81
+ case "init": {
82
+ let e = r.indexOf(t.storage), n = e >= 0 ? r[e + 1] : void 0;
83
+ return o(k(e >= 0 ? r.filter((t, n) => n !== e && n !== e + 1) : r), { storage: n });
84
+ }
32
85
  case "update": {
33
- let e = s.includes(i.force), t = r(m(s.filter((e) => e !== i.force)), { force: e });
34
- return t.push("", "Updated to the latest TracerKit."), t.push("If using Claude Code, restart your session to load changes."), t;
86
+ let e = r.includes(t.force), n = c(k(r.filter((e) => e !== t.force)), { force: e });
87
+ return n.push("", "Updated to the latest TracerKit."), n.push("If using Claude Code, restart your session to load changes."), n;
88
+ }
89
+ case "config": {
90
+ let e = O(r[0]) ? h(r[0]) : _();
91
+ return v(e, e === _() ? r : r.slice(1));
35
92
  }
36
- case "uninstall": return a(m(s));
37
- default: return p;
93
+ case "uninstall": return l(k(r));
94
+ default: return D;
38
95
  }
39
96
  }
40
97
  //#endregion
41
98
  //#region src/bin.ts
42
- var g = h(process.argv.slice(2));
43
- for (let e of g) console.log(e);
99
+ var j = A(process.argv.slice(2));
100
+ for (let e of j) console.log(e);
44
101
  //#endregion
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 } from "./uninstall-BhTMOfMb.js";
2
- export { t as COMMANDS, r as DEPRECATED_SKILLS, e as SKILL_NAMES, n as init, a as uninstall, i as update };
1
+ import { a as e, l as t, n, r, s as i, t as a } from "./uninstall-BkaWoWd0.js";
2
+ export { e as COMMANDS, i as DEPRECATED_SKILLS, t as SKILL_NAMES, n as init, a as uninstall, r as update };
@@ -0,0 +1,209 @@
1
+ import { existsSync as e, mkdirSync as t, readFileSync as n, readdirSync as r, rmSync as i, writeFileSync as a } from "node:fs";
2
+ import { dirname as o, join as s } from "node:path";
3
+ import { createHash as c } from "node:crypto";
4
+ import { fileURLToPath as l } from "node:url";
5
+ //#region src/config.ts
6
+ var u = "local", d = "github", f = [u, d], p = {
7
+ prds: ".tracerkit/prds",
8
+ plans: ".tracerkit/plans",
9
+ archives: ".tracerkit/archives"
10
+ }, m = { labels: {
11
+ prd: "tk:prd",
12
+ plan: "tk:plan"
13
+ } };
14
+ function h(t) {
15
+ let r = s(t, ".tracerkit", "config.json");
16
+ if (!e(r)) return {
17
+ storage: u,
18
+ paths: { ...p },
19
+ github: { ...m }
20
+ };
21
+ let i;
22
+ try {
23
+ i = JSON.parse(n(r, "utf8"));
24
+ } catch {
25
+ throw Error("Invalid .tracerkit/config.json — expected valid JSON");
26
+ }
27
+ return {
28
+ storage: g(i.storage),
29
+ paths: _(i.paths),
30
+ github: v(i.github)
31
+ };
32
+ }
33
+ function g(e) {
34
+ return typeof e == "string" && f.includes(e) ? e : u;
35
+ }
36
+ function _(e) {
37
+ let t = x(e) ? e : {};
38
+ return {
39
+ prds: typeof t.prds == "string" ? t.prds : p.prds,
40
+ plans: typeof t.plans == "string" ? t.plans : p.plans,
41
+ archives: typeof t.archives == "string" ? t.archives : p.archives
42
+ };
43
+ }
44
+ function v(e) {
45
+ let t = x(e) ? e : {}, n = x(t.labels) ? t.labels : {};
46
+ return {
47
+ ...typeof t.repo == "string" ? { repo: t.repo } : {},
48
+ labels: {
49
+ prd: typeof n.prd == "string" ? n.prd : m.labels.prd,
50
+ plan: typeof n.plan == "string" ? n.plan : m.labels.plan
51
+ }
52
+ };
53
+ }
54
+ function y(r, i) {
55
+ let c = s(r, ".tracerkit", "config.json");
56
+ t(o(c), { recursive: !0 });
57
+ let l = {};
58
+ if (e(c)) try {
59
+ l = JSON.parse(n(c, "utf8"));
60
+ } catch {
61
+ l = {};
62
+ }
63
+ let u = b(l, i);
64
+ a(c, JSON.stringify(u, null, 2) + "\n");
65
+ }
66
+ function b(e, t) {
67
+ let n = { ...e };
68
+ for (let e of Object.keys(t)) x(n[e]) && x(t[e]) ? n[e] = b(n[e], t[e]) : n[e] = t[e];
69
+ return n;
70
+ }
71
+ function x(e) {
72
+ return typeof e == "object" && !!e && !Array.isArray(e);
73
+ }
74
+ var S = [
75
+ "tk:brief",
76
+ "tk:prd",
77
+ "tk:plan",
78
+ "tk:check"
79
+ ], C = ["tk:verify"], w = {
80
+ force: "--force",
81
+ help: "--help",
82
+ storage: "--storage",
83
+ version: "--version"
84
+ }, T = [
85
+ "brief",
86
+ "progress",
87
+ "archive"
88
+ ], E = [
89
+ {
90
+ name: "init",
91
+ args: "[path]",
92
+ desc: "Install skills to ~/.claude/skills/ (or [path] if given)"
93
+ },
94
+ {
95
+ name: "update",
96
+ args: "[path]",
97
+ desc: "Refresh unchanged files from latest version, skip modified"
98
+ },
99
+ {
100
+ name: "config",
101
+ args: "[path] [key] [value]",
102
+ desc: "Get or set TracerKit configuration"
103
+ },
104
+ {
105
+ name: "uninstall",
106
+ args: "[path]",
107
+ desc: "Remove TracerKit skill directories, keep .tracerkit/ artifacts"
108
+ }
109
+ ], D = s(o(l(import.meta.url)), "..", "skills");
110
+ function O(e, t = "") {
111
+ let n = r(e, { withFileTypes: !0 }), i = [];
112
+ for (let r of n) {
113
+ let n = t ? `${t}/${r.name}` : r.name;
114
+ r.isDirectory() ? i.push(...O(s(e, r.name), n)) : i.push(n);
115
+ }
116
+ return i.sort();
117
+ }
118
+ function k(e) {
119
+ return `.claude/skills/tk:${e}`;
120
+ }
121
+ function A(e) {
122
+ return e.slice(18);
123
+ }
124
+ function j(e, t) {
125
+ let n = e;
126
+ t.paths.prds !== p.prds && (n = n.replaceAll(p.prds, t.paths.prds)), t.paths.plans !== p.plans && (n = n.replaceAll(p.plans, t.paths.plans)), t.paths.archives !== p.archives && (n = n.replaceAll(p.archives, t.paths.archives));
127
+ let r = t.storage ?? "local", i = r === "local" ? d : u;
128
+ return n = n.replace(RegExp(`<!-- if:${i} -->[^\\S\\n]*\\n[\\s\\S]*?<!-- end:${i} -->[^\\S\\n]*\\n?`, "g"), ""), n = n.replace(RegExp(`<!-- if:${r} -->[^\\S\\n]*\\n`, "g"), ""), n = n.replace(RegExp(`<!-- end:${r} -->[^\\S\\n]*\\n?`, "g"), ""), 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
+ }
130
+ function M(e, r, i) {
131
+ let c = i ?? O(D).map(k);
132
+ for (let i of c) {
133
+ let c = s(D, A(i)), l = s(e, i);
134
+ t(o(l), { recursive: !0 }), a(l, j(n(c, "utf8"), r));
135
+ }
136
+ return { copied: c };
137
+ }
138
+ function N(e) {
139
+ return c("sha256").update(e).digest("hex");
140
+ }
141
+ function P(t, r) {
142
+ let i = O(D).map(k), a = [], o = [], c = [];
143
+ for (let l of i) {
144
+ let i = s(t, l);
145
+ if (!e(i)) c.push(l);
146
+ else {
147
+ let e = j(n(s(D, A(l)), "utf8"), r);
148
+ N(Buffer.from(e)) === N(n(i)) ? a.push(l) : o.push(l);
149
+ }
150
+ }
151
+ return {
152
+ unchanged: a,
153
+ modified: o,
154
+ missing: c
155
+ };
156
+ }
157
+ //#endregion
158
+ //#region src/commands/update.ts
159
+ function F(t, n) {
160
+ if (!S.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — run `tracerkit init` first");
161
+ let r = h(t), { unchanged: a, modified: o, missing: c } = P(t, r), l = [];
162
+ for (let n of C) {
163
+ let r = s(t, ".claude", "skills", n);
164
+ e(r) && (i(r, {
165
+ recursive: !0,
166
+ force: !0
167
+ }), l.push(`✗ .claude/skills/${n}/ removed (deprecated)`));
168
+ }
169
+ let u = n?.force ?? !1, d = [
170
+ ...a,
171
+ ...c,
172
+ ...u ? o : []
173
+ ];
174
+ if (d.length > 0) {
175
+ M(t, r, d);
176
+ for (let e of a) l.push(`✓ ${e}`);
177
+ for (let e of c) l.push(`✓ ${e} (added)`);
178
+ if (u) for (let e of o) l.push(`✓ ${e} (replaced)`);
179
+ }
180
+ if (!u && o.length > 0) {
181
+ for (let e of o) l.push(`⚠ ${e} (skipped — modified)`);
182
+ l.push("", "Run `tracerkit update --force` to replace modified files with latest versions.");
183
+ }
184
+ return l;
185
+ }
186
+ //#endregion
187
+ //#region src/commands/init.ts
188
+ function I(t, n) {
189
+ let r = h(t), i = n?.storage && n.storage !== r.storage;
190
+ if (i && y(t, { storage: n.storage }), S.some((n) => e(s(t, ".claude", "skills", n)))) return F(t, { force: !!i });
191
+ let { copied: a } = M(t, h(t));
192
+ return a.map((e) => `✓ ${e}`);
193
+ }
194
+ //#endregion
195
+ //#region src/commands/uninstall.ts
196
+ function L(t) {
197
+ if (!S.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
198
+ let n = [];
199
+ for (let r of S) {
200
+ let a = s(t, ".claude", "skills", r);
201
+ e(a) && (i(a, {
202
+ recursive: !0,
203
+ force: !0
204
+ }), n.push(`✗ .claude/skills/${r}/ removed`));
205
+ }
206
+ return n;
207
+ }
208
+ //#endregion
209
+ export { E as a, w as c, h as d, y as f, M as i, S as l, I as n, T as o, F as r, C as s, L as t, u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracerkit",
3
- "version": "1.11.2",
3
+ "version": "1.12.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,7 +8,13 @@ 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
+ <!-- if:local -->
12
+
11
13
  - Available PRDs: !`ls .tracerkit/prds/ 2>&1`
14
+ <!-- end:local -->
15
+ <!-- if:github -->
16
+ - Available PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
17
+ <!-- end:github -->
12
18
 
13
19
  ## Algorithm
14
20
 
@@ -16,6 +22,8 @@ Follow these steps exactly to build the briefing table:
16
22
 
17
23
  ### 1. Discover features
18
24
 
25
+ <!-- if:local -->
26
+
19
27
  For each `.md` file in `.tracerkit/prds/`:
20
28
 
21
29
  1. Read the file
@@ -23,9 +31,22 @@ For each `.md` file in `.tracerkit/prds/`:
23
31
  3. Extract `status` and `created` fields
24
32
  4. Skip files where `status: done`
25
33
  5. The slug is the filename without `.md`
34
+ <!-- end:local -->
35
+ <!-- if:github -->
36
+
37
+ List open GitHub Issues with label `{{github.labels.prd}}`:
38
+
39
+ 6. For each issue, parse the `<!-- tk:metadata -->` comment in the body
40
+ 7. Extract `status` and `created` fields from the metadata
41
+ 8. Also check labels: `tk:created`, `tk:in-progress`
42
+ 9. Skip issues with `tk:done` label
43
+ 10. The slug is extracted from the title: `[{{github.labels.prd}}] <slug>: ...`
44
+ <!-- end:github -->
26
45
 
27
46
  ### 2. Count progress from plans
28
47
 
48
+ <!-- if:local -->
49
+
29
50
  For each slug, check if `.tracerkit/plans/<slug>.md` exists. If it does:
30
51
 
31
52
  1. Read the plan file
@@ -38,6 +59,23 @@ For each slug, check if `.tracerkit/plans/<slug>.md` exists. If it does:
38
59
 
39
60
  If no plan exists, progress is `—` and next is `—`.
40
61
 
62
+ <!-- end:local -->
63
+ <!-- if:github -->
64
+
65
+ For each slug, search for a GitHub Issue with label `{{github.labels.plan}}` and title matching `[{{github.labels.plan}}] <slug>:`. If found:
66
+
67
+ 1. Read the plan issue body
68
+ 2. Find every `## Phase N` heading (regex: `^## Phase \d+`)
69
+ 3. Within each phase section (until the next `## ` heading), count:
70
+ - Checked items: lines matching `^- \[x\] ` (case-insensitive)
71
+ - Unchecked items: lines matching `^- \[ \] `
72
+ 4. Sum checked and total across all phases → `checked/total`
73
+ 5. Find the first unchecked item (`^- \[ \] (.+)`) in the entire plan — that's the "Next" value. Strip any trailing `[tag]` markers.
74
+
75
+ If no plan issue exists, progress is `—` and next is `—`.
76
+
77
+ <!-- end:github -->
78
+
41
79
  ### 3. Build the table
42
80
 
43
81
  Sort features by `created` date ascending (no-date entries last). Calculate age from `created`:
@@ -74,6 +112,12 @@ Append below the table:
74
112
 
75
113
  Ask the user what they'd like to do:
76
114
 
115
+ <!-- if:local -->
116
+
77
117
  - Continue the focused feature (read its plan at `.tracerkit/plans/<slug>.md`)
118
+ <!-- end:local -->
119
+ <!-- if:github -->
120
+ - Continue the focused feature (read its plan issue)
121
+ <!-- end:github -->
78
122
  - Start a new feature with `/tk:prd`
79
123
  - Check progress on a feature with `/tk:check <slug>`
@@ -9,7 +9,13 @@ Check implementation against a plan. Update checks, stamp findings, transition s
9
9
 
10
10
  ## Pre-loaded context
11
11
 
12
+ <!-- if:local -->
13
+
12
14
  - Available plans: !`ls .tracerkit/plans/ 2>&1`
15
+ <!-- end:local -->
16
+ <!-- if:github -->
17
+ - Available plans: list open GitHub Issues with label `{{github.labels.plan}}`
18
+ <!-- end:github -->
13
19
 
14
20
  ## Input
15
21
 
@@ -25,11 +31,22 @@ If no argument is provided, build a summary table before asking which one to che
25
31
  | <slug> | ... | 3/7 |
26
32
  ```
27
33
 
34
+ <!-- if:local -->
35
+
28
36
  For each `.md` file in `.tracerkit/prds/`:
29
37
 
30
38
  1. Read the file, parse YAML frontmatter (block between `---` fences)
31
39
  2. Extract `status` — use `unknown` if missing
32
40
  3. If `.tracerkit/plans/<slug>.md` exists, count progress (see Progress Algorithm below). Show `—` if no plan.
41
+ <!-- end:local -->
42
+ <!-- if:github -->
43
+
44
+ List open GitHub Issues with label `{{github.labels.prd}}`:
45
+
46
+ 4. For each PRD issue, extract `status` from labels (`tk:created`, `tk:in-progress`)
47
+ 5. Find matching plan issue with label `{{github.labels.plan}}` and same slug in title
48
+ 6. If plan issue exists, count progress from checkboxes in its body (see Progress Algorithm below). Show `—` if no plan.
49
+ <!-- end:github -->
33
50
 
34
51
  After the table, ask which feature to verify.
35
52
 
@@ -48,12 +65,30 @@ To count progress for a plan file:
48
65
 
49
66
  ### 1. Load the plan
50
67
 
68
+ <!-- if:local -->
69
+
51
70
  Read `.tracerkit/plans/<slug>.md`. If it does not exist, list available plans and ask.
52
71
 
72
+ <!-- end:local -->
73
+ <!-- if:github -->
74
+
75
+ Find the plan issue: search for an open GitHub Issue with label `{{github.labels.plan}}` and title matching `[{{github.labels.plan}}] <slug>:`. Read its body. If not found, list available plan issues and ask.
76
+
77
+ <!-- end:github -->
78
+
53
79
  ### 2. Load the PRD
54
80
 
81
+ <!-- if:local -->
82
+
55
83
  Read the source PRD referenced in the plan header (`> Source PRD: ...`).
56
84
 
85
+ <!-- end:local -->
86
+ <!-- if:github -->
87
+
88
+ Read the source PRD issue referenced in the plan body (`> Source PRD: #<number>`).
89
+
90
+ <!-- end:github -->
91
+
57
92
  ### 3. Fast-path: check if implementation exists
58
93
 
59
94
  Before launching a subagent, check whether the primary module file(s) from Phase 1 exist. If none exist, skip the subagent entirely and report `0/N — not yet started`. List Phase 1's "Done when" items as next steps and jump to Step 5.
@@ -76,8 +111,17 @@ Collect findings into two categories:
76
111
 
77
112
  ### 3c. Update checkboxes
78
113
 
114
+ <!-- if:local -->
115
+
79
116
  Using the subagent's report, update each checkbox in `.tracerkit/plans/<slug>.md` to `[x]` or `[ ]`.
80
117
 
118
+ <!-- end:local -->
119
+ <!-- if:github -->
120
+
121
+ Using the subagent's report, update each checkbox in the plan issue body to `[x]` or `[ ]` by editing the issue.
122
+
123
+ <!-- end:github -->
124
+
81
125
  ### 4. Determine outcome
82
126
 
83
127
  Based on checks and findings, decide the status transition:
@@ -112,8 +156,17 @@ Total: checked/total
112
156
 
113
157
  ### 6. Stamp the plan
114
158
 
159
+ <!-- if:local -->
160
+
115
161
  Append a verdict block at the bottom of `.tracerkit/plans/<slug>.md`:
116
162
 
163
+ <!-- end:local -->
164
+ <!-- if:github -->
165
+
166
+ Append a verdict block at the bottom of the plan issue body by editing the issue:
167
+
168
+ <!-- end:github -->
169
+
117
170
  ```markdown
118
171
  ---
119
172
 
@@ -129,7 +182,11 @@ If a previous verdict block exists, replace it with the new one.
129
182
 
130
183
  ### 7. On `done` — archive
131
184
 
132
- If all checks pass and zero BLOCKERS, perform these steps in order:
185
+ If all checks pass and zero BLOCKERS:
186
+
187
+ <!-- if:local -->
188
+
189
+ Perform these steps in order:
133
190
 
134
191
  1. Create directory `.tracerkit/archives/<slug>/`
135
192
  2. If `.tracerkit/prds/<slug>.md` exists:
@@ -145,6 +202,22 @@ If all checks pass and zero BLOCKERS, perform these steps in order:
145
202
 
146
203
  Tell the user: archived to `.tracerkit/archives/<slug>/`, one-line summary of the feature.
147
204
 
205
+ <!-- end:local -->
206
+ <!-- if:github -->
207
+
208
+ Perform these steps in order:
209
+
210
+ 1. Update the PRD issue:
211
+ - Add `tk:done` label, remove `tk:in-progress` label
212
+ - Update the `<!-- tk:metadata -->` comment: set `status: done`, add `completed: <current UTC ISO 8601 timestamp>`
213
+ 2. Close the PRD issue with reason `completed`
214
+ 3. Close the plan issue with reason `completed`
215
+ 4. If there is a current PR associated with this work, reference it in a closing comment on the PRD issue
216
+
217
+ Tell the user: issues closed (include issue numbers), one-line summary of the feature.
218
+
219
+ <!-- end:github -->
220
+
148
221
  ### 8. On `in_progress` (no blockers)
149
222
 
150
223
  Show progress summary (checked/total per phase), list the next unchecked items to implement. Keep going.
@@ -164,5 +237,10 @@ List the blockers to fix, then re-run `/tk:check <slug>`.
164
237
 
165
238
  - Plan not found — list available plans and ask
166
239
  - PRD referenced in plan not found — warn and continue with plan checks only
240
+ <!-- if:local -->
167
241
  - `.tracerkit/plans/` missing — tell user to run `/tk:plan` first
168
242
  - `.tracerkit/archives/<slug>/` already exists — warn and ask whether to remove it first
243
+ <!-- end:local -->
244
+ <!-- if:github -->
245
+ - Issue update fails — report the error and suggest checking `gh auth status`
246
+ <!-- end:github -->
@@ -5,11 +5,28 @@ argument-hint: '[slug]'
5
5
 
6
6
  # PRD to Plan
7
7
 
8
- Break a PRD into phased vertical slices (tracer bullets). Output: `.tracerkit/plans/<slug>.md`.
8
+ Break a PRD into phased vertical slices (tracer bullets).
9
+
10
+ <!-- if:local -->
11
+
12
+ Output: `.tracerkit/plans/<slug>.md`.
13
+
14
+ <!-- end:local -->
15
+ <!-- if:github -->
16
+
17
+ Output: a GitHub Issue with label `{{github.labels.plan}}`.
18
+
19
+ <!-- end:github -->
9
20
 
10
21
  ## Pre-loaded context
11
22
 
23
+ <!-- if:local -->
24
+
12
25
  - Available PRDs: !`ls .tracerkit/prds/ 2>&1`
26
+ <!-- end:local -->
27
+ <!-- if:github -->
28
+ - Available PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
29
+ <!-- end:github -->
13
30
 
14
31
  ## Input
15
32
 
@@ -21,16 +38,38 @@ Use the argument as `<slug>` if given. If no argument is provided, list availabl
21
38
 
22
39
  ### 1. Read the PRD
23
40
 
41
+ <!-- if:local -->
42
+
24
43
  Read `.tracerkit/prds/<slug>.md`. If it does not exist, list available PRDs and ask.
25
44
 
26
45
  If `.tracerkit/plans/<slug>.md` already exists, tell the user and ask whether to overwrite or pick a new name.
27
46
 
47
+ <!-- end:local -->
48
+ <!-- if:github -->
49
+
50
+ Find the PRD issue: search for an open GitHub Issue with label `{{github.labels.prd}}` and title matching `[{{github.labels.prd}}] <slug>:`. Read its body. If not found, list available PRD issues and ask.
51
+
52
+ Search for an existing plan issue with label `{{github.labels.plan}}` and title matching `[{{github.labels.plan}}] <slug>:`. If found, tell the user and ask whether to update it or pick a new name.
53
+
54
+ <!-- end:github -->
55
+
28
56
  ### 1b. Update PRD status
29
57
 
58
+ <!-- if:local -->
59
+
30
60
  Update the YAML frontmatter in `.tracerkit/prds/<slug>.md` to `status: in_progress`. Change only the `status` field — do not touch any other frontmatter fields or the markdown content below the closing `---`.
31
61
 
32
62
  If the PRD has no frontmatter, skip this step silently.
33
63
 
64
+ <!-- end:local -->
65
+ <!-- if:github -->
66
+
67
+ Update the PRD issue:
68
+
69
+ - Remove `tk:created` label, add `tk:in-progress` label
70
+ - Update the `<!-- tk:metadata -->` comment in the issue body to `status: in_progress`
71
+ <!-- end:github -->
72
+
34
73
  ### 2. Explore the codebase
35
74
 
36
75
  Understand current architecture, existing patterns, and integration points. If you already have codebase context from a prior step in this conversation (e.g., you just ran `/tk:prd`), skip the exploration and note which context you're reusing.
@@ -97,13 +136,46 @@ Ask: Does the granularity feel right? Should any phases merge or split? Iterate
97
136
 
98
137
  ### 6. Save plan
99
138
 
139
+ <!-- if:local -->
140
+
100
141
  Save to `.tracerkit/plans/<slug>.md` (create `.tracerkit/plans/` if missing).
101
142
 
102
143
  ```markdown
103
144
  # Plan: <Feature Name>
104
145
 
105
146
  > Source PRD: `.tracerkit/prds/<slug>.md`
147
+ ```
148
+
149
+ <!-- end:local -->
150
+ <!-- if:github -->
106
151
 
152
+ Ensure the following labels exist (create if missing):
153
+
154
+ - `{{github.labels.plan}}` — TracerKit implementation plan
155
+ - `tk:in-progress` — Plan generated, implementation underway
156
+
157
+ Create a GitHub Issue with:
158
+
159
+ - **Title**: `[{{github.labels.plan}}] <slug>: Plan: <Feature Title>`
160
+ - **Labels**: `{{github.labels.plan}}`, `tk:in-progress`
161
+ - **Body**: the plan content below, with a source PRD reference by issue number
162
+
163
+ ```markdown
164
+ <!-- tk:metadata
165
+ source_prd: #<PRD issue number>
166
+ slug: <slug>
167
+ -->
168
+
169
+ # Plan: <Feature Name>
170
+
171
+ > Source PRD: #<PRD issue number>
172
+ ```
173
+
174
+ <!-- end:github -->
175
+
176
+ Use this structure for the plan body:
177
+
178
+ ```markdown
107
179
  ## Architectural Decisions
108
180
 
109
181
  Durable decisions that apply across all phases:
@@ -138,19 +210,38 @@ Carried forward from PRD verbatim.
138
210
  Gaps found in the PRD needing resolution. Blank if none.
139
211
  ```
140
212
 
213
+ <!-- if:local -->
214
+
141
215
  Print saved path and one line per phase: `Phase N — <title> (<condition summary>)`. Then ask: "Run `/tk:check <slug>` when ready?"
142
216
 
217
+ <!-- end:local -->
218
+ <!-- if:github -->
219
+
220
+ Print issue number/URL and one line per phase: `Phase N — <title> (<condition summary>)`. Then ask: "Run `/tk:check <slug>` when ready?"
221
+
222
+ <!-- end:github -->
223
+
143
224
  ## Rules
144
225
 
145
226
  - Phases derive from PRD user stories — never invented
146
227
  - Each phase must be demoable end-to-end on its own
147
228
  - "Done when" must be a checkbox list of testable conditions, not prose
148
229
  - **Safety valve**: if a phase has >5 "Done when" items, stop and split it into smaller phases before continuing
230
+ <!-- if:local -->
149
231
  - Never modify the source PRD content — only update frontmatter status fields
232
+ <!-- end:local -->
233
+ <!-- if:github -->
234
+ - Never modify the source PRD content — only update metadata and labels
235
+ <!-- end:github -->
150
236
  - Carry PRD's Out of Scope forward verbatim
151
237
 
152
238
  ## Error Handling
153
239
 
154
240
  - PRD not found — list available PRDs and ask
155
241
  - PRD missing sections — note gaps inline and continue
242
+ <!-- if:local -->
156
243
  - `.tracerkit/plans/` missing — create it
244
+ <!-- end:local -->
245
+ <!-- if:github -->
246
+ - Issue creation fails — report the error and suggest checking `gh auth status`
247
+ <!-- end:github -->
@@ -9,7 +9,13 @@ Skip steps already satisfied. If user provided a description via arguments, skip
9
9
 
10
10
  ## Pre-loaded context
11
11
 
12
+ <!-- if:local -->
13
+
12
14
  - Existing PRDs: !`ls .tracerkit/prds/ 2>&1`
15
+ <!-- end:local -->
16
+ <!-- if:github -->
17
+ - Existing PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
18
+ <!-- end:github -->
13
19
 
14
20
  ## Input
15
21
 
@@ -25,10 +31,21 @@ If the argument is provided, derive a slug using this exact algorithm:
25
31
  4. Take the first 4 remaining words (or fewer if less exist)
26
32
  5. Join with hyphens → `<slug>`
27
33
 
34
+ <!-- if:local -->
35
+
28
36
  The output file is `.tracerkit/prds/<slug>.md`.
29
37
 
30
38
  If `.tracerkit/prds/<slug>.md` already exists, tell the user and ask whether to overwrite or pick a new name.
31
39
 
40
+ <!-- end:local -->
41
+ <!-- if:github -->
42
+
43
+ The output is a GitHub Issue with label `{{github.labels.prd}}` and title `[{{github.labels.prd}}] <slug>: <Feature Title>`.
44
+
45
+ Search for an existing open issue with title matching `[{{github.labels.prd}}] <slug>:`. If found, tell the user and ask whether to update it or pick a new slug.
46
+
47
+ <!-- end:github -->
48
+
32
49
  ## Workflow
33
50
 
34
51
  ### 1. Gather problem description
@@ -84,6 +101,8 @@ Present modules to user. Confirm which need tests.
84
101
 
85
102
  ### 5. Write PRD
86
103
 
104
+ <!-- if:local -->
105
+
87
106
  Save to `.tracerkit/prds/<slug>.md` (create `.tracerkit/prds/` if missing).
88
107
 
89
108
  ```markdown
@@ -94,6 +113,38 @@ status: created
94
113
 
95
114
  # Feature Name
96
115
 
116
+ ...
117
+ ```
118
+
119
+ <!-- end:local -->
120
+ <!-- if:github -->
121
+
122
+ Ensure the following labels exist in the repository (create them if missing):
123
+
124
+ - `{{github.labels.prd}}` — TracerKit PRD
125
+ - `tk:created` — PRD written, no plan yet
126
+
127
+ Create a GitHub Issue with:
128
+
129
+ - **Title**: `[{{github.labels.prd}}] <slug>: <Feature Title>`
130
+ - **Labels**: `{{github.labels.prd}}`, `tk:created`
131
+ - **Body**: the PRD content below, with metadata in an HTML comment
132
+
133
+ ```markdown
134
+ <!-- tk:metadata
135
+ created: <current UTC timestamp, ISO 8601>
136
+ status: created
137
+ -->
138
+
139
+ # Feature Name
140
+
141
+ ...
142
+ ```
143
+
144
+ <!-- end:github -->
145
+
146
+ Use this structure for the PRD body (same for both local file and issue body):
147
+
97
148
  ## Problem Statement
98
149
 
99
150
  The problem from the user's perspective. Focus on pain and impact.
@@ -155,11 +206,28 @@ Omit any section whose content would be "None required" — only include section
155
206
  ## Out of Scope
156
207
 
157
208
  Explicit list. Be specific — vague exclusions invite scope creep.
158
- ```
209
+
210
+ ---
211
+
212
+ <!-- if:local -->
159
213
 
160
214
  Tell the user: file created, one-line summary. Then ask: "Run `/tk:plan <slug>` next?"
161
215
 
216
+ <!-- end:local -->
217
+ <!-- if:github -->
218
+
219
+ Tell the user: issue created (include issue number and URL), one-line summary. Then ask: "Run `/tk:plan <slug>` next?"
220
+
221
+ <!-- end:github -->
222
+
162
223
  ## Error Handling
163
224
 
225
+ <!-- if:local -->
226
+
164
227
  - `.tracerkit/prds/` missing — create it
228
+ <!-- end:local -->
229
+ <!-- if:github -->
230
+ - Labels missing — create them before creating the issue
231
+ - Issue creation fails — report the error and suggest checking `gh auth status`
232
+ <!-- end:github -->
165
233
  - Scope larger than expected — surface and re-scope with user before continuing
@@ -1,153 +0,0 @@
1
- import { existsSync as e, mkdirSync as t, readFileSync as n, readdirSync as r, rmSync as i, writeFileSync as a } from "node:fs";
2
- import { dirname as o, join as s } from "node:path";
3
- import { createHash as c } from "node:crypto";
4
- import { fileURLToPath as l } from "node:url";
5
- //#region src/config.ts
6
- var u = {
7
- prds: ".tracerkit/prds",
8
- plans: ".tracerkit/plans",
9
- archives: ".tracerkit/archives"
10
- };
11
- function d(t) {
12
- let r = s(t, ".tracerkit", "config.json");
13
- if (!e(r)) return { paths: { ...u } };
14
- let i, a;
15
- try {
16
- i = n(r, "utf8"), a = JSON.parse(i);
17
- } catch {
18
- throw Error("Invalid .tracerkit/config.json — expected valid JSON");
19
- }
20
- let o = typeof a.paths == "object" && a.paths !== null ? a.paths : {};
21
- return { paths: {
22
- prds: typeof o.prds == "string" ? o.prds : u.prds,
23
- plans: typeof o.plans == "string" ? o.plans : u.plans,
24
- archives: typeof o.archives == "string" ? o.archives : u.archives
25
- } };
26
- }
27
- var f = [
28
- "tk:brief",
29
- "tk:prd",
30
- "tk:plan",
31
- "tk:check"
32
- ], p = ["tk:verify"], m = {
33
- force: "--force",
34
- help: "--help",
35
- version: "--version"
36
- }, h = [
37
- "brief",
38
- "progress",
39
- "archive"
40
- ], g = [
41
- {
42
- name: "init",
43
- args: "[path]",
44
- desc: "Install skills to ~/.claude/skills/ (or [path] if given)"
45
- },
46
- {
47
- name: "update",
48
- args: "[path]",
49
- desc: "Refresh unchanged files from latest version, skip modified"
50
- },
51
- {
52
- name: "uninstall",
53
- args: "[path]",
54
- desc: "Remove TracerKit skill directories, keep .tracerkit/ artifacts"
55
- }
56
- ], _ = s(o(l(import.meta.url)), "..", "skills");
57
- function v(e, t = "") {
58
- let n = r(e, { withFileTypes: !0 }), i = [];
59
- for (let r of n) {
60
- let n = t ? `${t}/${r.name}` : r.name;
61
- r.isDirectory() ? i.push(...v(s(e, r.name), n)) : i.push(n);
62
- }
63
- return i.sort();
64
- }
65
- function y(e) {
66
- return `.claude/skills/tk:${e}`;
67
- }
68
- function b(e) {
69
- return e.slice(18);
70
- }
71
- function x(e, t) {
72
- let n = e;
73
- return t.paths.prds !== u.prds && (n = n.replaceAll(u.prds, t.paths.prds)), t.paths.plans !== u.plans && (n = n.replaceAll(u.plans, t.paths.plans)), t.paths.archives !== u.archives && (n = n.replaceAll(u.archives, t.paths.archives)), n;
74
- }
75
- function S(e, r, i) {
76
- let c = i ?? v(_).map(y);
77
- for (let i of c) {
78
- let c = s(_, b(i)), l = s(e, i);
79
- t(o(l), { recursive: !0 }), a(l, x(n(c, "utf8"), r));
80
- }
81
- return { copied: c };
82
- }
83
- function C(e) {
84
- return c("sha256").update(e).digest("hex");
85
- }
86
- function w(t, r) {
87
- let i = v(_).map(y), a = [], o = [], c = [];
88
- for (let l of i) {
89
- let i = s(t, l);
90
- if (!e(i)) c.push(l);
91
- else {
92
- let e = x(n(s(_, b(l)), "utf8"), r);
93
- C(Buffer.from(e)) === C(n(i)) ? a.push(l) : o.push(l);
94
- }
95
- }
96
- return {
97
- unchanged: a,
98
- modified: o,
99
- missing: c
100
- };
101
- }
102
- //#endregion
103
- //#region src/commands/update.ts
104
- function T(t, n) {
105
- if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — run `tracerkit init` first");
106
- let r = d(t), { unchanged: a, modified: o, missing: c } = w(t, r), l = [];
107
- for (let n of p) {
108
- let r = s(t, ".claude", "skills", n);
109
- e(r) && (i(r, {
110
- recursive: !0,
111
- force: !0
112
- }), l.push(`✗ .claude/skills/${n}/ removed (deprecated)`));
113
- }
114
- let u = n?.force ?? !1, m = [
115
- ...a,
116
- ...c,
117
- ...u ? o : []
118
- ];
119
- if (m.length > 0) {
120
- S(t, r, m);
121
- for (let e of a) l.push(`✓ ${e}`);
122
- for (let e of c) l.push(`✓ ${e} (added)`);
123
- if (u) for (let e of o) l.push(`✓ ${e} (replaced)`);
124
- }
125
- if (!u && o.length > 0) {
126
- for (let e of o) l.push(`⚠ ${e} (skipped — modified)`);
127
- l.push("", "Run `tracerkit update --force` to replace modified files with latest versions.");
128
- }
129
- return l;
130
- }
131
- //#endregion
132
- //#region src/commands/init.ts
133
- function E(t) {
134
- if (f.some((n) => e(s(t, ".claude", "skills", n)))) return T(t);
135
- let { copied: n } = S(t, d(t));
136
- return n.map((e) => `✓ ${e}`);
137
- }
138
- //#endregion
139
- //#region src/commands/uninstall.ts
140
- function D(t) {
141
- if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
142
- let n = [];
143
- for (let r of f) {
144
- let a = s(t, ".claude", "skills", r);
145
- e(a) && (i(a, {
146
- recursive: !0,
147
- force: !0
148
- }), n.push(`✗ .claude/skills/${r}/ removed`));
149
- }
150
- return n;
151
- }
152
- //#endregion
153
- export { h as a, f as c, g as i, E as n, p as o, T as r, m as s, D as t };