byterover-cli 1.2.0 → 1.3.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.
Files changed (91) hide show
  1. package/README.md +87 -9
  2. package/dist/constants.d.ts +0 -5
  3. package/dist/constants.js +0 -5
  4. package/dist/core/domain/cipher/agent/agent-info.d.ts +17 -17
  5. package/dist/core/domain/cipher/llm/schemas.d.ts +14 -14
  6. package/dist/core/domain/cipher/session/session-metadata.d.ts +2 -2
  7. package/dist/core/domain/entities/agent.js +6 -6
  8. package/dist/core/domain/entities/connector-type.d.ts +2 -1
  9. package/dist/core/domain/entities/connector-type.js +2 -1
  10. package/dist/core/domain/transport/schemas.d.ts +66 -66
  11. package/dist/core/interfaces/cipher/i-chat-session.d.ts +3 -1
  12. package/dist/core/interfaces/connectors/i-connector.d.ts +2 -2
  13. package/dist/core/interfaces/i-file-service.d.ts +7 -0
  14. package/dist/infra/auth/oauth-service.d.ts +15 -0
  15. package/dist/infra/auth/oauth-service.js +38 -2
  16. package/dist/infra/cipher/agent/agent-schemas.d.ts +42 -42
  17. package/dist/infra/cipher/llm/context/context-manager.js +7 -9
  18. package/dist/infra/cipher/llm/internal-llm-service.d.ts +5 -1
  19. package/dist/infra/cipher/llm/internal-llm-service.js +57 -46
  20. package/dist/infra/cipher/session/chat-session.d.ts +3 -1
  21. package/dist/infra/cipher/session/chat-session.js +5 -3
  22. package/dist/infra/cipher/system-prompt/contributor-schemas.d.ts +8 -8
  23. package/dist/infra/cipher/system-prompt/schemas.d.ts +5 -5
  24. package/dist/infra/cipher/tools/implementations/task-tool.js +3 -3
  25. package/dist/infra/connectors/connector-manager.js +2 -0
  26. package/dist/infra/connectors/hook/hook-connector.d.ts +1 -1
  27. package/dist/infra/connectors/hook/hook-connector.js +3 -3
  28. package/dist/infra/connectors/mcp/mcp-connector.d.ts +1 -1
  29. package/dist/infra/connectors/mcp/mcp-connector.js +4 -4
  30. package/dist/infra/connectors/rules/rules-connector.d.ts +1 -1
  31. package/dist/infra/connectors/rules/rules-connector.js +4 -4
  32. package/dist/infra/connectors/shared/template-service.js +4 -0
  33. package/dist/infra/connectors/skill/index.d.ts +1 -0
  34. package/dist/infra/connectors/skill/index.js +1 -0
  35. package/dist/infra/connectors/skill/skill-connector-config.d.ts +45 -0
  36. package/dist/infra/connectors/skill/skill-connector-config.js +26 -0
  37. package/dist/infra/connectors/skill/skill-connector.d.ts +39 -0
  38. package/dist/infra/connectors/skill/skill-connector.js +160 -0
  39. package/dist/infra/connectors/skill/skill-content-loader.d.ts +18 -0
  40. package/dist/infra/connectors/skill/skill-content-loader.js +33 -0
  41. package/dist/infra/file/fs-file-service.d.ts +7 -0
  42. package/dist/infra/file/fs-file-service.js +15 -1
  43. package/dist/infra/mcp/tools/task-result-waiter.js +8 -0
  44. package/dist/infra/process/agent-worker.js +30 -14
  45. package/dist/infra/process/task-queue-manager.d.ts +23 -34
  46. package/dist/infra/process/task-queue-manager.js +57 -118
  47. package/dist/infra/process/transport-handlers.js +1 -7
  48. package/dist/infra/repl/commands/connectors-command.js +1 -1
  49. package/dist/infra/transport/socket-io-transport-client.d.ts +9 -0
  50. package/dist/infra/transport/socket-io-transport-client.js +21 -2
  51. package/dist/infra/usecase/connectors-use-case.js +8 -2
  52. package/dist/infra/usecase/init-use-case.js +1 -1
  53. package/dist/infra/usecase/reset-use-case.d.ts +1 -0
  54. package/dist/infra/usecase/reset-use-case.js +4 -1
  55. package/dist/{commands → oclif/commands}/curate.d.ts +1 -1
  56. package/dist/{commands → oclif/commands}/curate.js +6 -6
  57. package/dist/{commands → oclif/commands}/hook-prompt-submit.d.ts +1 -1
  58. package/dist/{commands → oclif/commands}/hook-prompt-submit.js +3 -3
  59. package/dist/{commands → oclif/commands}/main.js +10 -10
  60. package/dist/{commands → oclif/commands}/mcp.js +2 -2
  61. package/dist/{commands → oclif/commands}/query.d.ts +1 -1
  62. package/dist/{commands → oclif/commands}/query.js +6 -6
  63. package/dist/{commands → oclif/commands}/status.d.ts +1 -1
  64. package/dist/{commands → oclif/commands}/status.js +8 -8
  65. package/dist/{commands → oclif/commands}/watch.d.ts +3 -3
  66. package/dist/{commands → oclif/commands}/watch.js +6 -6
  67. package/dist/oclif/constants.d.ts +11 -0
  68. package/dist/oclif/constants.js +11 -0
  69. package/dist/{hooks → oclif/hooks}/prerun/validate-brv-config-version.d.ts +1 -1
  70. package/dist/{hooks → oclif/hooks}/prerun/validate-brv-config-version.js +2 -2
  71. package/dist/templates/sections/command-reference.md +5 -96
  72. package/dist/templates/sections/workflow.md +21 -16
  73. package/dist/templates/skill/SKILL.md +91 -0
  74. package/dist/templates/skill/TROUBLESHOOTING.md +50 -0
  75. package/dist/templates/skill/WORKFLOWS.md +229 -0
  76. package/dist/utils/type-guards.d.ts +11 -0
  77. package/dist/utils/type-guards.js +13 -0
  78. package/oclif.manifest.json +8 -1
  79. package/package.json +9 -9
  80. package/dist/infra/process/constants.d.ts +0 -1
  81. package/dist/infra/process/constants.js +0 -1
  82. /package/dist/{commands → oclif/commands}/main.d.ts +0 -0
  83. /package/dist/{commands → oclif/commands}/mcp.d.ts +0 -0
  84. /package/dist/{hooks → oclif/hooks}/command_not_found/handle-invalid-commands.d.ts +0 -0
  85. /package/dist/{hooks → oclif/hooks}/command_not_found/handle-invalid-commands.js +0 -0
  86. /package/dist/{hooks → oclif/hooks}/error/clean-errors.d.ts +0 -0
  87. /package/dist/{hooks → oclif/hooks}/error/clean-errors.js +0 -0
  88. /package/dist/{hooks → oclif/hooks}/init/update-notifier.d.ts +0 -0
  89. /package/dist/{hooks → oclif/hooks}/init/update-notifier.js +0 -0
  90. /package/dist/{hooks → oclif/hooks}/init/welcome.d.ts +0 -0
  91. /package/dist/{hooks → oclif/hooks}/init/welcome.js +0 -0
@@ -1,21 +1,17 @@
1
1
  /**
2
- * TaskQueueManager - Manages in-memory task queues with concurrency control.
2
+ * TaskQueueManager - Manages in-memory task queue with FIFO sequential execution.
3
3
  *
4
4
  * Features:
5
- * - Separate queues for different task types (curate, query)
6
- * - Configurable concurrency limits per queue
5
+ * - Unified queue for all task types (curate, query)
6
+ * - Configurable concurrency limit (default: 1 for sequential execution)
7
7
  * - Task deduplication (same taskId can't be queued twice)
8
8
  * - Cancel tasks from queue before processing
9
- * - FIFO processing order
9
+ * - Strict FIFO processing order across all task types
10
10
  *
11
11
  * This class is extracted from agent-worker.ts to enable unit testing.
12
12
  */
13
- import type { TaskExecute } from '../../core/domain/transport/schemas.js';
14
- export type TaskType = 'curate' | 'query';
15
- export interface QueueConfig {
16
- /** Maximum concurrent tasks for this queue */
17
- maxConcurrent: number;
18
- }
13
+ import type { TaskExecute, TaskType } from '../../core/domain/transport/schemas.js';
14
+ export type { TaskType } from '../../core/domain/transport/schemas.js';
19
15
  export interface TaskQueueStats {
20
16
  /** Number of tasks currently being processed */
21
17
  active: number;
@@ -25,10 +21,10 @@ export interface TaskQueueStats {
25
21
  queued: number;
26
22
  }
27
23
  export interface TaskQueueManagerConfig {
28
- curate: QueueConfig;
24
+ /** Maximum concurrent tasks (default: 1 for sequential execution) */
25
+ maxConcurrent?: number;
29
26
  /** Optional callback for executor errors (for logging/debugging) */
30
27
  onExecutorError?: (taskId: string, error: unknown) => void;
31
- query: QueueConfig;
32
28
  }
33
29
  /**
34
30
  * Result of attempting to enqueue a task.
@@ -56,16 +52,14 @@ export type CancelResult = {
56
52
  */
57
53
  export type TaskExecutor = (task: TaskExecute) => Promise<void>;
58
54
  export declare class TaskQueueManager {
59
- private activeCurateTasks;
60
- private activeQueryTasks;
61
- private readonly config;
62
- private readonly curateQueue;
55
+ private activeTasks;
63
56
  /** Maps taskId → taskType for tracking (replaces Set for type awareness) */
64
57
  private readonly knownTasks;
58
+ private readonly maxConcurrent;
65
59
  private readonly onExecutorError?;
66
- private readonly queryQueue;
60
+ private readonly queue;
67
61
  private taskExecutor;
68
- constructor(config?: Partial<TaskQueueManagerConfig>);
62
+ constructor(config?: TaskQueueManagerConfig);
69
63
  /**
70
64
  * Cancel a task by taskId.
71
65
  * Removes from queue if waiting, or marks for cancellation if processing.
@@ -82,21 +76,22 @@ export declare class TaskQueueManager {
82
76
  */
83
77
  enqueue(task: TaskExecute): EnqueueResult;
84
78
  /**
85
- * Get total active task count across all queues.
79
+ * Get total active task count.
86
80
  */
87
81
  getActiveCount(): number;
88
82
  /**
89
- * Get all queue statistics.
83
+ * Get total queued task count.
90
84
  */
91
- getAllStats(): Record<TaskType, TaskQueueStats>;
85
+ getQueuedCount(): number;
92
86
  /**
93
- * Get total queued task count across all queues.
87
+ * Get a copy of all queued tasks (not including active tasks).
88
+ * Useful for notifying clients before clearing the queue.
94
89
  */
95
- getQueuedCount(): number;
90
+ getQueuedTasks(): readonly TaskExecute[];
96
91
  /**
97
- * Get statistics for a specific queue.
92
+ * Get statistics for the queue.
98
93
  */
99
- getStats(type: TaskType): TaskQueueStats;
94
+ getStats(): TaskQueueStats;
100
95
  /**
101
96
  * Check if there are any active tasks (currently being processed).
102
97
  * Used to prevent reinit during task execution.
@@ -111,7 +106,7 @@ export declare class TaskQueueManager {
111
106
  * Should be called by executor when task finishes.
112
107
  * Guards against underflow from duplicate/invalid calls.
113
108
  */
114
- markCompleted(taskId: string, type: TaskType): void;
109
+ markCompleted(taskId: string): void;
115
110
  /**
116
111
  * Set the task executor callback.
117
112
  * Called when a task is ready to be processed.
@@ -119,18 +114,12 @@ export declare class TaskQueueManager {
119
114
  */
120
115
  setExecutor(executor: TaskExecutor): void;
121
116
  /**
122
- * Process all possible tasks from a queue (up to maxConcurrent).
123
- * Handles Infinity maxConcurrent safely by using queue length as bound.
117
+ * Process all possible tasks from the queue (up to maxConcurrent).
124
118
  */
125
119
  private drainQueue;
126
120
  private executeTask;
127
121
  /**
128
- * Get queue state for a task type (DRY helper).
129
- */
130
- private getQueueState;
131
- /**
132
- * Try to process the next task from a specific queue.
133
- * Unified method replacing tryProcessNextCurate/tryProcessNextQuery.
122
+ * Try to process the next task from the queue.
134
123
  */
135
124
  private tryProcessNext;
136
125
  }
@@ -1,32 +1,26 @@
1
1
  /**
2
- * TaskQueueManager - Manages in-memory task queues with concurrency control.
2
+ * TaskQueueManager - Manages in-memory task queue with FIFO sequential execution.
3
3
  *
4
4
  * Features:
5
- * - Separate queues for different task types (curate, query)
6
- * - Configurable concurrency limits per queue
5
+ * - Unified queue for all task types (curate, query)
6
+ * - Configurable concurrency limit (default: 1 for sequential execution)
7
7
  * - Task deduplication (same taskId can't be queued twice)
8
8
  * - Cancel tasks from queue before processing
9
- * - FIFO processing order
9
+ * - Strict FIFO processing order across all task types
10
10
  *
11
11
  * This class is extracted from agent-worker.ts to enable unit testing.
12
12
  */
13
- import { CURATE_MAX_CONCURRENT } from './constants.js';
13
+ import { isValidTaskType } from '../../utils/type-guards.js';
14
14
  export class TaskQueueManager {
15
- activeCurateTasks = 0;
16
- activeQueryTasks = 0;
17
- config;
18
- curateQueue = [];
15
+ activeTasks = 0;
19
16
  /** Maps taskId → taskType for tracking (replaces Set for type awareness) */
20
17
  knownTasks = new Map();
18
+ maxConcurrent;
21
19
  onExecutorError;
22
- queryQueue = [];
20
+ queue = [];
23
21
  taskExecutor;
24
22
  constructor(config) {
25
- this.config = {
26
- curate: { maxConcurrent: config?.curate?.maxConcurrent ?? CURATE_MAX_CONCURRENT },
27
- // Query tasks are unlimited (Infinity) - lightweight and fast
28
- query: { maxConcurrent: config?.query?.maxConcurrent ?? Infinity },
29
- };
23
+ this.maxConcurrent = config?.maxConcurrent ?? 1;
30
24
  this.onExecutorError = config?.onExecutorError;
31
25
  }
32
26
  /**
@@ -34,21 +28,15 @@ export class TaskQueueManager {
34
28
  * Removes from queue if waiting, or marks for cancellation if processing.
35
29
  */
36
30
  cancel(taskId) {
37
- // Try to remove from curate queue
38
- const curateIndex = this.curateQueue.findIndex((t) => t.taskId === taskId);
39
- if (curateIndex !== -1) {
40
- this.curateQueue.splice(curateIndex, 1);
41
- this.knownTasks.delete(taskId);
42
- return { success: true, taskType: 'curate', wasQueued: true };
43
- }
44
- // Try to remove from query queue
45
- const queryIndex = this.queryQueue.findIndex((t) => t.taskId === taskId);
46
- if (queryIndex !== -1) {
47
- this.queryQueue.splice(queryIndex, 1);
31
+ // Try to remove from queue
32
+ const index = this.queue.findIndex((t) => t.taskId === taskId);
33
+ if (index !== -1) {
34
+ const task = this.queue[index];
35
+ this.queue.splice(index, 1);
48
36
  this.knownTasks.delete(taskId);
49
- return { success: true, taskType: 'query', wasQueued: true };
37
+ return { success: true, taskType: task.type, wasQueued: true };
50
38
  }
51
- // Check if task is currently processing - now we know the real taskType!
39
+ // Check if task is currently processing - get type from knownTasks
52
40
  const taskType = this.knownTasks.get(taskId);
53
41
  if (taskType) {
54
42
  // Task is processing - caller should handle cancellation via taskProcessor
@@ -61,10 +49,8 @@ export class TaskQueueManager {
61
49
  * Useful for testing or shutdown.
62
50
  */
63
51
  clear() {
64
- this.curateQueue.length = 0;
65
- this.queryQueue.length = 0;
66
- this.activeCurateTasks = 0;
67
- this.activeQueryTasks = 0;
52
+ this.queue.length = 0;
53
+ this.activeTasks = 0;
68
54
  this.knownTasks.clear();
69
55
  }
70
56
  /**
@@ -76,57 +62,43 @@ export class TaskQueueManager {
76
62
  if (this.knownTasks.has(task.taskId)) {
77
63
  return { reason: 'duplicate', success: false };
78
64
  }
79
- // Validate task type
80
- if (task.type !== 'curate' && task.type !== 'query') {
65
+ // Validate task type using type guard for compile-time safety
66
+ if (!isValidTaskType(task.type)) {
81
67
  return { reason: 'unknown_type', success: false };
82
68
  }
83
69
  // Register with type and enqueue
84
70
  this.knownTasks.set(task.taskId, task.type);
85
- if (task.type === 'curate') {
86
- this.curateQueue.push(task);
87
- this.tryProcessNext('curate');
88
- return { position: this.curateQueue.length, success: true };
89
- }
90
- this.queryQueue.push(task);
91
- this.tryProcessNext('query');
92
- return { position: this.queryQueue.length, success: true };
71
+ this.queue.push(task);
72
+ this.tryProcessNext();
73
+ return { position: this.queue.length, success: true };
93
74
  }
94
75
  /**
95
- * Get total active task count across all queues.
76
+ * Get total active task count.
96
77
  */
97
78
  getActiveCount() {
98
- return this.activeCurateTasks + this.activeQueryTasks;
79
+ return this.activeTasks;
99
80
  }
100
81
  /**
101
- * Get all queue statistics.
82
+ * Get total queued task count.
102
83
  */
103
- getAllStats() {
104
- return {
105
- curate: this.getStats('curate'),
106
- query: this.getStats('query'),
107
- };
84
+ getQueuedCount() {
85
+ return this.queue.length;
108
86
  }
109
87
  /**
110
- * Get total queued task count across all queues.
88
+ * Get a copy of all queued tasks (not including active tasks).
89
+ * Useful for notifying clients before clearing the queue.
111
90
  */
112
- getQueuedCount() {
113
- return this.curateQueue.length + this.queryQueue.length;
91
+ getQueuedTasks() {
92
+ return [...this.queue];
114
93
  }
115
94
  /**
116
- * Get statistics for a specific queue.
95
+ * Get statistics for the queue.
117
96
  */
118
- getStats(type) {
119
- if (type === 'curate') {
120
- return {
121
- active: this.activeCurateTasks,
122
- maxConcurrent: this.config.curate.maxConcurrent,
123
- queued: this.curateQueue.length,
124
- };
125
- }
97
+ getStats() {
126
98
  return {
127
- active: this.activeQueryTasks,
128
- maxConcurrent: this.config.query.maxConcurrent,
129
- queued: this.queryQueue.length,
99
+ active: this.activeTasks,
100
+ maxConcurrent: this.maxConcurrent,
101
+ queued: this.queue.length,
130
102
  };
131
103
  }
132
104
  /**
@@ -134,7 +106,7 @@ export class TaskQueueManager {
134
106
  * Used to prevent reinit during task execution.
135
107
  */
136
108
  hasActiveTasks() {
137
- return this.activeCurateTasks > 0 || this.activeQueryTasks > 0;
109
+ return this.activeTasks > 0;
138
110
  }
139
111
  /**
140
112
  * Check if a taskId is known (queued or processing).
@@ -147,24 +119,16 @@ export class TaskQueueManager {
147
119
  * Should be called by executor when task finishes.
148
120
  * Guards against underflow from duplicate/invalid calls.
149
121
  */
150
- markCompleted(taskId, type) {
122
+ markCompleted(taskId) {
151
123
  // Guard: only decrement if task was actually known (prevents underflow)
152
124
  if (!this.knownTasks.has(taskId)) {
153
125
  return;
154
126
  }
155
127
  this.knownTasks.delete(taskId);
156
- if (type === 'curate') {
157
- if (this.activeCurateTasks > 0) {
158
- this.activeCurateTasks--;
159
- }
160
- this.tryProcessNext('curate');
161
- }
162
- else {
163
- if (this.activeQueryTasks > 0) {
164
- this.activeQueryTasks--;
165
- }
166
- this.tryProcessNext('query');
128
+ if (this.activeTasks > 0) {
129
+ this.activeTasks--;
167
130
  }
131
+ this.tryProcessNext();
168
132
  }
169
133
  /**
170
134
  * Set the task executor callback.
@@ -174,26 +138,22 @@ export class TaskQueueManager {
174
138
  setExecutor(executor) {
175
139
  this.taskExecutor = executor;
176
140
  // Process any tasks that were queued before executor was set
177
- // Use queue length as upper bound to handle Infinity maxConcurrent safely
178
- this.drainQueue('curate');
179
- this.drainQueue('query');
141
+ this.drainQueue();
180
142
  }
181
143
  /**
182
- * Process all possible tasks from a queue (up to maxConcurrent).
183
- * Handles Infinity maxConcurrent safely by using queue length as bound.
144
+ * Process all possible tasks from the queue (up to maxConcurrent).
184
145
  */
185
- drainQueue(type) {
186
- const state = this.getQueueState(type);
187
- // Process up to queue length (safe for Infinity maxConcurrent)
188
- const toProcess = Math.min(state.queue.length, state.config.maxConcurrent);
146
+ drainQueue() {
147
+ // Process up to maxConcurrent tasks
148
+ const toProcess = Math.min(this.queue.length, this.maxConcurrent);
189
149
  for (let i = 0; i < toProcess; i++) {
190
- this.tryProcessNext(type);
150
+ this.tryProcessNext();
191
151
  }
192
152
  }
193
153
  // ============================================================================
194
154
  // Private Methods
195
155
  // ============================================================================
196
- executeTask(task, type) {
156
+ executeTask(task) {
197
157
  this.taskExecutor(task)
198
158
  .catch((error) => {
199
159
  // Notify caller of executor error (for logging/debugging)
@@ -201,46 +161,25 @@ export class TaskQueueManager {
201
161
  this.onExecutorError?.(task.taskId, error);
202
162
  })
203
163
  .finally(() => {
204
- this.markCompleted(task.taskId, type);
164
+ this.markCompleted(task.taskId);
205
165
  });
206
166
  }
207
167
  /**
208
- * Get queue state for a task type (DRY helper).
209
- */
210
- getQueueState(type) {
211
- if (type === 'curate') {
212
- return {
213
- active: this.activeCurateTasks,
214
- config: this.config.curate,
215
- incrementActive: () => this.activeCurateTasks++,
216
- queue: this.curateQueue,
217
- };
218
- }
219
- return {
220
- active: this.activeQueryTasks,
221
- config: this.config.query,
222
- incrementActive: () => this.activeQueryTasks++,
223
- queue: this.queryQueue,
224
- };
225
- }
226
- /**
227
- * Try to process the next task from a specific queue.
228
- * Unified method replacing tryProcessNextCurate/tryProcessNextQuery.
168
+ * Try to process the next task from the queue.
229
169
  */
230
- tryProcessNext(type) {
170
+ tryProcessNext() {
231
171
  // Don't process without executor - tasks stay in queue
232
172
  if (!this.taskExecutor) {
233
173
  return;
234
174
  }
235
- const state = this.getQueueState(type);
236
- if (state.active >= state.config.maxConcurrent) {
175
+ if (this.activeTasks >= this.maxConcurrent) {
237
176
  return;
238
177
  }
239
- if (state.queue.length === 0) {
178
+ if (this.queue.length === 0) {
240
179
  return;
241
180
  }
242
- const task = state.queue.shift();
243
- state.incrementActive();
244
- this.executeTask(task, type);
181
+ const task = this.queue.shift();
182
+ this.activeTasks++;
183
+ this.executeTask(task);
245
184
  }
246
185
  }
@@ -33,13 +33,7 @@
33
33
  import { AgentDisconnectedError, AgentNotAvailableError, AgentNotInitializedError, serializeTaskError, } from '../../core/domain/errors/task-error.js';
34
34
  import { AgentStatusEventNames, LlmEventNames, TransportAgentEventNames, TransportLlmEventList, TransportTaskEventNames, } from '../../core/domain/transport/schemas.js';
35
35
  import { eventLog, transportLog } from '../../utils/process-logger.js';
36
- /**
37
- * Type guard for valid task types.
38
- * Replaces unsafe `as` assertion per CLAUDE.md standards.
39
- */
40
- function isValidTaskType(type) {
41
- return type === 'curate' || type === 'query';
42
- }
36
+ import { isValidTaskType } from '../../utils/type-guards.js';
43
37
  // All message types are imported from core/domain/transport/schemas.ts
44
38
  // - TaskExecute: Transport → Agent (command)
45
39
  // - TaskStartedEvent, TaskCompletedEvent, TaskErrorEvent: Agent → Transport (task lifecycle events)
@@ -43,7 +43,7 @@ export const connectorsCommand = {
43
43
  }),
44
44
  aliases: [],
45
45
  autoExecute: true,
46
- description: 'Manage agent connectors (rules, hooks)',
46
+ description: 'Manage agent connectors (rules, hook, mcp, or skill)',
47
47
  kind: CommandKind.BUILT_IN,
48
48
  name: 'connectors',
49
49
  };
@@ -72,6 +72,15 @@ export declare class SocketIOTransportClient implements ITransportClient {
72
72
  * Used when initiating new connection attempt - counter preserved for backoff continuity.
73
73
  */
74
74
  private clearForceReconnectTimer;
75
+ /**
76
+ * Remove all registered event listeners from socket.
77
+ * Used before re-registering to prevent listener accumulation across reconnects.
78
+ *
79
+ * Why this is needed: Socket.IO preserves the Socket instance across internal reconnects.
80
+ * If we clear registeredSocketEvents without calling socket.off(), the old listeners
81
+ * remain attached, and registerPendingEventHandlers() adds new ones - causing duplicates.
82
+ */
83
+ private clearSocketEventListeners;
75
84
  /**
76
85
  * Handle system wake from sleep/hibernate.
77
86
  * Re-triggers reconnection if not connected and force reconnect has given up.
@@ -145,8 +145,10 @@ export class SocketIOTransportClient {
145
145
  clientLog(`Socket.IO built-in reconnect succeeded after ${attemptNumber} attempts`);
146
146
  this.setState('connected');
147
147
  // Re-register event handlers after reconnect
148
- // Clear tracking first since socket listeners were reset during reconnect
149
- this.registeredSocketEvents.clear();
148
+ // FIX: Remove existing socket listeners before re-registering to prevent
149
+ // listener accumulation. Socket.IO preserves the Socket instance across
150
+ // internal reconnects, so old listeners remain attached if not removed.
151
+ this.clearSocketEventListeners();
150
152
  this.registerPendingEventHandlers();
151
153
  // Auto-rejoin rooms after reconnect
152
154
  // Use process.nextTick to ensure socket.connected is true
@@ -408,6 +410,23 @@ export class SocketIOTransportClient {
408
410
  this.forceReconnectTimer = undefined;
409
411
  }
410
412
  }
413
+ /**
414
+ * Remove all registered event listeners from socket.
415
+ * Used before re-registering to prevent listener accumulation across reconnects.
416
+ *
417
+ * Why this is needed: Socket.IO preserves the Socket instance across internal reconnects.
418
+ * If we clear registeredSocketEvents without calling socket.off(), the old listeners
419
+ * remain attached, and registerPendingEventHandlers() adds new ones - causing duplicates.
420
+ */
421
+ clearSocketEventListeners() {
422
+ const { socket } = this;
423
+ if (!socket)
424
+ return;
425
+ for (const event of this.registeredSocketEvents) {
426
+ socket.off(event);
427
+ }
428
+ this.registeredSocketEvents.clear();
429
+ }
411
430
  /**
412
431
  * Handle system wake from sleep/hibernate.
413
432
  * Re-triggers reconnection if not connected and force reconnect has given up.
@@ -74,6 +74,9 @@ export class ConnectorsUseCase {
74
74
  case 'rules': {
75
75
  return `Agent reads instructions from rule file (${configPath})`;
76
76
  }
77
+ case 'skill': {
78
+ return `Agent reads skill files from project directory (${configPath})`;
79
+ }
77
80
  }
78
81
  }
79
82
  /**
@@ -90,6 +93,9 @@ export class ConnectorsUseCase {
90
93
  case 'rules': {
91
94
  return 'Rules';
92
95
  }
96
+ case 'skill': {
97
+ return 'Skill';
98
+ }
93
99
  }
94
100
  }
95
101
  /**
@@ -136,8 +142,8 @@ export class ConnectorsUseCase {
136
142
  this.terminal.log(` Installed: ${result.installResult.configPath}`);
137
143
  }
138
144
  // Show restart message for hook connector
139
- if ((result.toType === 'hook' || result.toType === 'mcp') && !result.installResult.alreadyInstalled) {
140
- this.terminal.warn(`\nPlease restart ${agent} to apply the new ${result.toType}.`);
145
+ if (['hook', 'mcp', 'skill'].includes(result.toType) && !result.installResult.alreadyInstalled) {
146
+ this.terminal.warn(`\n⚠️ Please restart ${agent} to apply the new ${result.toType}.`);
141
147
  }
142
148
  }
143
149
  else {
@@ -178,7 +178,7 @@ export class InitUseCase {
178
178
  this.terminal.log(`${selectedAgent} connected via ${defaultType}`);
179
179
  this.terminal.log(` Installed: ${result.configPath}`);
180
180
  // Show restart message for hook connector
181
- if (defaultType === 'hook' || defaultType === 'mcp') {
181
+ if (['hook', 'mcp', 'skill'].includes(defaultType)) {
182
182
  this.terminal.warn(`\n⚠️ Please restart ${selectedAgent} to apply the new ${defaultType}.`);
183
183
  }
184
184
  }
@@ -13,6 +13,7 @@ export declare class ResetUseCase implements IResetUseCase {
13
13
  private readonly terminal;
14
14
  constructor(options: ResetUseCaseOptions);
15
15
  protected confirmReset(): Promise<boolean>;
16
+ protected deleteContextTree(contextTreeDir: string): Promise<void>;
16
17
  run(options: {
17
18
  directory?: string;
18
19
  skipConfirmation: boolean;
@@ -17,6 +17,9 @@ export class ResetUseCase {
17
17
  message: 'Are you sure you want to reset the context tree? This will remove all existing context. Your context tree will be empty.',
18
18
  });
19
19
  }
20
+ async deleteContextTree(contextTreeDir) {
21
+ await rm(contextTreeDir, { force: true, recursive: true });
22
+ }
20
23
  async run(options) {
21
24
  try {
22
25
  // Check if context tree exists
@@ -36,7 +39,7 @@ export class ResetUseCase {
36
39
  // Remove existing context tree directory
37
40
  const baseDir = options.directory ?? process.cwd();
38
41
  const contextTreeDir = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR);
39
- await rm(contextTreeDir, { force: true, recursive: true });
42
+ await this.deleteContextTree(contextTreeDir);
40
43
  // Re-initialize empty context tree
41
44
  await this.contextTreeService.initialize(options.directory);
42
45
  // Re-initialize empty snapshot
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- import { ICurateUseCase } from '../core/interfaces/usecase/i-curate-use-case.js';
2
+ import { ICurateUseCase } from '../../core/interfaces/usecase/i-curate-use-case.js';
3
3
  export default class Curate extends Command {
4
4
  static args: {
5
5
  context: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
@@ -1,10 +1,10 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
- import { isDevelopment } from '../config/environment.js';
3
- import { FileGlobalConfigStore } from '../infra/storage/file-global-config-store.js';
4
- import { createTokenStore } from '../infra/storage/token-store.js';
5
- import { OclifTerminal } from '../infra/terminal/oclif-terminal.js';
6
- import { MixpanelTrackingService } from '../infra/tracking/mixpanel-tracking-service.js';
7
- import { CurateUseCase } from '../infra/usecase/curate-use-case.js';
2
+ import { isDevelopment } from '../../config/environment.js';
3
+ import { FileGlobalConfigStore } from '../../infra/storage/file-global-config-store.js';
4
+ import { createTokenStore } from '../../infra/storage/token-store.js';
5
+ import { OclifTerminal } from '../../infra/terminal/oclif-terminal.js';
6
+ import { MixpanelTrackingService } from '../../infra/tracking/mixpanel-tracking-service.js';
7
+ import { CurateUseCase } from '../../infra/usecase/curate-use-case.js';
8
8
  export default class Curate extends Command {
9
9
  static args = {
10
10
  context: Args.string({
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- import { ITemplateLoader } from '../core/interfaces/i-template-loader.js';
2
+ import { ITemplateLoader } from '../../core/interfaces/i-template-loader.js';
3
3
  /**
4
4
  * Dependencies required by HookPromptSubmit command.
5
5
  * Exported for test mocking.
@@ -1,7 +1,7 @@
1
1
  import { Command } from '@oclif/core';
2
- import { isDevelopment } from '../config/environment.js';
3
- import { FsFileService } from '../infra/file/fs-file-service.js';
4
- import { FsTemplateLoader } from '../infra/template/fs-template-loader.js';
2
+ import { isDevelopment } from '../../config/environment.js';
3
+ import { FsFileService } from '../../infra/file/fs-file-service.js';
4
+ import { FsTemplateLoader } from '../../infra/template/fs-template-loader.js';
5
5
  /**
6
6
  * Hidden command for coding agent pre-prompt hooks.
7
7
  * Outputs ByteRover workflow instructions to stdout.
@@ -1,15 +1,15 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import { randomUUID } from 'node:crypto';
3
- import { DEFAULT_SESSION_RETENTION } from '../core/domain/cipher/session/session-metadata.js';
4
- import { SessionMetadataStore } from '../infra/cipher/session/session-metadata-store.js';
5
- import { ProjectConfigStore } from '../infra/config/file-config-store.js';
6
- import { getProcessManager } from '../infra/process/index.js';
7
- import { startRepl } from '../infra/repl/repl-startup.js';
8
- import { FileGlobalConfigStore } from '../infra/storage/file-global-config-store.js';
9
- import { FileOnboardingPreferenceStore } from '../infra/storage/file-onboarding-preference-store.js';
10
- import { createTokenStore } from '../infra/storage/token-store.js';
11
- import { MixpanelTrackingService } from '../infra/tracking/mixpanel-tracking-service.js';
12
- import { initSessionLog, processManagerLog } from '../utils/process-logger.js';
3
+ import { DEFAULT_SESSION_RETENTION } from '../../core/domain/cipher/session/session-metadata.js';
4
+ import { SessionMetadataStore } from '../../infra/cipher/session/session-metadata-store.js';
5
+ import { ProjectConfigStore } from '../../infra/config/file-config-store.js';
6
+ import { getProcessManager } from '../../infra/process/index.js';
7
+ import { startRepl } from '../../infra/repl/repl-startup.js';
8
+ import { FileGlobalConfigStore } from '../../infra/storage/file-global-config-store.js';
9
+ import { FileOnboardingPreferenceStore } from '../../infra/storage/file-onboarding-preference-store.js';
10
+ import { createTokenStore } from '../../infra/storage/token-store.js';
11
+ import { MixpanelTrackingService } from '../../infra/tracking/mixpanel-tracking-service.js';
12
+ import { initSessionLog, processManagerLog } from '../../utils/process-logger.js';
13
13
  /**
14
14
  * Main command - Entry point for ByteRover CLI.
15
15
  *
@@ -1,6 +1,6 @@
1
1
  import { Command } from '@oclif/core';
2
- import { NoInstanceRunningError } from '../core/domain/errors/connection-error.js';
3
- import { ByteRoverMcpServer } from '../infra/mcp/index.js';
2
+ import { NoInstanceRunningError } from '../../core/domain/errors/connection-error.js';
3
+ import { ByteRoverMcpServer } from '../../infra/mcp/index.js';
4
4
  /**
5
5
  * MCP command - starts the MCP server for coding agent integration.
6
6
  *
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- import { IQueryUseCase } from '../core/interfaces/usecase/i-query-use-case.js';
2
+ import { IQueryUseCase } from '../../core/interfaces/usecase/i-query-use-case.js';
3
3
  export default class Query extends Command {
4
4
  static args: {
5
5
  query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;