gsd-pi 2.28.0-dev.b23c118 → 2.28.0-dev.e19bf89

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.
Files changed (25) hide show
  1. package/dist/resources/extensions/gsd/auto-post-unit.ts +3 -8
  2. package/dist/resources/extensions/gsd/auto-start.ts +9 -24
  3. package/dist/resources/extensions/gsd/auto.ts +2 -45
  4. package/dist/resources/extensions/gsd/commands.ts +0 -19
  5. package/dist/resources/extensions/gsd/doctor-types.ts +0 -13
  6. package/dist/resources/extensions/gsd/doctor.ts +6 -2
  7. package/dist/resources/extensions/gsd/export.ts +2 -28
  8. package/dist/resources/extensions/gsd/gsd-db.ts +0 -19
  9. package/package.json +3 -3
  10. package/src/resources/extensions/gsd/auto-post-unit.ts +3 -8
  11. package/src/resources/extensions/gsd/auto-start.ts +9 -24
  12. package/src/resources/extensions/gsd/auto.ts +2 -45
  13. package/src/resources/extensions/gsd/commands.ts +0 -19
  14. package/src/resources/extensions/gsd/doctor-types.ts +0 -13
  15. package/src/resources/extensions/gsd/doctor.ts +6 -2
  16. package/src/resources/extensions/gsd/export.ts +2 -28
  17. package/src/resources/extensions/gsd/gsd-db.ts +0 -19
  18. package/dist/resources/extensions/gsd/commands-logs.ts +0 -537
  19. package/dist/resources/extensions/gsd/session-lock.ts +0 -284
  20. package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +0 -241
  21. package/dist/resources/extensions/gsd/tests/session-lock.test.ts +0 -315
  22. package/src/resources/extensions/gsd/commands-logs.ts +0 -537
  23. package/src/resources/extensions/gsd/session-lock.ts +0 -284
  24. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +0 -241
  25. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -315
@@ -1,537 +0,0 @@
1
- /**
2
- * /gsd logs — Browse activity logs, debug logs, and metrics.
3
- *
4
- * Subcommands:
5
- * /gsd logs — List recent activity + debug logs
6
- * /gsd logs <N> — Show summary of activity log #N
7
- * /gsd logs debug — List debug log files
8
- * /gsd logs debug <N> — Show debug log summary #N
9
- * /gsd logs tail [N] — Show last N activity log entries (default 5)
10
- * /gsd logs clear — Remove old activity and debug logs
11
- */
12
-
13
- import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
14
- import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
15
- import { join } from "node:path";
16
- import { gsdRoot } from "./paths.js";
17
-
18
- // ─── Types ──────────────────────────────────────────────────────────────────
19
-
20
- interface LogEntry {
21
- seq: number;
22
- filename: string;
23
- unitType: string;
24
- unitId: string;
25
- size: number;
26
- mtime: Date;
27
- }
28
-
29
- interface DebugLogEntry {
30
- filename: string;
31
- size: number;
32
- mtime: Date;
33
- }
34
-
35
- // ─── Helpers ────────────────────────────────────────────────────────────────
36
-
37
- function activityDir(basePath: string): string {
38
- return join(gsdRoot(basePath), "activity");
39
- }
40
-
41
- function debugDir(basePath: string): string {
42
- return join(gsdRoot(basePath), "debug");
43
- }
44
-
45
- function listActivityLogs(basePath: string): LogEntry[] {
46
- const dir = activityDir(basePath);
47
- if (!existsSync(dir)) return [];
48
-
49
- const entries: LogEntry[] = [];
50
- try {
51
- for (const f of readdirSync(dir)) {
52
- if (!f.endsWith(".jsonl")) continue;
53
- // Filename format: {seq}-{unitType}-{unitId}.jsonl
54
- // unitType is lowercase-with-hyphens (e.g., "execute-task", "complete-slice")
55
- // unitId starts with M followed by digits (e.g., "M001-S01-T01")
56
- const match = f.match(/^(\d+)-([\w-]+?)-(M\d[\w-]*)\.jsonl$/);
57
- if (!match) continue;
58
-
59
- const filePath = join(dir, f);
60
- let stat;
61
- try { stat = statSync(filePath); } catch { continue; }
62
-
63
- entries.push({
64
- seq: parseInt(match[1], 10),
65
- filename: f,
66
- unitType: match[2],
67
- unitId: match[3].replace(/-/g, "/"),
68
- size: stat.size,
69
- mtime: stat.mtime,
70
- });
71
- }
72
- } catch { /* dir not readable */ }
73
-
74
- return entries.sort((a, b) => a.seq - b.seq);
75
- }
76
-
77
- function listDebugLogs(basePath: string): DebugLogEntry[] {
78
- const dir = debugDir(basePath);
79
- if (!existsSync(dir)) return [];
80
-
81
- const entries: DebugLogEntry[] = [];
82
- try {
83
- for (const f of readdirSync(dir)) {
84
- if (!f.endsWith(".log")) continue;
85
- const filePath = join(dir, f);
86
- let stat;
87
- try { stat = statSync(filePath); } catch { continue; }
88
- entries.push({ filename: f, size: stat.size, mtime: stat.mtime });
89
- }
90
- } catch { /* dir not readable */ }
91
-
92
- return entries.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
93
- }
94
-
95
- function formatSize(bytes: number): string {
96
- if (bytes < 1024) return `${bytes}B`;
97
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
98
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
99
- }
100
-
101
- function formatAge(date: Date): string {
102
- const ms = Date.now() - date.getTime();
103
- const mins = Math.floor(ms / 60_000);
104
- if (mins < 1) return "just now";
105
- if (mins < 60) return `${mins}m ago`;
106
- const hrs = Math.floor(mins / 60);
107
- if (hrs < 24) return `${hrs}h ago`;
108
- const days = Math.floor(hrs / 24);
109
- return `${days}d ago`;
110
- }
111
-
112
- /**
113
- * Extract a summary from an activity log JSONL file.
114
- * Parses the entries to count tool calls, errors, and extract key events.
115
- */
116
- function summarizeActivityLog(filePath: string): {
117
- toolCalls: number;
118
- errors: number;
119
- filesWritten: string[];
120
- commandsRun: Array<{ command: string; failed: boolean }>;
121
- lastReasoning: string;
122
- entryCount: number;
123
- } {
124
- const result = {
125
- toolCalls: 0,
126
- errors: 0,
127
- filesWritten: new Set<string>(),
128
- commandsRun: [] as Array<{ command: string; failed: boolean }>,
129
- lastReasoning: "",
130
- entryCount: 0,
131
- };
132
-
133
- let raw: string;
134
- try { raw = readFileSync(filePath, "utf-8"); } catch { return { ...result, filesWritten: [] }; }
135
-
136
- const lines = raw.split("\n").filter(l => l.trim());
137
- result.entryCount = lines.length;
138
-
139
- for (const line of lines) {
140
- let entry: Record<string, unknown>;
141
- try { entry = JSON.parse(line); } catch { continue; }
142
-
143
- // Count tool calls
144
- if (entry.type === "toolCall" || (entry.role === "assistant" && entry.content && Array.isArray(entry.content))) {
145
- if (entry.type === "toolCall") {
146
- result.toolCalls++;
147
- const name = entry.name as string | undefined;
148
- const args = entry.arguments as Record<string, unknown> | undefined;
149
-
150
- if (name === "write" || name === "edit") {
151
- const path = args?.file_path as string | undefined;
152
- if (path) result.filesWritten.add(path);
153
- }
154
- if (name === "bash") {
155
- const cmd = args?.command as string | undefined;
156
- if (cmd) result.commandsRun.push({ command: cmd.slice(0, 80), failed: false });
157
- }
158
- }
159
- }
160
-
161
- // Count errors
162
- if (entry.role === "toolResult" && entry.isError) {
163
- result.errors++;
164
- // Mark last command as failed
165
- if (result.commandsRun.length > 0) {
166
- result.commandsRun[result.commandsRun.length - 1].failed = true;
167
- }
168
- }
169
-
170
- // Track assistant reasoning
171
- if (entry.role === "assistant" && typeof entry.content === "string") {
172
- result.lastReasoning = entry.content.slice(0, 200);
173
- }
174
- }
175
-
176
- return {
177
- ...result,
178
- filesWritten: [...result.filesWritten],
179
- };
180
- }
181
-
182
- /**
183
- * Extract summary events from a debug log file.
184
- */
185
- function summarizeDebugLog(filePath: string): {
186
- events: number;
187
- duration: string;
188
- dispatches: number;
189
- errors: Array<{ event: string; message: string }>;
190
- } {
191
- const result = {
192
- events: 0,
193
- duration: "unknown",
194
- dispatches: 0,
195
- errors: [] as Array<{ event: string; message: string }>,
196
- };
197
-
198
- let raw: string;
199
- try { raw = readFileSync(filePath, "utf-8"); } catch { return result; }
200
-
201
- const lines = raw.split("\n").filter(l => l.trim());
202
- result.events = lines.length;
203
-
204
- let firstTs = 0;
205
- let lastTs = 0;
206
-
207
- for (const line of lines) {
208
- let entry: Record<string, unknown>;
209
- try { entry = JSON.parse(line); } catch { continue; }
210
-
211
- const ts = entry.ts as string | undefined;
212
- if (ts) {
213
- const t = new Date(ts).getTime();
214
- if (!firstTs) firstTs = t;
215
- lastTs = t;
216
- }
217
-
218
- const event = entry.event as string | undefined;
219
- if (!event) continue;
220
-
221
- if (event === "debug-summary") {
222
- result.dispatches = (entry.dispatches as number) ?? 0;
223
- }
224
-
225
- if (event.includes("error") || event.includes("failed")) {
226
- const msg = (entry.error as string) ?? (entry.message as string) ?? JSON.stringify(entry).slice(0, 100);
227
- result.errors.push({ event, message: msg });
228
- }
229
- }
230
-
231
- if (firstTs && lastTs) {
232
- const elapsed = lastTs - firstTs;
233
- const mins = Math.floor(elapsed / 60_000);
234
- if (mins < 1) result.duration = `${Math.floor(elapsed / 1000)}s`;
235
- else if (mins < 60) result.duration = `${mins}m`;
236
- else result.duration = `${Math.floor(mins / 60)}h ${mins % 60}m`;
237
- }
238
-
239
- return result;
240
- }
241
-
242
- // ─── Main Handler ───────────────────────────────────────────────────────────
243
-
244
- export async function handleLogs(args: string, ctx: ExtensionCommandContext): Promise<void> {
245
- const basePath = process.cwd();
246
- const parts = args.trim().split(/\s+/).filter(Boolean);
247
- const subCmd = parts[0] ?? "";
248
-
249
- // /gsd logs clear
250
- if (subCmd === "clear") {
251
- await handleLogsClear(basePath, ctx);
252
- return;
253
- }
254
-
255
- // /gsd logs debug [N]
256
- if (subCmd === "debug") {
257
- const idx = parts[1] ? parseInt(parts[1], 10) : undefined;
258
- await handleLogsDebug(basePath, ctx, idx);
259
- return;
260
- }
261
-
262
- // /gsd logs tail [N]
263
- if (subCmd === "tail") {
264
- const count = parts[1] ? parseInt(parts[1], 10) : 5;
265
- await handleLogsTail(basePath, ctx, count);
266
- return;
267
- }
268
-
269
- // /gsd logs <N> — show specific activity log
270
- if (subCmd && /^\d+$/.test(subCmd)) {
271
- const seq = parseInt(subCmd, 10);
272
- await handleLogsShow(basePath, ctx, seq);
273
- return;
274
- }
275
-
276
- // /gsd logs — list overview
277
- await handleLogsList(basePath, ctx);
278
- }
279
-
280
- // ─── Subcommand Handlers ────────────────────────────────────────────────────
281
-
282
- async function handleLogsList(basePath: string, ctx: ExtensionCommandContext): Promise<void> {
283
- const activities = listActivityLogs(basePath);
284
- const debugLogs = listDebugLogs(basePath);
285
-
286
- if (activities.length === 0 && debugLogs.length === 0) {
287
- ctx.ui.notify(
288
- "No logs found.\n\nActivity logs are created during auto-mode.\nDebug logs require GSD_DEBUG=1.",
289
- "info",
290
- );
291
- return;
292
- }
293
-
294
- const lines: string[] = [];
295
-
296
- if (activities.length > 0) {
297
- lines.push("Activity Logs (.gsd/activity/):");
298
- lines.push(" # Unit Type Unit ID Size Age");
299
- lines.push(" " + "─".repeat(70));
300
-
301
- // Show last 15 entries
302
- const recent = activities.slice(-15);
303
- for (const e of recent) {
304
- const seq = String(e.seq).padStart(3, " ");
305
- const type = e.unitType.padEnd(18, " ");
306
- const id = e.unitId.padEnd(20, " ");
307
- const size = formatSize(e.size).padStart(7, " ");
308
- const age = formatAge(e.mtime);
309
- lines.push(` ${seq} ${type} ${id} ${size} ${age}`);
310
- }
311
-
312
- if (activities.length > 15) {
313
- lines.push(` ... and ${activities.length - 15} older entries`);
314
- }
315
- lines.push("");
316
- lines.push(" View details: /gsd logs <#>");
317
- }
318
-
319
- if (debugLogs.length > 0) {
320
- lines.push("");
321
- lines.push("Debug Logs (.gsd/debug/):");
322
- for (let i = 0; i < debugLogs.length; i++) {
323
- const d = debugLogs[i];
324
- const size = formatSize(d.size).padStart(7, " ");
325
- const age = formatAge(d.mtime);
326
- lines.push(` ${i + 1}. ${d.filename} ${size} ${age}`);
327
- }
328
- lines.push("");
329
- lines.push(" View details: /gsd logs debug <#>");
330
- }
331
-
332
- // Metrics summary
333
- const metricsPath = join(gsdRoot(basePath), "metrics.json");
334
- if (existsSync(metricsPath)) {
335
- try {
336
- const metrics = JSON.parse(readFileSync(metricsPath, "utf-8"));
337
- const units = metrics?.units;
338
- if (Array.isArray(units) && units.length > 0) {
339
- const totalCost = units.reduce((sum: number, u: Record<string, unknown>) => sum + ((u.cost as number) ?? 0), 0);
340
- const totalTokens = units.reduce((sum: number, u: Record<string, unknown>) => {
341
- const t = u.tokens as Record<string, number> | undefined;
342
- return sum + (t?.total ?? 0);
343
- }, 0);
344
- lines.push("");
345
- lines.push(`Metrics: ${units.length} units tracked · $${totalCost.toFixed(2)} · ${(totalTokens / 1000).toFixed(0)}K tokens`);
346
- }
347
- } catch { /* ignore */ }
348
- }
349
-
350
- lines.push("");
351
- lines.push("Tip: Enable debug logging with GSD_DEBUG=1 before /gsd auto");
352
-
353
- ctx.ui.notify(lines.join("\n"), "info");
354
- }
355
-
356
- async function handleLogsShow(basePath: string, ctx: ExtensionCommandContext, seq: number): Promise<void> {
357
- const activities = listActivityLogs(basePath);
358
- const entry = activities.find(e => e.seq === seq);
359
-
360
- if (!entry) {
361
- ctx.ui.notify(`Activity log #${seq} not found. Run /gsd logs to see available logs.`, "warning");
362
- return;
363
- }
364
-
365
- const filePath = join(activityDir(basePath), entry.filename);
366
- const summary = summarizeActivityLog(filePath);
367
-
368
- const lines: string[] = [];
369
- lines.push(`Activity Log #${entry.seq}: ${entry.unitType} — ${entry.unitId}`);
370
- lines.push("─".repeat(60));
371
- lines.push(`File: ${entry.filename}`);
372
- lines.push(`Size: ${formatSize(entry.size)} | Age: ${formatAge(entry.mtime)}`);
373
- lines.push(`Entries: ${summary.entryCount} | Tool calls: ${summary.toolCalls} | Errors: ${summary.errors}`);
374
-
375
- if (summary.filesWritten.length > 0) {
376
- lines.push("");
377
- lines.push("Files written/edited:");
378
- for (const f of summary.filesWritten.slice(0, 10)) {
379
- lines.push(` ${f}`);
380
- }
381
- if (summary.filesWritten.length > 10) {
382
- lines.push(` ... and ${summary.filesWritten.length - 10} more`);
383
- }
384
- }
385
-
386
- if (summary.commandsRun.length > 0) {
387
- lines.push("");
388
- lines.push("Commands run:");
389
- for (const c of summary.commandsRun.slice(0, 10)) {
390
- const status = c.failed ? " FAILED" : "";
391
- lines.push(` ${c.command}${status}`);
392
- }
393
- if (summary.commandsRun.length > 10) {
394
- lines.push(` ... and ${summary.commandsRun.length - 10} more`);
395
- }
396
- }
397
-
398
- if (summary.errors > 0) {
399
- lines.push("");
400
- lines.push(`${summary.errors} error(s) encountered during this unit.`);
401
- }
402
-
403
- if (summary.lastReasoning) {
404
- lines.push("");
405
- lines.push("Last reasoning:");
406
- lines.push(` "${summary.lastReasoning}${summary.lastReasoning.length >= 200 ? "..." : ""}"`);
407
- }
408
-
409
- lines.push("");
410
- lines.push(`Full log: ${filePath}`);
411
-
412
- ctx.ui.notify(lines.join("\n"), "info");
413
- }
414
-
415
- async function handleLogsDebug(basePath: string, ctx: ExtensionCommandContext, idx?: number): Promise<void> {
416
- const debugLogs = listDebugLogs(basePath);
417
-
418
- if (debugLogs.length === 0) {
419
- ctx.ui.notify(
420
- "No debug logs found.\n\nEnable debug logging: GSD_DEBUG=1 gsd auto",
421
- "info",
422
- );
423
- return;
424
- }
425
-
426
- if (idx === undefined) {
427
- // List debug logs
428
- const lines: string[] = ["Debug Logs (.gsd/debug/):", ""];
429
- for (let i = 0; i < debugLogs.length; i++) {
430
- const d = debugLogs[i];
431
- lines.push(` ${i + 1}. ${d.filename} ${formatSize(d.size)} ${formatAge(d.mtime)}`);
432
- }
433
- lines.push("");
434
- lines.push("View details: /gsd logs debug <#>");
435
- ctx.ui.notify(lines.join("\n"), "info");
436
- return;
437
- }
438
-
439
- // Show specific debug log
440
- if (idx < 1 || idx > debugLogs.length) {
441
- ctx.ui.notify(`Debug log #${idx} not found. Available: 1-${debugLogs.length}`, "warning");
442
- return;
443
- }
444
-
445
- const entry = debugLogs[idx - 1];
446
- const filePath = join(debugDir(basePath), entry.filename);
447
- const summary = summarizeDebugLog(filePath);
448
-
449
- const lines: string[] = [];
450
- lines.push(`Debug Log: ${entry.filename}`);
451
- lines.push("─".repeat(60));
452
- lines.push(`Size: ${formatSize(entry.size)} | Age: ${formatAge(entry.mtime)}`);
453
- lines.push(`Events: ${summary.events} | Duration: ${summary.duration} | Dispatches: ${summary.dispatches}`);
454
-
455
- if (summary.errors.length > 0) {
456
- lines.push("");
457
- lines.push("Errors/failures:");
458
- for (const e of summary.errors.slice(0, 10)) {
459
- lines.push(` [${e.event}] ${e.message}`);
460
- }
461
- if (summary.errors.length > 10) {
462
- lines.push(` ... and ${summary.errors.length - 10} more`);
463
- }
464
- }
465
-
466
- lines.push("");
467
- lines.push(`Full log: ${filePath}`);
468
-
469
- ctx.ui.notify(lines.join("\n"), "info");
470
- }
471
-
472
- async function handleLogsTail(basePath: string, ctx: ExtensionCommandContext, count: number): Promise<void> {
473
- const activities = listActivityLogs(basePath);
474
-
475
- if (activities.length === 0) {
476
- ctx.ui.notify("No activity logs found. Logs are created during auto-mode.", "info");
477
- return;
478
- }
479
-
480
- const recent = activities.slice(-Math.max(1, Math.min(count, 20)));
481
- const lines: string[] = [`Last ${recent.length} activity log(s):`, ""];
482
-
483
- for (const e of recent) {
484
- const filePath = join(activityDir(basePath), e.filename);
485
- const summary = summarizeActivityLog(filePath);
486
- const status = summary.errors > 0 ? `${summary.errors} err` : "ok";
487
- lines.push(` #${e.seq} ${e.unitType} ${e.unitId} — ${summary.toolCalls} tools, ${status}, ${formatAge(e.mtime)}`);
488
- }
489
-
490
- ctx.ui.notify(lines.join("\n"), "info");
491
- }
492
-
493
- async function handleLogsClear(basePath: string, ctx: ExtensionCommandContext): Promise<void> {
494
- let removedActivity = 0;
495
- let removedDebug = 0;
496
-
497
- // Clear activity logs older than 7 days, keep the 5 most recent
498
- const activities = listActivityLogs(basePath);
499
- const keepRecent = activities.slice(-5);
500
- const keepSeqs = new Set(keepRecent.map(e => e.seq));
501
- const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
502
-
503
- for (const e of activities) {
504
- if (keepSeqs.has(e.seq)) continue;
505
- if (e.mtime.getTime() < cutoff) {
506
- try {
507
- unlinkSync(join(activityDir(basePath), e.filename));
508
- removedActivity++;
509
- } catch { /* ignore */ }
510
- }
511
- }
512
-
513
- // Clear debug logs older than 3 days, keep latest 2
514
- const debugLogs = listDebugLogs(basePath);
515
- const keepDebug = debugLogs.slice(-2);
516
- const keepDebugNames = new Set(keepDebug.map(d => d.filename));
517
- const debugCutoff = Date.now() - 3 * 24 * 60 * 60 * 1000;
518
-
519
- for (const d of debugLogs) {
520
- if (keepDebugNames.has(d.filename)) continue;
521
- if (d.mtime.getTime() < debugCutoff) {
522
- try {
523
- unlinkSync(join(debugDir(basePath), d.filename));
524
- removedDebug++;
525
- } catch { /* ignore */ }
526
- }
527
- }
528
-
529
- if (removedActivity === 0 && removedDebug === 0) {
530
- ctx.ui.notify("No old logs to clear.", "info");
531
- } else {
532
- ctx.ui.notify(
533
- `Cleared ${removedActivity} activity log(s) and ${removedDebug} debug log(s).`,
534
- "info",
535
- );
536
- }
537
- }