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 +9 -13
- package/dist/bin.js +22 -168
- package/dist/index.js +2 -2
- package/dist/{uninstall-C4dWMK0k.js → uninstall-Bgs58wa2.js} +25 -36
- package/package.json +1 -1
- package/templates/.claude/skills/tk:brief/SKILL.md +56 -9
- package/templates/.claude/skills/tk:check/SKILL.md +42 -19
- package/templates/.claude/skills/tk:plan/SKILL.md +8 -2
- package/templates/.claude/skills/tk:prd/SKILL.md +26 -11
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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) |
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import { dirname as
|
|
5
|
-
import { fileURLToPath as
|
|
6
|
-
import { homedir as
|
|
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:
|
|
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(
|
|
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
|
|
21
|
+
function m(e, t = u()) {
|
|
152
22
|
let n = e.find((e) => !e.startsWith("-"));
|
|
153
|
-
return n ?
|
|
154
|
-
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 =
|
|
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(
|
|
181
|
-
|
|
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
|
|
189
|
-
for (let e of
|
|
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 {
|
|
2
|
-
export { t as COMMANDS,
|
|
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
|
-
],
|
|
68
|
-
function
|
|
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(...
|
|
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
|
|
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
|
|
80
|
-
let c = i ?? _
|
|
68
|
+
function b(e, r, i) {
|
|
69
|
+
let c = i ?? v(_);
|
|
81
70
|
for (let i of c) {
|
|
82
|
-
let c = s(
|
|
83
|
-
t(o(l), { recursive: !0 }), a(l,
|
|
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
|
|
76
|
+
function x(e) {
|
|
88
77
|
return c("sha256").update(e).digest("hex");
|
|
89
78
|
}
|
|
90
|
-
function
|
|
91
|
-
let i = _
|
|
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 =
|
|
97
|
-
|
|
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
|
|
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 } =
|
|
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
|
-
|
|
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
|
|
138
|
-
if (f.some((n) => e(s(t, ".claude", "skills", n)))) return
|
|
139
|
-
let { copied: n } =
|
|
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
|
|
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 {
|
|
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
|
@@ -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
|
-
-
|
|
11
|
+
- Available PRDs: !`ls {{paths.prds}}/ 2>&1`
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Algorithm
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Follow these steps exactly to build the briefing table:
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### 1. Discover features
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
For each `.md` file in `{{paths.prds}}/`:
|
|
20
20
|
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
- Otherwise, the oldest feature by `created` date is selected
|
|
27
|
+
### 2. Count progress from plans
|
|
25
28
|
|
|
26
|
-
|
|
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,
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
tracerkit archive <slug>
|
|
118
|
-
```
|
|
132
|
+
If all checks pass and zero BLOCKERS, perform these steps in order:
|
|
119
133
|
|
|
120
|
-
|
|
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
|
|
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 —
|
|
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 —
|
|
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,
|
|
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:**
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|