ccjk 12.2.1 → 12.2.2

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.
@@ -1,87 +1,13 @@
1
- import { l as logger } from '../shared/ccjk.DG_o24cZ.mjs';
1
+ import { h as hookRegistry } from '../shared/ccjk.CCcQfbni.mjs';
2
2
  import { createRequire } from 'node:module';
3
3
  import { c as commonjsGlobal } from '../shared/ccjk.BAGoDD49.mjs';
4
4
  import require$$0 from 'crypto';
5
+ import { l as logger } from '../shared/ccjk.DG_o24cZ.mjs';
5
6
  import { existsSync, readFileSync } from 'fs';
6
7
  import { homedir } from 'os';
7
8
  import { join } from 'path';
8
9
  import './index2.mjs';
9
10
 
10
- class HookRegistry {
11
- hooks = /* @__PURE__ */ new Map();
12
- /**
13
- * Register a hook for an event
14
- */
15
- register(event, hook) {
16
- const hooks = this.hooks.get(event) || [];
17
- hooks.push(hook);
18
- hooks.sort((a, b) => (b.priority || 0) - (a.priority || 0));
19
- this.hooks.set(event, hooks);
20
- logger.debug(`Registered hook "${hook.name}" for event "${event}"`);
21
- }
22
- /**
23
- * Unregister a hook
24
- */
25
- unregister(event, hookName) {
26
- const hooks = this.hooks.get(event);
27
- if (!hooks) return;
28
- const filtered = hooks.filter((h) => h.name !== hookName);
29
- this.hooks.set(event, filtered);
30
- logger.debug(`Unregistered hook "${hookName}" for event "${event}"`);
31
- }
32
- /**
33
- * Execute all hooks for an event
34
- * Returns false if any hook returns continue: false
35
- */
36
- async execute(context) {
37
- const hooks = this.hooks.get(context.event) || [];
38
- const enabledHooks = hooks.filter((h) => h.enabled !== false);
39
- if (enabledHooks.length === 0) {
40
- return { continue: true };
41
- }
42
- let currentContext = context;
43
- let aggregatedData = {};
44
- for (const hook of enabledHooks) {
45
- try {
46
- const result = await hook.fn(currentContext);
47
- if (result.data) {
48
- aggregatedData = { ...aggregatedData, ...result.data };
49
- }
50
- if (!result.continue) {
51
- return {
52
- continue: false,
53
- data: aggregatedData,
54
- error: result.error
55
- };
56
- }
57
- currentContext = {
58
- ...currentContext,
59
- data: { ...currentContext.data, ...aggregatedData }
60
- };
61
- } catch (error) {
62
- logger.error(`Hook "${hook.name}" failed:`, error);
63
- }
64
- }
65
- return {
66
- continue: true,
67
- data: aggregatedData
68
- };
69
- }
70
- /**
71
- * Get all registered hooks for an event
72
- */
73
- getHooks(event) {
74
- return this.hooks.get(event) || [];
75
- }
76
- /**
77
- * Clear all hooks
78
- */
79
- clear() {
80
- this.hooks.clear();
81
- }
82
- }
83
- const hookRegistry = new HookRegistry();
84
-
85
11
  var cuid2 = {};
86
12
 
87
13
  var src = {};
@@ -1,9 +1,19 @@
1
1
  import { EventEmitter } from 'node:events';
2
+ import { contextLoader } from './context-loader.mjs';
3
+ import { l as logger } from '../shared/ccjk.DG_o24cZ.mjs';
4
+ import { h as hookRegistry } from '../shared/ccjk.CCcQfbni.mjs';
5
+ import { Buffer } from 'node:buffer';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
8
+ import { d as dirname, j as join } from '../shared/ccjk.bQ7Dh1g4.mjs';
2
9
  import { a as getGlobalStateManager, g as getGlobalConvoyManager, S as SessionIntelligence } from './convoy-manager.mjs';
3
10
  import { n as nanoid } from '../shared/ccjk.BoApaI4j.mjs';
4
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
- import { j as join } from '../shared/ccjk.bQ7Dh1g4.mjs';
6
11
  import process__default from 'node:process';
12
+ import 'tinyglobby';
13
+ import '../shared/ccjk.CePkJq2S.mjs';
14
+ import './index2.mjs';
15
+ import '../shared/ccjk.BAGoDD49.mjs';
16
+ import '../shared/ccjk.BxSmJ8B7.mjs';
7
17
  import 'node:child_process';
8
18
  import 'node:fs/promises';
9
19
  import 'node:os';
@@ -12,7 +22,918 @@ import './main.mjs';
12
22
  import 'module';
13
23
  import 'node:stream';
14
24
  import 'node:readline';
15
- import 'node:crypto';
25
+
26
+ async function emitCommandHookEvent(event, data, sessionId) {
27
+ try {
28
+ const context = {
29
+ event,
30
+ sessionId,
31
+ data,
32
+ timestamp: Date.now(),
33
+ metadata: {
34
+ source: "brain-router"
35
+ }
36
+ };
37
+ await hookRegistry.execute(context);
38
+ } catch (error) {
39
+ logger.debug(`Command hook bridge skipped for "${event}": ${String(error)}`);
40
+ }
41
+ }
42
+
43
+ const DEFAULT_CONFIG = {
44
+ enablePersistence: false,
45
+ maxHistorySize: 1e3,
46
+ messageRetentionTime: 24 * 60 * 60 * 1e3,
47
+ // 24 hours
48
+ enableLogging: true,
49
+ logLevel: "info",
50
+ enableValidation: true,
51
+ maxMessageSize: 1024 * 1024,
52
+ // 1MB
53
+ enableDeadLetterQueue: true
54
+ };
55
+ class FileMessageStorage {
56
+ filePath;
57
+ constructor(filePath) {
58
+ this.filePath = filePath;
59
+ this.ensureDirectory();
60
+ }
61
+ ensureDirectory() {
62
+ const dir = dirname(this.filePath);
63
+ if (!existsSync(dir)) {
64
+ mkdirSync(dir, { recursive: true });
65
+ }
66
+ }
67
+ async save(message) {
68
+ try {
69
+ const messages = await this.load();
70
+ messages.push(message);
71
+ writeFileSync(this.filePath, JSON.stringify(messages, null, 2), "utf-8");
72
+ } catch (error) {
73
+ console.error("Failed to save message:", error);
74
+ throw error;
75
+ }
76
+ }
77
+ async load(filter) {
78
+ try {
79
+ if (!existsSync(this.filePath)) {
80
+ return [];
81
+ }
82
+ const content = readFileSync(this.filePath, "utf-8");
83
+ const messages = JSON.parse(content);
84
+ if (filter) {
85
+ return messages.filter(filter);
86
+ }
87
+ return messages;
88
+ } catch (error) {
89
+ console.error("Failed to load messages:", error);
90
+ return [];
91
+ }
92
+ }
93
+ async delete(messageId) {
94
+ try {
95
+ const messages = await this.load();
96
+ const filtered = messages.filter((msg) => msg.id !== messageId);
97
+ writeFileSync(this.filePath, JSON.stringify(filtered, null, 2), "utf-8");
98
+ } catch (error) {
99
+ console.error("Failed to delete message:", error);
100
+ throw error;
101
+ }
102
+ }
103
+ async clear() {
104
+ try {
105
+ writeFileSync(this.filePath, JSON.stringify([], null, 2), "utf-8");
106
+ } catch (error) {
107
+ console.error("Failed to clear messages:", error);
108
+ throw error;
109
+ }
110
+ }
111
+ async getStats() {
112
+ try {
113
+ if (!existsSync(this.filePath)) {
114
+ return { count: 0, size: 0 };
115
+ }
116
+ const messages = await this.load();
117
+ const content = readFileSync(this.filePath, "utf-8");
118
+ return {
119
+ count: messages.length,
120
+ size: Buffer.byteLength(content, "utf-8")
121
+ };
122
+ } catch (error) {
123
+ console.error("Failed to get storage stats:", error);
124
+ return { count: 0, size: 0 };
125
+ }
126
+ }
127
+ }
128
+ class MessageBus {
129
+ config;
130
+ subscriptions;
131
+ messageHistory;
132
+ deadLetterQueue;
133
+ storage;
134
+ stats;
135
+ constructor(config = {}) {
136
+ this.config = { ...DEFAULT_CONFIG, ...config };
137
+ this.subscriptions = /* @__PURE__ */ new Map();
138
+ this.messageHistory = [];
139
+ this.deadLetterQueue = [];
140
+ if (this.config.enablePersistence && this.config.persistencePath) {
141
+ this.storage = new FileMessageStorage(this.config.persistencePath);
142
+ this.loadPersistedMessages();
143
+ }
144
+ this.stats = {
145
+ totalMessages: 0,
146
+ messagesByType: {},
147
+ messagesByStatus: {},
148
+ activeSubscriptions: 0,
149
+ historySize: 0,
150
+ deadLetterQueueSize: 0,
151
+ avgProcessingTime: 0
152
+ };
153
+ this.startCleanupInterval();
154
+ }
155
+ /**
156
+ * Publish a message to the bus
157
+ */
158
+ async publish(type, from, to, subject, payload, options = {}) {
159
+ const message = {
160
+ id: randomUUID(),
161
+ type,
162
+ from,
163
+ to,
164
+ subject,
165
+ payload,
166
+ priority: options.priority || "normal",
167
+ status: "pending",
168
+ timestamp: Date.now(),
169
+ correlationId: options.correlationId,
170
+ replyTo: options.replyTo,
171
+ metadata: options.metadata
172
+ };
173
+ if (this.config.enableValidation) {
174
+ this.validateMessage(message);
175
+ }
176
+ this.logMessage("publish", message);
177
+ this.updateStats(message);
178
+ this.addToHistory(message);
179
+ if (this.storage) {
180
+ await this.storage.save(message);
181
+ }
182
+ await this.routeMessage(message);
183
+ return message.id;
184
+ }
185
+ /**
186
+ * Subscribe to messages
187
+ */
188
+ subscribe(subscriber, handler, options = {}) {
189
+ const subscription = {
190
+ id: randomUUID(),
191
+ subscriber,
192
+ options,
193
+ handler,
194
+ createdAt: Date.now(),
195
+ unsubscribe: () => this.unsubscribe(subscription.id)
196
+ };
197
+ this.subscriptions.set(subscription.id, subscription);
198
+ this.stats.activeSubscriptions = this.subscriptions.size;
199
+ this.log("info", `Agent ${subscriber} subscribed with ID ${subscription.id}`);
200
+ return subscription;
201
+ }
202
+ /**
203
+ * Unsubscribe from messages
204
+ */
205
+ unsubscribe(subscriptionId) {
206
+ const subscription = this.subscriptions.get(subscriptionId);
207
+ if (subscription) {
208
+ this.subscriptions.delete(subscriptionId);
209
+ this.stats.activeSubscriptions = this.subscriptions.size;
210
+ this.log("info", `Subscription ${subscriptionId} removed`);
211
+ }
212
+ }
213
+ /**
214
+ * Get message by ID
215
+ */
216
+ getMessage(messageId) {
217
+ return this.messageHistory.find((msg) => msg.id === messageId);
218
+ }
219
+ /**
220
+ * Get messages by filter
221
+ */
222
+ getMessages(filter) {
223
+ return this.messageHistory.filter(filter);
224
+ }
225
+ /**
226
+ * Update message status
227
+ */
228
+ async updateMessageStatus(messageId, status, error) {
229
+ const message = this.getMessage(messageId);
230
+ if (message) {
231
+ message.status = status;
232
+ if (error) {
233
+ message.error = error;
234
+ }
235
+ if (status === "failed" && this.config.enableDeadLetterQueue) {
236
+ this.deadLetterQueue.push(message);
237
+ this.stats.deadLetterQueueSize = this.deadLetterQueue.length;
238
+ }
239
+ this.stats.messagesByStatus[status] = (this.stats.messagesByStatus[status] || 0) + 1;
240
+ if (this.storage) {
241
+ await this.storage.save(message);
242
+ }
243
+ this.log("debug", `Message ${messageId} status updated to ${status}`);
244
+ }
245
+ }
246
+ /**
247
+ * Get message bus statistics
248
+ */
249
+ getStats() {
250
+ return { ...this.stats };
251
+ }
252
+ /**
253
+ * Clear message history
254
+ */
255
+ async clearHistory() {
256
+ this.messageHistory = [];
257
+ this.stats.historySize = 0;
258
+ if (this.storage) {
259
+ await this.storage.clear();
260
+ }
261
+ this.log("info", "Message history cleared");
262
+ }
263
+ /**
264
+ * Clear dead letter queue
265
+ */
266
+ clearDeadLetterQueue() {
267
+ this.deadLetterQueue = [];
268
+ this.stats.deadLetterQueueSize = 0;
269
+ this.log("info", "Dead letter queue cleared");
270
+ }
271
+ /**
272
+ * Get dead letter queue messages
273
+ */
274
+ getDeadLetterQueue() {
275
+ return [...this.deadLetterQueue];
276
+ }
277
+ /**
278
+ * Shutdown message bus
279
+ */
280
+ async shutdown() {
281
+ this.log("info", "Shutting down message bus");
282
+ this.subscriptions.clear();
283
+ this.stats.activeSubscriptions = 0;
284
+ if (this.storage && this.messageHistory.length > 0) {
285
+ for (const message of this.messageHistory) {
286
+ await this.storage.save(message);
287
+ }
288
+ }
289
+ this.log("info", "Message bus shutdown complete");
290
+ }
291
+ /**
292
+ * Route message to subscribers
293
+ */
294
+ async routeMessage(message) {
295
+ const startTime = Date.now();
296
+ const matchingSubscriptions = this.findMatchingSubscriptions(message);
297
+ this.log("debug", `Routing message ${message.id} to ${matchingSubscriptions.length} subscribers`);
298
+ for (const subscription of matchingSubscriptions) {
299
+ try {
300
+ message.status = "processing";
301
+ if (subscription.options.async) {
302
+ await subscription.handler(message);
303
+ } else {
304
+ subscription.handler(message);
305
+ }
306
+ message.status = "completed";
307
+ } catch (error) {
308
+ this.log("error", `Error handling message ${message.id} in subscription ${subscription.id}:`, error);
309
+ await this.updateMessageStatus(message.id, "failed", {
310
+ code: "HANDLER_ERROR",
311
+ message: error instanceof Error ? error.message : String(error),
312
+ stack: error instanceof Error ? error.stack : void 0
313
+ });
314
+ }
315
+ }
316
+ const processingTime = Date.now() - startTime;
317
+ this.stats.avgProcessingTime = (this.stats.avgProcessingTime + processingTime) / 2;
318
+ }
319
+ /**
320
+ * Find subscriptions matching the message
321
+ */
322
+ findMatchingSubscriptions(message) {
323
+ const matching = [];
324
+ const subscriptions = Array.from(this.subscriptions.values());
325
+ for (const subscription of subscriptions) {
326
+ const isAddressed = message.to === "all" || message.to === subscription.subscriber || Array.isArray(message.to) && message.to.includes(subscription.subscriber);
327
+ if (!isAddressed) {
328
+ continue;
329
+ }
330
+ if (!this.matchesSubscriptionFilters(message, subscription.options)) {
331
+ continue;
332
+ }
333
+ matching.push(subscription);
334
+ }
335
+ return matching;
336
+ }
337
+ /**
338
+ * Check if message matches subscription filters
339
+ */
340
+ matchesSubscriptionFilters(message, options) {
341
+ if (options.type) {
342
+ const types = Array.isArray(options.type) ? options.type : [options.type];
343
+ if (!types.includes(message.type)) {
344
+ return false;
345
+ }
346
+ }
347
+ if (options.from) {
348
+ const senders = Array.isArray(options.from) ? options.from : [options.from];
349
+ if (!senders.includes(message.from)) {
350
+ return false;
351
+ }
352
+ }
353
+ if (options.priority) {
354
+ const priorities = Array.isArray(options.priority) ? options.priority : [options.priority];
355
+ if (!priorities.includes(message.priority)) {
356
+ return false;
357
+ }
358
+ }
359
+ if (options.filter && !options.filter(message)) {
360
+ return false;
361
+ }
362
+ return true;
363
+ }
364
+ /**
365
+ * Validate message
366
+ */
367
+ validateMessage(message) {
368
+ if (!message.id) {
369
+ throw new Error("Message ID is required");
370
+ }
371
+ if (!message.type) {
372
+ throw new Error("Message type is required");
373
+ }
374
+ if (!message.from) {
375
+ throw new Error("Message sender is required");
376
+ }
377
+ if (!message.to) {
378
+ throw new Error("Message recipient is required");
379
+ }
380
+ if (!message.subject) {
381
+ throw new Error("Message subject is required");
382
+ }
383
+ if (this.config.maxMessageSize) {
384
+ const messageSize = Buffer.byteLength(JSON.stringify(message), "utf-8");
385
+ if (messageSize > this.config.maxMessageSize) {
386
+ throw new Error(`Message size ${messageSize} exceeds maximum ${this.config.maxMessageSize}`);
387
+ }
388
+ }
389
+ }
390
+ /**
391
+ * Add message to history
392
+ */
393
+ addToHistory(message) {
394
+ this.messageHistory.push(message);
395
+ this.stats.historySize = this.messageHistory.length;
396
+ const maxHistorySize = this.config.maxHistorySize ?? 1e3;
397
+ if (this.messageHistory.length > maxHistorySize) {
398
+ this.messageHistory.shift();
399
+ this.stats.historySize = this.messageHistory.length;
400
+ }
401
+ }
402
+ /**
403
+ * Update statistics
404
+ */
405
+ updateStats(message) {
406
+ this.stats.totalMessages++;
407
+ this.stats.messagesByType[message.type] = (this.stats.messagesByType[message.type] || 0) + 1;
408
+ this.stats.messagesByStatus[message.status] = (this.stats.messagesByStatus[message.status] || 0) + 1;
409
+ }
410
+ /**
411
+ * Load persisted messages
412
+ */
413
+ async loadPersistedMessages() {
414
+ if (!this.storage) {
415
+ return;
416
+ }
417
+ try {
418
+ const messages = await this.storage.load();
419
+ this.messageHistory = messages;
420
+ this.stats.historySize = messages.length;
421
+ this.log("info", `Loaded ${messages.length} persisted messages`);
422
+ } catch (error) {
423
+ this.log("error", "Failed to load persisted messages:", error);
424
+ }
425
+ }
426
+ /**
427
+ * Start cleanup interval for old messages
428
+ */
429
+ startCleanupInterval() {
430
+ setInterval(() => {
431
+ const now = Date.now();
432
+ const retentionTime = this.config.messageRetentionTime ?? 864e5;
433
+ this.messageHistory = this.messageHistory.filter(
434
+ (msg) => now - msg.timestamp < retentionTime
435
+ );
436
+ this.stats.historySize = this.messageHistory.length;
437
+ this.deadLetterQueue = this.deadLetterQueue.filter(
438
+ (msg) => now - msg.timestamp < retentionTime
439
+ );
440
+ this.stats.deadLetterQueueSize = this.deadLetterQueue.length;
441
+ this.log("debug", "Cleanup completed");
442
+ }, 60 * 60 * 1e3);
443
+ }
444
+ /**
445
+ * Log message
446
+ */
447
+ logMessage(action, message) {
448
+ if (!this.config.enableLogging) {
449
+ return;
450
+ }
451
+ const logLevel = this.config.logLevel;
452
+ const shouldLog = logLevel === "debug" || logLevel === "info" && ["publish", "subscribe"].includes(action) || logLevel === "warn" && message.priority === "high" || logLevel === "error" && message.status === "failed";
453
+ if (shouldLog) {
454
+ this.log("debug", `[${action}] ${message.type} from ${message.from} to ${message.to}: ${message.subject}`);
455
+ }
456
+ }
457
+ /**
458
+ * Internal logging
459
+ */
460
+ log(level, message, ...args) {
461
+ if (!this.config.enableLogging) {
462
+ return;
463
+ }
464
+ const levels = ["debug", "info", "warn", "error"];
465
+ const configLevel = levels.indexOf(this.config.logLevel ?? "info");
466
+ const messageLevel = levels.indexOf(level);
467
+ if (messageLevel >= configLevel) {
468
+ console[level](`[MessageBus] ${message}`, ...args);
469
+ }
470
+ }
471
+ }
472
+ let globalMessageBus = null;
473
+ function getMessageBus(config) {
474
+ if (!globalMessageBus) {
475
+ globalMessageBus = new MessageBus(config);
476
+ }
477
+ return globalMessageBus;
478
+ }
479
+
480
+ class SkillRegistry extends EventEmitter {
481
+ skills;
482
+ filePathIndex;
483
+ triggerIndex;
484
+ messageBus = getMessageBus();
485
+ constructor() {
486
+ super();
487
+ this.skills = /* @__PURE__ */ new Map();
488
+ this.filePathIndex = /* @__PURE__ */ new Map();
489
+ this.triggerIndex = /* @__PURE__ */ new Map();
490
+ }
491
+ /**
492
+ * Register a skill
493
+ *
494
+ * @param skill - Parsed skill file
495
+ * @param source - Skill source type
496
+ * @returns Registered entry
497
+ */
498
+ register(skill, source = "user") {
499
+ const id = skill.metadata.name;
500
+ const existing = this.skills.get(id);
501
+ const dependencies = this.extractDependencies(skill.metadata);
502
+ const entry = {
503
+ id,
504
+ metadata: skill.metadata,
505
+ content: skill.content,
506
+ filePath: skill.filePath,
507
+ enabled: existing?.enabled ?? true,
508
+ source,
509
+ registeredAt: existing?.registeredAt ?? Date.now(),
510
+ modifiedAt: skill.modifiedAt?.getTime() ?? Date.now(),
511
+ estimatedTokens: this.estimateTokens(skill),
512
+ dependencies,
513
+ dependents: existing?.dependents || /* @__PURE__ */ new Set()
514
+ };
515
+ this.skills.set(id, entry);
516
+ this.filePathIndex.set(skill.filePath, id);
517
+ this.updateTriggerIndex(id, entry.metadata.triggers);
518
+ this.updateDependencyGraph(id, dependencies);
519
+ if (existing) {
520
+ this.emit("skill:updated", existing, entry);
521
+ this.publishMessage("skill:updated", { oldSkill: existing, newSkill: entry });
522
+ } else {
523
+ this.emit("skill:registered", entry);
524
+ this.publishMessage("skill:registered", entry);
525
+ }
526
+ return entry;
527
+ }
528
+ /**
529
+ * Unregister a skill by ID
530
+ *
531
+ * @param id - Skill ID
532
+ * @returns True if skill was unregistered
533
+ */
534
+ unregister(id) {
535
+ const entry = this.skills.get(id);
536
+ if (!entry)
537
+ return false;
538
+ if (entry.dependents.size > 0) {
539
+ const dependentList = Array.from(entry.dependents).join(", ");
540
+ throw new Error(`Cannot unregister skill "${id}": depended upon by: ${dependentList}`);
541
+ }
542
+ this.skills.delete(id);
543
+ this.filePathIndex.delete(entry.filePath);
544
+ for (const trigger of entry.metadata.triggers) {
545
+ const skills = this.triggerIndex.get(trigger);
546
+ if (skills) {
547
+ skills.delete(id);
548
+ if (skills.size === 0)
549
+ this.triggerIndex.delete(trigger);
550
+ }
551
+ }
552
+ for (const dep of entry.dependencies) {
553
+ const depEntry = this.skills.get(dep);
554
+ if (depEntry)
555
+ depEntry.dependents.delete(id);
556
+ }
557
+ this.emit("skill:unregistered", entry);
558
+ this.publishMessage("skill:unregistered", entry);
559
+ return true;
560
+ }
561
+ /**
562
+ * Unregister a skill by file path
563
+ *
564
+ * @param filePath - File path
565
+ * @returns True if skill was unregistered
566
+ */
567
+ unregisterByPath(filePath) {
568
+ const id = this.filePathIndex.get(filePath);
569
+ return id ? this.unregister(id) : false;
570
+ }
571
+ /**
572
+ * Get a skill by ID
573
+ *
574
+ * @param id - Skill ID
575
+ * @returns Skill entry or undefined
576
+ */
577
+ getById(id) {
578
+ return this.skills.get(id);
579
+ }
580
+ /**
581
+ * Get a skill by file path
582
+ *
583
+ * @param filePath - File path
584
+ * @returns Skill entry or undefined
585
+ */
586
+ getByPath(filePath) {
587
+ const id = this.filePathIndex.get(filePath);
588
+ return id ? this.skills.get(id) : void 0;
589
+ }
590
+ /**
591
+ * Get skills by trigger
592
+ *
593
+ * @param trigger - Trigger string (e.g., '/commit')
594
+ * @returns Array of matching skills
595
+ */
596
+ getByTrigger(trigger) {
597
+ const ids = this.triggerIndex.get(trigger);
598
+ if (!ids)
599
+ return [];
600
+ return Array.from(ids).map((id) => this.skills.get(id)).filter((e) => e !== void 0 && e.enabled);
601
+ }
602
+ /**
603
+ * Lookup skills with filters
604
+ *
605
+ * @param options - Lookup options
606
+ * @returns Array of matching skills
607
+ */
608
+ lookup(options = {}) {
609
+ let results = Array.from(this.skills.values());
610
+ if (options.enabled !== void 0) {
611
+ results = results.filter((e) => e.enabled === options.enabled);
612
+ }
613
+ if (options.category) {
614
+ results = results.filter((e) => e.metadata.category === options.category);
615
+ }
616
+ if (options.source) {
617
+ results = results.filter((e) => e.source === options.source);
618
+ }
619
+ if (options.userInvocable !== void 0) {
620
+ results = results.filter(
621
+ (e) => (e.metadata.user_invocable ?? true) === options.userInvocable
622
+ );
623
+ }
624
+ if (options.autoActivate !== void 0) {
625
+ results = results.filter(
626
+ (e) => (e.metadata.auto_activate ?? false) === options.autoActivate
627
+ );
628
+ }
629
+ if (options.agent) {
630
+ results = results.filter(
631
+ (e) => e.metadata.agent === options.agent || e.metadata.agents?.includes(options.agent)
632
+ );
633
+ }
634
+ if (options.search) {
635
+ const query = options.search.toLowerCase();
636
+ results = results.filter(
637
+ (e) => e.id.toLowerCase().includes(query) || e.metadata.description.toLowerCase().includes(query) || e.metadata.tags?.some((t) => t.toLowerCase().includes(query))
638
+ );
639
+ }
640
+ if (options.sortBy) {
641
+ const dir = options.sortDir === "desc" ? -1 : 1;
642
+ results.sort((a, b) => {
643
+ switch (options.sortBy) {
644
+ case "name":
645
+ return a.id.localeCompare(b.id) * dir;
646
+ case "priority":
647
+ return ((a.metadata.priority ?? 5) - (b.metadata.priority ?? 5)) * dir;
648
+ case "registeredAt":
649
+ return (a.registeredAt - b.registeredAt) * dir;
650
+ case "modifiedAt":
651
+ return (a.modifiedAt - b.modifiedAt) * dir;
652
+ default:
653
+ return 0;
654
+ }
655
+ });
656
+ }
657
+ if (options.limit) {
658
+ results = results.slice(0, options.limit);
659
+ }
660
+ return results;
661
+ }
662
+ /**
663
+ * Enable a skill
664
+ *
665
+ * @param id - Skill ID
666
+ * @returns True if enabled
667
+ */
668
+ enable(id) {
669
+ const entry = this.skills.get(id);
670
+ if (!entry || entry.enabled)
671
+ return false;
672
+ entry.enabled = true;
673
+ this.emit("skill:enabled", entry);
674
+ this.publishMessage("skill:enabled", entry);
675
+ return true;
676
+ }
677
+ /**
678
+ * Disable a skill
679
+ *
680
+ * @param id - Skill ID
681
+ * @returns True if disabled
682
+ */
683
+ disable(id) {
684
+ const entry = this.skills.get(id);
685
+ if (!entry || !entry.enabled)
686
+ return false;
687
+ entry.enabled = false;
688
+ this.emit("skill:disabled", entry);
689
+ this.publishMessage("skill:disabled", entry);
690
+ return true;
691
+ }
692
+ /**
693
+ * Toggle skill enabled state
694
+ *
695
+ * @param id - Skill ID
696
+ * @returns New enabled state
697
+ */
698
+ toggle(id) {
699
+ const entry = this.skills.get(id);
700
+ if (!entry)
701
+ return false;
702
+ entry.enabled = !entry.enabled;
703
+ const event = entry.enabled ? "skill:enabled" : "skill:disabled";
704
+ this.emit(event, entry);
705
+ this.publishMessage(event, entry);
706
+ return entry.enabled;
707
+ }
708
+ /**
709
+ * Check if a skill exists
710
+ *
711
+ * @param id - Skill ID
712
+ * @returns True if skill exists
713
+ */
714
+ has(id) {
715
+ return this.skills.has(id);
716
+ }
717
+ /**
718
+ * Check if a skill is enabled
719
+ *
720
+ * @param id - Skill ID
721
+ * @returns True if enabled, false if disabled or not found
722
+ */
723
+ isEnabled(id) {
724
+ return this.skills.get(id)?.enabled ?? false;
725
+ }
726
+ /**
727
+ * Get all skill IDs
728
+ *
729
+ * @returns Array of skill IDs
730
+ */
731
+ getIds() {
732
+ return Array.from(this.skills.keys());
733
+ }
734
+ /**
735
+ * Get all entries
736
+ *
737
+ * @returns Array of all entries
738
+ */
739
+ getAll() {
740
+ return Array.from(this.skills.values());
741
+ }
742
+ /**
743
+ * Get enabled entries
744
+ *
745
+ * @returns Array of enabled entries
746
+ */
747
+ getEnabled() {
748
+ return this.lookup({ enabled: true });
749
+ }
750
+ /**
751
+ * Get registry statistics
752
+ *
753
+ * @returns Registry statistics
754
+ */
755
+ getStats() {
756
+ const all = this.getAll();
757
+ const enabled = this.getEnabled();
758
+ const byCategory = {
759
+ dev: 0,
760
+ git: 0,
761
+ review: 0,
762
+ testing: 0,
763
+ docs: 0,
764
+ devops: 0,
765
+ planning: 0,
766
+ debugging: 0,
767
+ custom: 0
768
+ };
769
+ const bySource = { builtin: 0, user: 0, marketplace: 0 };
770
+ let totalTokens = 0;
771
+ let lastRegistered = 0;
772
+ let lastModified = 0;
773
+ for (const entry of all) {
774
+ byCategory[entry.metadata.category]++;
775
+ bySource[entry.source]++;
776
+ totalTokens += entry.estimatedTokens;
777
+ if (entry.registeredAt > lastRegistered)
778
+ lastRegistered = entry.registeredAt;
779
+ if (entry.modifiedAt > lastModified)
780
+ lastModified = entry.modifiedAt;
781
+ }
782
+ return {
783
+ totalSkills: all.length,
784
+ enabledSkills: enabled.length,
785
+ disabledSkills: all.length - enabled.length,
786
+ byCategory,
787
+ bySource,
788
+ totalTokens,
789
+ lastRegistered: lastRegistered > 0 ? this.getById(this.getEntriesSortedBy("registeredAt")[0]?.id || "")?.id : void 0,
790
+ lastModified: lastModified > 0 ? this.getById(this.getEntriesSortedBy("modifiedAt")[0]?.id || "")?.id : void 0
791
+ };
792
+ }
793
+ /**
794
+ * Get dependent skills
795
+ *
796
+ * @param id - Skill ID
797
+ * @returns Array of dependent skill IDs
798
+ */
799
+ getDependents(id) {
800
+ return Array.from(this.skills.get(id)?.dependents || []);
801
+ }
802
+ /**
803
+ * Get skill dependencies
804
+ *
805
+ * @param id - Skill ID
806
+ * @returns Array of dependency IDs
807
+ */
808
+ getDependencies(id) {
809
+ return this.skills.get(id)?.dependencies || [];
810
+ }
811
+ /**
812
+ * Check for missing dependencies
813
+ *
814
+ * @param id - Skill ID
815
+ * @returns Array of missing dependency IDs
816
+ */
817
+ getMissingDependencies(id) {
818
+ const deps = this.getDependencies(id);
819
+ return deps.filter((dep) => !this.has(dep));
820
+ }
821
+ /**
822
+ * Validate all dependencies
823
+ *
824
+ * @returns Map of skill ID to missing dependencies
825
+ */
826
+ validateDependencies() {
827
+ const missing = /* @__PURE__ */ new Map();
828
+ Array.from(this.skills.entries()).forEach(([id, entry]) => {
829
+ const missingDeps = entry.dependencies.filter((dep) => !this.has(dep));
830
+ if (missingDeps.length > 0) {
831
+ missing.set(id, missingDeps);
832
+ }
833
+ });
834
+ return missing;
835
+ }
836
+ /**
837
+ * Clear all skills from registry
838
+ */
839
+ clear() {
840
+ this.skills.clear();
841
+ this.filePathIndex.clear();
842
+ this.triggerIndex.clear();
843
+ this.emit("registry:cleared");
844
+ this.publishMessage("registry:cleared", {});
845
+ }
846
+ /**
847
+ * Get the size of the registry
848
+ *
849
+ * @returns Number of registered skills
850
+ */
851
+ size() {
852
+ return this.skills.size;
853
+ }
854
+ // ==========================================================================
855
+ // Private Methods
856
+ // ==========================================================================
857
+ /**
858
+ * Extract dependencies from skill metadata
859
+ */
860
+ extractDependencies(metadata) {
861
+ const deps = [];
862
+ if (metadata.agents) {
863
+ deps.push(...metadata.agents);
864
+ }
865
+ if (metadata.related_skills) {
866
+ deps.push(...metadata.related_skills);
867
+ }
868
+ return deps;
869
+ }
870
+ /**
871
+ * Update trigger index
872
+ */
873
+ updateTriggerIndex(id, triggers) {
874
+ Array.from(this.triggerIndex.entries()).forEach(([trigger, skillIds]) => {
875
+ skillIds.delete(id);
876
+ if (skillIds.size === 0)
877
+ this.triggerIndex.delete(trigger);
878
+ });
879
+ for (const trigger of triggers) {
880
+ if (!this.triggerIndex.has(trigger)) {
881
+ this.triggerIndex.set(trigger, /* @__PURE__ */ new Set());
882
+ }
883
+ this.triggerIndex.get(trigger).add(id);
884
+ }
885
+ }
886
+ /**
887
+ * Update dependency graph
888
+ */
889
+ updateDependencyGraph(id, dependencies) {
890
+ Array.from(this.skills.entries()).forEach(([_depId, entry]) => {
891
+ if (entry.dependencies.includes(id)) {
892
+ entry.dependents.delete(id);
893
+ }
894
+ });
895
+ for (const dep of dependencies) {
896
+ const depEntry = this.skills.get(dep);
897
+ if (depEntry) {
898
+ depEntry.dependents.add(id);
899
+ }
900
+ }
901
+ }
902
+ /**
903
+ * Estimate token count for a skill
904
+ */
905
+ estimateTokens(skill) {
906
+ const contentTokens = Math.ceil(skill.content.length / 4);
907
+ const metadataTokens = Math.ceil(JSON.stringify(skill.metadata).length / 4);
908
+ return contentTokens + metadataTokens;
909
+ }
910
+ /**
911
+ * Get entries sorted by a field
912
+ */
913
+ getEntriesSortedBy(field) {
914
+ return Array.from(this.skills.values()).sort((a, b) => b[field] - a[field]);
915
+ }
916
+ /**
917
+ * Publish message to event bus
918
+ */
919
+ publishMessage(type, payload) {
920
+ this.messageBus.publish(
921
+ type,
922
+ "coordinator",
923
+ "all",
924
+ `Skill registry event: ${type}`,
925
+ payload,
926
+ { priority: "normal" }
927
+ ).catch(console.error);
928
+ }
929
+ }
930
+ let registryInstance = null;
931
+ function getSkillRegistry() {
932
+ if (!registryInstance) {
933
+ registryInstance = new SkillRegistry();
934
+ }
935
+ return registryInstance;
936
+ }
16
937
 
17
938
  class ProgressTracker extends EventEmitter {
18
939
  config;
@@ -1879,6 +2800,11 @@ class AutoExecutor extends EventEmitter {
1879
2800
  const executionId = `exec-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1880
2801
  const startedAt = Date.now();
1881
2802
  this.emit("execution:started", { input: userInput });
2803
+ this.emitCommandHook("command-start", {
2804
+ executionId,
2805
+ inputLength: userInput.length,
2806
+ source: "auto-executor"
2807
+ });
1882
2808
  this.config.telemetry.record({
1883
2809
  executionId,
1884
2810
  phase: "execution",
@@ -1904,6 +2830,13 @@ class AutoExecutor extends EventEmitter {
1904
2830
  complexity: routeResult.intent.complexity
1905
2831
  }
1906
2832
  });
2833
+ this.emitCommandHook("command-telemetry", {
2834
+ executionId,
2835
+ phase: "intent",
2836
+ action: "route",
2837
+ route: routeResult.route,
2838
+ confidence: routeResult.intent.confidence
2839
+ });
1907
2840
  const elicitationResult = await this.resolveRouteWithElicitation(
1908
2841
  routeResult.route,
1909
2842
  routeResult.intent,
@@ -2039,6 +2972,12 @@ class AutoExecutor extends EventEmitter {
2039
2972
  complexity: intent.complexity
2040
2973
  }
2041
2974
  });
2975
+ this.emitCommandHook("command-telemetry", {
2976
+ executionId,
2977
+ phase: "route",
2978
+ action: route,
2979
+ durationMs: routeDuration
2980
+ });
2042
2981
  const totalDuration = Date.now() - startedAt;
2043
2982
  this.metricsCollector.recordResponseTime("auto-executor", totalDuration);
2044
2983
  this.metricsCollector.recordTaskCompletion("auto-executor", true, totalDuration);
@@ -2055,6 +2994,13 @@ class AutoExecutor extends EventEmitter {
2055
2994
  mcpToolsUsed: mcpToolsUsed.length
2056
2995
  }
2057
2996
  });
2997
+ this.emitCommandHook("command-complete", {
2998
+ executionId,
2999
+ success: true,
3000
+ route,
3001
+ durationMs: totalDuration,
3002
+ mcpToolsUsed: mcpToolsUsed.length
3003
+ });
2058
3004
  result.insights = this.buildExecutionInsights({
2059
3005
  initialRoute: routeResult.route,
2060
3006
  resolvedRoute: route,
@@ -2082,6 +3028,12 @@ class AutoExecutor extends EventEmitter {
2082
3028
  error: errorMessage
2083
3029
  }
2084
3030
  });
3031
+ this.emitCommandHook("command-complete", {
3032
+ executionId,
3033
+ success: false,
3034
+ durationMs: totalDuration,
3035
+ error: errorMessage
3036
+ });
2085
3037
  this.emit("execution:failed", { error, input: userInput });
2086
3038
  throw error;
2087
3039
  }
@@ -2552,6 +3504,9 @@ class AutoExecutor extends EventEmitter {
2552
3504
  getTelemetryEvents(limit = 50) {
2553
3505
  return this.config.telemetry.getRecent(limit);
2554
3506
  }
3507
+ clearTelemetry() {
3508
+ this.config.telemetry.clear();
3509
+ }
2555
3510
  getErrorMessage(error) {
2556
3511
  if (error instanceof Error) {
2557
3512
  return error.message;
@@ -2566,6 +3521,9 @@ class AutoExecutor extends EventEmitter {
2566
3521
  console.log(`[AutoExecutor] ${message}`);
2567
3522
  }
2568
3523
  }
3524
+ emitCommandHook(event, data) {
3525
+ void emitCommandHookEvent(event, data);
3526
+ }
2569
3527
  }
2570
3528
  let globalExecutor = null;
2571
3529
  function getGlobalAutoExecutor(config) {
@@ -2674,6 +3632,7 @@ class CliInterceptor extends EventEmitter {
2674
3632
  autoExecute: config.autoExecute !== void 0 ? config.autoExecute : true,
2675
3633
  showIntent: config.showIntent !== void 0 ? config.showIntent : true,
2676
3634
  bypassKeywords: config.bypassKeywords || [],
3635
+ ccjkOwnedSlashPrefixes: config.ccjkOwnedSlashPrefixes || ["/ccjk", "/ccjk:", "/plugin", "/plugins", "/skill"],
2677
3636
  verbose: config.verbose !== void 0 ? config.verbose : false
2678
3637
  };
2679
3638
  }
@@ -2690,6 +3649,7 @@ class CliInterceptor extends EventEmitter {
2690
3649
  this.emit("intercept:started", { input: userInput });
2691
3650
  const bypassCheck = this.shouldBypass(userInput);
2692
3651
  if (bypassCheck.bypass) {
3652
+ await this.handleBypassedCommand(userInput, bypassCheck.reason);
2693
3653
  this.emit("intercept:bypassed", { input: userInput, reason: bypassCheck.reason });
2694
3654
  return {
2695
3655
  intercepted: false,
@@ -2720,6 +3680,12 @@ class CliInterceptor extends EventEmitter {
2720
3680
  */
2721
3681
  shouldBypass(input) {
2722
3682
  const normalized = input.trim().toLowerCase();
3683
+ if (normalized.startsWith("/")) {
3684
+ const isCcjkOwned = this.config.ccjkOwnedSlashPrefixes.some((prefix) => normalized.startsWith(prefix));
3685
+ if (!isCcjkOwned) {
3686
+ return { bypass: true, reason: "Native slash command passthrough" };
3687
+ }
3688
+ }
2723
3689
  if (this.systemCommands.some((cmd) => normalized.startsWith(cmd))) {
2724
3690
  return { bypass: true, reason: "System command" };
2725
3691
  }
@@ -2742,6 +3708,31 @@ class CliInterceptor extends EventEmitter {
2742
3708
  console.log(" System will automatically handle: skills, agents, MCP tools\n");
2743
3709
  console.log(" Smart mode: ambiguity checks + capability-ranked tool selection + telemetry\n");
2744
3710
  }
3711
+ async handleBypassedCommand(input, reason) {
3712
+ const normalized = input.trim().toLowerCase();
3713
+ const command = this.extractCommandName(normalized);
3714
+ if (normalized.startsWith("/clear")) {
3715
+ this.autoExecutor.clearTelemetry();
3716
+ contextLoader.clearCache();
3717
+ getSkillRegistry().clear();
3718
+ await emitCommandHookEvent("command-clear", {
3719
+ command,
3720
+ cleared: ["telemetry", "context-cache", "skill-registry"]
3721
+ });
3722
+ }
3723
+ if (normalized.startsWith("/")) {
3724
+ await emitCommandHookEvent("command-bypass", {
3725
+ command,
3726
+ reason
3727
+ });
3728
+ }
3729
+ }
3730
+ extractCommandName(input) {
3731
+ if (!input.startsWith("/")) {
3732
+ return "";
3733
+ }
3734
+ return input.split(/\s+/)[0];
3735
+ }
2745
3736
  /**
2746
3737
  * Enable interceptor
2747
3738
  */
@@ -1,3 +1,3 @@
1
- const version = "12.2.1";
1
+ const version = "12.2.2";
2
2
 
3
3
  export { version };
@@ -0,0 +1,78 @@
1
+ import { l as logger } from './ccjk.DG_o24cZ.mjs';
2
+
3
+ class HookRegistry {
4
+ hooks = /* @__PURE__ */ new Map();
5
+ /**
6
+ * Register a hook for an event
7
+ */
8
+ register(event, hook) {
9
+ const hooks = this.hooks.get(event) || [];
10
+ hooks.push(hook);
11
+ hooks.sort((a, b) => (b.priority || 0) - (a.priority || 0));
12
+ this.hooks.set(event, hooks);
13
+ logger.debug(`Registered hook "${hook.name}" for event "${event}"`);
14
+ }
15
+ /**
16
+ * Unregister a hook
17
+ */
18
+ unregister(event, hookName) {
19
+ const hooks = this.hooks.get(event);
20
+ if (!hooks) return;
21
+ const filtered = hooks.filter((h) => h.name !== hookName);
22
+ this.hooks.set(event, filtered);
23
+ logger.debug(`Unregistered hook "${hookName}" for event "${event}"`);
24
+ }
25
+ /**
26
+ * Execute all hooks for an event
27
+ * Returns false if any hook returns continue: false
28
+ */
29
+ async execute(context) {
30
+ const hooks = this.hooks.get(context.event) || [];
31
+ const enabledHooks = hooks.filter((h) => h.enabled !== false);
32
+ if (enabledHooks.length === 0) {
33
+ return { continue: true };
34
+ }
35
+ let currentContext = context;
36
+ let aggregatedData = {};
37
+ for (const hook of enabledHooks) {
38
+ try {
39
+ const result = await hook.fn(currentContext);
40
+ if (result.data) {
41
+ aggregatedData = { ...aggregatedData, ...result.data };
42
+ }
43
+ if (!result.continue) {
44
+ return {
45
+ continue: false,
46
+ data: aggregatedData,
47
+ error: result.error
48
+ };
49
+ }
50
+ currentContext = {
51
+ ...currentContext,
52
+ data: { ...currentContext.data, ...aggregatedData }
53
+ };
54
+ } catch (error) {
55
+ logger.error(`Hook "${hook.name}" failed:`, error);
56
+ }
57
+ }
58
+ return {
59
+ continue: true,
60
+ data: aggregatedData
61
+ };
62
+ }
63
+ /**
64
+ * Get all registered hooks for an event
65
+ */
66
+ getHooks(event) {
67
+ return this.hooks.get(event) || [];
68
+ }
69
+ /**
70
+ * Clear all hooks
71
+ */
72
+ clear() {
73
+ this.hooks.clear();
74
+ }
75
+ }
76
+ const hookRegistry = new HookRegistry();
77
+
78
+ export { hookRegistry as h };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ccjk",
3
3
  "type": "module",
4
- "version": "12.2.1",
4
+ "version": "12.2.2",
5
5
  "packageManager": "pnpm@10.17.1",
6
6
  "description": "CLI toolkit for Claude Code and Codex setup. Simplifies MCP service installation, API configuration, workflow management, and multi-provider support with guided interactive setup.",
7
7
  "author": {