tracerkit 1.14.0 → 1.16.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 +26 -39
- package/dist/bin.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{uninstall-C4K7aNpf.js → uninstall-Ba_zfPOx.js} +170 -89
- package/package.json +2 -2
- package/skills/brief/SKILL.md +6 -6
- package/skills/check/SKILL.md +15 -15
- package/skills/plan/SKILL.md +47 -3
- package/skills/prd/SKILL.md +8 -6
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/tracerkit)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
|
|
12
|
-
Replace ad-hoc AI prompts with a repeatable spec-driven workflow: from idea to verified
|
|
12
|
+
Replace ad-hoc AI prompts with a repeatable spec-driven workflow: from idea to verified spec.
|
|
13
13
|
|
|
14
14
|
Named after the tracer-bullet technique from _The Pragmatic Programmer_: **Tracer** + **Kit**.
|
|
15
15
|
|
|
@@ -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
|
-
|
|
24
|
+
Three skills drive the workflow: **define** (`/tk:prd`), **plan** (`/tk:plan`), **verify** (`/tk:check`). The AI reads your specs directly, counts progress, and marks completed work done. Pure Markdown, zero runtime deps.
|
|
25
25
|
|
|
26
26
|
## Get Started
|
|
27
27
|
|
|
@@ -75,10 +75,10 @@ You: # open the plan, implement each phase, write tests...
|
|
|
75
75
|
|
|
76
76
|
You: /tk:check dark-mode-support
|
|
77
77
|
AI: Status: done | Total: 5/5
|
|
78
|
-
|
|
78
|
+
Marked complete in .tracerkit/prds/dark-mode-support.md
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Between sessions, `/tk:brief` shows active features and picks up where you left off:
|
|
82
82
|
|
|
83
83
|
```
|
|
84
84
|
You: /tk:brief
|
|
@@ -101,49 +101,36 @@ tracerkit config storage github # set current project to use GitHub
|
|
|
101
101
|
tracerkit config github.repo org/repo # set target repo
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
PRDs and plans become GitHub Issues with `tk:prd` and `tk:plan` labels. On `/tk:check` pass, issues are closed
|
|
104
|
+
PRDs and plans become GitHub Issues with `tk:prd` and `tk:plan` labels. On `/tk:check` pass, issues are closed with `completed` reason and any related PRs are linked automatically. Each project can use a different backend; local is the default. See [Configuration](docs/configuration.md) for details.
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
## Skills
|
|
109
|
-
|
|
110
|
-
TracerKit ships skills that take a feature from idea to verified archive.
|
|
111
|
-
|
|
112
|
-
### `/tk:prd <idea>`: Write a PRD
|
|
113
|
-
|
|
114
|
-
Interactive interview that explores your codebase, asks scoping questions one at a time, designs deep modules, and writes a structured PRD.
|
|
115
|
-
|
|
116
|
-
**Output:** `.tracerkit/prds/<slug>.md` (local) or GitHub Issue with `tk:prd` label
|
|
117
|
-
|
|
118
|
-
### `/tk:plan <slug>`: Create an implementation plan
|
|
106
|
+
To migrate existing artifacts between backends:
|
|
119
107
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
### `/tk:brief`: Session briefing
|
|
125
|
-
|
|
126
|
-
Shows active features, their progress, and suggested focus. Use at the start of a session to orient.
|
|
127
|
-
|
|
128
|
-
**Output:** Feature dashboard in the terminal. No files written.
|
|
108
|
+
```bash
|
|
109
|
+
tracerkit migrate-storage # local→github or github→local (auto-detected)
|
|
110
|
+
```
|
|
129
111
|
|
|
130
|
-
|
|
112
|
+
Direction is inferred from the current `storage` config. All artifacts are migrated, existing duplicates are skipped, and the config is flipped to the target backend. Source artifacts are left intact as backup.
|
|
131
113
|
|
|
132
|
-
|
|
114
|
+
</details>
|
|
133
115
|
|
|
134
|
-
|
|
116
|
+
## Skills
|
|
135
117
|
|
|
136
|
-
|
|
118
|
+
| Skill | What it does | Output |
|
|
119
|
+
| ------------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `/tk:prd <idea>` | Interview → codebase scan → structured PRD | `.tracerkit/prds/<slug>.md` or GitHub Issue |
|
|
121
|
+
| `/tk:plan <slug>` | PRD → phased vertical slices, each demoable on its own | `.tracerkit/plans/<slug>.md` or GitHub Issue |
|
|
122
|
+
| `/tk:brief` | Feature dashboard with progress and suggested focus | Terminal only, no files |
|
|
123
|
+
| `/tk:check [slug]` | Verify done-when checkboxes against codebase and tests | Verdict block in plan. On `done`: status updated (local) or issues closed + PRs linked (GitHub) |
|
|
137
124
|
|
|
138
125
|
## Docs
|
|
139
126
|
|
|
140
|
-
| Document | Description
|
|
141
|
-
| ------------------------------------------------ |
|
|
142
|
-
| [Examples](docs/examples.md) | Walk through end-to-end usage scenarios
|
|
143
|
-
| [CLI Reference](docs/cli-reference.md) | Commands: init, update, config, uninstall
|
|
144
|
-
| [Configuration](docs/configuration.md) | Storage backends, GitHub options, custom paths
|
|
145
|
-
| [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions
|
|
146
|
-
| [Comparison](docs/comparison.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec
|
|
127
|
+
| Document | Description |
|
|
128
|
+
| ------------------------------------------------ | ---------------------------------------------------------- |
|
|
129
|
+
| [Examples](docs/examples.md) | Walk through end-to-end usage scenarios |
|
|
130
|
+
| [CLI Reference](docs/cli-reference.md) | Commands: init, update, config, migrate-storage, uninstall |
|
|
131
|
+
| [Configuration](docs/configuration.md) | Storage backends, GitHub options, custom paths |
|
|
132
|
+
| [Metadata Lifecycle](docs/metadata-lifecycle.md) | Understand YAML frontmatter states and transitions |
|
|
133
|
+
| [Comparison](docs/comparison.md) | Compare TracerKit to Spec Kit, Kiro, and OpenSpec |
|
|
147
134
|
|
|
148
135
|
## Contributing
|
|
149
136
|
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as e, f as t, i as n, l as r, n as i, o as a, p as o, r as s, s as c, t as l, u } from "./uninstall-
|
|
2
|
+
import { a as e, f as t, i as n, l as r, n as i, o as a, p as o, r as s, s as c, t as l, u } from "./uninstall-Ba_zfPOx.js";
|
|
3
3
|
import { existsSync as d, readFileSync as f, statSync as p } from "node:fs";
|
|
4
4
|
import { dirname as m, join as h, resolve as g } from "node:path";
|
|
5
5
|
import { fileURLToPath as _ } from "node:url";
|
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, u as o } from "./uninstall-
|
|
1
|
+
import { c as e, i as t, n, o as r, r as i, t as a, u as o } from "./uninstall-Ba_zfPOx.js";
|
|
2
2
|
export { r as COMMANDS, e as DEPRECATED_SKILLS, o as SKILL_NAMES, i as init, n as migrateStorage, a as uninstall, t as update };
|
|
@@ -6,8 +6,7 @@ import { execSync as d } from "node:child_process";
|
|
|
6
6
|
//#region src/config.ts
|
|
7
7
|
var f = "local", p = "github", m = [f, p], h = {
|
|
8
8
|
prds: ".tracerkit/prds",
|
|
9
|
-
plans: ".tracerkit/plans"
|
|
10
|
-
archives: ".tracerkit/archives"
|
|
9
|
+
plans: ".tracerkit/plans"
|
|
11
10
|
}, g = { labels: {
|
|
12
11
|
prd: "tk:prd",
|
|
13
12
|
plan: "tk:plan"
|
|
@@ -38,8 +37,7 @@ function y(e) {
|
|
|
38
37
|
let t = C(e) ? e : {};
|
|
39
38
|
return {
|
|
40
39
|
prds: typeof t.prds == "string" ? t.prds : h.prds,
|
|
41
|
-
plans: typeof t.plans == "string" ? t.plans : h.plans
|
|
42
|
-
archives: typeof t.archives == "string" ? t.archives : h.archives
|
|
40
|
+
plans: typeof t.plans == "string" ? t.plans : h.plans
|
|
43
41
|
};
|
|
44
42
|
}
|
|
45
43
|
function b(e) {
|
|
@@ -128,7 +126,7 @@ function M(e) {
|
|
|
128
126
|
}
|
|
129
127
|
function N(e, t) {
|
|
130
128
|
let n = e;
|
|
131
|
-
return t.paths.prds !== h.prds && (n = n.replaceAll(h.prds, t.paths.prds)), t.paths.plans !== h.plans && (n = n.replaceAll(h.plans, t.paths.plans)), t.
|
|
129
|
+
return t.paths.prds !== h.prds && (n = n.replaceAll(h.prds, t.paths.prds)), t.paths.plans !== h.plans && (n = n.replaceAll(h.plans, t.paths.plans)), 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;
|
|
132
130
|
}
|
|
133
131
|
function P(e, r, i) {
|
|
134
132
|
let o = i ?? A(k).map(j);
|
|
@@ -188,19 +186,23 @@ function L(t, n) {
|
|
|
188
186
|
}
|
|
189
187
|
//#endregion
|
|
190
188
|
//#region src/commands/init.ts
|
|
191
|
-
function
|
|
189
|
+
function ee(t) {
|
|
192
190
|
if (w.some((n) => e(c(t, ".claude", "skills", n)))) return L(t, { force: !1 });
|
|
193
191
|
let { copied: n } = P(t, _(t));
|
|
194
192
|
return n.map((e) => `✓ ${e}`);
|
|
195
193
|
}
|
|
196
194
|
//#endregion
|
|
197
195
|
//#region src/commands/migrate-storage.ts
|
|
198
|
-
var
|
|
196
|
+
var te = {
|
|
199
197
|
created: "tk:created",
|
|
200
198
|
in_progress: "tk:in-progress",
|
|
201
199
|
done: "tk:done"
|
|
200
|
+
}, R = {
|
|
201
|
+
"tk:created": "created",
|
|
202
|
+
"tk:in-progress": "in_progress",
|
|
203
|
+
"tk:done": "done"
|
|
202
204
|
};
|
|
203
|
-
function
|
|
205
|
+
function z(e) {
|
|
204
206
|
let t = e.match(/^---\n([\s\S]*?)---\n([\s\S]*)$/);
|
|
205
207
|
if (!t) return {
|
|
206
208
|
metadata: {},
|
|
@@ -218,91 +220,91 @@ function B(e) {
|
|
|
218
220
|
body: t[2]
|
|
219
221
|
};
|
|
220
222
|
}
|
|
221
|
-
function
|
|
223
|
+
function B(e) {
|
|
222
224
|
let t = Object.entries(e);
|
|
223
225
|
return t.length === 0 ? "<!-- tk:metadata\n-->" : `<!-- tk:metadata\n${t.map(([e, t]) => `${e}: ${t}`).join("\n")}\n-->`;
|
|
224
226
|
}
|
|
227
|
+
function V(e) {
|
|
228
|
+
return te[e] ?? "tk:created";
|
|
229
|
+
}
|
|
225
230
|
function H(e) {
|
|
226
|
-
|
|
231
|
+
let t = e.match(/<!--\s*tk:metadata\n([\s\S]*?)-->\n*([\s\S]*)$/);
|
|
232
|
+
if (!t) return {
|
|
233
|
+
metadata: {},
|
|
234
|
+
body: e
|
|
235
|
+
};
|
|
236
|
+
let n = {};
|
|
237
|
+
for (let e of t[1].split("\n")) {
|
|
238
|
+
let t = e.indexOf(":");
|
|
239
|
+
if (t === -1) continue;
|
|
240
|
+
let r = e.slice(0, t).trim(), i = e.slice(t + 1).trim();
|
|
241
|
+
r && (n[r] = i);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
metadata: n,
|
|
245
|
+
body: t[2]
|
|
246
|
+
};
|
|
227
247
|
}
|
|
228
248
|
function U(e) {
|
|
249
|
+
let t = Object.entries(e);
|
|
250
|
+
return t.length === 0 ? "---\n---\n" : `---\n${t.map(([e, t]) => `${e}: ${t}`).join("\n")}\n---\n`;
|
|
251
|
+
}
|
|
252
|
+
function W(e) {
|
|
229
253
|
let t = e.match(/\[[^\]]+\]\s+([^:]+):/);
|
|
230
254
|
return t ? t[1].trim() : null;
|
|
231
255
|
}
|
|
232
|
-
function
|
|
256
|
+
function G(e) {
|
|
233
257
|
let t = e.match(/^#\s+(.+)$/m);
|
|
234
258
|
return t ? t[1].trim() : "Untitled";
|
|
235
259
|
}
|
|
236
|
-
function
|
|
260
|
+
function K(t, i) {
|
|
237
261
|
let a = [], s = c(t, i.paths.prds);
|
|
238
262
|
if (e(s)) for (let e of r(s)) {
|
|
239
263
|
if (!e.endsWith(".md")) continue;
|
|
240
|
-
let t = o(e, ".md"), { metadata: r, body: i } =
|
|
264
|
+
let t = o(e, ".md"), { metadata: r, body: i } = z(n(c(s, e), "utf8"));
|
|
241
265
|
a.push({
|
|
242
266
|
slug: t,
|
|
243
267
|
type: "prd",
|
|
244
268
|
metadata: r,
|
|
245
269
|
body: i,
|
|
246
|
-
title:
|
|
247
|
-
archived: !1
|
|
270
|
+
title: G(i)
|
|
248
271
|
});
|
|
249
272
|
}
|
|
250
273
|
let l = c(t, i.paths.plans);
|
|
251
274
|
if (e(l)) for (let e of r(l)) {
|
|
252
275
|
if (!e.endsWith(".md")) continue;
|
|
253
|
-
let t = o(e, ".md"), r = n(c(l, e), "utf8");
|
|
276
|
+
let t = o(e, ".md"), r = n(c(l, e), "utf8"), { metadata: i, body: s } = z(r);
|
|
254
277
|
a.push({
|
|
255
278
|
slug: t,
|
|
256
279
|
type: "plan",
|
|
257
|
-
metadata:
|
|
258
|
-
body:
|
|
259
|
-
title:
|
|
260
|
-
archived: !1
|
|
280
|
+
metadata: i,
|
|
281
|
+
body: s,
|
|
282
|
+
title: G(s || r)
|
|
261
283
|
});
|
|
262
284
|
}
|
|
263
|
-
let u = c(t, i.paths.archives);
|
|
264
|
-
if (e(u)) for (let t of r(u, { withFileTypes: !0 })) {
|
|
265
|
-
if (!t.isDirectory()) continue;
|
|
266
|
-
let r = t.name, i = c(u, r, "prd.md"), o = c(u, r, "plan.md");
|
|
267
|
-
if (e(i)) {
|
|
268
|
-
let { metadata: e, body: t } = B(n(i, "utf8"));
|
|
269
|
-
a.push({
|
|
270
|
-
slug: r,
|
|
271
|
-
type: "prd",
|
|
272
|
-
metadata: {
|
|
273
|
-
...e,
|
|
274
|
-
status: "done"
|
|
275
|
-
},
|
|
276
|
-
body: t,
|
|
277
|
-
title: W(t),
|
|
278
|
-
archived: !0
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (e(o)) {
|
|
282
|
-
let e = n(o, "utf8");
|
|
283
|
-
a.push({
|
|
284
|
-
slug: r,
|
|
285
|
-
type: "plan",
|
|
286
|
-
metadata: { status: "done" },
|
|
287
|
-
body: e,
|
|
288
|
-
title: W(e),
|
|
289
|
-
archived: !0
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
285
|
return a;
|
|
294
286
|
}
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
287
|
+
function q(e) {
|
|
288
|
+
let t = e;
|
|
289
|
+
if (t.code === "ENOENT") return /* @__PURE__ */ Error("gh CLI not found — install it: https://cli.github.com");
|
|
290
|
+
let n = (t.stderr ?? t.message ?? "").toLowerCase();
|
|
291
|
+
return n.includes("not logged in") || n.includes("authentication") ? /* @__PURE__ */ Error("Not authenticated with GitHub. Run: gh auth login") : n.includes("rate limit") || n.includes("403") ? /* @__PURE__ */ Error("GitHub rate limit exceeded. Wait and retry.") : n.includes("not found") || n.includes("404") ? /* @__PURE__ */ Error("Repository not found. Check github.repo in .tracerkit/config.json") : e instanceof Error ? e : Error(String(e));
|
|
292
|
+
}
|
|
293
|
+
function J(e) {
|
|
294
|
+
try {
|
|
295
|
+
return d(`gh ${e.map((e) => `'${e.replace(/'/g, "'\\''")}'`).join(" ")}`, {
|
|
296
|
+
encoding: "utf8",
|
|
297
|
+
stdio: [
|
|
298
|
+
"pipe",
|
|
299
|
+
"pipe",
|
|
300
|
+
"pipe"
|
|
301
|
+
]
|
|
302
|
+
}).trim();
|
|
303
|
+
} catch (e) {
|
|
304
|
+
throw q(e);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function Y(e, t) {
|
|
306
308
|
return e.github.repo ? e.github.repo : t([
|
|
307
309
|
"repo",
|
|
308
310
|
"view",
|
|
@@ -312,7 +314,7 @@ function q(e, t) {
|
|
|
312
314
|
".nameWithOwner"
|
|
313
315
|
]);
|
|
314
316
|
}
|
|
315
|
-
function
|
|
317
|
+
function X(e, t, n) {
|
|
316
318
|
let r = n([
|
|
317
319
|
"issue",
|
|
318
320
|
"list",
|
|
@@ -323,16 +325,22 @@ function J(e, t, n) {
|
|
|
323
325
|
"--state",
|
|
324
326
|
"all",
|
|
325
327
|
"--json",
|
|
326
|
-
"number,title,labels,state",
|
|
328
|
+
"number,title,body,labels,state",
|
|
327
329
|
"--limit",
|
|
328
330
|
"1000"
|
|
329
331
|
]);
|
|
330
332
|
return JSON.parse(r || "[]");
|
|
331
333
|
}
|
|
332
|
-
function
|
|
333
|
-
return t.some((t) =>
|
|
334
|
+
function Z(e, t) {
|
|
335
|
+
return t.some((t) => {
|
|
336
|
+
if (t.body) {
|
|
337
|
+
let { metadata: n } = H(t.body);
|
|
338
|
+
if (n.slug) return n.slug === e;
|
|
339
|
+
}
|
|
340
|
+
return W(t.title) === e;
|
|
341
|
+
});
|
|
334
342
|
}
|
|
335
|
-
function
|
|
343
|
+
function ne(e, t, n) {
|
|
336
344
|
for (let r of t) n([
|
|
337
345
|
"label",
|
|
338
346
|
"create",
|
|
@@ -342,7 +350,7 @@ function X(e, t, n) {
|
|
|
342
350
|
"--force"
|
|
343
351
|
]);
|
|
344
352
|
}
|
|
345
|
-
function
|
|
353
|
+
function re(e, t, n) {
|
|
346
354
|
let r = [
|
|
347
355
|
"issue",
|
|
348
356
|
"create",
|
|
@@ -357,7 +365,7 @@ function Z(e, t, n) {
|
|
|
357
365
|
let i = n(r).match(/\/(\d+)\s*$/);
|
|
358
366
|
return i ? parseInt(i[1], 10) : 0;
|
|
359
367
|
}
|
|
360
|
-
function
|
|
368
|
+
function ie(e, t, n) {
|
|
361
369
|
n([
|
|
362
370
|
"issue",
|
|
363
371
|
"close",
|
|
@@ -366,36 +374,109 @@ function Q(e, t, n) {
|
|
|
366
374
|
e
|
|
367
375
|
]);
|
|
368
376
|
}
|
|
369
|
-
function
|
|
370
|
-
let
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
function ae(e, t, n) {
|
|
378
|
+
let r = n([
|
|
379
|
+
"pr",
|
|
380
|
+
"list",
|
|
381
|
+
"--repo",
|
|
382
|
+
e,
|
|
383
|
+
"--search",
|
|
384
|
+
t,
|
|
385
|
+
"--state",
|
|
386
|
+
"merged",
|
|
387
|
+
"--json",
|
|
388
|
+
"number,title",
|
|
389
|
+
"--limit",
|
|
390
|
+
"5"
|
|
391
|
+
]);
|
|
392
|
+
return JSON.parse(r || "[]");
|
|
393
|
+
}
|
|
394
|
+
function oe(e, t, n, r) {
|
|
395
|
+
r([
|
|
396
|
+
"issue",
|
|
397
|
+
"comment",
|
|
398
|
+
String(t),
|
|
399
|
+
"--repo",
|
|
400
|
+
e,
|
|
401
|
+
"--body",
|
|
402
|
+
n
|
|
403
|
+
]);
|
|
404
|
+
}
|
|
405
|
+
function se(e) {
|
|
406
|
+
for (let t of e) {
|
|
407
|
+
let e = R[t];
|
|
408
|
+
if (e) return e;
|
|
409
|
+
}
|
|
410
|
+
return "created";
|
|
411
|
+
}
|
|
412
|
+
function Q(e, n) {
|
|
413
|
+
t(s(e), { recursive: !0 }), a(e, n);
|
|
414
|
+
}
|
|
415
|
+
function ce(e, t) {
|
|
416
|
+
let n = t?.runGh ?? J, r = _(e);
|
|
417
|
+
return r.storage === "local" ? le(e, r, n) : ue(e, r, n);
|
|
418
|
+
}
|
|
419
|
+
function le(e, t, n) {
|
|
420
|
+
let r = [], i = Y(t, n), a = K(e, t);
|
|
421
|
+
if (a.length === 0) return x(e, { storage: p }), r.push("No artifacts found — nothing to migrate."), r.push(`✓ Storage switched to "${p}".`), r;
|
|
422
|
+
let o = t.github.labels?.prd ?? "tk:prd", s = t.github.labels?.plan ?? "tk:plan";
|
|
423
|
+
ne(i, [
|
|
424
|
+
o,
|
|
376
425
|
s,
|
|
377
|
-
|
|
378
|
-
...[...new Set(o.map((e) => H(e.metadata.status ?? "created")))]
|
|
426
|
+
...[...new Set(a.map((e) => V(e.metadata.status ?? "created")))]
|
|
379
427
|
], n);
|
|
380
|
-
let
|
|
381
|
-
for (let e of
|
|
382
|
-
let t = e.type === "prd" ?
|
|
383
|
-
if (
|
|
384
|
-
|
|
428
|
+
let c = X(i, o, n), l = X(i, s, n);
|
|
429
|
+
for (let e of a) {
|
|
430
|
+
let t = e.type === "prd" ? o : s, a = e.type === "prd" ? c : l;
|
|
431
|
+
if (Z(e.slug, a)) {
|
|
432
|
+
r.push(`⚠ skip ${e.type} "${e.slug}" — already exists on GitHub`);
|
|
385
433
|
continue;
|
|
386
434
|
}
|
|
387
|
-
let
|
|
435
|
+
let u = e.metadata.status ?? "created", d = V(u), f = `${B(e.metadata)}\n\n${e.body.replace(/^\n/, "")}`, p = re(i, {
|
|
388
436
|
title: `[${t}] ${e.slug}: ${e.title}`,
|
|
389
|
-
body:
|
|
390
|
-
labels: [t,
|
|
437
|
+
body: f,
|
|
438
|
+
labels: [t, d]
|
|
391
439
|
}, n);
|
|
392
|
-
|
|
440
|
+
u === "done" ? (ie(i, p, n), $(i, e.slug, p, n), r.push(`✓ ${e.type} "${e.slug}" → issue #${p} (closed)`)) : r.push(`✓ ${e.type} "${e.slug}" → issue #${p}`);
|
|
393
441
|
}
|
|
394
|
-
return x(e, { storage: p }),
|
|
442
|
+
return x(e, { storage: p }), r.push(`✓ Storage switched to "${p}".`), r;
|
|
443
|
+
}
|
|
444
|
+
function $(e, t, n, r) {
|
|
445
|
+
let i = ae(e, t, r);
|
|
446
|
+
i.length !== 0 && oe(e, n, `Linked PR: ${i.map((e) => `#${e.number}`).join(", ")}`, r);
|
|
447
|
+
}
|
|
448
|
+
function ue(t, n, r) {
|
|
449
|
+
let i = [], a = Y(n, r), o = n.github.labels?.prd ?? "tk:prd", s = n.github.labels?.plan ?? "tk:plan", l = X(a, o, r), u = X(a, s, r), d = [...l.map((e) => ({
|
|
450
|
+
...e,
|
|
451
|
+
type: "prd"
|
|
452
|
+
})), ...u.map((e) => ({
|
|
453
|
+
...e,
|
|
454
|
+
type: "plan"
|
|
455
|
+
}))];
|
|
456
|
+
if (d.length === 0) return x(t, { storage: f }), i.push("No GitHub issues found — nothing to migrate."), i.push(`✓ Storage switched to "${f}".`), i;
|
|
457
|
+
for (let r of d) {
|
|
458
|
+
let { metadata: a, body: o } = H(r.body ?? ""), s = a.slug ?? W(r.title);
|
|
459
|
+
if (!s) continue;
|
|
460
|
+
let l = r.labels.map((e) => e.name), u = a.status ?? se(l), d = c(t, r.type === "prd" ? n.paths.prds : n.paths.plans, `${s}.md`);
|
|
461
|
+
if (e(d)) {
|
|
462
|
+
i.push(`⚠ skip ${r.type} "${s}" — local file already exists`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (r.type === "prd") Q(d, `${U({
|
|
466
|
+
...a,
|
|
467
|
+
status: u
|
|
468
|
+
})}\n${o}`);
|
|
469
|
+
else {
|
|
470
|
+
let e = {};
|
|
471
|
+
a.source_prd && (e.source_prd = a.source_prd), (a.slug || s) && (e.slug = a.slug ?? s), u && (e.status = u), a.completed && (e.completed = a.completed), Q(d, `${U(e)}\n${o}`);
|
|
472
|
+
}
|
|
473
|
+
i.push(`✓ ${r.type} "${s}" → ${d}`);
|
|
474
|
+
}
|
|
475
|
+
return x(t, { storage: f }), i.push(`✓ Storage switched to "${f}".`), i;
|
|
395
476
|
}
|
|
396
477
|
//#endregion
|
|
397
478
|
//#region src/commands/uninstall.ts
|
|
398
|
-
function
|
|
479
|
+
function de(t) {
|
|
399
480
|
if (!w.some((n) => e(c(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
|
|
400
481
|
let n = [];
|
|
401
482
|
for (let r of w) {
|
|
@@ -408,4 +489,4 @@ function ee(t) {
|
|
|
408
489
|
return n;
|
|
409
490
|
}
|
|
410
491
|
//#endregion
|
|
411
|
-
export { P as a, T as c, f as d, _ as f, L as i, E as l,
|
|
492
|
+
export { P as a, T as c, f as d, _ as f, L as i, E as l, ce as n, O as o, x as p, ee as r, D as s, de as t, w as u };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tracerkit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Spec-driven workflow for
|
|
3
|
+
"version": "1.16.0",
|
|
4
|
+
"description": "Spec-driven workflow for AI coding agents — PRD → plan → verify. Pure Markdown skills, zero runtime deps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "helderberto",
|
package/skills/brief/SKILL.md
CHANGED
|
@@ -12,7 +12,7 @@ Overview of active features, progress, and suggested focus.
|
|
|
12
12
|
|
|
13
13
|
<!-- if:local -->
|
|
14
14
|
|
|
15
|
-
- Available PRDs: !`ls .tracerkit/prds/
|
|
15
|
+
- Available PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
|
|
16
16
|
<!-- end:local -->
|
|
17
17
|
<!-- if:github -->
|
|
18
18
|
- Available PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
|
|
@@ -31,11 +31,11 @@ For each `.md` file in `.tracerkit/prds/`: parse frontmatter, extract `status` a
|
|
|
31
31
|
|
|
32
32
|
List open GitHub Issues with label `{{github.labels.prd}}`:
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
1. For each issue, parse the `<!-- tk:metadata -->` comment in the body
|
|
35
|
+
2. Extract `status` and `created` fields from the metadata
|
|
36
|
+
3. Also check labels: `tk:created`, `tk:in-progress`
|
|
37
|
+
4. Skip issues with `tk:done` label
|
|
38
|
+
5. The slug is extracted from the title: `[{{github.labels.prd}}] <slug>: ...`
|
|
39
39
|
<!-- end:github -->
|
|
40
40
|
|
|
41
41
|
### 2. Count progress from plans
|
package/skills/check/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Verify implementation against plan. Shows progress, finds blockers, and
|
|
2
|
+
description: Verify implementation against plan. Shows progress, finds blockers, and marks complete when done. Use after implementing a plan, or without arguments to see a feature dashboard.
|
|
3
3
|
argument-hint: '[slug]'
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -7,13 +7,13 @@ argument-hint: '[slug]'
|
|
|
7
7
|
|
|
8
8
|
# Check Implementation
|
|
9
9
|
|
|
10
|
-
Check implementation against a plan. Update checks, stamp findings, transition status, and
|
|
10
|
+
Check implementation against a plan. Update checks, stamp findings, transition status, and mark complete when done.
|
|
11
11
|
|
|
12
12
|
## Pre-loaded context
|
|
13
13
|
|
|
14
14
|
<!-- if:local -->
|
|
15
15
|
|
|
16
|
-
- Available plans: !`ls .tracerkit/plans/
|
|
16
|
+
- Available plans: !`ls .tracerkit/plans/*.md 2>/dev/null || echo "(none)"`
|
|
17
17
|
<!-- end:local -->
|
|
18
18
|
<!-- if:github -->
|
|
19
19
|
- Available plans: list open GitHub Issues with label `{{github.labels.plan}}`
|
|
@@ -175,25 +175,25 @@ Append a verdict block at the bottom of the plan issue body by editing the issue
|
|
|
175
175
|
|
|
176
176
|
If a previous verdict block exists, replace it with the new one.
|
|
177
177
|
|
|
178
|
-
### 7. On `done` —
|
|
178
|
+
### 7. On `done` — mark complete
|
|
179
179
|
|
|
180
180
|
If all checks pass and zero BLOCKERS:
|
|
181
181
|
|
|
182
182
|
<!-- if:local -->
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
1. Copy PRD → `prd.md` (set `status: done`, add `completed` timestamp in frontmatter)
|
|
187
|
-
2. Copy plan → `plan.md` (append `## Archived` with date)
|
|
188
|
-
3. Delete originals
|
|
184
|
+
1. Update PRD frontmatter: `status: done`, add `completed: <UTC ISO 8601>`
|
|
185
|
+
2. Update plan frontmatter: `status: done`, add `completed: <UTC ISO 8601>`
|
|
189
186
|
|
|
190
187
|
<!-- end:local -->
|
|
191
188
|
<!-- if:github -->
|
|
192
189
|
|
|
193
|
-
1.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
190
|
+
1. If on a feature branch with commits ahead of default:
|
|
191
|
+
a. Push branch if not pushed
|
|
192
|
+
b. Open PR with `Closes #<prd-number>, Closes #<plan-number>` (or update existing PR body)
|
|
193
|
+
2. Search merged PRs matching slug: `gh pr list --search <slug> --state merged` — add comment on PRD issue linking found PRs
|
|
194
|
+
3. PRD issue: add `tk:done`, remove `tk:in-progress`, update metadata `status: done` + `completed` timestamp
|
|
195
|
+
4. Close PRD issue (reason: `completed`)
|
|
196
|
+
5. Close plan issue (reason: `completed`)
|
|
197
197
|
|
|
198
198
|
<!-- end:github -->
|
|
199
199
|
|
|
@@ -208,6 +208,6 @@ List the blockers to fix, then re-run `/tk:check <slug>`.
|
|
|
208
208
|
## Rules
|
|
209
209
|
|
|
210
210
|
- The review subagent must be **read-only** — it must not create, edit, or delete any files
|
|
211
|
-
- The only file writes this skill makes are: checkboxes + verdict block in the plan, and the
|
|
211
|
+
- The only file writes this skill makes are: checkboxes + verdict block in the plan, and the status updates on `done`
|
|
212
212
|
- Never modify implementation code — only observe and report
|
|
213
|
-
- If the PRD file is missing but all checks pass, warn and proceed —
|
|
213
|
+
- If the PRD file is missing but all checks pass, warn and proceed — mark the plan complete only (skip PRD status update)
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -24,7 +24,7 @@ Output: a GitHub Issue with label `{{github.labels.plan}}`.
|
|
|
24
24
|
|
|
25
25
|
<!-- if:local -->
|
|
26
26
|
|
|
27
|
-
- Available PRDs: !`ls .tracerkit/prds/
|
|
27
|
+
- Available PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
|
|
28
28
|
<!-- end:local -->
|
|
29
29
|
<!-- if:github -->
|
|
30
30
|
- Available PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
|
|
@@ -102,7 +102,7 @@ Each phase: thin vertical slice through all layers (schema → service → API
|
|
|
102
102
|
|
|
103
103
|
**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.
|
|
104
104
|
|
|
105
|
-
**Done when:** checkbox list of atomic, verifiable conditions
|
|
105
|
+
**Done when:** checkbox list of atomic, verifiable conditions. Each must name a test file/name, a shell command, or a file+content to verify. No prose-only conditions. Test: "Can an agent verify by reading files, running a command, or checking a test?"
|
|
106
106
|
|
|
107
107
|
**Layer-by-layer exception:** if complex schema changes underpin all modules and no story stands alone, build data foundation first, then slice vertically.
|
|
108
108
|
|
|
@@ -131,15 +131,23 @@ Present breakdown (title, user stories covered, done-when per phase). Ask: granu
|
|
|
131
131
|
Save to `.tracerkit/plans/<slug>.md` (create dir if missing).
|
|
132
132
|
|
|
133
133
|
```markdown
|
|
134
|
+
---
|
|
135
|
+
source_prd: .tracerkit/prds/<slug>.md
|
|
136
|
+
slug: <slug>
|
|
137
|
+
status: in_progress
|
|
138
|
+
---
|
|
139
|
+
|
|
134
140
|
# Plan: <Feature Name>
|
|
135
141
|
|
|
136
142
|
> Source PRD: `.tracerkit/prds/<slug>.md`
|
|
137
143
|
```
|
|
138
144
|
|
|
145
|
+
Then update PRD frontmatter: add `plan: .tracerkit/plans/<slug>.md` field.
|
|
146
|
+
|
|
139
147
|
<!-- end:local -->
|
|
140
148
|
<!-- if:github -->
|
|
141
149
|
|
|
142
|
-
Ensure labels exist: `{{github.labels.plan}}`, `tk:in-progress
|
|
150
|
+
Ensure labels exist: `gh label create {{github.labels.plan}} --repo {{github.repo}} --force`, `gh label create tk:in-progress --repo {{github.repo}} --force`.
|
|
143
151
|
|
|
144
152
|
Create GitHub Issue — title: `[{{github.labels.plan}}] <slug>: Plan: <Feature Title>`, labels: `{{github.labels.plan}}`, `tk:in-progress`.
|
|
145
153
|
|
|
@@ -147,6 +155,7 @@ Create GitHub Issue — title: `[{{github.labels.plan}}] <slug>: Plan: <Feature
|
|
|
147
155
|
<!-- tk:metadata
|
|
148
156
|
source_prd: #<PRD issue number>
|
|
149
157
|
slug: <slug>
|
|
158
|
+
status: in_progress
|
|
150
159
|
-->
|
|
151
160
|
|
|
152
161
|
# Plan: <Feature Name>
|
|
@@ -156,6 +165,19 @@ slug: <slug>
|
|
|
156
165
|
|
|
157
166
|
<!-- end:github -->
|
|
158
167
|
|
|
168
|
+
### 6b. Backlink PRD
|
|
169
|
+
|
|
170
|
+
<!-- if:local -->
|
|
171
|
+
|
|
172
|
+
Already linked via PRD frontmatter `plan:` field (set in step 6).
|
|
173
|
+
|
|
174
|
+
<!-- end:local -->
|
|
175
|
+
<!-- if:github -->
|
|
176
|
+
|
|
177
|
+
Add comment on PRD issue: "Plan: #<plan-issue-number>" (creates cross-reference).
|
|
178
|
+
|
|
179
|
+
<!-- end:github -->
|
|
180
|
+
|
|
159
181
|
Use this structure for the plan body:
|
|
160
182
|
|
|
161
183
|
```markdown
|
|
@@ -195,6 +217,28 @@ Gaps found in the PRD needing resolution. Blank if none.
|
|
|
195
217
|
|
|
196
218
|
Print one line per phase: `Phase N — <title> (<condition summary>)`. Then ask: "Run `/tk:check <slug>` when ready?"
|
|
197
219
|
|
|
220
|
+
## Execution guidance
|
|
221
|
+
|
|
222
|
+
When implementing this plan, **always offer to create a feature branch** before writing any code:
|
|
223
|
+
|
|
224
|
+
> "Create branch `feat/<slug>` for this work? (Y/n)"
|
|
225
|
+
|
|
226
|
+
If accepted, create the branch from the default branch.
|
|
227
|
+
|
|
228
|
+
### During implementation
|
|
229
|
+
|
|
230
|
+
Mark each "Done when" checkbox `[x]` **immediately after verifying** the condition.
|
|
231
|
+
|
|
232
|
+
Always update the local plan file (`.tracerkit/plans/<slug>.md`): change `- [ ]` → `- [x]`. This file is the working copy for both local and GitHub modes.
|
|
233
|
+
|
|
234
|
+
<!-- if:github -->
|
|
235
|
+
|
|
236
|
+
**Sync to GitHub at phase boundaries**: after completing all items in a phase, update the plan issue body with `gh issue edit` to reflect the local state. This avoids per-item API calls.
|
|
237
|
+
|
|
238
|
+
After all phases, open a PR with body containing `Closes #<prd-issue>, Closes #<plan-issue>`.
|
|
239
|
+
|
|
240
|
+
<!-- end:github -->
|
|
241
|
+
|
|
198
242
|
## Rules
|
|
199
243
|
|
|
200
244
|
- Phases derive from PRD user stories — never invented
|
package/skills/prd/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ Skip satisfied steps. If argument provided, skip to Step 2.
|
|
|
13
13
|
|
|
14
14
|
<!-- if:local -->
|
|
15
15
|
|
|
16
|
-
- Existing PRDs: !`ls .tracerkit/prds/
|
|
16
|
+
- Existing PRDs: !`ls .tracerkit/prds/*.md 2>/dev/null || echo "(none)"`
|
|
17
17
|
<!-- end:local -->
|
|
18
18
|
<!-- if:github -->
|
|
19
19
|
- Existing PRDs: list open GitHub Issues with label `{{github.labels.prd}}`
|
|
@@ -26,10 +26,11 @@ The argument is: $ARGUMENTS
|
|
|
26
26
|
If empty, go to Step 1; derive slug after gathering the idea. If provided, derive slug:
|
|
27
27
|
|
|
28
28
|
1. Take only the text before the first `—` or `–` (if present)
|
|
29
|
-
2.
|
|
30
|
-
3.
|
|
31
|
-
4.
|
|
32
|
-
5.
|
|
29
|
+
2. Strip leading command verbs: create, build, implement, add, update, fix, make, write, plan, get, show, support
|
|
30
|
+
3. Lowercase the text
|
|
31
|
+
4. Remove filler words: a, an, the, for, of, to, in, on, with, and, or, but, is, be
|
|
32
|
+
5. Take the first 4 remaining words (or fewer if less exist)
|
|
33
|
+
6. Join with hyphens → `<slug>`
|
|
33
34
|
|
|
34
35
|
<!-- if:local -->
|
|
35
36
|
|
|
@@ -98,12 +99,13 @@ status: created
|
|
|
98
99
|
<!-- end:local -->
|
|
99
100
|
<!-- if:github -->
|
|
100
101
|
|
|
101
|
-
Ensure labels exist: `{{github.labels.prd}}`, `tk:created
|
|
102
|
+
Ensure labels exist: `gh label create {{github.labels.prd}} --repo {{github.repo}} --force`, `gh label create tk:created --repo {{github.repo}} --force`.
|
|
102
103
|
|
|
103
104
|
Create GitHub Issue — title: `[{{github.labels.prd}}] <slug>: <Feature Title>`, labels: `{{github.labels.prd}}`, `tk:created`.
|
|
104
105
|
|
|
105
106
|
```markdown
|
|
106
107
|
<!-- tk:metadata
|
|
108
|
+
slug: <slug>
|
|
107
109
|
created: <UTC ISO 8601>
|
|
108
110
|
status: created
|
|
109
111
|
-->
|