prjct-cli 1.6.9 → 1.6.10

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
@@ -1,12 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.10] - 2026-02-07
4
+
5
+ ### Bug Fixes
6
+
7
+ - resolve signal handler and EventBus listener accumulation leaks (PRJ-287) (#135)
8
+
9
+
10
+ ## [1.6.12] - 2026-02-07
11
+
12
+ ### Bug Fixes
13
+ - **Fix signal handler and EventBus listener accumulation leaks (PRJ-287)**: WatchService signal handlers (`SIGINT`/`SIGTERM`) are now stored by reference and removed in `stop()`, preventing accumulation on restart cycles. `pendingChanges` Set is cleared on stop. EventBus gains `flush()` to clear history and stale once-listeners, and `removeAllListeners(event?)` for targeted cleanup.
14
+
15
+ ### Implementation Details
16
+ Stored signal handler references as class properties (`sigintHandler`, `sigtermHandler`). In `start()`, old handlers are removed before new ones are added. In `stop()`, handlers are removed via `process.off()` and `pendingChanges` is cleared. EventBus `flush()` clears history array and all once-listeners. `removeAllListeners()` supports both targeted (single event) and global cleanup.
17
+
18
+ ### Learnings
19
+ - Arrow functions passed to `process.on()` cannot be removed — must store named handler references for `process.off()`.
20
+ - Cleanup code after `process.exit(0)` is unreachable — perform all cleanup before the exit call.
21
+
22
+ ### Test Plan
23
+
24
+ #### For QA
25
+ 1. Start/stop watch mode 10 times — verify only 2 signal handlers (not 20)
26
+ 2. Trigger file changes, stop — verify `pendingChanges` cleared
27
+ 3. Emit 50 events, call `flush()` — verify history empty
28
+ 4. Register `once()` for unfired event, `flush()` — verify listener removed
29
+ 5. `removeAllListeners('event')` — verify only that event cleared
30
+ 6. `removeAllListeners()` — verify all cleared
31
+
32
+ #### For Users
33
+ **What changed:** WatchService no longer leaks signal handlers on restart. EventBus has `flush()` and `removeAllListeners()`.
34
+ **Breaking changes:** None.
35
+
3
36
  ## [1.6.9] - 2026-02-07
4
37
 
5
38
  ### Bug Fixes
6
39
 
7
40
  - resolve SSE zombie connections and infinite promise leak (PRJ-286) (#134)
8
41
 
9
-
10
42
  ## [1.6.11] - 2026-02-07
11
43
 
12
44
  ### Bug Fixes
package/core/bus/bus.ts CHANGED
@@ -237,6 +237,30 @@ class EventBus {
237
237
  this.onceListeners.clear()
238
238
  }
239
239
 
240
+ /**
241
+ * Flush event history and clean up stale once-listeners.
242
+ * Call on task completion, project switch, or periodically.
243
+ */
244
+ flush(): void {
245
+ this.history = []
246
+
247
+ // Remove once-listeners for events that were never fired
248
+ this.onceListeners.clear()
249
+ }
250
+
251
+ /**
252
+ * Remove all listeners for a specific event, or all events if none specified.
253
+ */
254
+ removeAllListeners(event?: string): void {
255
+ if (event) {
256
+ this.listeners.delete(event)
257
+ this.onceListeners.delete(event)
258
+ } else {
259
+ this.listeners.clear()
260
+ this.onceListeners.clear()
261
+ }
262
+ }
263
+
240
264
  /**
241
265
  * Get count of listeners for an event
242
266
  */
@@ -102,6 +102,8 @@ class WatchService {
102
102
  }
103
103
  private isRunning: boolean = false
104
104
  private syncCount: number = 0
105
+ private sigintHandler: (() => void) | null = null
106
+ private sigtermHandler: (() => void) | null = null
105
107
 
106
108
  /**
107
109
  * Start watching for file changes
@@ -151,9 +153,13 @@ class WatchService {
151
153
  .on('unlink', (filePath: string) => this.handleChange('unlink', filePath))
152
154
  .on('error', (error: unknown) => this.handleError(error as Error))
153
155
 
154
- // Handle graceful shutdown
155
- process.on('SIGINT', () => this.stop())
156
- process.on('SIGTERM', () => this.stop())
156
+ // Handle graceful shutdown — store references for proper removal
157
+ if (this.sigintHandler) process.off('SIGINT', this.sigintHandler)
158
+ if (this.sigtermHandler) process.off('SIGTERM', this.sigtermHandler)
159
+ this.sigintHandler = () => this.stop()
160
+ this.sigtermHandler = () => this.stop()
161
+ process.on('SIGINT', this.sigintHandler)
162
+ process.on('SIGTERM', this.sigtermHandler)
157
163
 
158
164
  return { success: true }
159
165
  }
@@ -177,6 +183,17 @@ class WatchService {
177
183
  this.watcher = null
178
184
  }
179
185
 
186
+ // Remove signal handlers to prevent accumulation
187
+ if (this.sigintHandler) {
188
+ process.off('SIGINT', this.sigintHandler)
189
+ this.sigintHandler = null
190
+ }
191
+ if (this.sigtermHandler) {
192
+ process.off('SIGTERM', this.sigtermHandler)
193
+ this.sigtermHandler = null
194
+ }
195
+
196
+ this.pendingChanges.clear()
180
197
  this.isRunning = false
181
198
  process.exit(0)
182
199
  }
@@ -23753,6 +23753,8 @@ var init_watch_service = __esm({
23753
23753
  };
23754
23754
  isRunning = false;
23755
23755
  syncCount = 0;
23756
+ sigintHandler = null;
23757
+ sigtermHandler = null;
23756
23758
  /**
23757
23759
  * Start watching for file changes
23758
23760
  */
@@ -23783,8 +23785,12 @@ var init_watch_service = __esm({
23783
23785
  }
23784
23786
  });
23785
23787
  this.watcher.on("add", (filePath) => this.handleChange("add", filePath)).on("change", (filePath) => this.handleChange("change", filePath)).on("unlink", (filePath) => this.handleChange("unlink", filePath)).on("error", (error) => this.handleError(error));
23786
- process.on("SIGINT", () => this.stop());
23787
- process.on("SIGTERM", () => this.stop());
23788
+ if (this.sigintHandler) process.off("SIGINT", this.sigintHandler);
23789
+ if (this.sigtermHandler) process.off("SIGTERM", this.sigtermHandler);
23790
+ this.sigintHandler = () => this.stop();
23791
+ this.sigtermHandler = () => this.stop();
23792
+ process.on("SIGINT", this.sigintHandler);
23793
+ process.on("SIGTERM", this.sigtermHandler);
23788
23794
  return { success: true };
23789
23795
  }
23790
23796
  /**
@@ -23804,6 +23810,15 @@ var init_watch_service = __esm({
23804
23810
  await this.watcher.close();
23805
23811
  this.watcher = null;
23806
23812
  }
23813
+ if (this.sigintHandler) {
23814
+ process.off("SIGINT", this.sigintHandler);
23815
+ this.sigintHandler = null;
23816
+ }
23817
+ if (this.sigtermHandler) {
23818
+ process.off("SIGTERM", this.sigtermHandler);
23819
+ this.sigtermHandler = null;
23820
+ }
23821
+ this.pendingChanges.clear();
23807
23822
  this.isRunning = false;
23808
23823
  process.exit(0);
23809
23824
  }
@@ -28649,7 +28664,7 @@ var require_package = __commonJS({
28649
28664
  "package.json"(exports, module) {
28650
28665
  module.exports = {
28651
28666
  name: "prjct-cli",
28652
- version: "1.6.9",
28667
+ version: "1.6.10",
28653
28668
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
28654
28669
  main: "core/index.ts",
28655
28670
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "1.6.9",
3
+ "version": "1.6.10",
4
4
  "description": "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
5
5
  "main": "core/index.ts",
6
6
  "bin": {