@gotgenes/pi-subagents 4.1.1 → 5.1.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/CHANGELOG.md CHANGED
@@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v5.0.0...pi-subagents-v5.1.0) (2026-05-19)
9
+
10
+
11
+ ### Features
12
+
13
+ * add debugLog utility gated on PI_SUBAGENTS_DEBUG ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([2b8874a](https://github.com/gotgenes/pi-packages/commit/2b8874aeaa28bc09550dd0f8977b7b57d996b254))
14
+ * thread debugLog into agent-manager and notification catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([07943a3](https://github.com/gotgenes/pi-packages/commit/07943a341fbe0a0a35f25af3f4b15bc28cdee7a2))
15
+ * thread debugLog into custom-agents and memory catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([e239925](https://github.com/gotgenes/pi-packages/commit/e2399253410dc644b38269b52de6e6a4bfa75d3a))
16
+ * thread debugLog into env catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([f5ff82f](https://github.com/gotgenes/pi-packages/commit/f5ff82f0c06be3c1dfe5d5ec0ff3621105b55ad0))
17
+ * thread debugLog into output-file catch block ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([a72b11b](https://github.com/gotgenes/pi-packages/commit/a72b11bc1f36bf256f520b9f13e546295fc6cb64))
18
+ * thread debugLog into skill-loader catch block ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([b57231c](https://github.com/gotgenes/pi-packages/commit/b57231cc5de56a834c34f9e171b909d03939505c))
19
+ * thread debugLog into worktree catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([049f489](https://github.com/gotgenes/pi-packages/commit/049f4891ab5b09c9d1a301f7d8af797cb165cb4c))
20
+
21
+
22
+ ### Documentation
23
+
24
+ * plan structured debug logging for silenced catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([28e403e](https://github.com/gotgenes/pi-packages/commit/28e403ea2605405da1a57871af946ee2971ee289))
25
+
26
+ ## [5.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v4.1.1...pi-subagents-v5.0.0) (2026-05-19)
27
+
28
+
29
+ ### ⚠ BREAKING CHANGES
30
+
31
+ * All @earendil-works/pi-* peerDependencies and devDependencies now require >=0.75.0, aligning with Pi's Node 22 minimum.
32
+ * Minimum supported Node.js version is now >=22, aligning with Pi v0.75.0. tsconfig target raised from ES2023 to ES2024.
33
+ - ES2024 APIs (Promise.withResolvers, Object.groupBy, Map.groupBy, Array.fromAsync) are now allowed.
34
+ - @types/node catalog aligned to ^22.15.3.
35
+ - pi-autoformat now declares engines.node for consistency.
36
+
37
+ ### Features
38
+
39
+ * raise minimum Node.js version to 22 and bump tsconfig target to ES2024 ([98a5b01](https://github.com/gotgenes/pi-packages/commit/98a5b01ca20aa1feed14a60bfa7bb9e082c9914b))
40
+ * raise minimum Pi dependency to v0.75.0 ([1068329](https://github.com/gotgenes/pi-packages/commit/10683290d2a789880848bf7eb093d4307b6eff40))
41
+
42
+
43
+ ### Documentation
44
+
45
+ * **retro:** add retro notes for issue [#54](https://github.com/gotgenes/pi-packages/issues/54) ([d753eb3](https://github.com/gotgenes/pi-packages/commit/d753eb3f836a28f089197a45dd582dc4be88872d))
46
+
8
47
  ## [4.1.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v4.1.0...pi-subagents-v4.1.1) (2026-05-18)
9
48
 
10
49
 
@@ -0,0 +1,184 @@
1
+ ---
2
+ issue: 57
3
+ issue_title: "feat: structured debug logging for silenced catch blocks"
4
+ ---
5
+
6
+ # Structured debug logging for silenced catch blocks
7
+
8
+ ## Problem Statement
9
+
10
+ The codebase contains ~20 `catch { /* ignore */ }` blocks spread across
11
+ `agent-manager.ts`, `worktree.ts`, `output-file.ts`, `skill-loader.ts`,
12
+ `custom-agents.ts`, `memory.ts`, `env.ts`, and `notification.ts`.
13
+ Each block correctly suppresses a non-essential side-effect failure, but
14
+ in development the silence makes it impossible to diagnose what is actually
15
+ failing inside those paths.
16
+
17
+ ## Goals
18
+
19
+ - Add a `debugLog(context, err)` utility in `src/debug.ts` gated on
20
+ `PI_SUBAGENTS_DEBUG=1`.
21
+ - Thread `debugLog` into every silenced `catch` block listed in the issue scope.
22
+ - Leave production behavior **completely unchanged** — no new log output unless
23
+ the env var is explicitly set.
24
+ - Provide a `debug.test.ts` unit test covering the on/off branching.
25
+
26
+ ## Non-Goals
27
+
28
+ - `usage.ts` catch blocks — they return `0`/`null` on failure and are already
29
+ recoverable, not truly "silent drops"; they are out of scope.
30
+ - `settings.ts` catch block — it returns `false` on failure, already surfaced
31
+ to the caller; out of scope.
32
+ - Log rotation, structured JSON output, or stderr vs. stdout routing.
33
+ - Wiring debug output to the Pi UI; `console.warn` to stderr is sufficient.
34
+
35
+ ## Background
36
+
37
+ The `AGENTS.md` rule "prefer explicit configuration over hidden behavior" aligns
38
+ with gating noise behind an opt-in env var rather than always-on logging.
39
+ The code-style skill notes that business logic should remain pure — `debug.ts`
40
+ must not import the Pi SDK; it only reads `process.env`.
41
+
42
+ The `DEBUG` constant is evaluated once at module import time (top-level `const`).
43
+ That matches how similar opt-in env vars work in Node.js tooling and keeps the
44
+ hot path to a single boolean branch.
45
+
46
+ ### Catch blocks in scope
47
+
48
+ | File | Line(s) | Context label(s) |
49
+ | ------------------ | ------------------------------------ | ------------------------------------------------------- |
50
+ | `env.ts` | 15, 23 | `"git rev-parse"`, `"git branch"` |
51
+ | `skill-loader.ts` | 74 | `"readdirSync skill root"` |
52
+ | `custom-agents.ts` | 37, 47 | `"readdirSync agents dir"`, `"readFileSync agent file"` |
53
+ | `memory.ts` | 33, 47 | `"lstatSync"`, `"readFileSync"` |
54
+ | `output-file.ts` | 83 | `"write JSONL chunk"` |
55
+ | `worktree.ts` | 40, 56, 110, 130, 132, 147, 151, 161 | `"git rev-parse"`, `"git worktree add"`, etc. |
56
+ | `agent-manager.ts` | 233, 249, 266, 275, 480 | `"outputCleanup"`, `"onComplete callback"`, etc. |
57
+ | `notification.ts` | 145 | `"notification render"` |
58
+
59
+ ## Design Overview
60
+
61
+ ### `src/debug.ts`
62
+
63
+ ```typescript
64
+ export const DEBUG = process.env.PI_SUBAGENTS_DEBUG === "1";
65
+
66
+ export function debugLog(context: string, err: unknown): void {
67
+ if (DEBUG) console.warn(`[pi-subagents:debug] ${context}:`, err);
68
+ }
69
+ ```
70
+
71
+ The module-level `DEBUG` constant is the canonical source.
72
+ `debugLog` is a pure side-effect function: no return value, no SDK dependency,
73
+ no coupling to Pi types.
74
+
75
+ ### Threading pattern
76
+
77
+ Every silenced `catch` block is updated from:
78
+
79
+ ```typescript
80
+ } catch { /* ignore */ }
81
+ ```
82
+
83
+ to:
84
+
85
+ ```typescript
86
+ } catch (err) { debugLog("<context label>", err); }
87
+ ```
88
+
89
+ When the block already had a named error (`catch (err)`), only the `debugLog`
90
+ call is added; the existing comment, if descriptive, is removed in favour of
91
+ the context string.
92
+
93
+ ### Context label convention
94
+
95
+ Labels read as `"<verb> <noun>"`, lower-case, mirroring the surrounding
96
+ function name when unambiguous (e.g., `"removeWorktree"`, `"outputCleanup"`).
97
+
98
+ ## Module-Level Changes
99
+
100
+ | File | Change |
101
+ | ---------------------- | ---------------------------------------------------------------------------- |
102
+ | `src/debug.ts` | **New.** Exports `DEBUG` constant and `debugLog` function. |
103
+ | `test/debug.test.ts` | **New.** Unit tests for `debugLog` on/off behaviour. |
104
+ | `src/env.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
105
+ | `src/skill-loader.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
106
+ | `src/custom-agents.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
107
+ | `src/memory.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
108
+ | `src/output-file.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
109
+ | `src/worktree.ts` | Add `debugLog` import; name the eight bare `catch` errors; call `debugLog`. |
110
+ | `src/agent-manager.ts` | Add `debugLog` import; name/update the five `catch` blocks; call `debugLog`. |
111
+ | `src/notification.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
112
+
113
+ ## Test Impact Analysis
114
+
115
+ 1. **New tests enabled** — `debug.test.ts` can directly unit-test `debugLog` in
116
+ isolation.
117
+ Asserting `console.warn` is/isn't called based on the env var is now trivial.
118
+ Previously this path was entirely untested.
119
+
120
+ 2. **Existing tests become redundant** — None.
121
+ The catch blocks were never exercised by existing tests (they were silent
122
+ drops), so no existing test coverage needs to be removed.
123
+
124
+ 3. **Tests that must stay as-is** — All existing tests remain valid.
125
+ The threading adds a call inside each catch but does not change control flow,
126
+ return values, or thrown exceptions; no test expectations change.
127
+
128
+ ## TDD Order
129
+
130
+ 1. **`src/debug.ts` + `test/debug.test.ts`**
131
+ Test surface: `debugLog` calls `console.warn` when `PI_SUBAGENTS_DEBUG=1`;
132
+ does nothing when unset or `"0"`.
133
+ Use `vi.spyOn(console, 'warn')` + `vi.stubEnv('PI_SUBAGENTS_DEBUG', '1')` +
134
+ `vi.resetModules()` + dynamic import to exercise both branches.
135
+ Suggested commit: `feat: add debugLog utility gated on PI_SUBAGENTS_DEBUG (#57)`
136
+
137
+ 2. **Thread into `env.ts`**
138
+ Test surface: `env.test.ts` — existing tests still pass (no new assertions
139
+ required because the catch paths are tested implicitly by the non-git-dir
140
+ test already covering the swallowed failures).
141
+ Suggested commit: `feat: thread debugLog into env catch blocks (#57)`
142
+
143
+ 3. **Thread into `skill-loader.ts`**
144
+ Test surface: `skill-loader.test.ts` — existing tests still pass.
145
+ The readdirSync-error catch is exercised when the skill root does not exist
146
+ (covered by existing "not found" test paths).
147
+ Suggested commit: `feat: thread debugLog into skill-loader catch block (#57)`
148
+
149
+ 4. **Thread into `custom-agents.ts` and `memory.ts`**
150
+ These two files have structurally identical filesystem-error catch blocks and
151
+ can land in a single step.
152
+ Test surface: `custom-agents.test.ts`, `memory.test.ts` — existing tests
153
+ still pass.
154
+ Suggested commit: `feat: thread debugLog into custom-agents and memory catch blocks (#57)`
155
+
156
+ 5. **Thread into `output-file.ts`**
157
+ Test surface: `output-file.test.ts` — existing tests still pass.
158
+ Suggested commit: `feat: thread debugLog into output-file catch block (#57)`
159
+
160
+ 6. **Thread into `worktree.ts`**
161
+ Test surface: `worktree.test.ts` — existing tests still pass.
162
+ Eight catch sites; name each with its enclosing function for label clarity.
163
+ Suggested commit: `feat: thread debugLog into worktree catch blocks (#57)`
164
+
165
+ 7. **Thread into `agent-manager.ts` and `notification.ts`**
166
+ Test surface: `agent-manager.test.ts`, `notification.test.ts` — existing
167
+ tests still pass.
168
+ Suggested commit: `feat: thread debugLog into agent-manager and notification catch blocks (#57)`
169
+
170
+ ## Risks and Mitigations
171
+
172
+ | Risk | Mitigation |
173
+ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | `DEBUG` is a module-level constant — `vi.stubEnv` alone won't flip it mid-test | Use `vi.resetModules()` + dynamic `import()` inside a test that sets the env var before re-importing; the test skill documents this pattern |
175
+ | Adding `(err)` to a bare `catch` in TypeScript is type-safe (`unknown`) but a `noImplicitAny`-adjacent change | `tsconfig` already uses `"useUnknownInCatchVariables": true` (default in strict mode); no cast needed |
176
+ | Verbose worktree.ts threading (8 sites) could miss one | The TDD step runs `pnpm vitest run worktree` and `pnpm run check` before committing |
177
+
178
+ ## Open Questions
179
+
180
+ - Should a future issue expose `debugLog` as part of the public `service.ts` API
181
+ so consumer extensions can share the same debug flag?
182
+ Deferred — out of scope for this change; no consumer currently needs it.
183
+ - Should `PI_SUBAGENTS_DEBUG` be documented in the package `README.md`?
184
+ Likely yes, but deferred to a follow-up doc PR.
@@ -0,0 +1,38 @@
1
+ ---
2
+ issue: 54
3
+ issue_title: "refactor: decompose src/index.ts into tool + menu modules"
4
+ ---
5
+
6
+ # Retro: #54 — decompose index.ts into tool + menu modules
7
+
8
+ ## Final Retrospective (2026-05-18T02:20:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Decomposed `src/index.ts` from 1,619 lines to 265 lines across 8 commits.
13
+ Extracted 7 new modules (`tools/helpers.ts`, `renderer.ts`, `notification.ts`, `tools/agent-tool.ts`, `tools/get-result-tool.ts`, `tools/steer-tool.ts`, `ui/agent-menu.ts`) with 66 new tests (379 → 445 total).
14
+ Released as `pi-subagents-v4.1.1`.
15
+ Filed follow-up #66 (replace `as any` casts with proper SDK types) and #67 (flaky `pi-autoformat` acceptance test).
16
+
17
+ ### Observations
18
+
19
+ #### What went well
20
+
21
+ - Leaf-first extraction order worked cleanly — helpers, then renderer, then notification, then tools, then menu. Each step left the repo green with no cascading breakage.
22
+ - The `createNotificationSystem` factory pattern with arrow-closure capture of `widget` (assigned after `AgentManager` construction) preserved the existing deferred-reference semantics without restructuring initialization order.
23
+
24
+ #### What caused friction (agent side)
25
+
26
+ - `wrong-abstraction` — Applied the code-style skill's "keep Pi SDK imports out of business-logic modules" rule to tool/menu modules, which are SDK consumers, not business logic. Used `unknown` for `ExtensionContext`, `AgentSession`, `ModelRegistry` in factory dep interfaces, requiring 9 `as any` casts in `index.ts`. User caught this post-ship. Impact: filed #66 as a follow-up cleanup; the casts are cosmetic (no runtime effect) but degrade type safety. Fixed the code-style skill to clarify the boundary. (user-caught)
27
+
28
+ - `missing-context` — Four test files (`notification.test.ts`, `get-result-tool.test.ts`, `steer-tool.test.ts`, `agent-tool.test.ts`) omitted `compactionCount: 0` from `AgentRecord` factories. Caught at the final `pnpm run check` step, not during test writing. The testing skill already says "grep for ALL test files that construct a compatible mock." Impact: one extra fix cycle delegated to a subagent, no rework beyond that step. (self-identified)
29
+
30
+ - `other` — `Edit` tool failed 3 times matching the UTF-8 middle dot (`·`, U+00B7) in the steer tool's `stateParts.join(" · ")` line. The third attempt produced a partial match that left the file in a broken state (dangling orphan code after the replacement anchor). Required `git restore` and a fallback to `python3` line-range replacement. The same `python3` approach for the menu extraction lost the closing `}` of the default export function. Impact: ~5 minutes of rework across the two extraction steps, plus one `git restore`.
31
+
32
+ #### What caused friction (user side)
33
+
34
+ - The `as any` casts could have been caught earlier if the user had flagged the `unknown` types during the planning phase. However, the plan didn't prescribe exact interface types — that was an implementation decision. The user's post-ship review ("Why did we have to cast `as any`? Take a look at `packages/pi-permission-system/` as a model") was an efficient redirect that immediately scoped the investigation.
35
+
36
+ ### Changes made
37
+
38
+ 1. `.pi/skills/code-style/SKILL.md` — Clarified SDK-boundary guidance: tool definitions, event handlers, and command handlers may import SDK types directly; the restriction targets pure helpers and domain modules.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "4.1.1",
3
+ "version": "5.1.0",
4
4
  "exports": {
5
5
  ".": "./src/service.ts"
6
6
  },
@@ -30,20 +30,20 @@
30
30
  "access": "public"
31
31
  },
32
32
  "peerDependencies": {
33
- "@earendil-works/pi-ai": ">=0.74.0",
34
- "@earendil-works/pi-coding-agent": ">=0.74.0",
35
- "@earendil-works/pi-tui": ">=0.74.0"
33
+ "@earendil-works/pi-ai": ">=0.75.0",
34
+ "@earendil-works/pi-coding-agent": ">=0.75.0",
35
+ "@earendil-works/pi-tui": ">=0.75.0"
36
36
  },
37
37
  "dependencies": {
38
38
  "@sinclair/typebox": "^0.34.49",
39
39
  "nanoid": "^5.0.0"
40
40
  },
41
41
  "engines": {
42
- "node": ">=20"
42
+ "node": ">=22"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@biomejs/biome": "^2.4.14",
46
- "@types/node": "^25.6.2",
46
+ "@types/node": "^22.15.3",
47
47
  "typescript": "^6.0.3",
48
48
  "vitest": "^4.1.5",
49
49
  "rumdl": "^0.1.93"
@@ -10,6 +10,7 @@ import { randomUUID } from "node:crypto";
10
10
  import type { Model } from "@earendil-works/pi-ai";
11
11
  import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
12
12
  import { resumeAgent, runAgent, type ToolActivity } from "./agent-runner.js";
13
+ import { debugLog } from "./debug.js";
13
14
  import type { AgentInvocation, AgentRecord, IsolationMode, SubagentType, ThinkingLevel } from "./types.js";
14
15
  import { addUsage } from "./usage.js";
15
16
  import { cleanupWorktree, createWorktree, pruneWorktrees, } from "./worktree.js";
@@ -230,7 +231,7 @@ export class AgentManager {
230
231
 
231
232
  // Final flush of streaming output file
232
233
  if (record.outputCleanup) {
233
- try { record.outputCleanup(); } catch { /* ignore */ }
234
+ try { record.outputCleanup(); } catch (err) { debugLog("outputCleanup", err); }
234
235
  record.outputCleanup = undefined;
235
236
  }
236
237
 
@@ -246,7 +247,7 @@ export class AgentManager {
246
247
 
247
248
  if (options.isBackground) {
248
249
  this.runningBackground--;
249
- try { this.onComplete?.(record); } catch { /* ignore completion side-effect errors */ }
250
+ try { this.onComplete?.(record); } catch (err) { debugLog("onComplete callback", err); }
250
251
  this.drainQueue();
251
252
  }
252
253
  return responseText;
@@ -263,7 +264,7 @@ export class AgentManager {
263
264
 
264
265
  // Final flush of streaming output file on error
265
266
  if (record.outputCleanup) {
266
- try { record.outputCleanup(); } catch { /* ignore */ }
267
+ try { record.outputCleanup(); } catch (err) { debugLog("outputCleanup on error", err); }
267
268
  record.outputCleanup = undefined;
268
269
  }
269
270
 
@@ -272,7 +273,7 @@ export class AgentManager {
272
273
  try {
273
274
  const wtResult = cleanupWorktree(ctx.cwd, record.worktree, options.description);
274
275
  record.worktreeResult = wtResult;
275
- } catch { /* ignore cleanup errors */ }
276
+ } catch (err) { debugLog("cleanupWorktree on agent error", err); }
276
277
  }
277
278
 
278
279
  if (options.isBackground) {
@@ -477,6 +478,6 @@ export class AgentManager {
477
478
  }
478
479
  this.agents.clear();
479
480
  // Prune any orphaned git worktrees (crash recovery)
480
- try { pruneWorktrees(process.cwd()); } catch { /* ignore */ }
481
+ try { pruneWorktrees(process.cwd()); } catch (err) { debugLog("pruneWorktrees on dispose", err); }
481
482
  }
482
483
  }
@@ -6,6 +6,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
6
6
  import { basename, join } from "node:path";
7
7
  import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
8
8
  import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
9
+ import { debugLog } from "./debug.js";
9
10
  import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
10
11
 
11
12
  /**
@@ -34,7 +35,8 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
34
35
  let files: string[];
35
36
  try {
36
37
  files = readdirSync(dir).filter(f => f.endsWith(".md"));
37
- } catch {
38
+ } catch (err) {
39
+ debugLog("readdirSync agents dir", err);
38
40
  return;
39
41
  }
40
42
 
@@ -44,7 +46,8 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
44
46
  let content: string;
45
47
  try {
46
48
  content = readFileSync(join(dir, file), "utf-8");
47
- } catch {
49
+ } catch (err) {
50
+ debugLog("readFileSync agent file", err);
48
51
  continue;
49
52
  }
50
53
 
package/src/debug.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * debug.ts — Debug logging utility for silenced catch blocks.
3
+ *
4
+ * Set PI_SUBAGENTS_DEBUG=1 to reveal silent failures in catch blocks
5
+ * throughout the package. Production behavior is unchanged when unset.
6
+ */
7
+
8
+ export const DEBUG = process.env.PI_SUBAGENTS_DEBUG === "1";
9
+
10
+ export function debugLog(context: string, err: unknown): void {
11
+ if (DEBUG) console.warn(`[pi-subagents:debug] ${context}:`, err);
12
+ }
package/src/env.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { debugLog } from "./debug.js";
6
7
  import type { EnvInfo } from "./types.js";
7
8
 
8
9
  export async function detectEnv(pi: ExtensionAPI, cwd: string): Promise<EnvInfo> {
@@ -12,15 +13,16 @@ export async function detectEnv(pi: ExtensionAPI, cwd: string): Promise<EnvInfo>
12
13
  try {
13
14
  const result = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"], { cwd, timeout: 5000 });
14
15
  isGitRepo = result.code === 0 && result.stdout.trim() === "true";
15
- } catch {
16
- // Not a git repo or git not installed
16
+ } catch (err) {
17
+ debugLog("git rev-parse", err);
17
18
  }
18
19
 
19
20
  if (isGitRepo) {
20
21
  try {
21
22
  const result = await pi.exec("git", ["branch", "--show-current"], { cwd, timeout: 5000 });
22
23
  branch = result.code === 0 ? result.stdout.trim() : "unknown";
23
- } catch {
24
+ } catch (err) {
25
+ debugLog("git branch", err);
24
26
  branch = "unknown";
25
27
  }
26
28
  }
package/src/memory.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  import { existsSync, lstatSync, mkdirSync, readFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { join, } from "node:path";
13
+ import { debugLog } from "./debug.js";
13
14
  import type { MemoryScope } from "./types.js";
14
15
 
15
16
  /** Maximum lines to read from MEMORY.md */
@@ -30,7 +31,8 @@ export function isUnsafeName(name: string): boolean {
30
31
  export function isSymlink(filePath: string): boolean {
31
32
  try {
32
33
  return lstatSync(filePath).isSymbolicLink();
33
- } catch {
34
+ } catch (err) {
35
+ debugLog("lstatSync", err);
34
36
  return false;
35
37
  }
36
38
  }
@@ -44,7 +46,8 @@ export function safeReadFile(filePath: string): string | undefined {
44
46
  if (isSymlink(filePath)) return undefined;
45
47
  try {
46
48
  return readFileSync(filePath, "utf-8");
47
- } catch {
49
+ } catch (err) {
50
+ debugLog("readFileSync", err);
48
51
  return undefined;
49
52
  }
50
53
  }
@@ -1,3 +1,4 @@
1
+ import { debugLog } from "./debug.js";
1
2
  import type { AgentRecord, NotificationDetails } from "./types.js";
2
3
  import type { AgentActivity } from "./ui/agent-widget.js";
3
4
  import { getLifetimeTotal, getSessionContextPercent } from "./usage.js";
@@ -142,8 +143,8 @@ export function createNotificationSystem(deps: NotificationDeps): NotificationSy
142
143
  pendingNudges.delete(key);
143
144
  try {
144
145
  send();
145
- } catch {
146
- /* ignore stale completion side-effect errors */
146
+ } catch (err) {
147
+ debugLog("notification render", err);
147
148
  }
148
149
  }, delay),
149
150
  );
@@ -9,6 +9,7 @@ import { appendFileSync, chmodSync, mkdirSync, writeFileSync } from "node:fs";
9
9
  import { tmpdir } from "node:os";
10
10
  import { join } from "node:path";
11
11
  import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
12
+ import { debugLog } from "./debug.js";
12
13
 
13
14
  /**
14
15
  * Encode a cwd path as a filesystem-safe directory name. Handles:
@@ -80,7 +81,9 @@ export function streamToOutputFile(
80
81
  };
81
82
  try {
82
83
  appendFileSync(path, JSON.stringify(entry) + "\n", "utf-8");
83
- } catch { /* ignore write errors */ }
84
+ } catch (err) {
85
+ debugLog("write JSONL chunk", err);
86
+ }
84
87
  writtenCount++;
85
88
  }
86
89
  };
@@ -23,6 +23,7 @@ import { existsSync, readdirSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
  import { join } from "node:path";
25
25
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
26
+ import { debugLog } from "./debug.js";
26
27
  import { isSymlink, isUnsafeName, safeReadFile } from "./memory.js";
27
28
 
28
29
  export interface PreloadedSkill {
@@ -71,7 +72,8 @@ function findSkillDirectory(root: string, name: string): string | undefined {
71
72
  let entries: Dirent<string>[];
72
73
  try {
73
74
  entries = readdirSync(current, { withFileTypes: true });
74
- } catch {
75
+ } catch (err) {
76
+ debugLog("readdirSync skill root", err);
75
77
  continue;
76
78
  }
77
79
 
package/src/worktree.ts CHANGED
@@ -11,6 +11,7 @@ import { randomUUID } from "node:crypto";
11
11
  import { existsSync } from "node:fs";
12
12
  import { tmpdir } from "node:os";
13
13
  import { join } from "node:path";
14
+ import { debugLog } from "./debug.js";
14
15
 
15
16
  export interface WorktreeInfo {
16
17
  /** Absolute path to the worktree directory. */
@@ -37,7 +38,8 @@ export function createWorktree(cwd: string, agentId: string): WorktreeInfo | und
37
38
  try {
38
39
  execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
39
40
  execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 });
40
- } catch {
41
+ } catch (err) {
42
+ debugLog("createWorktree git rev-parse", err);
41
43
  return undefined;
42
44
  }
43
45
 
@@ -53,8 +55,8 @@ export function createWorktree(cwd: string, agentId: string): WorktreeInfo | und
53
55
  timeout: 30000,
54
56
  });
55
57
  return { path: worktreePath, branch };
56
- } catch {
57
- // If worktree creation fails, return undefined (agent runs in normal cwd)
58
+ } catch (err) {
59
+ debugLog("git worktree add", err);
58
60
  return undefined;
59
61
  }
60
62
  }
@@ -107,8 +109,8 @@ export function cleanupWorktree(
107
109
  stdio: "pipe",
108
110
  timeout: 5000,
109
111
  });
110
- } catch {
111
- // Branch already exists — use a unique suffix
112
+ } catch (err) {
113
+ debugLog("git branch", err);
112
114
  branchName = `${worktree.branch}-${Date.now()}`;
113
115
  execFileSync("git", ["branch", branchName], {
114
116
  cwd: worktree.path,
@@ -127,9 +129,9 @@ export function cleanupWorktree(
127
129
  branch: worktree.branch,
128
130
  path: worktree.path,
129
131
  };
130
- } catch {
131
- // Best effort cleanup on error
132
- try { removeWorktree(cwd, worktree.path); } catch { /* ignore */ }
132
+ } catch (err) {
133
+ debugLog("cleanupWorktree", err);
134
+ try { removeWorktree(cwd, worktree.path); } catch (removeErr) { debugLog("removeWorktree on cleanup error", removeErr); }
133
135
  return { hasChanges: false };
134
136
  }
135
137
  }
@@ -144,11 +146,11 @@ function removeWorktree(cwd: string, worktreePath: string): void {
144
146
  stdio: "pipe",
145
147
  timeout: 10000,
146
148
  });
147
- } catch {
148
- // If git worktree remove fails, try pruning
149
+ } catch (err) {
150
+ debugLog("git worktree remove", err);
149
151
  try {
150
152
  execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
151
- } catch { /* ignore */ }
153
+ } catch (pruneErr) { debugLog("git worktree prune", pruneErr); }
152
154
  }
153
155
  }
154
156
 
@@ -158,5 +160,5 @@ function removeWorktree(cwd: string, worktreePath: string): void {
158
160
  export function pruneWorktrees(cwd: string): void {
159
161
  try {
160
162
  execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
161
- } catch { /* ignore */ }
163
+ } catch (err) { debugLog("pruneWorktrees", err); }
162
164
  }