ai-project-manage-cli 8.1.21 → 8.1.22
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 +0 -4
- package/dist/index.js +622 -656
- package/dist/scripts/branch_has_changes.sh +53 -0
- package/dist/scripts/commit_if_dirty.sh +21 -0
- package/dist/scripts/ensure_baseline.sh +15 -0
- package/dist/scripts/ensure_feature_branch.sh +55 -0
- package/dist/scripts/push_code.sh +21 -0
- package/dist/worker-script.js +2584 -3120
- package/package.json +2 -2
- package/template/README.md +13 -0
- package/template/hooks/after-agent-commit-repos.cjs +112 -0
- package/template/hooks/before-prompt-prepare.cjs +206 -0
- package/template/hooks.json +18 -0
- package/template/mcp.json +18 -0
- package/template/skills/webide-run-retro/SKILL.md +0 -103
- package/template/skills/webide-run-retro/attribution.md +0 -26
- package/template/skills/webide-run-retro/scripts/inspect-run.mjs +0 -335
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Resolve a Cursor SDK run-id or agent-id to the local transcript and a
|
|
4
|
-
* structured conversation summary. Execute this script; do not reimplement.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* node inspect-run.mjs --id run-<uuid>
|
|
8
|
-
* node inspect-run.mjs --id agent-<uuid>
|
|
9
|
-
* node inspect-run.mjs --id <uuid>
|
|
10
|
-
*/
|
|
11
|
-
import fs from "node:fs";
|
|
12
|
-
import os from "node:os";
|
|
13
|
-
import path from "node:path";
|
|
14
|
-
import { DatabaseSync } from "node:sqlite";
|
|
15
|
-
|
|
16
|
-
const UUID_RE =
|
|
17
|
-
"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
18
|
-
const RUN_RE = new RegExp(`run-${UUID_RE}`, "gi");
|
|
19
|
-
const AGENT_RE = new RegExp(`agent-${UUID_RE}`, "gi");
|
|
20
|
-
|
|
21
|
-
function arg(name, fallback) {
|
|
22
|
-
const idx = process.argv.indexOf(name);
|
|
23
|
-
if (idx >= 0 && process.argv[idx + 1]) return process.argv[idx + 1];
|
|
24
|
-
return fallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function walkFiles(root, matcher, acc = [], depth = 0) {
|
|
28
|
-
if (!root || depth > 6 || acc.length > 80) return acc;
|
|
29
|
-
let entries = [];
|
|
30
|
-
try {
|
|
31
|
-
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
32
|
-
} catch {
|
|
33
|
-
return acc;
|
|
34
|
-
}
|
|
35
|
-
for (const ent of entries) {
|
|
36
|
-
const full = path.join(root, ent.name);
|
|
37
|
-
if (ent.isDirectory()) {
|
|
38
|
-
if (["node_modules", ".git", "target"].includes(ent.name)) continue;
|
|
39
|
-
walkFiles(full, matcher, acc, depth + 1);
|
|
40
|
-
} else if (matcher(full, ent.name)) {
|
|
41
|
-
acc.push(full);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return acc;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function normalizeId(raw) {
|
|
48
|
-
const id = String(raw || "").trim();
|
|
49
|
-
if (!id) throw new Error("missing --id");
|
|
50
|
-
if (id.startsWith("run-") || id.startsWith("agent-")) return id;
|
|
51
|
-
if (new RegExp(`^${UUID_RE}$`, "i").test(id)) {
|
|
52
|
-
return { uuid: id, ambiguous: true };
|
|
53
|
-
}
|
|
54
|
-
return id;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function texts(rec) {
|
|
58
|
-
const content = rec?.message?.content;
|
|
59
|
-
if (!Array.isArray(content)) return [];
|
|
60
|
-
return content
|
|
61
|
-
.filter((c) => c?.type === "text" && c.text)
|
|
62
|
-
.map((c) => String(c.text).replace(/\[REDACTED\]/g, "").trim())
|
|
63
|
-
.filter(Boolean);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function tools(rec) {
|
|
67
|
-
const content = rec?.message?.content;
|
|
68
|
-
if (!Array.isArray(content)) return [];
|
|
69
|
-
return content.filter((c) => c?.type === "tool_use").map((c) => c.name);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function parseTranscript(filePath) {
|
|
73
|
-
const lines = fs.readFileSync(filePath, "utf8").trim().split("\n").filter(Boolean);
|
|
74
|
-
const records = lines.map((line, i) => {
|
|
75
|
-
try {
|
|
76
|
-
return { i, ...JSON.parse(line) };
|
|
77
|
-
} catch {
|
|
78
|
-
return { i, role: "unknown", parseError: true };
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const rounds = [];
|
|
83
|
-
let cur = null;
|
|
84
|
-
for (const rec of records) {
|
|
85
|
-
if (rec.role === "user") {
|
|
86
|
-
const raw = texts(rec).join("\n");
|
|
87
|
-
if (raw.startsWith("<dynamic_tools>")) continue;
|
|
88
|
-
if (cur) rounds.push(cur);
|
|
89
|
-
const ts = (raw.match(/<timestamp>(.*?)<\/timestamp>/) || [])[1] || "";
|
|
90
|
-
const action = (raw.match(/\baction=([^\s]+)/) || [])[1] || "";
|
|
91
|
-
const taskId = (raw.match(/\btaskId=([^\s]+)/) || [])[1] || "";
|
|
92
|
-
const title = (raw.match(/标题:(.+)/) || [])[1]?.trim() || "";
|
|
93
|
-
const dm = raw.match(/【现状】\s*([\s\S]*?)\s*【预期】/);
|
|
94
|
-
const em = raw.match(/【预期】\s*([\s\S]*?)(?:\n\n##|\npayload=|$)/);
|
|
95
|
-
cur = {
|
|
96
|
-
ts,
|
|
97
|
-
action,
|
|
98
|
-
taskId,
|
|
99
|
-
title,
|
|
100
|
-
defect: dm ? dm[1].trim().replace(/\s+/g, " ") : "",
|
|
101
|
-
expected: em ? em[1].trim().replace(/\s+/g, " ") : "",
|
|
102
|
-
userPreview: raw.replace(/\s+/g, " ").slice(0, 280),
|
|
103
|
-
lastReply: "",
|
|
104
|
-
assistantNotes: [],
|
|
105
|
-
toolCount: 0,
|
|
106
|
-
};
|
|
107
|
-
} else if (cur && rec.role === "assistant") {
|
|
108
|
-
const t = texts(rec).join(" ").trim();
|
|
109
|
-
cur.toolCount += tools(rec).length;
|
|
110
|
-
if (t) {
|
|
111
|
-
cur.assistantNotes.push(t.slice(0, 240));
|
|
112
|
-
cur.lastReply = t.slice(0, 400);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (cur) rounds.push(cur);
|
|
117
|
-
|
|
118
|
-
const first = rounds[0] || {};
|
|
119
|
-
return {
|
|
120
|
-
recordCount: records.length,
|
|
121
|
-
taskId: rounds.map((r) => r.taskId).find(Boolean) || "",
|
|
122
|
-
title: rounds.map((r) => r.title).find(Boolean) || "",
|
|
123
|
-
rounds,
|
|
124
|
-
defects: rounds
|
|
125
|
-
.filter((r) => r.action === "report-defect" || r.defect)
|
|
126
|
-
.map((r, idx) => ({
|
|
127
|
-
n: idx + 1,
|
|
128
|
-
ts: r.ts,
|
|
129
|
-
defect: r.defect,
|
|
130
|
-
expected: r.expected,
|
|
131
|
-
lastReply: r.lastReply,
|
|
132
|
-
toolCount: r.toolCount,
|
|
133
|
-
})),
|
|
134
|
-
actions: rounds.map((r) => r.action).filter(Boolean),
|
|
135
|
-
firstAction: first.action || "",
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function readLiveSqlite(dbPath) {
|
|
140
|
-
const out = { agents: [], runs: [] };
|
|
141
|
-
try {
|
|
142
|
-
const db = new DatabaseSync(dbPath);
|
|
143
|
-
try {
|
|
144
|
-
out.agents = db.prepare("SELECT * FROM agents").all();
|
|
145
|
-
out.runs = db.prepare("SELECT * FROM runs").all();
|
|
146
|
-
} finally {
|
|
147
|
-
db.close();
|
|
148
|
-
}
|
|
149
|
-
} catch (err) {
|
|
150
|
-
out.error = err.message;
|
|
151
|
-
}
|
|
152
|
-
return out;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function scanSqliteBinary(dbPath) {
|
|
156
|
-
const buf = fs.readFileSync(dbPath);
|
|
157
|
-
const text = buf.toString("latin1");
|
|
158
|
-
return {
|
|
159
|
-
agentIds: [...new Set(text.match(AGENT_RE) || [])],
|
|
160
|
-
runIds: [...new Set(text.match(RUN_RE) || [])],
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function discoverRoots() {
|
|
165
|
-
const cwd = process.cwd();
|
|
166
|
-
const home = os.homedir();
|
|
167
|
-
const transcriptsEnv = process.env.AGENT_TRANSCRIPTS;
|
|
168
|
-
const cursorHome = fs.existsSync(path.join(home, ".cursor"))
|
|
169
|
-
? path.join(home, ".cursor")
|
|
170
|
-
: path.join("/data", "cursor-home");
|
|
171
|
-
const projectsDir = path.join(cursorHome, "projects");
|
|
172
|
-
return { cwd, home, transcriptsEnv, cursorHome, projectsDir };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function findTranscripts(roots) {
|
|
176
|
-
const dirs = [];
|
|
177
|
-
if (roots.transcriptsEnv && fs.existsSync(roots.transcriptsEnv)) {
|
|
178
|
-
dirs.push(roots.transcriptsEnv);
|
|
179
|
-
}
|
|
180
|
-
if (fs.existsSync(roots.projectsDir)) {
|
|
181
|
-
for (const name of fs.readdirSync(roots.projectsDir)) {
|
|
182
|
-
const p = path.join(roots.projectsDir, name, "agent-transcripts");
|
|
183
|
-
if (fs.existsSync(p)) dirs.push(p);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return [...new Set(dirs)];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function findStores(roots) {
|
|
190
|
-
const files = [];
|
|
191
|
-
if (fs.existsSync(roots.projectsDir)) {
|
|
192
|
-
for (const name of fs.readdirSync(roots.projectsDir)) {
|
|
193
|
-
const store = path.join(roots.projectsDir, name, "sdk-agent-store");
|
|
194
|
-
walkFiles(store, (full, base) => base === "index.db", files, 0);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
walkFiles(
|
|
198
|
-
path.join(roots.cwd, ".apm"),
|
|
199
|
-
(full, base) => base === "cursor-agent.json",
|
|
200
|
-
files,
|
|
201
|
-
0,
|
|
202
|
-
);
|
|
203
|
-
return files;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function findTranscriptFile(transcriptDirs, agentId) {
|
|
207
|
-
const stems = [agentId, agentId.replace(/^agent-/, ""), `agent-${agentId.replace(/^agent-/, "")}`];
|
|
208
|
-
for (const dir of transcriptDirs) {
|
|
209
|
-
for (const stem of stems) {
|
|
210
|
-
const nested = path.join(dir, stem, `${stem}.jsonl`);
|
|
211
|
-
if (fs.existsSync(nested)) return nested;
|
|
212
|
-
const flat = path.join(dir, `${stem}.jsonl`);
|
|
213
|
-
if (fs.existsSync(flat)) return flat;
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
for (const name of fs.readdirSync(dir)) {
|
|
217
|
-
if (name.includes(agentId.replace(/^agent-/, ""))) {
|
|
218
|
-
const candidate = path.join(dir, name, `${name}.jsonl`);
|
|
219
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
} catch {
|
|
223
|
-
/* ignore */
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function main() {
|
|
230
|
-
const rawId = arg("--id", process.argv[2]);
|
|
231
|
-
const normalized = normalizeId(rawId);
|
|
232
|
-
const roots = discoverRoots();
|
|
233
|
-
const transcriptDirs = findTranscripts(roots);
|
|
234
|
-
const storeFiles = findStores(roots);
|
|
235
|
-
|
|
236
|
-
const recovered = { agentIds: new Set(), runIds: new Set(), liveRuns: [] };
|
|
237
|
-
for (const file of storeFiles.filter((f) => f.endsWith("index.db"))) {
|
|
238
|
-
const live = readLiveSqlite(file);
|
|
239
|
-
for (const row of live.runs || []) {
|
|
240
|
-
recovered.liveRuns.push({
|
|
241
|
-
store: file,
|
|
242
|
-
runId: row.run_id || row.runId,
|
|
243
|
-
agentId: row.agent_id || row.agentId,
|
|
244
|
-
status: row.status,
|
|
245
|
-
turnNumber: row.turn_number || row.turnNumber,
|
|
246
|
-
});
|
|
247
|
-
if (row.run_id) recovered.runIds.add(row.run_id);
|
|
248
|
-
if (row.agent_id) recovered.agentIds.add(row.agent_id);
|
|
249
|
-
}
|
|
250
|
-
for (const row of live.agents || []) {
|
|
251
|
-
if (row.agent_id) recovered.agentIds.add(row.agent_id);
|
|
252
|
-
}
|
|
253
|
-
const scanned = scanSqliteBinary(file);
|
|
254
|
-
scanned.agentIds.forEach((id) => recovered.agentIds.add(id));
|
|
255
|
-
scanned.runIds.forEach((id) => recovered.runIds.add(id));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
for (const file of storeFiles.filter((f) => f.endsWith("cursor-agent.json"))) {
|
|
259
|
-
try {
|
|
260
|
-
const j = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
261
|
-
if (j.agentId) recovered.agentIds.add(j.agentId);
|
|
262
|
-
} catch {
|
|
263
|
-
/* ignore */
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
let input = typeof normalized === "string" ? normalized : `uuid:${normalized.uuid}`;
|
|
268
|
-
let kind = "unknown";
|
|
269
|
-
let agentId = null;
|
|
270
|
-
let matchedRunId = null;
|
|
271
|
-
|
|
272
|
-
const asString = typeof normalized === "string" ? normalized : normalized.uuid;
|
|
273
|
-
const wantRun = asString.startsWith("run-")
|
|
274
|
-
? asString
|
|
275
|
-
: recovered.runIds.has(`run-${asString}`)
|
|
276
|
-
? `run-${asString}`
|
|
277
|
-
: null;
|
|
278
|
-
const wantAgent = asString.startsWith("agent-")
|
|
279
|
-
? asString
|
|
280
|
-
: recovered.agentIds.has(`agent-${asString}`)
|
|
281
|
-
? `agent-${asString}`
|
|
282
|
-
: asString.startsWith("agent-")
|
|
283
|
-
? asString
|
|
284
|
-
: `agent-${asString.replace(/^agent-/, "")}`;
|
|
285
|
-
|
|
286
|
-
if (asString.startsWith("run-") || recovered.runIds.has(wantRun)) {
|
|
287
|
-
kind = "run";
|
|
288
|
-
matchedRunId = wantRun || asString;
|
|
289
|
-
const live = recovered.liveRuns.find((r) => r.runId === matchedRunId);
|
|
290
|
-
if (live?.agentId) agentId = live.agentId;
|
|
291
|
-
if (!agentId && recovered.agentIds.size === 1) {
|
|
292
|
-
agentId = [...recovered.agentIds][0];
|
|
293
|
-
}
|
|
294
|
-
} else if (asString.startsWith("agent-") || recovered.agentIds.has(wantAgent)) {
|
|
295
|
-
kind = "agent";
|
|
296
|
-
agentId = wantAgent.startsWith("agent-") ? wantAgent : `agent-${wantAgent}`;
|
|
297
|
-
} else if (asString.startsWith("agent-") || asString.length === 36) {
|
|
298
|
-
kind = asString.startsWith("run-") ? "run" : "agent";
|
|
299
|
-
agentId = kind === "agent" ? (asString.startsWith("agent-") ? asString : `agent-${asString}`) : null;
|
|
300
|
-
matchedRunId = kind === "run" ? asString : null;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (!agentId && kind === "agent") {
|
|
304
|
-
agentId = asString.startsWith("agent-") ? asString : `agent-${asString}`;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
let transcriptPath = agentId ? findTranscriptFile(transcriptDirs, agentId) : null;
|
|
308
|
-
if (!transcriptPath && kind === "agent") {
|
|
309
|
-
transcriptPath = findTranscriptFile(transcriptDirs, asString);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const conversation = transcriptPath ? parseTranscript(transcriptPath) : null;
|
|
313
|
-
|
|
314
|
-
const result = {
|
|
315
|
-
input: rawId,
|
|
316
|
-
kind,
|
|
317
|
-
agentId,
|
|
318
|
-
matchedRunId,
|
|
319
|
-
recoveredAgentIds: [...recovered.agentIds],
|
|
320
|
-
recoveredRunIds: [...recovered.runIds],
|
|
321
|
-
liveRuns: recovered.liveRuns,
|
|
322
|
-
transcriptPath,
|
|
323
|
-
transcriptDirs,
|
|
324
|
-
storeFiles,
|
|
325
|
-
conversation,
|
|
326
|
-
note: !transcriptPath
|
|
327
|
-
? "No transcript found. Check AGENT_TRANSCRIPTS or ~/.cursor/projects/*/agent-transcripts."
|
|
328
|
-
: recovered.liveRuns.length === 0 && matchedRunId
|
|
329
|
-
? "Run tables were empty; mapping used leftover SQLite pages plus the only local agent-id."
|
|
330
|
-
: undefined,
|
|
331
|
-
};
|
|
332
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
main();
|