tracerkit 1.11.3 → 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 +23 -9
- package/dist/bin.js +80 -23
- package/dist/index.js +2 -2
- package/dist/uninstall-BkaWoWd0.js +209 -0
- package/package.json +1 -1
- package/skills/brief/SKILL.md +44 -0
- package/skills/check/SKILL.md +79 -1
- package/skills/plan/SKILL.md +92 -1
- package/skills/prd/SKILL.md +69 -1
- package/dist/uninstall-BhTMOfMb.js +0 -153
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) |
|
|
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,
|
|
3
|
-
import { readFileSync as
|
|
4
|
-
import { dirname as
|
|
5
|
-
import { fileURLToPath as
|
|
6
|
-
import { homedir as
|
|
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:
|
|
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
|
-
...
|
|
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
|
|
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 ?
|
|
24
|
-
}
|
|
25
|
-
function
|
|
26
|
-
if (
|
|
27
|
-
if (
|
|
28
|
-
let
|
|
29
|
-
if (
|
|
30
|
-
switch (
|
|
31
|
-
case "init":
|
|
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 =
|
|
34
|
-
return
|
|
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
|
|
37
|
-
default: return
|
|
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
|
|
43
|
-
for (let e of
|
|
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 {
|
|
2
|
-
export {
|
|
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
package/skills/brief/SKILL.md
CHANGED
|
@@ -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>`
|
package/skills/check/SKILL.md
CHANGED
|
@@ -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
|
|
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 -->
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -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).
|
|
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 -->
|
package/skills/prd/SKILL.md
CHANGED
|
@@ -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 };
|