tracerkit 1.7.1 → 1.8.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/dist/bin.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync as
|
|
4
|
-
import { dirname as
|
|
5
|
-
import { fileURLToPath as
|
|
6
|
-
import { homedir as
|
|
2
|
+
import { i as e, n as t, r as n, s as r, t as i } from "./uninstall-DO2YpTSz.js";
|
|
3
|
+
import { existsSync as a, mkdirSync as o, readFileSync as s, readdirSync as c, rmSync as l, unlinkSync as u, writeFileSync as d } from "node:fs";
|
|
4
|
+
import { dirname as f, join as p, resolve as m } from "node:path";
|
|
5
|
+
import { fileURLToPath as h } from "node:url";
|
|
6
|
+
import { homedir as g } from "node:os";
|
|
7
7
|
//#region src/frontmatter.ts
|
|
8
|
-
var
|
|
9
|
-
function
|
|
8
|
+
var _ = /^---\n([\s\S]*?)\n---(?:\n|$)/;
|
|
9
|
+
function v(e) {
|
|
10
10
|
return e.replace(/\r\n/g, "\n");
|
|
11
11
|
}
|
|
12
|
-
function
|
|
13
|
-
let
|
|
12
|
+
function y(e) {
|
|
13
|
+
let t = v(e).match(_);
|
|
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 b(e, t, n) {
|
|
25
|
+
let r = v(e), i = r.match(_);
|
|
14
26
|
if (!i) return `---\n${t}: ${n}\n---\n${r}`;
|
|
15
27
|
let a = i[1].split("\n"), o = RegExp(`^${t}\\s*:`), s = a.findIndex((e) => o.test(e));
|
|
16
28
|
s === -1 ? a.push(`${t}: ${n}`) : a[s] = `${t}: ${n}`;
|
|
@@ -19,21 +31,21 @@ function _(e, t, n) {
|
|
|
19
31
|
}
|
|
20
32
|
//#endregion
|
|
21
33
|
//#region src/commands/archive.ts
|
|
22
|
-
function
|
|
23
|
-
let
|
|
24
|
-
if (!
|
|
25
|
-
if (
|
|
26
|
-
|
|
34
|
+
function x(e, t) {
|
|
35
|
+
let n = r(e), i = p(e, n.paths.prds, `${t}.md`), c = p(e, n.paths.plans, `${t}.md`), f = p(e, n.paths.archives, t), m = a(i);
|
|
36
|
+
if (!a(c)) throw Error(`Plan "${t}" not found at ${c}`);
|
|
37
|
+
if (a(f)) throw Error(`Archive "${t}" already exists at ${f}`);
|
|
38
|
+
o(f, { recursive: !0 });
|
|
27
39
|
try {
|
|
28
|
-
let e = (/* @__PURE__ */ new Date()).toISOString(),
|
|
40
|
+
let e = (/* @__PURE__ */ new Date()).toISOString(), r = [];
|
|
29
41
|
if (m) {
|
|
30
|
-
let t =
|
|
31
|
-
t =
|
|
32
|
-
} else
|
|
33
|
-
let
|
|
34
|
-
return
|
|
42
|
+
let t = s(i, "utf8");
|
|
43
|
+
t = b(t, "status", "done"), t = b(t, "completed", e), d(p(f, "prd.md"), t);
|
|
44
|
+
} else r.push(`Warning: PRD "${t}" missing, archiving plan only`);
|
|
45
|
+
let a = s(c, "utf8");
|
|
46
|
+
return a += `\n## Archived\n\nArchived on ${e.slice(0, 10)}.\n`, d(p(f, "plan.md"), a), m && u(i), u(c), r.push(`Archived "${t}" to ${n.paths.archives}/${t}/`), m && r.push(` prd.md — status: done, completed: ${e}`), r.push(" plan.md — archived block appended"), r;
|
|
35
47
|
} catch (e) {
|
|
36
|
-
throw
|
|
48
|
+
throw l(f, {
|
|
37
49
|
recursive: !0,
|
|
38
50
|
force: !0
|
|
39
51
|
}), e;
|
|
@@ -41,11 +53,11 @@ function v(e, n) {
|
|
|
41
53
|
}
|
|
42
54
|
//#endregion
|
|
43
55
|
//#region src/plan.ts
|
|
44
|
-
var
|
|
45
|
-
function
|
|
56
|
+
var S = /^## (Phase \d+\s*.*)$/, C = /^- \[x\] /i, w = /^- \[ \] /;
|
|
57
|
+
function T(e) {
|
|
46
58
|
let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = null;
|
|
47
59
|
for (let e of t) {
|
|
48
|
-
let t = e.trimStart(), i = t.match(
|
|
60
|
+
let t = e.trimStart(), i = t.match(S);
|
|
49
61
|
if (t.startsWith("## ")) {
|
|
50
62
|
i ? (r = {
|
|
51
63
|
title: i[1].trim(),
|
|
@@ -54,32 +66,80 @@ function S(e) {
|
|
|
54
66
|
}, n.push(r)) : r = null;
|
|
55
67
|
continue;
|
|
56
68
|
}
|
|
57
|
-
r && (
|
|
69
|
+
r && (C.test(t) ? (r.checked++, r.total++) : w.test(t) && r.total++);
|
|
58
70
|
}
|
|
59
71
|
return { phases: n };
|
|
60
72
|
}
|
|
61
73
|
//#endregion
|
|
74
|
+
//#region src/commands/brief.ts
|
|
75
|
+
var E = /^- \[ \] (.+)/;
|
|
76
|
+
function D(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 O(e) {
|
|
83
|
+
for (let t of e.split("\n")) {
|
|
84
|
+
let e = t.trimStart().match(E);
|
|
85
|
+
if (e) return e[1].replace(/\s*\[.*?\]\s*$/, "").trim();
|
|
86
|
+
}
|
|
87
|
+
return "—";
|
|
88
|
+
}
|
|
89
|
+
function k(e, t = /* @__PURE__ */ new Date()) {
|
|
90
|
+
let n = r(e), i = p(e, n.paths.prds);
|
|
91
|
+
if (!a(i)) return ["No features found — run `/tk:prd` to start one."];
|
|
92
|
+
let o = c(i).filter((e) => e.endsWith(".md")).sort();
|
|
93
|
+
if (o.length === 0) return ["No features found — run `/tk:prd` to start one."];
|
|
94
|
+
let l = p(e, n.paths.plans), u = [];
|
|
95
|
+
for (let e of o) {
|
|
96
|
+
let t = e.replace(/\.md$/, ""), n = y(s(p(i, e), "utf8"));
|
|
97
|
+
if (n.status === "done") continue;
|
|
98
|
+
let r = n.status || "unknown", o = n.created || "", c = o && !isNaN(new Date(o).getTime()) ? o : "", d = "—", f = "—", m = p(l, `${t}.md`);
|
|
99
|
+
if (a(m)) {
|
|
100
|
+
let e = s(m, "utf8"), { phases: t } = T(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 = O(e);
|
|
102
|
+
}
|
|
103
|
+
u.push({
|
|
104
|
+
slug: t,
|
|
105
|
+
status: r,
|
|
106
|
+
created: c,
|
|
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 n = e.created ? D(e.created, t) : "";
|
|
115
|
+
return `| ${e.slug} | ${e.status} | ${n} | ${e.progress} | ${e.next} |`;
|
|
116
|
+
}), f = u.filter((e) => e.status === "in_progress"), m = f.length === 1 ? f[0] : f[0] ?? u[0];
|
|
117
|
+
return [
|
|
118
|
+
"| Feature | Status | Age | Progress | Next |",
|
|
119
|
+
"|---------|--------|-----|----------|------|",
|
|
120
|
+
...d,
|
|
121
|
+
"",
|
|
122
|
+
`**Focus → ${m.slug}**`
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
62
126
|
//#region src/commands/progress.ts
|
|
63
|
-
function
|
|
64
|
-
let
|
|
65
|
-
if (!
|
|
66
|
-
let { phases:
|
|
67
|
-
if (
|
|
68
|
-
let
|
|
69
|
-
for (let e of
|
|
70
|
-
return
|
|
127
|
+
function A(e, t) {
|
|
128
|
+
let n = p(e, r(e).paths.plans, `${t}.md`);
|
|
129
|
+
if (!a(n)) throw Error(`Plan "${t}" not found at ${n}`);
|
|
130
|
+
let { phases: i } = T(s(n, "utf8"));
|
|
131
|
+
if (i.length === 0) return ["No phases found in plan.", "Total: 0/0"];
|
|
132
|
+
let o = [], c = 0, l = 0;
|
|
133
|
+
for (let e of i) c += e.checked, l += e.total, o.push(` ${e.title}: ${e.checked}/${e.total}`);
|
|
134
|
+
return o.push(""), o.push(`Total: ${c}/${l}`), o;
|
|
71
135
|
}
|
|
72
136
|
//#endregion
|
|
73
137
|
//#region src/cli.ts
|
|
74
|
-
var { version:
|
|
138
|
+
var { version: j } = JSON.parse(s(m(f(h(import.meta.url)), "..", "package.json"), "utf8")), M = Math.max(...e.map((e) => `${e.name} ${e.args}`.length)), N = [
|
|
75
139
|
"Usage: tracerkit <command> [path]",
|
|
76
140
|
"",
|
|
77
141
|
"Commands:",
|
|
78
|
-
|
|
79
|
-
" update [path] Refresh unchanged files from latest version, skip modified",
|
|
80
|
-
" uninstall [path] Remove TracerKit skill directories, keep .tracerkit/ artifacts",
|
|
81
|
-
" progress <slug> Show per-phase checkbox progress for a plan",
|
|
82
|
-
" archive <slug> Archive a completed feature (PRD + plan)",
|
|
142
|
+
...e.map((e) => ` ${`${e.name} ${e.args}`.padEnd(M + 2)}${e.desc}`),
|
|
83
143
|
"",
|
|
84
144
|
"Options:",
|
|
85
145
|
" --force Overwrite modified files during update",
|
|
@@ -88,42 +148,43 @@ var { version: w } = JSON.parse(o(f(u(p(import.meta.url)), "..", "package.json")
|
|
|
88
148
|
"",
|
|
89
149
|
"All commands default to the home directory when no path is given."
|
|
90
150
|
];
|
|
91
|
-
function
|
|
151
|
+
function P(e, t = g()) {
|
|
92
152
|
let n = e.find((e) => !e.startsWith("-"));
|
|
93
|
-
return n ?
|
|
153
|
+
return n ? m(n) : t;
|
|
94
154
|
}
|
|
95
|
-
function
|
|
155
|
+
function F(e, t) {
|
|
96
156
|
let n = e.findIndex((e) => !e.startsWith("-"));
|
|
97
157
|
if (n === -1) return [
|
|
98
158
|
"Error: missing <slug> argument",
|
|
99
159
|
"",
|
|
100
|
-
...
|
|
160
|
+
...N
|
|
101
161
|
];
|
|
102
162
|
let r = e[n], i = e.filter((e, t) => t !== n);
|
|
103
163
|
try {
|
|
104
|
-
return t(
|
|
164
|
+
return t(P(i, process.cwd()), r);
|
|
105
165
|
} catch (e) {
|
|
106
166
|
return [`Error: ${e instanceof Error ? e.message : String(e)}`];
|
|
107
167
|
}
|
|
108
168
|
}
|
|
109
|
-
function
|
|
110
|
-
if (
|
|
111
|
-
if (
|
|
112
|
-
let
|
|
113
|
-
switch (
|
|
114
|
-
case "
|
|
169
|
+
function I(e) {
|
|
170
|
+
if (e.includes("--help") || e.includes("-h")) return N;
|
|
171
|
+
if (e.includes("--version") || e.includes("-v")) return [`tracerkit/${j}`];
|
|
172
|
+
let r = e[0], a = e.slice(1);
|
|
173
|
+
switch (r) {
|
|
174
|
+
case "brief": return k(P(a, process.cwd()));
|
|
175
|
+
case "init": return n(P(a));
|
|
115
176
|
case "update": {
|
|
116
|
-
let
|
|
177
|
+
let e = a.includes("--force"), n = t(P(a.filter((e) => e !== "--force")), { force: e });
|
|
117
178
|
return n.push("", "Updated to the latest TracerKit."), n.push("If using Claude Code, restart your session to load changes."), n;
|
|
118
179
|
}
|
|
119
|
-
case "uninstall": return
|
|
120
|
-
case "progress": return
|
|
121
|
-
case "archive": return
|
|
122
|
-
default: return
|
|
180
|
+
case "uninstall": return i(P(a));
|
|
181
|
+
case "progress": return F(a, A);
|
|
182
|
+
case "archive": return F(a, x);
|
|
183
|
+
default: return N;
|
|
123
184
|
}
|
|
124
185
|
}
|
|
125
186
|
//#endregion
|
|
126
187
|
//#region src/bin.ts
|
|
127
|
-
var
|
|
128
|
-
for (let e of
|
|
188
|
+
var L = I(process.argv.slice(2));
|
|
189
|
+
for (let e of L) console.log(e);
|
|
129
190
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as e, i as t, n, r,
|
|
2
|
-
export { t as
|
|
1
|
+
import { a as e, i as t, n, o as r, r as i, t as a } from "./uninstall-DO2YpTSz.js";
|
|
2
|
+
export { t as COMMANDS, e as DEPRECATED_SKILLS, r as SKILL_NAMES, i as init, a as uninstall, n as update };
|
|
@@ -25,40 +25,72 @@ function d(t) {
|
|
|
25
25
|
} };
|
|
26
26
|
}
|
|
27
27
|
var f = [
|
|
28
|
+
"tk:brief",
|
|
28
29
|
"tk:prd",
|
|
29
30
|
"tk:plan",
|
|
30
31
|
"tk:check"
|
|
31
|
-
], p = ["tk:verify"], m =
|
|
32
|
-
|
|
32
|
+
], p = ["tk:verify"], m = [
|
|
33
|
+
{
|
|
34
|
+
name: "init",
|
|
35
|
+
args: "[path]",
|
|
36
|
+
desc: "Install skills to ~/.claude/skills/ (or [path] if given)"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "update",
|
|
40
|
+
args: "[path]",
|
|
41
|
+
desc: "Refresh unchanged files from latest version, skip modified"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "uninstall",
|
|
45
|
+
args: "[path]",
|
|
46
|
+
desc: "Remove TracerKit skill directories, keep .tracerkit/ artifacts"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "brief",
|
|
50
|
+
args: "[path]",
|
|
51
|
+
desc: "Show active features, progress, and suggested focus"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "progress",
|
|
55
|
+
args: "<slug>",
|
|
56
|
+
desc: "Show per-phase checkbox progress for a plan"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "archive",
|
|
60
|
+
args: "<slug>",
|
|
61
|
+
desc: "Archive a completed feature (PRD + plan)"
|
|
62
|
+
}
|
|
63
|
+
], h = s(o(l(import.meta.url)), "..", "templates");
|
|
64
|
+
function g(e, t = "") {
|
|
33
65
|
let n = r(e, { withFileTypes: !0 }), i = [];
|
|
34
66
|
for (let r of n) {
|
|
35
67
|
let n = t ? `${t}/${r.name}` : r.name;
|
|
36
|
-
r.isDirectory() ? i.push(...
|
|
68
|
+
r.isDirectory() ? i.push(...g(s(e, r.name), n)) : i.push(n);
|
|
37
69
|
}
|
|
38
70
|
return i.sort();
|
|
39
71
|
}
|
|
40
|
-
function
|
|
72
|
+
function _(e, t) {
|
|
41
73
|
return e.replaceAll("{{paths.prds}}", t.paths.prds).replaceAll("{{paths.plans}}", t.paths.plans).replaceAll("{{paths.archives}}", t.paths.archives);
|
|
42
74
|
}
|
|
43
|
-
function
|
|
44
|
-
let c = i ?? h
|
|
75
|
+
function v(e, r, i) {
|
|
76
|
+
let c = i ?? g(h);
|
|
45
77
|
for (let i of c) {
|
|
46
|
-
let c = s(
|
|
47
|
-
t(o(l), { recursive: !0 }), a(l,
|
|
78
|
+
let c = s(h, i), l = s(e, i);
|
|
79
|
+
t(o(l), { recursive: !0 }), a(l, _(n(c, "utf8"), r));
|
|
48
80
|
}
|
|
49
81
|
return { copied: c };
|
|
50
82
|
}
|
|
51
|
-
function
|
|
83
|
+
function y(e) {
|
|
52
84
|
return c("sha256").update(e).digest("hex");
|
|
53
85
|
}
|
|
54
|
-
function
|
|
55
|
-
let i = h
|
|
86
|
+
function b(t, r) {
|
|
87
|
+
let i = g(h), a = [], o = [], c = [];
|
|
56
88
|
for (let l of i) {
|
|
57
89
|
let i = s(t, l);
|
|
58
90
|
if (!e(i)) c.push(l);
|
|
59
91
|
else {
|
|
60
|
-
let e =
|
|
61
|
-
|
|
92
|
+
let e = _(n(s(h, l), "utf8"), r);
|
|
93
|
+
y(Buffer.from(e)) === y(n(i)) ? a.push(l) : o.push(l);
|
|
62
94
|
}
|
|
63
95
|
}
|
|
64
96
|
return {
|
|
@@ -69,16 +101,16 @@ function y(t, r) {
|
|
|
69
101
|
}
|
|
70
102
|
//#endregion
|
|
71
103
|
//#region src/commands/init.ts
|
|
72
|
-
function
|
|
104
|
+
function x(t) {
|
|
73
105
|
for (let n of f) if (e(s(t, ".claude", "skills", n))) throw Error(`.claude/skills/${n}/ already exists — aborting`);
|
|
74
|
-
let { copied: n } =
|
|
106
|
+
let { copied: n } = v(t, d(t));
|
|
75
107
|
return n.map((e) => `✓ ${e}`);
|
|
76
108
|
}
|
|
77
109
|
//#endregion
|
|
78
110
|
//#region src/commands/update.ts
|
|
79
|
-
function
|
|
111
|
+
function S(t, n) {
|
|
80
112
|
if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — run `tracerkit init` first");
|
|
81
|
-
let r = d(t), { unchanged: a, modified: o, missing: c } =
|
|
113
|
+
let r = d(t), { unchanged: a, modified: o, missing: c } = b(t, r), l = [];
|
|
82
114
|
for (let n of p) {
|
|
83
115
|
let r = s(t, ".claude", "skills", n);
|
|
84
116
|
e(r) && (i(r, {
|
|
@@ -92,7 +124,7 @@ function x(t, n) {
|
|
|
92
124
|
...u ? o : []
|
|
93
125
|
];
|
|
94
126
|
if (m.length > 0) {
|
|
95
|
-
|
|
127
|
+
v(t, r, m);
|
|
96
128
|
for (let e of a) l.push(`✓ ${e}`);
|
|
97
129
|
for (let e of c) l.push(`✓ ${e} (added)`);
|
|
98
130
|
if (u) for (let e of o) l.push(`✓ ${e} (replaced)`);
|
|
@@ -105,7 +137,7 @@ function x(t, n) {
|
|
|
105
137
|
}
|
|
106
138
|
//#endregion
|
|
107
139
|
//#region src/commands/uninstall.ts
|
|
108
|
-
function
|
|
140
|
+
function C(t) {
|
|
109
141
|
if (!f.some((n) => e(s(t, ".claude", "skills", n)))) throw Error("TracerKit not initialized — nothing to uninstall");
|
|
110
142
|
let n = [];
|
|
111
143
|
for (let r of f) {
|
|
@@ -118,4 +150,4 @@ function S(t) {
|
|
|
118
150
|
return n;
|
|
119
151
|
}
|
|
120
152
|
//#endregion
|
|
121
|
-
export {
|
|
153
|
+
export { p as a, m as i, S as n, f as o, x as r, d as s, C as t };
|
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Session briefing — shows active features, progress, and suggested focus. Use at the start of a session to orient.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Session Briefing
|
|
6
|
+
|
|
7
|
+
Get a quick overview of all active features, their progress, and what to focus on next.
|
|
8
|
+
|
|
9
|
+
## Pre-loaded context
|
|
10
|
+
|
|
11
|
+
- Briefing: !`npx tracerkit brief 2>/dev/null || echo "no {{paths.prds}}/ directory found"`
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
|
|
15
|
+
### 1. Present the briefing
|
|
16
|
+
|
|
17
|
+
Display the output above as-is. The table includes:
|
|
18
|
+
|
|
19
|
+
- **Feature**: slug (PRD filename without `.md`)
|
|
20
|
+
- **Status**: from PRD frontmatter (`created`, `in_progress`) — `unknown` if no frontmatter
|
|
21
|
+
- **Age**: time since `created` date
|
|
22
|
+
- **Progress**: checked/total from plan checkboxes, or `—` if no plan
|
|
23
|
+
- **Next**: first unchecked item from the plan, or `—`
|
|
24
|
+
|
|
25
|
+
Features with `status: done` are excluded (already archived).
|
|
26
|
+
|
|
27
|
+
### 2. Focus recommendation
|
|
28
|
+
|
|
29
|
+
The **Focus** line at the bottom suggests which feature to work on:
|
|
30
|
+
|
|
31
|
+
- If exactly 1 feature is `in_progress`, it's auto-selected
|
|
32
|
+
- Otherwise, the oldest feature by `created` date is selected
|
|
33
|
+
|
|
34
|
+
### 3. Offer next steps
|
|
35
|
+
|
|
36
|
+
Ask the user what they'd like to do:
|
|
37
|
+
|
|
38
|
+
- Continue the focused feature (read its plan at `{{paths.plans}}/<slug>.md`)
|
|
39
|
+
- Start a new feature with `/tk:prd`
|
|
40
|
+
- Check progress on a feature with `/tk:check <slug>`
|
|
@@ -47,7 +47,7 @@ Before launching a subagent, check whether the primary module file(s) from Phase
|
|
|
47
47
|
|
|
48
48
|
### 3b. Launch read-only review
|
|
49
49
|
|
|
50
|
-
Use a **
|
|
50
|
+
Use a **general-purpose subagent** (not `code-review` — that agent is for PR reviews). The subagent must be **read-only** (no file writes, no edits). It should:
|
|
51
51
|
|
|
52
52
|
1. Read every section of the plan — architectural decisions, each phase, done-when checkboxes
|
|
53
53
|
2. For each phase, check every `- [ ]` / `- [x]` item against the codebase
|