ce-workflow 0.4.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/dist/index.js ADDED
@@ -0,0 +1,4197 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/services/container.ts
12
+ var ServiceContainer;
13
+ var init_container = __esm({
14
+ "src/services/container.ts"() {
15
+ "use strict";
16
+ ServiceContainer = class {
17
+ factories = /* @__PURE__ */ new Map();
18
+ instances = /* @__PURE__ */ new Map();
19
+ register(key, factory) {
20
+ this.factories.set(key, factory);
21
+ }
22
+ get(key) {
23
+ const existing = this.instances.get(key);
24
+ if (existing !== void 0) return existing;
25
+ const factory = this.factories.get(key);
26
+ if (!factory) throw new Error(`Service not registered: ${key}`);
27
+ const instance = factory();
28
+ this.instances.set(key, instance);
29
+ return instance;
30
+ }
31
+ has(key) {
32
+ return this.factories.has(key);
33
+ }
34
+ // Typed accessors for core services
35
+ get config() {
36
+ return this.get("config");
37
+ }
38
+ get projects() {
39
+ return this.get("projects");
40
+ }
41
+ get rag() {
42
+ return this.get("rag");
43
+ }
44
+ get indexing() {
45
+ return this.get("indexing");
46
+ }
47
+ get tasks() {
48
+ return this.get("tasks");
49
+ }
50
+ get sessions() {
51
+ return this.get("sessions");
52
+ }
53
+ get prompts() {
54
+ return this.get("prompts");
55
+ }
56
+ get symbols() {
57
+ return this.get("symbols");
58
+ }
59
+ get dependencies() {
60
+ return this.get("dependencies");
61
+ }
62
+ get context() {
63
+ return this.get("context");
64
+ }
65
+ get logger() {
66
+ return this.get("logger");
67
+ }
68
+ };
69
+ }
70
+ });
71
+
72
+ // src/services/logger.ts
73
+ import { EventEmitter } from "events";
74
+ import * as fs from "fs";
75
+ import * as path from "path";
76
+ var Logger;
77
+ var init_logger = __esm({
78
+ "src/services/logger.ts"() {
79
+ "use strict";
80
+ Logger = class extends EventEmitter {
81
+ logPath;
82
+ maxBytes = 2 * 1024 * 1024;
83
+ trimToBytes = 512 * 1024;
84
+ constructor(logPath) {
85
+ super();
86
+ this.logPath = logPath;
87
+ }
88
+ write(level, message, data) {
89
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
90
+ let logMessage = `[${timestamp}] [${level}] ${message}`;
91
+ if (data) {
92
+ if (data instanceof Error) {
93
+ logMessage += `
94
+ ${data.stack || data.message}`;
95
+ } else {
96
+ try {
97
+ logMessage += `
98
+ ${JSON.stringify(data, null, 2)}`;
99
+ } catch {
100
+ logMessage += `
101
+ [Circular or invalid data]`;
102
+ }
103
+ }
104
+ }
105
+ logMessage += "\n";
106
+ this.emit("line", logMessage.trimEnd());
107
+ try {
108
+ const dir = path.dirname(this.logPath);
109
+ if (!fs.existsSync(dir)) {
110
+ fs.mkdirSync(dir, { recursive: true });
111
+ }
112
+ this.trimIfNeeded();
113
+ fs.appendFileSync(this.logPath, logMessage);
114
+ } catch (e) {
115
+ console.error(`[Logger Failure] Could not write to ${this.logPath}`, e);
116
+ }
117
+ }
118
+ trimIfNeeded() {
119
+ try {
120
+ if (!fs.existsSync(this.logPath)) return;
121
+ const stats = fs.statSync(this.logPath);
122
+ if (stats.size <= this.maxBytes) return;
123
+ const start = Math.max(0, stats.size - this.trimToBytes);
124
+ const fd = fs.openSync(this.logPath, "r");
125
+ const buffer = Buffer.alloc(stats.size - start);
126
+ fs.readSync(fd, buffer, 0, buffer.length, start);
127
+ fs.closeSync(fd);
128
+ const header = `[${(/* @__PURE__ */ new Date()).toISOString()}] [INFO] Log truncated to last ${this.trimToBytes} bytes
129
+ `;
130
+ fs.writeFileSync(this.logPath, header + buffer.toString("utf-8"));
131
+ } catch {
132
+ }
133
+ }
134
+ info(message, data) {
135
+ this.write("INFO", message, data);
136
+ }
137
+ error(message, error) {
138
+ this.write("ERROR", message, error);
139
+ }
140
+ warn(message, data) {
141
+ this.write("WARN", message, data);
142
+ }
143
+ debug(message, data) {
144
+ this.write("DEBUG", message, data);
145
+ }
146
+ getLogPath() {
147
+ return this.logPath;
148
+ }
149
+ };
150
+ }
151
+ });
152
+
153
+ // src/types/config.ts
154
+ var DEFAULT_MCP_CONFIG, DEFAULT_PERMISSIONS;
155
+ var init_config = __esm({
156
+ "src/types/config.ts"() {
157
+ "use strict";
158
+ DEFAULT_MCP_CONFIG = {
159
+ server: {
160
+ port: 3e3,
161
+ autoStart: false
162
+ },
163
+ projects: [],
164
+ defaults: {
165
+ includeNew: false,
166
+ permissions: {
167
+ knowledge: true,
168
+ tasks: true,
169
+ refs: true
170
+ },
171
+ semanticSearch: {
172
+ enabled: true,
173
+ model: "Xenova/all-MiniLM-L6-v2"
174
+ }
175
+ }
176
+ };
177
+ DEFAULT_PERMISSIONS = {
178
+ knowledge: true,
179
+ tasks: true,
180
+ refs: true
181
+ };
182
+ }
183
+ });
184
+
185
+ // src/lib/paths.ts
186
+ var paths_exports = {};
187
+ __export(paths_exports, {
188
+ detectWorkspaceRoot: () => detectWorkspaceRoot,
189
+ ensureDir: () => ensureDir,
190
+ getAgentCoreDir: () => getAgentCoreDir,
191
+ getConfigPath: () => getConfigPath,
192
+ getDataPath: () => getDataPath,
193
+ getHome: () => getHome,
194
+ getKnowledgePath: () => getKnowledgePath,
195
+ getLogPath: () => getLogPath,
196
+ getPreferencesPath: () => getPreferencesPath,
197
+ getRefsPath: () => getRefsPath,
198
+ getTasksPath: () => getTasksPath,
199
+ getWorkspaceName: () => getWorkspaceName
200
+ });
201
+ import * as fs2 from "fs";
202
+ import * as path2 from "path";
203
+ function getHome() {
204
+ return process.env.CE_WORKFLOW_HOME || path2.join(process.env.HOME || "~", ".ce-workflow");
205
+ }
206
+ function getConfigPath() {
207
+ return path2.join(getHome(), "config.yaml");
208
+ }
209
+ function getLogPath() {
210
+ return path2.join(getHome(), "mcp-server.log");
211
+ }
212
+ function getDataPath(projectName) {
213
+ return path2.join(getHome(), "workspaces", projectName);
214
+ }
215
+ function getKnowledgePath(projectName) {
216
+ return path2.join(getDataPath(projectName), "knowledge");
217
+ }
218
+ function getTasksPath(projectName) {
219
+ return path2.join(getDataPath(projectName), "tasks");
220
+ }
221
+ function getRefsPath(projectName) {
222
+ return path2.join(getDataPath(projectName), "refs");
223
+ }
224
+ function getPreferencesPath() {
225
+ return path2.join(getHome(), "preferences.json");
226
+ }
227
+ function ensureDir(dirPath) {
228
+ if (!fs2.existsSync(dirPath)) {
229
+ fs2.mkdirSync(dirPath, { recursive: true });
230
+ }
231
+ }
232
+ function detectWorkspaceRoot() {
233
+ const envOverride = process.env.CE_WORKFLOW_WORKSPACE;
234
+ if (envOverride) return envOverride;
235
+ let current = process.cwd();
236
+ while (current !== "/") {
237
+ if (fs2.existsSync(path2.join(current, ".git"))) {
238
+ return current;
239
+ }
240
+ current = path2.dirname(current);
241
+ }
242
+ return process.cwd();
243
+ }
244
+ function getWorkspaceName(workspaceRoot) {
245
+ return path2.basename(workspaceRoot);
246
+ }
247
+ function getAgentCoreDir() {
248
+ const packageRoot = path2.resolve(import.meta.dirname, "..", "..");
249
+ return path2.join(packageRoot, "agent-core");
250
+ }
251
+ var init_paths = __esm({
252
+ "src/lib/paths.ts"() {
253
+ "use strict";
254
+ }
255
+ });
256
+
257
+ // src/services/config-service.ts
258
+ import { EventEmitter as EventEmitter2 } from "events";
259
+ import * as fs3 from "fs";
260
+ import * as path3 from "path";
261
+ import YAML from "yaml";
262
+ var ConfigService;
263
+ var init_config_service = __esm({
264
+ "src/services/config-service.ts"() {
265
+ "use strict";
266
+ init_config();
267
+ init_paths();
268
+ ConfigService = class extends EventEmitter2 {
269
+ cache = null;
270
+ cacheTime = 0;
271
+ TTL = 5e3;
272
+ load() {
273
+ const now = Date.now();
274
+ if (this.cache && now - this.cacheTime < this.TTL) {
275
+ return this.cache;
276
+ }
277
+ this.cache = this.loadFromDisk();
278
+ this.cacheTime = now;
279
+ return this.cache;
280
+ }
281
+ save(config) {
282
+ const oldConfig = this.cache;
283
+ this.saveToDisk(config);
284
+ this.cache = config;
285
+ this.cacheTime = Date.now();
286
+ this.emit("change", oldConfig || this.loadFromDisk(), config);
287
+ }
288
+ invalidate() {
289
+ this.cache = null;
290
+ }
291
+ isCacheValid() {
292
+ return this.cache !== null && Date.now() - this.cacheTime < this.TTL;
293
+ }
294
+ getConfigFilePath() {
295
+ return getConfigPath();
296
+ }
297
+ // --- Config mutation helpers ---
298
+ setProject(config, name, expose, permissions, projectPath, semanticSearch) {
299
+ const existing = this.findProject(config, { name, path: projectPath });
300
+ if (existing) {
301
+ existing.expose = expose;
302
+ if (projectPath && !existing.path) existing.path = projectPath;
303
+ if (permissions) existing.permissions = { ...existing.permissions, ...permissions };
304
+ if (semanticSearch) existing.semanticSearch = semanticSearch;
305
+ } else {
306
+ config.projects.push({
307
+ name,
308
+ path: projectPath,
309
+ expose,
310
+ permissions: permissions ? { ...DEFAULT_PERMISSIONS, ...permissions } : { ...DEFAULT_PERMISSIONS },
311
+ semanticSearch
312
+ });
313
+ }
314
+ return config;
315
+ }
316
+ removeProject(config, name) {
317
+ config.projects = config.projects.filter((p) => p.name !== name);
318
+ return config;
319
+ }
320
+ isProjectExposed(config, name, projectPath) {
321
+ const project = this.findProject(config, { name, path: projectPath });
322
+ return project ? project.expose : config.defaults.includeNew;
323
+ }
324
+ getProjectPermissions(config, name, projectPath) {
325
+ const project = this.findProject(config, { name, path: projectPath });
326
+ return project?.permissions ?? config.defaults.permissions;
327
+ }
328
+ findProject(config, identifier) {
329
+ const targetPath = identifier.path ? this.normalizePath(identifier.path) : void 0;
330
+ return config.projects.find((p) => {
331
+ const configPath = p.path ? this.normalizePath(p.path) : void 0;
332
+ if (targetPath && configPath) return configPath === targetPath;
333
+ if (!targetPath && !configPath && identifier.name) return p.name === identifier.name;
334
+ if (targetPath && !configPath && identifier.name) return p.name === identifier.name;
335
+ return false;
336
+ });
337
+ }
338
+ cleanStaleProjects(config) {
339
+ const removed = [];
340
+ config.projects = config.projects.filter((project) => {
341
+ const exists = project.path ? fs3.existsSync(project.path) : true;
342
+ if (!exists) removed.push(project.name);
343
+ return exists;
344
+ });
345
+ return { config, removed };
346
+ }
347
+ // --- Private ---
348
+ normalizePath(p) {
349
+ let normalized = p;
350
+ while (normalized.length > 1 && (normalized.endsWith("/") || normalized.endsWith("\\"))) {
351
+ normalized = normalized.slice(0, -1);
352
+ }
353
+ return normalized;
354
+ }
355
+ loadFromDisk() {
356
+ const configPath = getConfigPath();
357
+ if (!fs3.existsSync(configPath)) return { ...DEFAULT_MCP_CONFIG };
358
+ try {
359
+ const content = fs3.readFileSync(configPath, "utf-8");
360
+ return this.parseConfig(content);
361
+ } catch {
362
+ return { ...DEFAULT_MCP_CONFIG };
363
+ }
364
+ }
365
+ saveToDisk(config) {
366
+ const configPath = getConfigPath();
367
+ ensureDir(path3.dirname(configPath));
368
+ const header = `# CE-Workflow MCP Hub Configuration
369
+ # Manages which projects are exposed via MCP
370
+
371
+ `;
372
+ const content = header + YAML.stringify(config, { indent: 2, lineWidth: 0 });
373
+ fs3.writeFileSync(configPath, content);
374
+ }
375
+ parseConfig(content) {
376
+ try {
377
+ const parsed = YAML.parse(content);
378
+ return {
379
+ server: {
380
+ port: parsed?.server?.port ?? DEFAULT_MCP_CONFIG.server.port,
381
+ autoStart: parsed?.server?.autoStart ?? DEFAULT_MCP_CONFIG.server.autoStart
382
+ },
383
+ defaults: {
384
+ includeNew: parsed?.defaults?.includeNew ?? DEFAULT_MCP_CONFIG.defaults.includeNew,
385
+ permissions: {
386
+ knowledge: parsed?.defaults?.permissions?.knowledge ?? DEFAULT_PERMISSIONS.knowledge,
387
+ tasks: parsed?.defaults?.permissions?.tasks ?? DEFAULT_PERMISSIONS.tasks,
388
+ refs: parsed?.defaults?.permissions?.refs ?? DEFAULT_PERMISSIONS.refs
389
+ },
390
+ semanticSearch: parsed?.defaults?.semanticSearch ?? DEFAULT_MCP_CONFIG.defaults.semanticSearch
391
+ },
392
+ projects: Array.isArray(parsed?.projects) ? parsed.projects.map((p) => ({
393
+ name: p.name || "",
394
+ path: p.path,
395
+ expose: p.expose ?? true,
396
+ permissions: {
397
+ knowledge: p.permissions?.knowledge ?? DEFAULT_PERMISSIONS.knowledge,
398
+ tasks: p.permissions?.tasks ?? DEFAULT_PERMISSIONS.tasks,
399
+ refs: p.permissions?.refs ?? DEFAULT_PERMISSIONS.refs
400
+ },
401
+ semanticSearch: p.semanticSearch,
402
+ last_synced_version: p.last_synced_version
403
+ })) : [],
404
+ last_synced_version: parsed?.last_synced_version
405
+ };
406
+ } catch {
407
+ return { ...DEFAULT_MCP_CONFIG };
408
+ }
409
+ }
410
+ };
411
+ }
412
+ });
413
+
414
+ // src/lib/detection.ts
415
+ import * as fs4 from "fs";
416
+ import * as path4 from "path";
417
+ function scanForProjects(options = {}) {
418
+ const { excludeWorkspace, knownProjects } = options;
419
+ const projects = [];
420
+ const seenPaths = /* @__PURE__ */ new Set();
421
+ if (knownProjects && knownProjects.length > 0) {
422
+ for (const p of knownProjects) {
423
+ try {
424
+ if (!p.path || !fs4.existsSync(p.path)) continue;
425
+ if (p.name === excludeWorkspace) continue;
426
+ const dataPath = getDataPath(p.name);
427
+ if (seenPaths.has(dataPath)) continue;
428
+ seenPaths.add(dataPath);
429
+ const knowledgePath = path4.join(dataPath, "knowledge");
430
+ const refsPath = path4.join(dataPath, "refs");
431
+ const tasksPath = path4.join(dataPath, "tasks");
432
+ projects.push({
433
+ name: p.name,
434
+ path: p.path,
435
+ sourcePath: p.path,
436
+ dataPath,
437
+ source: "global",
438
+ knowledgePath: fs4.existsSync(knowledgePath) ? knowledgePath : void 0,
439
+ refsPath: fs4.existsSync(refsPath) ? refsPath : void 0,
440
+ tasksPath: fs4.existsSync(tasksPath) ? tasksPath : void 0,
441
+ semanticSearchEnabled: true
442
+ });
443
+ } catch {
444
+ }
445
+ }
446
+ }
447
+ const workspacesDir = path4.join(getHome(), "workspaces");
448
+ if (fs4.existsSync(workspacesDir)) {
449
+ try {
450
+ const entries = fs4.readdirSync(workspacesDir, { withFileTypes: true });
451
+ for (const entry of entries) {
452
+ if (!entry.isDirectory()) continue;
453
+ if (entry.name === excludeWorkspace) continue;
454
+ const dataPath = path4.join(workspacesDir, entry.name);
455
+ if (seenPaths.has(dataPath)) continue;
456
+ seenPaths.add(dataPath);
457
+ const knowledgePath = path4.join(dataPath, "knowledge");
458
+ const refsPath = path4.join(dataPath, "refs");
459
+ const tasksPath = path4.join(dataPath, "tasks");
460
+ const configPath = path4.join(dataPath, "config.yaml");
461
+ let sourcePath;
462
+ if (fs4.existsSync(configPath)) {
463
+ try {
464
+ const content = fs4.readFileSync(configPath, "utf-8");
465
+ const match = content.match(/sourcePath:\s*["']?([^"'\n\r]+)["']?/);
466
+ sourcePath = match?.[1]?.trim();
467
+ } catch {
468
+ }
469
+ }
470
+ projects.push({
471
+ name: entry.name,
472
+ path: sourcePath || dataPath,
473
+ sourcePath,
474
+ dataPath,
475
+ source: "global",
476
+ knowledgePath: fs4.existsSync(knowledgePath) ? knowledgePath : void 0,
477
+ refsPath: fs4.existsSync(refsPath) ? refsPath : void 0,
478
+ tasksPath: fs4.existsSync(tasksPath) ? tasksPath : void 0,
479
+ semanticSearchEnabled: true
480
+ });
481
+ }
482
+ } catch {
483
+ }
484
+ }
485
+ return projects;
486
+ }
487
+ function findClosestProject(projects, cwd = process.cwd()) {
488
+ const matches = projects.map((p) => {
489
+ let matchPath = "";
490
+ if (cwd === p.path) matchPath = p.path;
491
+ else if (p.sourcePath && cwd === p.sourcePath) matchPath = p.sourcePath;
492
+ else if (cwd.startsWith(p.path + "/")) matchPath = p.path;
493
+ else if (p.sourcePath && cwd.startsWith(p.sourcePath + "/")) matchPath = p.sourcePath;
494
+ return { project: p, matchPath };
495
+ }).filter((m) => m.matchPath !== "");
496
+ matches.sort((a, b) => b.matchPath.length - a.matchPath.length);
497
+ return matches[0]?.project;
498
+ }
499
+ var init_detection = __esm({
500
+ "src/lib/detection.ts"() {
501
+ "use strict";
502
+ init_paths();
503
+ }
504
+ });
505
+
506
+ // src/services/project-service.ts
507
+ var ProjectService;
508
+ var init_project_service = __esm({
509
+ "src/services/project-service.ts"() {
510
+ "use strict";
511
+ init_detection();
512
+ ProjectService = class {
513
+ cache = null;
514
+ cacheTime = 0;
515
+ TTL = 3e4;
516
+ lastOptions = null;
517
+ scan(options) {
518
+ const now = Date.now();
519
+ const optionsMatch = JSON.stringify(options) === JSON.stringify(this.lastOptions);
520
+ if (this.cache && optionsMatch && now - this.cacheTime < this.TTL) {
521
+ return this.cache;
522
+ }
523
+ this.cache = scanForProjects(options);
524
+ this.cacheTime = now;
525
+ this.lastOptions = options ?? null;
526
+ return this.cache;
527
+ }
528
+ refresh(options) {
529
+ this.invalidate();
530
+ return this.scan(options);
531
+ }
532
+ invalidate() {
533
+ this.cache = null;
534
+ this.lastOptions = null;
535
+ }
536
+ getCached() {
537
+ if (this.cache && Date.now() - this.cacheTime < this.TTL) {
538
+ return this.cache;
539
+ }
540
+ return null;
541
+ }
542
+ findClosest(cwd) {
543
+ const projects = this.getCached() ?? this.scan();
544
+ return findClosestProject(projects, cwd);
545
+ }
546
+ };
547
+ }
548
+ });
549
+
550
+ // src/services/rag-service.ts
551
+ import * as fs5 from "fs";
552
+ import * as path5 from "path";
553
+ var INDEX_VERSION, DEFAULT_MODEL, BATCH_SIZE, RAGService;
554
+ var init_rag_service = __esm({
555
+ "src/services/rag-service.ts"() {
556
+ "use strict";
557
+ INDEX_VERSION = "1.0.0";
558
+ DEFAULT_MODEL = "Xenova/all-MiniLM-L6-v2";
559
+ BATCH_SIZE = 16;
560
+ RAGService = class _RAGService {
561
+ static pipelineInstance = null;
562
+ static activeModelName = null;
563
+ static loadPromise = null;
564
+ logger;
565
+ modelName;
566
+ constructor(logger, modelName = DEFAULT_MODEL) {
567
+ this.logger = logger;
568
+ this.modelName = modelName;
569
+ }
570
+ // --- Pipeline ---
571
+ async getPipeline() {
572
+ if (_RAGService.activeModelName === this.modelName && _RAGService.pipelineInstance) {
573
+ return _RAGService.pipelineInstance;
574
+ }
575
+ if (_RAGService.activeModelName === this.modelName && _RAGService.loadPromise) {
576
+ return _RAGService.loadPromise;
577
+ }
578
+ this.logger.info(`[RAG] Initializing model ${this.modelName}...`);
579
+ _RAGService.activeModelName = this.modelName;
580
+ _RAGService.loadPromise = (async () => {
581
+ try {
582
+ const { pipeline } = await import("@xenova/transformers");
583
+ const pipe = await pipeline("feature-extraction", this.modelName);
584
+ _RAGService.pipelineInstance = pipe;
585
+ this.logger.info(`[RAG] Model ${this.modelName} initialized.`);
586
+ return pipe;
587
+ } catch (error) {
588
+ _RAGService.pipelineInstance = null;
589
+ _RAGService.activeModelName = null;
590
+ _RAGService.loadPromise = null;
591
+ this.logger.error(`[RAG] Failed to initialize model ${this.modelName}`, error);
592
+ throw error;
593
+ }
594
+ })();
595
+ return _RAGService.loadPromise;
596
+ }
597
+ // --- Batch Embedding (key improvement) ---
598
+ async generateEmbedding(text) {
599
+ const pipe = await this.getPipeline();
600
+ const output = await pipe(text, { pooling: "mean", normalize: true });
601
+ return Array.from(output.data);
602
+ }
603
+ async generateEmbeddings(texts) {
604
+ if (texts.length === 0) return [];
605
+ if (texts.length === 1) return [await this.generateEmbedding(texts[0])];
606
+ const pipe = await this.getPipeline();
607
+ const results = [];
608
+ for (let i = 0; i < texts.length; i += BATCH_SIZE) {
609
+ const batch = texts.slice(i, i + BATCH_SIZE);
610
+ const batchResults = await Promise.all(
611
+ batch.map(async (text) => {
612
+ const output = await pipe(text, { pooling: "mean", normalize: true });
613
+ return Array.from(output.data);
614
+ })
615
+ );
616
+ results.push(...batchResults);
617
+ }
618
+ return results;
619
+ }
620
+ // --- Index Management ---
621
+ loadIndex(indexPath) {
622
+ if (fs5.existsSync(indexPath)) {
623
+ try {
624
+ return JSON.parse(fs5.readFileSync(indexPath, "utf-8"));
625
+ } catch {
626
+ }
627
+ }
628
+ return { version: INDEX_VERSION, baseModel: this.modelName, chunks: [] };
629
+ }
630
+ saveIndex(indexPath, index) {
631
+ const dir = path5.dirname(indexPath);
632
+ if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
633
+ const tmpPath = `${indexPath}.tmp`;
634
+ fs5.writeFileSync(tmpPath, JSON.stringify(index, null, 2));
635
+ fs5.renameSync(tmpPath, indexPath);
636
+ }
637
+ // --- Incremental Indexing ---
638
+ async indexFile(indexPath, filePath, content, mtime) {
639
+ const index = this.loadIndex(indexPath);
640
+ if (!index.fileMetadata) index.fileMetadata = {};
641
+ if (mtime !== void 0 && index.fileMetadata[filePath]?.mtime === mtime) {
642
+ return false;
643
+ }
644
+ index.chunks = index.chunks.filter((c) => c.filePath !== filePath);
645
+ const textChunks = this.chunkContent(content);
646
+ const embeddings = await this.generateEmbeddings(textChunks);
647
+ for (let i = 0; i < textChunks.length; i++) {
648
+ index.chunks.push({
649
+ id: `${filePath}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
650
+ filePath,
651
+ content: textChunks[i],
652
+ embedding: embeddings[i],
653
+ mtime
654
+ });
655
+ }
656
+ index.fileMetadata[filePath] = { mtime: mtime ?? Date.now(), chunkCount: textChunks.length };
657
+ this.saveIndex(indexPath, index);
658
+ return true;
659
+ }
660
+ async indexCodeFile(indexPath, filePath, chunks, mtime) {
661
+ const index = this.loadIndex(indexPath);
662
+ if (!index.fileMetadata) index.fileMetadata = {};
663
+ if (index.fileMetadata[filePath]?.mtime === mtime) return;
664
+ index.chunks = index.chunks.filter((c) => c.filePath !== filePath);
665
+ const texts = chunks.map((c) => c.content);
666
+ const embeddings = await this.generateEmbeddings(texts);
667
+ for (let i = 0; i < chunks.length; i++) {
668
+ const chunk = chunks[i];
669
+ index.chunks.push({
670
+ id: `${filePath}-${chunk.lineStart}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
671
+ filePath,
672
+ content: chunk.content,
673
+ embedding: embeddings[i],
674
+ mtime,
675
+ lineStart: chunk.lineStart,
676
+ lineEnd: chunk.lineEnd,
677
+ context: chunk.context,
678
+ language: chunk.language
679
+ });
680
+ }
681
+ index.fileMetadata[filePath] = { mtime, chunkCount: chunks.length };
682
+ this.saveIndex(indexPath, index);
683
+ }
684
+ // --- Batch Indexing (load once, save once) ---
685
+ async indexFileBatch(index, filePath, content, mtime) {
686
+ if (!index.fileMetadata) index.fileMetadata = {};
687
+ if (mtime !== void 0 && index.fileMetadata[filePath]?.mtime === mtime) {
688
+ return false;
689
+ }
690
+ index.chunks = index.chunks.filter((c) => c.filePath !== filePath);
691
+ const textChunks = this.chunkContent(content);
692
+ const embeddings = await this.generateEmbeddings(textChunks);
693
+ for (let i = 0; i < textChunks.length; i++) {
694
+ index.chunks.push({
695
+ id: `${filePath}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
696
+ filePath,
697
+ content: textChunks[i],
698
+ embedding: embeddings[i],
699
+ mtime
700
+ });
701
+ }
702
+ index.fileMetadata[filePath] = { mtime: mtime ?? Date.now(), chunkCount: textChunks.length };
703
+ return true;
704
+ }
705
+ async indexCodeFileBatch(index, filePath, chunks, mtime) {
706
+ if (!index.fileMetadata) index.fileMetadata = {};
707
+ if (index.fileMetadata[filePath]?.mtime === mtime) return false;
708
+ index.chunks = index.chunks.filter((c) => c.filePath !== filePath);
709
+ const texts = chunks.map((c) => c.content);
710
+ const embeddings = await this.generateEmbeddings(texts);
711
+ for (let i = 0; i < chunks.length; i++) {
712
+ const chunk = chunks[i];
713
+ index.chunks.push({
714
+ id: `${filePath}-${chunk.lineStart}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
715
+ filePath,
716
+ content: chunk.content,
717
+ embedding: embeddings[i],
718
+ mtime,
719
+ lineStart: chunk.lineStart,
720
+ lineEnd: chunk.lineEnd,
721
+ context: chunk.context,
722
+ language: chunk.language
723
+ });
724
+ }
725
+ index.fileMetadata[filePath] = { mtime, chunkCount: chunks.length };
726
+ return true;
727
+ }
728
+ removeFileBatch(index, filePath) {
729
+ index.chunks = index.chunks.filter((c) => c.filePath !== filePath);
730
+ if (index.fileMetadata) delete index.fileMetadata[filePath];
731
+ }
732
+ removeFile(indexPath, filePath) {
733
+ const index = this.loadIndex(indexPath);
734
+ index.chunks = index.chunks.filter((c) => c.filePath !== filePath);
735
+ if (index.fileMetadata) delete index.fileMetadata[filePath];
736
+ this.saveIndex(indexPath, index);
737
+ }
738
+ needsReindex(indexPath, filePath, mtime) {
739
+ const index = this.loadIndex(indexPath);
740
+ return index.fileMetadata?.[filePath]?.mtime !== mtime;
741
+ }
742
+ // --- Search ---
743
+ async search(indexPath, query, limit = 5, minScore = 0) {
744
+ const index = this.loadIndex(indexPath);
745
+ if (index.chunks.length === 0) return [];
746
+ const queryEmbedding = await this.generateEmbedding(query);
747
+ const scored = index.chunks.map((chunk) => ({
748
+ ...chunk,
749
+ score: this.cosineSimilarity(queryEmbedding, chunk.embedding)
750
+ }));
751
+ scored.sort((a, b) => b.score - a.score);
752
+ return scored.filter((r) => r.score >= minScore).slice(0, limit);
753
+ }
754
+ // --- Stats ---
755
+ getStats(indexPath) {
756
+ const index = this.loadIndex(indexPath);
757
+ return {
758
+ totalChunks: index.chunks.length,
759
+ totalFiles: index.fileMetadata ? Object.keys(index.fileMetadata).length : 0,
760
+ lastFullIndex: index.lastFullIndex
761
+ };
762
+ }
763
+ getIndexedFiles(indexPath) {
764
+ const index = this.loadIndex(indexPath);
765
+ return index.fileMetadata ? Object.keys(index.fileMetadata) : [];
766
+ }
767
+ // --- Symbol-Aware Chunking ---
768
+ chunkCodeBySymbols(content, filePath, symbolService, maxChunkSize = 1500) {
769
+ const result = symbolService.extract(content, filePath);
770
+ const lines = content.split("\n");
771
+ if (result.symbols.length === 0) {
772
+ return this.chunkContentWithLines(content, maxChunkSize);
773
+ }
774
+ const sorted = [...result.symbols].sort((a, b) => a.line - b.line);
775
+ const chunks = [];
776
+ let lastEnd = 0;
777
+ for (const sym of sorted) {
778
+ const symStart = sym.line - 1;
779
+ const symEnd = (sym.endLine ?? sym.line) - 1;
780
+ if (symStart > lastEnd) {
781
+ const gapContent = lines.slice(lastEnd, symStart).join("\n").trim();
782
+ if (gapContent.length > 50) {
783
+ chunks.push({ content: gapContent, lineStart: lastEnd + 1, lineEnd: symStart });
784
+ }
785
+ }
786
+ const symContent = lines.slice(symStart, symEnd + 1).join("\n");
787
+ if (symContent.length <= maxChunkSize) {
788
+ chunks.push({ content: symContent, lineStart: sym.line, lineEnd: sym.endLine ?? sym.line });
789
+ } else {
790
+ const subChunks = this.chunkContentWithLines(symContent, maxChunkSize, 100);
791
+ for (const sc of subChunks) {
792
+ chunks.push({
793
+ content: sc.content,
794
+ lineStart: symStart + sc.lineStart,
795
+ lineEnd: symStart + sc.lineEnd
796
+ });
797
+ }
798
+ }
799
+ lastEnd = symEnd + 1;
800
+ }
801
+ if (lastEnd < lines.length) {
802
+ const trailContent = lines.slice(lastEnd).join("\n").trim();
803
+ if (trailContent.length > 50) {
804
+ chunks.push({ content: trailContent, lineStart: lastEnd + 1, lineEnd: lines.length });
805
+ }
806
+ }
807
+ return chunks;
808
+ }
809
+ // --- Helpers ---
810
+ cosineSimilarity(a, b) {
811
+ let dot = 0, normA = 0, normB = 0;
812
+ for (let i = 0; i < a.length; i++) {
813
+ const av = a[i] ?? 0, bv = b[i] ?? 0;
814
+ dot += av * bv;
815
+ normA += av * av;
816
+ normB += bv * bv;
817
+ }
818
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
819
+ }
820
+ chunkContent(content, maxChunkSize = 1e3, overlap = 100) {
821
+ const chunks = [];
822
+ let start = 0;
823
+ while (start < content.length) {
824
+ let end = start + maxChunkSize;
825
+ if (end >= content.length) {
826
+ end = content.length;
827
+ } else {
828
+ const lastNewline = content.lastIndexOf("\n", end);
829
+ if (lastNewline > start + maxChunkSize / 2) end = lastNewline;
830
+ }
831
+ const chunk = content.substring(start, end).trim();
832
+ if (chunk.length > 50) chunks.push(chunk);
833
+ if (end === content.length) break;
834
+ start = end - overlap;
835
+ }
836
+ return chunks;
837
+ }
838
+ chunkContentWithLines(content, maxChunkSize = 1e3, overlap = 100) {
839
+ const chunks = [];
840
+ const lines = content.split("\n");
841
+ let currentChunk = "";
842
+ let chunkStartLine = 1;
843
+ let currentLine = 1;
844
+ for (let i = 0; i < lines.length; i++) {
845
+ const line = lines[i] ?? "";
846
+ const potential = currentChunk + (currentChunk ? "\n" : "") + line;
847
+ if (potential.length > maxChunkSize && currentChunk.length > 0) {
848
+ if (currentChunk.trim().length > 50) {
849
+ chunks.push({ content: currentChunk.trim(), lineStart: chunkStartLine, lineEnd: currentLine - 1 });
850
+ }
851
+ const overlapLines = [];
852
+ let overlapSize = 0;
853
+ for (let j = i - 1; j >= 0 && overlapSize < overlap; j--) {
854
+ const prev = lines[j] ?? "";
855
+ overlapLines.unshift(prev);
856
+ overlapSize += prev.length + 1;
857
+ }
858
+ currentChunk = overlapLines.join("\n") + (overlapLines.length > 0 ? "\n" : "") + line;
859
+ chunkStartLine = currentLine - overlapLines.length;
860
+ } else {
861
+ currentChunk = potential;
862
+ }
863
+ currentLine++;
864
+ }
865
+ if (currentChunk.trim().length > 50) {
866
+ chunks.push({ content: currentChunk.trim(), lineStart: chunkStartLine, lineEnd: lines.length });
867
+ }
868
+ return chunks;
869
+ }
870
+ };
871
+ }
872
+ });
873
+
874
+ // src/services/indexing-service.ts
875
+ import { EventEmitter as EventEmitter3 } from "events";
876
+ var IndexingService;
877
+ var init_indexing_service = __esm({
878
+ "src/services/indexing-service.ts"() {
879
+ "use strict";
880
+ IndexingService = class extends EventEmitter3 {
881
+ constructor(rag, logger) {
882
+ super();
883
+ this.rag = rag;
884
+ this.logger = logger;
885
+ }
886
+ jobs = /* @__PURE__ */ new Map();
887
+ getProgress(project) {
888
+ const job = this.jobs.get(project);
889
+ if (job) return { ...job.progress };
890
+ return { project, state: "idle", itemsDone: 0 };
891
+ }
892
+ isRunning(project) {
893
+ return this.getProgress(project).state === "running";
894
+ }
895
+ update(project, patch) {
896
+ const existing = this.jobs.get(project);
897
+ const next = {
898
+ ...existing?.progress ?? this.getProgress(project),
899
+ ...patch,
900
+ project
901
+ };
902
+ this.jobs.set(project, { ...existing ?? { progress: next }, progress: next });
903
+ this.emit("progress", next);
904
+ }
905
+ startOrStatus(project, runner) {
906
+ const current = this.jobs.get(project);
907
+ if (current?.progress.state === "running" && current.promise) {
908
+ return { status: "already_running", progress: { ...current.progress } };
909
+ }
910
+ const initial = {
911
+ project,
912
+ state: "running",
913
+ startedAt: Date.now(),
914
+ itemsDone: 0
915
+ };
916
+ const job = { progress: initial, promise: void 0 };
917
+ this.jobs.set(project, job);
918
+ this.emit("progress", initial);
919
+ job.promise = (async () => {
920
+ try {
921
+ await runner();
922
+ this.update(project, { state: "complete", completedAt: Date.now(), currentItem: void 0 });
923
+ } catch (err) {
924
+ const msg = err instanceof Error ? err.message : String(err);
925
+ this.logger.error(`[Indexing] Job failed for '${project}'`, err);
926
+ this.update(project, { state: "failed", completedAt: Date.now(), lastError: msg });
927
+ }
928
+ })();
929
+ return { status: "started", progress: { ...initial } };
930
+ }
931
+ };
932
+ }
933
+ });
934
+
935
+ // src/services/task-service.ts
936
+ import * as fs6 from "fs";
937
+ import * as path6 from "path";
938
+ var TaskService;
939
+ var init_task_service = __esm({
940
+ "src/services/task-service.ts"() {
941
+ "use strict";
942
+ init_paths();
943
+ TaskService = class {
944
+ list(projectName) {
945
+ const tasksDir = getTasksPath(projectName);
946
+ if (!fs6.existsSync(tasksDir)) return [];
947
+ const tasks = [];
948
+ try {
949
+ for (const entry of fs6.readdirSync(tasksDir, { withFileTypes: true })) {
950
+ if (!entry.isDirectory()) continue;
951
+ const metaPath = path6.join(tasksDir, entry.name, "meta.json");
952
+ if (fs6.existsSync(metaPath)) {
953
+ try {
954
+ const meta = JSON.parse(fs6.readFileSync(metaPath, "utf-8"));
955
+ tasks.push({ task_slug: entry.name, ...meta });
956
+ } catch {
957
+ }
958
+ }
959
+ }
960
+ } catch {
961
+ }
962
+ return tasks;
963
+ }
964
+ get(projectName, taskSlug) {
965
+ const metaPath = path6.join(getTasksPath(projectName), taskSlug, "meta.json");
966
+ if (!fs6.existsSync(metaPath)) return null;
967
+ try {
968
+ const meta = JSON.parse(fs6.readFileSync(metaPath, "utf-8"));
969
+ return { task_slug: taskSlug, ...meta };
970
+ } catch {
971
+ return null;
972
+ }
973
+ }
974
+ create(projectName, taskSlug, data) {
975
+ const taskDir = path6.join(getTasksPath(projectName), taskSlug);
976
+ ensureDir(taskDir);
977
+ const meta = {
978
+ task_slug: taskSlug,
979
+ title: data.title || taskSlug,
980
+ summary: data.summary || "",
981
+ status: "draft",
982
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
983
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
984
+ };
985
+ fs6.writeFileSync(path6.join(taskDir, "meta.json"), JSON.stringify(meta, null, 2));
986
+ return meta;
987
+ }
988
+ update(projectName, taskSlug, updates) {
989
+ const metaPath = path6.join(getTasksPath(projectName), taskSlug, "meta.json");
990
+ if (!fs6.existsSync(metaPath)) return null;
991
+ try {
992
+ const meta = JSON.parse(fs6.readFileSync(metaPath, "utf-8"));
993
+ const updated = { ...meta, ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
994
+ fs6.writeFileSync(metaPath, JSON.stringify(updated, null, 2));
995
+ return { task_slug: taskSlug, ...updated };
996
+ } catch {
997
+ return null;
998
+ }
999
+ }
1000
+ delete(projectName, taskSlug) {
1001
+ const taskDir = path6.join(getTasksPath(projectName), taskSlug);
1002
+ if (!fs6.existsSync(taskDir)) return false;
1003
+ try {
1004
+ fs6.rmSync(taskDir, { recursive: true, force: true });
1005
+ return true;
1006
+ } catch {
1007
+ return false;
1008
+ }
1009
+ }
1010
+ search(projectName, options) {
1011
+ let tasks = this.list(projectName);
1012
+ const { keyword, status, agent, since, limit = 20 } = options;
1013
+ if (keyword) {
1014
+ const kw = keyword.toLowerCase();
1015
+ tasks = tasks.filter(
1016
+ (t) => t.title.toLowerCase().includes(kw) || t.summary.toLowerCase().includes(kw)
1017
+ );
1018
+ }
1019
+ if (status) tasks = tasks.filter((t) => t.status === status);
1020
+ if (agent) tasks = tasks.filter((t) => t.agent === agent);
1021
+ if (since) {
1022
+ const sinceDate = new Date(since).getTime();
1023
+ tasks = tasks.filter((t) => {
1024
+ const updated = t.updated_at ? new Date(t.updated_at).getTime() : 0;
1025
+ return updated >= sinceDate;
1026
+ });
1027
+ }
1028
+ return tasks.slice(0, limit);
1029
+ }
1030
+ validatePhase(projectName, taskSlug, phase) {
1031
+ const task = this.get(projectName, taskSlug);
1032
+ if (!task) return { valid: false, missing: ["Task not found"], suggestions: [] };
1033
+ const missing = [];
1034
+ const suggestions = [];
1035
+ if (phase === "planning" && !task.status?.includes("research")) {
1036
+ missing.push("Research phase not completed");
1037
+ suggestions.push("Complete research first");
1038
+ }
1039
+ if (phase === "execution" && !task.status?.includes("plan")) {
1040
+ missing.push("Planning phase not completed");
1041
+ suggestions.push("Complete planning first");
1042
+ }
1043
+ return { valid: missing.length === 0, missing, suggestions };
1044
+ }
1045
+ };
1046
+ }
1047
+ });
1048
+
1049
+ // src/services/session-service.ts
1050
+ import * as fs7 from "fs";
1051
+ import * as path7 from "path";
1052
+ var SessionService;
1053
+ var init_session_service = __esm({
1054
+ "src/services/session-service.ts"() {
1055
+ "use strict";
1056
+ init_paths();
1057
+ SessionService = class {
1058
+ start(projectName, taskSlug, agent, phase) {
1059
+ const taskDir = path7.join(getTasksPath(projectName), taskSlug);
1060
+ ensureDir(taskDir);
1061
+ const session = {
1062
+ task_slug: taskSlug,
1063
+ agent,
1064
+ phase,
1065
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
1066
+ status: "active"
1067
+ };
1068
+ const sessionPath = path7.join(taskDir, "session.json");
1069
+ fs7.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
1070
+ return { success: true, message: `Session started: ${agent}/${phase} on ${taskSlug}` };
1071
+ }
1072
+ end(projectName, taskSlug) {
1073
+ const sessionPath = path7.join(getTasksPath(projectName), taskSlug, "session.json");
1074
+ if (!fs7.existsSync(sessionPath)) {
1075
+ return { success: false, message: `No active session for ${taskSlug}` };
1076
+ }
1077
+ try {
1078
+ const session = JSON.parse(fs7.readFileSync(sessionPath, "utf-8"));
1079
+ session.ended_at = (/* @__PURE__ */ new Date()).toISOString();
1080
+ session.status = "completed";
1081
+ fs7.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
1082
+ return { success: true, message: `Session ended for ${taskSlug}` };
1083
+ } catch {
1084
+ return { success: false, message: `Failed to end session for ${taskSlug}` };
1085
+ }
1086
+ }
1087
+ getSession(projectName, taskSlug) {
1088
+ const sessionPath = path7.join(getTasksPath(projectName), taskSlug, "session.json");
1089
+ if (!fs7.existsSync(sessionPath)) return null;
1090
+ try {
1091
+ return JSON.parse(fs7.readFileSync(sessionPath, "utf-8"));
1092
+ } catch {
1093
+ return null;
1094
+ }
1095
+ }
1096
+ updateTodos(projectName, taskSlug, phase, agent, items) {
1097
+ const taskDir = path7.join(getTasksPath(projectName), taskSlug);
1098
+ ensureDir(taskDir);
1099
+ const todos = {
1100
+ phase,
1101
+ agent,
1102
+ items,
1103
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1104
+ };
1105
+ const todosPath = path7.join(taskDir, "agent-todos.json");
1106
+ fs7.writeFileSync(todosPath, JSON.stringify(todos, null, 2));
1107
+ return { success: true, message: `Updated ${items.length} todos for ${taskSlug}` };
1108
+ }
1109
+ getTodos(projectName, taskSlug) {
1110
+ const todosPath = path7.join(getTasksPath(projectName), taskSlug, "agent-todos.json");
1111
+ if (!fs7.existsSync(todosPath)) return null;
1112
+ try {
1113
+ return JSON.parse(fs7.readFileSync(todosPath, "utf-8"));
1114
+ } catch {
1115
+ return null;
1116
+ }
1117
+ }
1118
+ };
1119
+ }
1120
+ });
1121
+
1122
+ // src/services/prompt-service.ts
1123
+ import * as fs8 from "fs";
1124
+ import * as path8 from "path";
1125
+ var PromptService;
1126
+ var init_prompt_service = __esm({
1127
+ "src/services/prompt-service.ts"() {
1128
+ "use strict";
1129
+ init_paths();
1130
+ PromptService = class {
1131
+ baseProtocolCache = null;
1132
+ getAllPrompts() {
1133
+ const promptsDir = path8.join(getAgentCoreDir(), "prompts");
1134
+ if (!fs8.existsSync(promptsDir)) return [];
1135
+ const prompts = [];
1136
+ try {
1137
+ for (const file of fs8.readdirSync(promptsDir)) {
1138
+ if (!file.endsWith(".md") || file.startsWith("_")) continue;
1139
+ const filePath = path8.join(promptsDir, file);
1140
+ const parsed = this.parsePromptFile(filePath);
1141
+ if (parsed) prompts.push(parsed);
1142
+ }
1143
+ } catch {
1144
+ }
1145
+ return prompts;
1146
+ }
1147
+ getPrompt(name) {
1148
+ const all = this.getAllPrompts();
1149
+ const search = name.toLowerCase();
1150
+ return all.find(
1151
+ (p) => p.name === name || p.id === name || p.name.toLowerCase() === search || p.id.toLowerCase() === search
1152
+ );
1153
+ }
1154
+ renderPrompt(content, args) {
1155
+ let rendered = content;
1156
+ for (const [key, val] of Object.entries(args)) {
1157
+ rendered = rendered.replace(new RegExp(`{{${key}}}`, "g"), val);
1158
+ }
1159
+ return rendered;
1160
+ }
1161
+ renderWithContext(content, args, context) {
1162
+ const renderArgs = { ...args };
1163
+ if (!renderArgs["CE_DATA"]) renderArgs["CE_DATA"] = context.dataPath;
1164
+ if (!renderArgs["CE_HOME"]) renderArgs["CE_HOME"] = context.home;
1165
+ if (!renderArgs["WORKSPACE_ROOT"]) renderArgs["WORKSPACE_ROOT"] = context.workspaceRoot;
1166
+ if (!renderArgs["WORKSPACE_NAME"]) renderArgs["WORKSPACE_NAME"] = context.workspaceName;
1167
+ const agentContent = this.renderPrompt(content, renderArgs);
1168
+ const base = this.loadBaseProtocol();
1169
+ const rendered = base ? `${base}
1170
+ ${agentContent}` : agentContent;
1171
+ return {
1172
+ rendered,
1173
+ context: {
1174
+ CE_DATA: renderArgs["CE_DATA"],
1175
+ CE_HOME: renderArgs["CE_HOME"],
1176
+ WORKSPACE_ROOT: renderArgs["WORKSPACE_ROOT"],
1177
+ WORKSPACE_NAME: renderArgs["WORKSPACE_NAME"]
1178
+ }
1179
+ };
1180
+ }
1181
+ loadBaseProtocol() {
1182
+ if (this.baseProtocolCache !== null) return this.baseProtocolCache;
1183
+ const basePath = path8.join(getAgentCoreDir(), "prompts", "_base.md");
1184
+ if (fs8.existsSync(basePath)) {
1185
+ const content = fs8.readFileSync(basePath, "utf-8");
1186
+ this.baseProtocolCache = content.replace(/^---[\s\S]*?---\n*/, "");
1187
+ return this.baseProtocolCache;
1188
+ }
1189
+ this.baseProtocolCache = "";
1190
+ return "";
1191
+ }
1192
+ parsePromptFile(filePath) {
1193
+ try {
1194
+ const raw = fs8.readFileSync(filePath, "utf-8");
1195
+ const fmMatch = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
1196
+ if (!fmMatch) return null;
1197
+ const fmText = fmMatch[1];
1198
+ const content = raw.slice(fmMatch[0].length);
1199
+ const id = path8.basename(filePath, ".md");
1200
+ const nameMatch = fmText.match(/name:\s*(.+)/);
1201
+ const descMatch = fmText.match(/description:\s*(.+)/);
1202
+ if (!nameMatch || !descMatch) return null;
1203
+ const args = [];
1204
+ const reqSection = fmText.match(/required-args:\s*\n((?:\s+-[\s\S]*?)(?=\n\w|\n$|$))/);
1205
+ if (reqSection) {
1206
+ const items = reqSection[1].match(/- name:\s*(\w+)/g);
1207
+ items?.forEach((item) => {
1208
+ const name = item.match(/- name:\s*(\w+)/)?.[1];
1209
+ if (name) args.push({ name, description: name, required: true });
1210
+ });
1211
+ }
1212
+ const optSection = fmText.match(/optional-args:\s*\n((?:\s+-[\s\S]*?)(?=\n\w|\n$|$))/);
1213
+ if (optSection) {
1214
+ const items = optSection[1].match(/- name:\s*(\w+)/g);
1215
+ items?.forEach((item) => {
1216
+ const name = item.match(/- name:\s*(\w+)/)?.[1];
1217
+ if (name) args.push({ name, description: name, required: false });
1218
+ });
1219
+ }
1220
+ return {
1221
+ id,
1222
+ name: nameMatch[1].trim(),
1223
+ description: descMatch[1].trim(),
1224
+ arguments: args,
1225
+ content
1226
+ };
1227
+ } catch {
1228
+ return null;
1229
+ }
1230
+ }
1231
+ };
1232
+ }
1233
+ });
1234
+
1235
+ // src/services/symbol-service.ts
1236
+ var SymbolService;
1237
+ var init_symbol_service = __esm({
1238
+ "src/services/symbol-service.ts"() {
1239
+ "use strict";
1240
+ SymbolService = class {
1241
+ extract(content, filePath) {
1242
+ const lines = content.split("\n");
1243
+ const symbols = [];
1244
+ const exports = [];
1245
+ const imports = [];
1246
+ const language = this.getLanguage(filePath);
1247
+ for (let i = 0; i < lines.length; i++) {
1248
+ const line = lines[i] ?? "";
1249
+ const lineNum = i + 1;
1250
+ const trimmed = line.trim();
1251
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || !trimmed) continue;
1252
+ const importMatch = line.match(/^import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/);
1253
+ if (importMatch) {
1254
+ imports.push(importMatch[3] ?? "");
1255
+ continue;
1256
+ }
1257
+ const reExportMatch = line.match(/^export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
1258
+ if (reExportMatch) {
1259
+ const items = (reExportMatch[1] ?? "").split(",").map((s) => s.trim().split(" as ")[0]?.trim() ?? "");
1260
+ exports.push(...items.filter(Boolean));
1261
+ continue;
1262
+ }
1263
+ const isExported = trimmed.startsWith("export ");
1264
+ const cleanLine = isExported ? trimmed.replace(/^export\s+(default\s+)?/, "") : trimmed;
1265
+ const funcMatch = cleanLine.match(/^(?:async\s+)?function\s+(\w+)\s*(\([^)]*\))/);
1266
+ if (funcMatch?.[1]) {
1267
+ const endLine = this.findBlockEnd(lines, i);
1268
+ symbols.push({ name: funcMatch[1], type: "function", line: lineNum, endLine, signature: `function ${funcMatch[1]}${funcMatch[2] ?? "()"}`, exported: isExported });
1269
+ if (isExported) exports.push(funcMatch[1]);
1270
+ continue;
1271
+ }
1272
+ const arrowMatch = cleanLine.match(/^(const|let|var)\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/);
1273
+ if (arrowMatch?.[2]) {
1274
+ const endLine = this.findBlockEnd(lines, i);
1275
+ symbols.push({ name: arrowMatch[2], type: "function", line: lineNum, endLine, signature: `const ${arrowMatch[2]} = (...)`, exported: isExported });
1276
+ if (isExported) exports.push(arrowMatch[2]);
1277
+ continue;
1278
+ }
1279
+ const classMatch = cleanLine.match(/^(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/);
1280
+ if (classMatch?.[1]) {
1281
+ let sig = `class ${classMatch[1]}`;
1282
+ if (classMatch[2]) sig += ` extends ${classMatch[2]}`;
1283
+ const endLine = this.findBlockEnd(lines, i);
1284
+ symbols.push({ name: classMatch[1], type: "class", line: lineNum, endLine, signature: sig, exported: isExported });
1285
+ if (isExported) exports.push(classMatch[1]);
1286
+ continue;
1287
+ }
1288
+ const ifaceMatch = cleanLine.match(/^interface\s+(\w+)/);
1289
+ if (ifaceMatch?.[1]) {
1290
+ const endLine = this.findBlockEnd(lines, i);
1291
+ symbols.push({ name: ifaceMatch[1], type: "interface", line: lineNum, endLine, signature: `interface ${ifaceMatch[1]}`, exported: isExported });
1292
+ if (isExported) exports.push(ifaceMatch[1]);
1293
+ continue;
1294
+ }
1295
+ const typeMatch = cleanLine.match(/^type\s+(\w+)(?:<[^>]+>)?\s*=/);
1296
+ if (typeMatch?.[1]) {
1297
+ symbols.push({ name: typeMatch[1], type: "type", line: lineNum, signature: `type ${typeMatch[1]}`, exported: isExported });
1298
+ if (isExported) exports.push(typeMatch[1]);
1299
+ continue;
1300
+ }
1301
+ const enumMatch = cleanLine.match(/^(?:const\s+)?enum\s+(\w+)/);
1302
+ if (enumMatch?.[1]) {
1303
+ const endLine = this.findBlockEnd(lines, i);
1304
+ symbols.push({ name: enumMatch[1], type: "enum", line: lineNum, endLine, signature: `enum ${enumMatch[1]}`, exported: isExported });
1305
+ if (isExported) exports.push(enumMatch[1]);
1306
+ continue;
1307
+ }
1308
+ const varMatch = cleanLine.match(/^(const|let|var)\s+(\w+)\s*(?::\s*([^=]+))?\s*=/);
1309
+ if (varMatch?.[2] && !line.includes("=>")) {
1310
+ symbols.push({ name: varMatch[2], type: varMatch[1] === "const" ? "const" : "variable", line: lineNum, signature: `${varMatch[1]} ${varMatch[2]}`, exported: isExported });
1311
+ if (isExported) exports.push(varMatch[2]);
1312
+ continue;
1313
+ }
1314
+ }
1315
+ return { filePath, language, symbols, exports: [...new Set(exports)], imports: [...new Set(imports)] };
1316
+ }
1317
+ /**
1318
+ * Find the end of a brace-delimited block starting from the given line index.
1319
+ * Returns 1-based line number of the closing brace.
1320
+ */
1321
+ findBlockEnd(lines, startIdx) {
1322
+ let depth = 0;
1323
+ let foundOpen = false;
1324
+ for (let i = startIdx; i < lines.length; i++) {
1325
+ const line = lines[i] ?? "";
1326
+ for (const ch of line) {
1327
+ if (ch === "{") {
1328
+ depth++;
1329
+ foundOpen = true;
1330
+ } else if (ch === "}") {
1331
+ depth--;
1332
+ }
1333
+ if (foundOpen && depth === 0) return i + 1;
1334
+ }
1335
+ }
1336
+ return startIdx + 1;
1337
+ }
1338
+ search(results, query, options = {}) {
1339
+ const { type = "any", fuzzy = true, limit = 10, minScore = 0.3 } = options;
1340
+ const matches = [];
1341
+ for (const result of results) {
1342
+ for (const symbol of result.symbols) {
1343
+ if (type !== "any" && symbol.type !== type) continue;
1344
+ const score = fuzzy ? this.fuzzyScore(query, symbol.name) : symbol.name.toLowerCase().includes(query.toLowerCase()) ? 1 : 0;
1345
+ if (score >= minScore) matches.push({ ...symbol, file: result.filePath, score });
1346
+ }
1347
+ }
1348
+ matches.sort((a, b) => b.score !== a.score ? b.score - a.score : a.name.length - b.name.length);
1349
+ return matches.slice(0, limit);
1350
+ }
1351
+ fuzzyScore(query, name) {
1352
+ const q = query.toLowerCase(), s = name.toLowerCase();
1353
+ if (q === s) return 1;
1354
+ if (s.startsWith(q)) return 0.9;
1355
+ if (s.includes(q)) return 0.7;
1356
+ if (q.startsWith(s)) return 0.6;
1357
+ const dist = this.levenshtein(q, s);
1358
+ return Math.max(0, (1 - dist / Math.max(q.length, s.length)) * 0.5);
1359
+ }
1360
+ levenshtein(a, b) {
1361
+ const m = [];
1362
+ for (let i = 0; i <= b.length; i++) m[i] = [i];
1363
+ for (let j = 0; j <= a.length; j++) m[0][j] = j;
1364
+ for (let i = 1; i <= b.length; i++) {
1365
+ for (let j = 1; j <= a.length; j++) {
1366
+ m[i][j] = b[i - 1] === a[j - 1] ? m[i - 1][j - 1] : Math.min(m[i - 1][j - 1] + 1, m[i][j - 1] + 1, m[i - 1][j] + 1);
1367
+ }
1368
+ }
1369
+ return m[b.length][a.length];
1370
+ }
1371
+ getLanguage(filePath) {
1372
+ const ext = filePath.toLowerCase().split(".").pop() ?? "";
1373
+ const map = {
1374
+ ts: "typescript",
1375
+ tsx: "typescript",
1376
+ js: "javascript",
1377
+ jsx: "javascript",
1378
+ mjs: "javascript",
1379
+ cjs: "javascript",
1380
+ py: "python",
1381
+ go: "go",
1382
+ rs: "rust",
1383
+ java: "java",
1384
+ kt: "kotlin",
1385
+ rb: "ruby",
1386
+ php: "php",
1387
+ swift: "swift",
1388
+ c: "c",
1389
+ cpp: "cpp",
1390
+ h: "c",
1391
+ hpp: "cpp",
1392
+ cs: "csharp"
1393
+ };
1394
+ return map[ext] ?? "unknown";
1395
+ }
1396
+ };
1397
+ }
1398
+ });
1399
+
1400
+ // src/lib/gitignore.ts
1401
+ import * as fs9 from "fs";
1402
+ import * as path9 from "path";
1403
+ import ignore from "ignore";
1404
+ function loadGitignore(root) {
1405
+ const ig = ignore();
1406
+ const gitignorePath = path9.join(root, ".gitignore");
1407
+ if (fs9.existsSync(gitignorePath)) {
1408
+ try {
1409
+ const content = fs9.readFileSync(gitignorePath, "utf-8");
1410
+ ig.add(content);
1411
+ } catch {
1412
+ }
1413
+ }
1414
+ ig.add(["node_modules", ".git", "dist", "build", ".next", "__pycache__", "venv", ".venv", "target", "vendor", "coverage"]);
1415
+ return ig;
1416
+ }
1417
+ var init_gitignore = __esm({
1418
+ "src/lib/gitignore.ts"() {
1419
+ "use strict";
1420
+ }
1421
+ });
1422
+
1423
+ // src/services/dependency-service.ts
1424
+ import * as fs10 from "fs";
1425
+ import * as path10 from "path";
1426
+ var IMPORT_PATTERNS, DependencyService;
1427
+ var init_dependency_service = __esm({
1428
+ "src/services/dependency-service.ts"() {
1429
+ "use strict";
1430
+ init_gitignore();
1431
+ IMPORT_PATTERNS = {
1432
+ typescript: [
1433
+ /import\s+(?:(?:[\w*{}\s,]+)\s+from\s+)?['"]([^'"]+)['"]/g,
1434
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1435
+ /export\s+(?:\*|{[^}]*})\s+from\s+['"]([^'"]+)['"]/g,
1436
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
1437
+ ],
1438
+ javascript: [
1439
+ /import\s+(?:(?:[\w*{}\s,]+)\s+from\s+)?['"]([^'"]+)['"]/g,
1440
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1441
+ /export\s+(?:\*|{[^}]*})\s+from\s+['"]([^'"]+)['"]/g,
1442
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
1443
+ ],
1444
+ python: [/from\s+([\w.]+)\s+import/g, /^import\s+([\w.]+)/gm],
1445
+ go: [/import\s+(?:\w+\s+)?["']([^"']+)["']/g, /^\s*["']([^"']+)["']\s*$/gm],
1446
+ rust: [/use\s+([\w:]+)/g, /mod\s+(\w+)/g, /extern\s+crate\s+(\w+)/g],
1447
+ java: [/import\s+([\w.]+)/g]
1448
+ };
1449
+ DependencyService = class {
1450
+ parseImports(filePath, content) {
1451
+ const ext = path10.extname(filePath).toLowerCase().replace(/^\./, "");
1452
+ const language = this.getLanguage(ext);
1453
+ const patterns = IMPORT_PATTERNS[language] || IMPORT_PATTERNS.javascript || [];
1454
+ const edges = [];
1455
+ for (const pattern of patterns) {
1456
+ pattern.lastIndex = 0;
1457
+ let match;
1458
+ while ((match = pattern.exec(content)) !== null) {
1459
+ const importPath = match[1];
1460
+ if (!importPath) continue;
1461
+ let importType = "static";
1462
+ if (pattern.source.includes("import\\s*\\(")) importType = "dynamic";
1463
+ else if (pattern.source.includes("export")) importType = "re-export";
1464
+ const resolved = this.resolveImport(filePath, importPath, language);
1465
+ edges.push({ source: filePath, target: resolved.path, importType, importPath, isResolved: resolved.isResolved });
1466
+ }
1467
+ }
1468
+ return edges;
1469
+ }
1470
+ buildGraph(files) {
1471
+ const graph = { edges: [], files: /* @__PURE__ */ new Set(), lastUpdated: Date.now() };
1472
+ for (const file of files) {
1473
+ graph.files.add(file.path);
1474
+ graph.edges.push(...this.parseImports(file.path, file.content));
1475
+ }
1476
+ return graph;
1477
+ }
1478
+ findRelated(filePath, graph, options = {}) {
1479
+ const { includeImports = true, includeImportedBy = true, depth = 1 } = options;
1480
+ const results = [];
1481
+ const visited = /* @__PURE__ */ new Set();
1482
+ const traverse = (file, d) => {
1483
+ if (d > depth || visited.has(file)) return;
1484
+ visited.add(file);
1485
+ if (includeImports) {
1486
+ for (const e of graph.edges.filter((e2) => e2.source === file && e2.isResolved)) {
1487
+ if (!visited.has(e.target)) {
1488
+ results.push({ file: e.target, relationship: "imports", importPath: e.importPath });
1489
+ if (d < depth) traverse(e.target, d + 1);
1490
+ }
1491
+ }
1492
+ }
1493
+ if (includeImportedBy) {
1494
+ for (const e of graph.edges.filter((e2) => e2.target === file && e2.isResolved)) {
1495
+ if (!visited.has(e.source)) {
1496
+ results.push({ file: e.source, relationship: "imported-by", importPath: e.importPath });
1497
+ if (d < depth) traverse(e.source, d + 1);
1498
+ }
1499
+ }
1500
+ }
1501
+ };
1502
+ traverse(filePath, 1);
1503
+ return results;
1504
+ }
1505
+ async scanProject(projectRoot, options = {}) {
1506
+ const {
1507
+ extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java"]
1508
+ } = options;
1509
+ const ig = loadGitignore(projectRoot);
1510
+ const files = [];
1511
+ const scan = (dir) => {
1512
+ try {
1513
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
1514
+ const full = path10.join(dir, entry.name);
1515
+ const rel = path10.relative(projectRoot, full);
1516
+ if (ig.ignores(rel)) continue;
1517
+ if (entry.isDirectory()) {
1518
+ scan(full);
1519
+ } else if (extensions.includes(path10.extname(entry.name).toLowerCase())) {
1520
+ try {
1521
+ files.push({ path: full, content: fs10.readFileSync(full, "utf-8") });
1522
+ } catch {
1523
+ }
1524
+ }
1525
+ }
1526
+ } catch {
1527
+ }
1528
+ };
1529
+ scan(projectRoot);
1530
+ return this.buildGraph(files);
1531
+ }
1532
+ resolveImport(fromFile, importPath, language) {
1533
+ if (importPath.startsWith(".")) {
1534
+ const fromDir = path10.dirname(fromFile);
1535
+ const base = path10.resolve(fromDir, importPath);
1536
+ for (const c of this.candidates(base, language)) {
1537
+ if (fs10.existsSync(c)) return { path: c, isResolved: true };
1538
+ }
1539
+ return { path: base, isResolved: false };
1540
+ }
1541
+ return { path: importPath, isResolved: false };
1542
+ }
1543
+ candidates(base, language) {
1544
+ const cs = [];
1545
+ if (path10.extname(base)) cs.push(base);
1546
+ switch (language) {
1547
+ case "typescript":
1548
+ cs.push(`${base}.ts`, `${base}.tsx`, `${base}/index.ts`, `${base}/index.tsx`, `${base}.js`, `${base}/index.js`);
1549
+ break;
1550
+ case "javascript":
1551
+ cs.push(`${base}.js`, `${base}.jsx`, `${base}/index.js`, `${base}.mjs`, `${base}.cjs`);
1552
+ break;
1553
+ case "python":
1554
+ cs.push(`${base}.py`, `${base}/__init__.py`);
1555
+ break;
1556
+ case "go":
1557
+ cs.push(`${base}.go`);
1558
+ break;
1559
+ case "rust":
1560
+ cs.push(`${base}.rs`, `${base}/mod.rs`);
1561
+ break;
1562
+ case "java":
1563
+ cs.push(`${base}.java`);
1564
+ break;
1565
+ default:
1566
+ cs.push(base);
1567
+ }
1568
+ return cs;
1569
+ }
1570
+ getLanguage(ext) {
1571
+ const map = {
1572
+ ts: "typescript",
1573
+ tsx: "typescript",
1574
+ js: "javascript",
1575
+ jsx: "javascript",
1576
+ mjs: "javascript",
1577
+ cjs: "javascript",
1578
+ py: "python",
1579
+ go: "go",
1580
+ rs: "rust",
1581
+ java: "java"
1582
+ };
1583
+ return map[ext] ?? "javascript";
1584
+ }
1585
+ };
1586
+ }
1587
+ });
1588
+
1589
+ // src/services/context-service.ts
1590
+ var ContextService;
1591
+ var init_context_service = __esm({
1592
+ "src/services/context-service.ts"() {
1593
+ "use strict";
1594
+ ContextService = class {
1595
+ constructor(container) {
1596
+ this.container = container;
1597
+ }
1598
+ getLanguageFromExtension(ext) {
1599
+ const e = ext.toLowerCase().replace(/^\./, "");
1600
+ const map = {
1601
+ ts: "typescript",
1602
+ tsx: "typescript",
1603
+ mts: "typescript",
1604
+ cts: "typescript",
1605
+ js: "javascript",
1606
+ jsx: "javascript",
1607
+ mjs: "javascript",
1608
+ cjs: "javascript",
1609
+ py: "python",
1610
+ pyw: "python",
1611
+ go: "go",
1612
+ rs: "rust",
1613
+ java: "java",
1614
+ kt: "java",
1615
+ kts: "java"
1616
+ };
1617
+ return map[e] ?? "unknown";
1618
+ }
1619
+ extractContext(content, lineStart, language) {
1620
+ if (language === "unknown") return void 0;
1621
+ try {
1622
+ const lines = content.split("\n");
1623
+ const ctx = this.findEnclosing(lines, lineStart - 1, language);
1624
+ return ctx ? this.formatContext(ctx) : void 0;
1625
+ } catch {
1626
+ return void 0;
1627
+ }
1628
+ }
1629
+ extractAllContexts(content, language) {
1630
+ const contexts = /* @__PURE__ */ new Map();
1631
+ if (language === "unknown") return contexts;
1632
+ const lines = content.split("\n");
1633
+ const patterns = this.getPatterns(language);
1634
+ for (let i = 0; i < lines.length; i++) {
1635
+ const line = lines[i] ?? "";
1636
+ for (const p of patterns) {
1637
+ const m = line.match(p.regex);
1638
+ if (m) {
1639
+ const name = m[1] ?? m[2] ?? "unknown";
1640
+ contexts.set(i + 1, { type: p.type, name, line: i + 1 });
1641
+ break;
1642
+ }
1643
+ }
1644
+ }
1645
+ return contexts;
1646
+ }
1647
+ findEnclosing(lines, targetIdx, language) {
1648
+ const patterns = this.getPatterns(language);
1649
+ let braceDepth = 0;
1650
+ for (let i = targetIdx; i >= 0; i--) {
1651
+ const line = lines[i] ?? "";
1652
+ if (language !== "python") {
1653
+ braceDepth += this.countChar(line, "}") - this.countChar(line, "{");
1654
+ }
1655
+ for (const p of patterns) {
1656
+ const m = line.match(p.regex);
1657
+ if (m) {
1658
+ const name = m[1] ?? m[2] ?? "unknown";
1659
+ if (language === "python") {
1660
+ const targetIndent = this.getIndent(lines[targetIdx] ?? "");
1661
+ if (this.getIndent(line) < targetIndent) return { type: p.type, name, line: i + 1 };
1662
+ } else if (braceDepth >= 0) {
1663
+ return { type: p.type, name, line: i + 1 };
1664
+ }
1665
+ }
1666
+ }
1667
+ }
1668
+ return void 0;
1669
+ }
1670
+ getPatterns(lang) {
1671
+ switch (lang) {
1672
+ case "typescript":
1673
+ case "javascript":
1674
+ return [
1675
+ { regex: /^\s*(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/, type: "class" },
1676
+ { regex: /^\s*(?:export\s+)?interface\s+(\w+)/, type: "interface" },
1677
+ { regex: /^\s*(?:export\s+)?type\s+(\w+)/, type: "type" },
1678
+ { regex: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, type: "function" },
1679
+ { regex: /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>|\w+\s*=>)/, type: "const" },
1680
+ { regex: /^\s*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{?/, type: "method" }
1681
+ ];
1682
+ case "python":
1683
+ return [
1684
+ { regex: /^(\s*)class\s+(\w+)/, type: "class" },
1685
+ { regex: /^(\s*)(?:async\s+)?def\s+(\w+)/, type: "function" }
1686
+ ];
1687
+ case "go":
1688
+ return [
1689
+ { regex: /^func\s+(?:\([^)]+\)\s+)?(\w+)/, type: "function" },
1690
+ { regex: /^type\s+(\w+)\s+(?:struct|interface)/, type: "type" }
1691
+ ];
1692
+ case "rust":
1693
+ return [
1694
+ { regex: /^\s*(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/, type: "function" },
1695
+ { regex: /^\s*impl(?:<[^>]+>)?\s+(\w+)/, type: "class" },
1696
+ { regex: /^\s*(?:pub\s+)?struct\s+(\w+)/, type: "type" }
1697
+ ];
1698
+ case "java":
1699
+ return [
1700
+ { regex: /^\s*(?:public|private|protected)?\s*(?:static\s+)?class\s+(\w+)/, type: "class" },
1701
+ { regex: /^\s*(?:public|private|protected)?\s*interface\s+(\w+)/, type: "interface" }
1702
+ ];
1703
+ default:
1704
+ return [];
1705
+ }
1706
+ }
1707
+ formatContext(m) {
1708
+ switch (m.type) {
1709
+ case "class":
1710
+ return `class ${m.name}`;
1711
+ case "interface":
1712
+ return `interface ${m.name}`;
1713
+ case "type":
1714
+ return `type ${m.name}`;
1715
+ case "function":
1716
+ return `function ${m.name}()`;
1717
+ case "method":
1718
+ return `${m.name}()`;
1719
+ case "const":
1720
+ return `const ${m.name}`;
1721
+ }
1722
+ }
1723
+ countChar(str, char) {
1724
+ let c = 0;
1725
+ for (let i = 0; i < str.length; i++) if (str[i] === char) c++;
1726
+ return c;
1727
+ }
1728
+ getIndent(line) {
1729
+ const m = line.match(/^(\s*)/);
1730
+ let level = 0;
1731
+ for (const ch of m?.[1] ?? "") level += ch === " " ? 4 : 1;
1732
+ return level;
1733
+ }
1734
+ };
1735
+ }
1736
+ });
1737
+
1738
+ // src/services/index.ts
1739
+ function createServices() {
1740
+ const container = new ServiceContainer();
1741
+ container.register("logger", () => new Logger(getLogPath()));
1742
+ container.register("config", () => new ConfigService());
1743
+ container.register("projects", () => new ProjectService());
1744
+ container.register("rag", () => new RAGService(container.logger));
1745
+ container.register("indexing", () => new IndexingService(container.rag, container.logger));
1746
+ container.register("tasks", () => new TaskService());
1747
+ container.register("sessions", () => new SessionService());
1748
+ container.register("prompts", () => new PromptService());
1749
+ container.register("symbols", () => new SymbolService());
1750
+ container.register("dependencies", () => new DependencyService());
1751
+ container.register("context", () => new ContextService(container));
1752
+ return container;
1753
+ }
1754
+ var init_services = __esm({
1755
+ "src/services/index.ts"() {
1756
+ "use strict";
1757
+ init_container();
1758
+ init_logger();
1759
+ init_config_service();
1760
+ init_project_service();
1761
+ init_rag_service();
1762
+ init_indexing_service();
1763
+ init_task_service();
1764
+ init_session_service();
1765
+ init_prompt_service();
1766
+ init_symbol_service();
1767
+ init_dependency_service();
1768
+ init_context_service();
1769
+ init_paths();
1770
+ init_container();
1771
+ init_logger();
1772
+ init_config_service();
1773
+ init_project_service();
1774
+ init_rag_service();
1775
+ init_indexing_service();
1776
+ init_task_service();
1777
+ init_session_service();
1778
+ init_prompt_service();
1779
+ init_symbol_service();
1780
+ init_dependency_service();
1781
+ init_context_service();
1782
+ }
1783
+ });
1784
+
1785
+ // src/mcp/registry.ts
1786
+ import "zod";
1787
+ var ToolRegistry;
1788
+ var init_registry = __esm({
1789
+ "src/mcp/registry.ts"() {
1790
+ "use strict";
1791
+ ToolRegistry = class {
1792
+ tools = /* @__PURE__ */ new Map();
1793
+ handlers = /* @__PURE__ */ new Map();
1794
+ schemas = /* @__PURE__ */ new Map();
1795
+ register(def, handler, schema) {
1796
+ this.tools.set(def.name, def);
1797
+ this.handlers.set(def.name, handler);
1798
+ if (schema) this.schemas.set(def.name, schema);
1799
+ }
1800
+ registerMany(registrations) {
1801
+ for (const reg of registrations) {
1802
+ this.register(reg.definition, reg.handler, reg.schema);
1803
+ }
1804
+ }
1805
+ async dispatch(name, args) {
1806
+ const handler = this.handlers.get(name);
1807
+ if (!handler) {
1808
+ return {
1809
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
1810
+ isError: true
1811
+ };
1812
+ }
1813
+ const schema = this.schemas.get(name);
1814
+ if (schema) {
1815
+ const result = schema.safeParse(args);
1816
+ if (!result.success) {
1817
+ return {
1818
+ content: [{ type: "text", text: `Invalid arguments for ${name}: ${result.error.message}` }],
1819
+ isError: true
1820
+ };
1821
+ }
1822
+ }
1823
+ return handler(args);
1824
+ }
1825
+ listTools() {
1826
+ return Array.from(this.tools.values());
1827
+ }
1828
+ has(name) {
1829
+ return this.tools.has(name);
1830
+ }
1831
+ };
1832
+ }
1833
+ });
1834
+
1835
+ // src/mcp/tools/project.ts
1836
+ import { z as z2 } from "zod";
1837
+ function createProjectTools(container) {
1838
+ return [
1839
+ {
1840
+ definition: {
1841
+ name: "resolve_path",
1842
+ description: "Resolve configuration paths for a project.",
1843
+ inputSchema: {
1844
+ type: "object",
1845
+ properties: {
1846
+ project: { type: "string", description: "Name of the project" },
1847
+ path: { type: "string", description: "Absolute path to the project root" }
1848
+ }
1849
+ }
1850
+ },
1851
+ schema: ResolvePathSchema,
1852
+ handler: async (args) => {
1853
+ const { project, path: projectPath } = args;
1854
+ const { getDataPath: getDataPath3, getHome: getHome2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
1855
+ const name = project || "unknown";
1856
+ const result = {
1857
+ project: name,
1858
+ dataPath: getDataPath3(name),
1859
+ home: getHome2(),
1860
+ sourcePath: projectPath
1861
+ };
1862
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1863
+ }
1864
+ },
1865
+ {
1866
+ definition: {
1867
+ name: "list_projects",
1868
+ description: "List all projects exposed via MCP.",
1869
+ inputSchema: { type: "object", properties: {} }
1870
+ },
1871
+ handler: async () => {
1872
+ const config = container.config.load();
1873
+ const projects = container.projects.scan({
1874
+ knownProjects: config.projects.filter((p) => !!p.path).map((p) => ({ name: p.name, path: p.path }))
1875
+ });
1876
+ const exposed = projects.filter((p) => container.config.isProjectExposed(config, p.name, p.sourcePath || p.path));
1877
+ const list = exposed.map((p) => ({ name: p.name, source: p.source, path: p.path }));
1878
+ return {
1879
+ content: [{
1880
+ type: "text",
1881
+ text: JSON.stringify(list, null, 2) + "\n\nTip: Use these project names for project-specific tools."
1882
+ }]
1883
+ };
1884
+ }
1885
+ },
1886
+ {
1887
+ definition: {
1888
+ name: "get_project_context",
1889
+ description: "Get the project context/architecture for a specific project.",
1890
+ inputSchema: {
1891
+ type: "object",
1892
+ properties: { project: { type: "string", description: "Name of the project" } },
1893
+ required: ["project"]
1894
+ }
1895
+ },
1896
+ schema: GetProjectContextSchema,
1897
+ handler: async (args) => {
1898
+ const { project } = args;
1899
+ const { getKnowledgePath: getKnowledgePath2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
1900
+ const fs16 = await import("fs");
1901
+ const path15 = await import("path");
1902
+ const knowledgeDir = getKnowledgePath2(project);
1903
+ const contextFile = path15.join(knowledgeDir, "context.md");
1904
+ if (!fs16.existsSync(contextFile)) {
1905
+ return { content: [{ type: "text", text: `No project context found for "${project}".` }], isError: true };
1906
+ }
1907
+ const content = fs16.readFileSync(contextFile, "utf-8");
1908
+ return { content: [{ type: "text", text: content }] };
1909
+ }
1910
+ },
1911
+ {
1912
+ definition: {
1913
+ name: "index_knowledge",
1914
+ description: "Update the semantic search index for a specific project.",
1915
+ inputSchema: {
1916
+ type: "object",
1917
+ properties: {
1918
+ project: { type: "string", description: "Name of the project" },
1919
+ force: { type: "boolean", description: "Force re-indexing" },
1920
+ clean: { type: "boolean", description: "Wipe index and rebuild" }
1921
+ },
1922
+ required: ["project"]
1923
+ }
1924
+ },
1925
+ schema: IndexKnowledgeSchema,
1926
+ handler: async (args) => {
1927
+ const { project, force, clean } = args;
1928
+ const { getKnowledgePath: getKnowledgePath2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
1929
+ const fs16 = await import("fs");
1930
+ const path15 = await import("path");
1931
+ const knowledgeDir = getKnowledgePath2(project);
1932
+ if (!fs16.existsSync(knowledgeDir)) {
1933
+ return { content: [{ type: "text", text: `No knowledge directory for "${project}".` }], isError: true };
1934
+ }
1935
+ const indexPath = path15.join(knowledgeDir, "embeddings.json");
1936
+ if (clean && fs16.existsSync(indexPath)) fs16.unlinkSync(indexPath);
1937
+ const index = container.rag.loadIndex(indexPath);
1938
+ const files = fs16.readdirSync(knowledgeDir).filter((f) => f.endsWith(".md"));
1939
+ let indexed = 0;
1940
+ for (const file of files) {
1941
+ const filePath = path15.join(knowledgeDir, file);
1942
+ const content = fs16.readFileSync(filePath, "utf-8");
1943
+ const mtime = force ? void 0 : fs16.statSync(filePath).mtimeMs;
1944
+ const didIndex = await container.rag.indexFileBatch(index, filePath, content, mtime);
1945
+ if (didIndex) indexed++;
1946
+ }
1947
+ if (indexed > 0) container.rag.saveIndex(indexPath, index);
1948
+ return {
1949
+ content: [{ type: "text", text: JSON.stringify({ project, indexed, total: files.length, indexPath }, null, 2) }]
1950
+ };
1951
+ }
1952
+ },
1953
+ {
1954
+ definition: {
1955
+ name: "reload_config",
1956
+ description: "Reload MCP configuration from disk.",
1957
+ inputSchema: { type: "object", properties: {} }
1958
+ },
1959
+ handler: async () => {
1960
+ container.config.invalidate();
1961
+ const config = container.config.load();
1962
+ return {
1963
+ content: [{
1964
+ type: "text",
1965
+ text: `Config reloaded. ${config.projects.length} projects configured.`
1966
+ }]
1967
+ };
1968
+ }
1969
+ }
1970
+ ];
1971
+ }
1972
+ var ResolvePathSchema, GetProjectContextSchema, IndexKnowledgeSchema;
1973
+ var init_project = __esm({
1974
+ "src/mcp/tools/project.ts"() {
1975
+ "use strict";
1976
+ ResolvePathSchema = z2.object({
1977
+ project: z2.string().optional(),
1978
+ path: z2.string().optional()
1979
+ });
1980
+ GetProjectContextSchema = z2.object({
1981
+ project: z2.string()
1982
+ });
1983
+ IndexKnowledgeSchema = z2.object({
1984
+ project: z2.string(),
1985
+ force: z2.boolean().optional(),
1986
+ clean: z2.boolean().optional()
1987
+ });
1988
+ }
1989
+ });
1990
+
1991
+ // src/mcp/tools/search.ts
1992
+ import { z as z3 } from "zod";
1993
+ import * as fs11 from "fs";
1994
+ import * as path11 from "path";
1995
+ function createSearchTools(container) {
1996
+ return [
1997
+ {
1998
+ definition: {
1999
+ name: "search_knowledge",
2000
+ description: "Search across project knowledge bases. Returns results with relevance scores.",
2001
+ inputSchema: {
2002
+ type: "object",
2003
+ properties: {
2004
+ query: { type: "string", description: "Search query" },
2005
+ project: { type: "string", description: "Optional: limit to specific project" },
2006
+ max_tokens: { type: "number", description: "Optional: max tokens for response" },
2007
+ min_score: { type: "number", description: "Optional: min relevance score (0-1)" }
2008
+ },
2009
+ required: ["query"]
2010
+ }
2011
+ },
2012
+ schema: SearchKnowledgeSchema,
2013
+ handler: async (args) => {
2014
+ const { query, project, min_score } = args;
2015
+ const config = container.config.load();
2016
+ const projects = container.projects.scan({
2017
+ knownProjects: config.projects.filter((p) => !!p.path).map((p) => ({ name: p.name, path: p.path }))
2018
+ });
2019
+ const exposed = projects.filter((p) => container.config.isProjectExposed(config, p.name, p.sourcePath || p.path));
2020
+ const targets = project ? exposed.filter((p) => p.name === project) : exposed;
2021
+ const allResults = [];
2022
+ for (const p of targets) {
2023
+ const indexPath = path11.join(getKnowledgePath(p.name), "embeddings.json");
2024
+ if (!fs11.existsSync(indexPath)) continue;
2025
+ const results = await container.rag.search(indexPath, query, 10, min_score || 0);
2026
+ allResults.push(...results.map((r) => ({ ...r, project: p.name })));
2027
+ }
2028
+ return { content: [{ type: "text", text: JSON.stringify({ results: allResults }, null, 2) }] };
2029
+ }
2030
+ },
2031
+ {
2032
+ definition: {
2033
+ name: "search_code",
2034
+ description: "Semantic search across code files. Returns code snippets with line numbers.",
2035
+ inputSchema: {
2036
+ type: "object",
2037
+ properties: {
2038
+ query: { type: "string", description: "Search query" },
2039
+ project: { type: "string", description: "Optional: limit to specific project" },
2040
+ limit: { type: "number", description: "Max results (default 10)" },
2041
+ max_tokens: { type: "number", description: "Optional: max tokens" },
2042
+ min_score: { type: "number", description: "Optional: min score (0-1)" }
2043
+ },
2044
+ required: ["query"]
2045
+ }
2046
+ },
2047
+ schema: SearchCodeSchema,
2048
+ handler: async (args) => {
2049
+ const { query, project, limit, min_score } = args;
2050
+ const config = container.config.load();
2051
+ const projects = container.projects.scan({
2052
+ knownProjects: config.projects.filter((p) => !!p.path).map((p) => ({ name: p.name, path: p.path }))
2053
+ });
2054
+ const exposed = projects.filter((p) => container.config.isProjectExposed(config, p.name, p.sourcePath || p.path));
2055
+ const targets = project ? exposed.filter((p) => p.name === project) : exposed;
2056
+ const allResults = [];
2057
+ for (const p of targets) {
2058
+ const indexPath = path11.join(getKnowledgePath(p.name), "code-embeddings.json");
2059
+ if (!fs11.existsSync(indexPath)) continue;
2060
+ const results = await container.rag.search(indexPath, query, limit || 10, min_score || 0);
2061
+ allResults.push(...results.map((r) => ({ ...r, project: p.name })));
2062
+ }
2063
+ if (allResults.length === 0) {
2064
+ return {
2065
+ content: [{ type: "text", text: "No code matches found. Run `index_knowledge` first to build the code index." }]
2066
+ };
2067
+ }
2068
+ return { content: [{ type: "text", text: JSON.stringify({ results: allResults }, null, 2) }] };
2069
+ }
2070
+ },
2071
+ {
2072
+ definition: {
2073
+ name: "find_related_files",
2074
+ description: "Find files related through import/dependency relationships.",
2075
+ inputSchema: {
2076
+ type: "object",
2077
+ properties: {
2078
+ file: { type: "string", description: "Path to the file" },
2079
+ project: { type: "string", description: "Project name" },
2080
+ include_imports: { type: "boolean", description: "Include imported files (default true)" },
2081
+ include_imported_by: { type: "boolean", description: "Include importing files (default true)" },
2082
+ depth: { type: "number", description: "Traversal depth (default 1)" }
2083
+ },
2084
+ required: ["file", "project"]
2085
+ }
2086
+ },
2087
+ schema: FindRelatedSchema,
2088
+ handler: async (args) => {
2089
+ const params = args;
2090
+ const config = container.config.load();
2091
+ const projects = container.projects.scan({
2092
+ knownProjects: config.projects.filter((p) => !!p.path).map((p) => ({ name: p.name, path: p.path }))
2093
+ });
2094
+ const project = projects.find((p) => p.name === params.project);
2095
+ if (!project?.sourcePath) {
2096
+ return { content: [{ type: "text", text: `Project "${params.project}" not found.` }], isError: true };
2097
+ }
2098
+ const graph = await container.dependencies.scanProject(project.sourcePath);
2099
+ const related = container.dependencies.findRelated(params.file, graph, {
2100
+ includeImports: params.include_imports,
2101
+ includeImportedBy: params.include_imported_by,
2102
+ depth: params.depth
2103
+ });
2104
+ return { content: [{ type: "text", text: JSON.stringify(related, null, 2) }] };
2105
+ }
2106
+ },
2107
+ {
2108
+ definition: {
2109
+ name: "search_symbols",
2110
+ description: "Search for code symbols (functions, classes, types) by name.",
2111
+ inputSchema: {
2112
+ type: "object",
2113
+ properties: {
2114
+ name: { type: "string", description: "Symbol name" },
2115
+ project: { type: "string", description: "Project name" },
2116
+ type: { type: "string", description: "Filter by type" },
2117
+ fuzzy: { type: "boolean", description: "Use fuzzy matching (default true)" },
2118
+ limit: { type: "number", description: "Max results (default 10)" }
2119
+ },
2120
+ required: ["name", "project"]
2121
+ }
2122
+ },
2123
+ schema: SearchSymbolsSchema,
2124
+ handler: async (args) => {
2125
+ const params = args;
2126
+ const config = container.config.load();
2127
+ const projects = container.projects.scan({
2128
+ knownProjects: config.projects.filter((p) => !!p.path).map((p) => ({ name: p.name, path: p.path }))
2129
+ });
2130
+ const project = projects.find((p) => p.name === params.project);
2131
+ if (!project?.sourcePath) {
2132
+ return { content: [{ type: "text", text: `Project "${params.project}" not found.` }], isError: true };
2133
+ }
2134
+ const codeFiles = scanCodeFiles(project.sourcePath);
2135
+ const results = codeFiles.map((f) => container.symbols.extract(f.content, f.path));
2136
+ const matches = container.symbols.search(results, params.name, {
2137
+ type: params.type,
2138
+ fuzzy: params.fuzzy,
2139
+ limit: params.limit
2140
+ });
2141
+ return { content: [{ type: "text", text: JSON.stringify({ matches }, null, 2) }] };
2142
+ }
2143
+ },
2144
+ {
2145
+ definition: {
2146
+ name: "get_file_summary",
2147
+ description: "Get a quick summary of a file (language, LOC, exports, imports, symbols).",
2148
+ inputSchema: {
2149
+ type: "object",
2150
+ properties: {
2151
+ file: { type: "string", description: "Path to the file" },
2152
+ project: { type: "string", description: "Project name" }
2153
+ },
2154
+ required: ["file", "project"]
2155
+ }
2156
+ },
2157
+ schema: GetFileSummarySchema,
2158
+ handler: async (args) => {
2159
+ const { file, project } = args;
2160
+ if (!fs11.existsSync(file)) {
2161
+ return { content: [{ type: "text", text: `File not found: ${file}` }], isError: true };
2162
+ }
2163
+ const content = fs11.readFileSync(file, "utf-8");
2164
+ const result = container.symbols.extract(content, file);
2165
+ const lines = content.split("\n").length;
2166
+ return {
2167
+ content: [{
2168
+ type: "text",
2169
+ text: JSON.stringify({
2170
+ file,
2171
+ language: result.language,
2172
+ lines,
2173
+ exports: result.exports,
2174
+ imports: result.imports,
2175
+ symbols: result.symbols.length
2176
+ }, null, 2)
2177
+ }]
2178
+ };
2179
+ }
2180
+ },
2181
+ {
2182
+ definition: {
2183
+ name: "get_context_bundle",
2184
+ description: "Get bundled context: project context + knowledge + code in one call.",
2185
+ inputSchema: {
2186
+ type: "object",
2187
+ properties: {
2188
+ query: { type: "string", description: "Natural language query" },
2189
+ project: { type: "string", description: "Project name" },
2190
+ task_slug: { type: "string", description: "Optional task slug" },
2191
+ max_tokens: { type: "number", description: "Max tokens (default 4000)" },
2192
+ include: { type: "object", description: "What to include", properties: {
2193
+ project_context: { type: "boolean" },
2194
+ knowledge: { type: "boolean" },
2195
+ code: { type: "boolean" },
2196
+ related_files: { type: "boolean" }
2197
+ } }
2198
+ },
2199
+ required: ["query", "project"]
2200
+ }
2201
+ },
2202
+ schema: GetContextBundleSchema,
2203
+ handler: async (args) => {
2204
+ const params = args;
2205
+ const bundle = { project: params.project, query: params.query };
2206
+ if (params.include?.project_context !== false) {
2207
+ const contextFile = path11.join(getKnowledgePath(params.project), "context.md");
2208
+ if (fs11.existsSync(contextFile)) bundle.projectContext = fs11.readFileSync(contextFile, "utf-8");
2209
+ }
2210
+ if (params.include?.knowledge !== false) {
2211
+ const indexPath = path11.join(getKnowledgePath(params.project), "embeddings.json");
2212
+ if (fs11.existsSync(indexPath)) {
2213
+ bundle.knowledge = await container.rag.search(indexPath, params.query, 5);
2214
+ }
2215
+ }
2216
+ if (params.include?.code !== false) {
2217
+ const codeIndexPath = path11.join(getKnowledgePath(params.project), "code-embeddings.json");
2218
+ if (fs11.existsSync(codeIndexPath)) {
2219
+ bundle.code = await container.rag.search(codeIndexPath, params.query, 5);
2220
+ }
2221
+ }
2222
+ return { content: [{ type: "text", text: JSON.stringify(bundle, null, 2) }] };
2223
+ }
2224
+ },
2225
+ {
2226
+ definition: {
2227
+ name: "prefetch_task_context",
2228
+ description: "Pre-gather all context for a task in a single call.",
2229
+ inputSchema: {
2230
+ type: "object",
2231
+ properties: {
2232
+ project: { type: "string", description: "Project name" },
2233
+ task_slug: { type: "string", description: "Task slug" },
2234
+ max_tokens: { type: "number", description: "Max tokens (default 6000)" }
2235
+ },
2236
+ required: ["project", "task_slug"]
2237
+ }
2238
+ },
2239
+ schema: PrefetchTaskContextSchema,
2240
+ handler: async (args) => {
2241
+ const { project, task_slug } = args;
2242
+ const task = container.tasks.get(project, task_slug);
2243
+ if (!task) {
2244
+ return { content: [{ type: "text", text: `Task "${task_slug}" not found.` }], isError: true };
2245
+ }
2246
+ const bundle = { task };
2247
+ const indexPath = path11.join(getKnowledgePath(project), "embeddings.json");
2248
+ if (fs11.existsSync(indexPath) && task.title) {
2249
+ bundle.knowledge = await container.rag.search(indexPath, task.title, 5);
2250
+ }
2251
+ return { content: [{ type: "text", text: JSON.stringify(bundle, null, 2) }] };
2252
+ }
2253
+ }
2254
+ ];
2255
+ }
2256
+ function scanCodeFiles(root) {
2257
+ const ig = loadGitignore(root);
2258
+ const files = [];
2259
+ const exts = [".ts", ".tsx", ".js", ".jsx"];
2260
+ const scan = (dir) => {
2261
+ try {
2262
+ for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
2263
+ const full = path11.join(dir, entry.name);
2264
+ const rel = path11.relative(root, full);
2265
+ if (ig.ignores(rel)) continue;
2266
+ if (entry.isDirectory()) scan(full);
2267
+ else if (entry.isFile() && exts.includes(path11.extname(entry.name).toLowerCase())) {
2268
+ try {
2269
+ files.push({ path: full, content: fs11.readFileSync(full, "utf-8") });
2270
+ } catch {
2271
+ }
2272
+ }
2273
+ }
2274
+ } catch {
2275
+ }
2276
+ };
2277
+ scan(root);
2278
+ return files;
2279
+ }
2280
+ var SearchKnowledgeSchema, SearchCodeSchema, FindRelatedSchema, SearchSymbolsSchema, GetFileSummarySchema, GetContextBundleSchema, PrefetchTaskContextSchema;
2281
+ var init_search = __esm({
2282
+ "src/mcp/tools/search.ts"() {
2283
+ "use strict";
2284
+ init_paths();
2285
+ init_gitignore();
2286
+ SearchKnowledgeSchema = z3.object({
2287
+ query: z3.string(),
2288
+ project: z3.string().optional(),
2289
+ max_tokens: z3.number().optional(),
2290
+ min_score: z3.number().optional()
2291
+ });
2292
+ SearchCodeSchema = z3.object({
2293
+ query: z3.string(),
2294
+ project: z3.string().optional(),
2295
+ limit: z3.number().optional(),
2296
+ max_tokens: z3.number().optional(),
2297
+ min_score: z3.number().optional()
2298
+ });
2299
+ FindRelatedSchema = z3.object({
2300
+ file: z3.string(),
2301
+ project: z3.string(),
2302
+ include_imports: z3.boolean().optional(),
2303
+ include_imported_by: z3.boolean().optional(),
2304
+ depth: z3.number().optional()
2305
+ });
2306
+ SearchSymbolsSchema = z3.object({
2307
+ name: z3.string(),
2308
+ project: z3.string(),
2309
+ type: z3.string().optional(),
2310
+ fuzzy: z3.boolean().optional(),
2311
+ limit: z3.number().optional()
2312
+ });
2313
+ GetFileSummarySchema = z3.object({
2314
+ file: z3.string(),
2315
+ project: z3.string()
2316
+ });
2317
+ GetContextBundleSchema = z3.object({
2318
+ query: z3.string(),
2319
+ project: z3.string(),
2320
+ task_slug: z3.string().optional(),
2321
+ max_tokens: z3.number().optional(),
2322
+ include: z3.object({
2323
+ project_context: z3.boolean().optional(),
2324
+ knowledge: z3.boolean().optional(),
2325
+ code: z3.boolean().optional(),
2326
+ related_files: z3.boolean().optional()
2327
+ }).optional()
2328
+ });
2329
+ PrefetchTaskContextSchema = z3.object({
2330
+ project: z3.string(),
2331
+ task_slug: z3.string(),
2332
+ max_tokens: z3.number().optional()
2333
+ });
2334
+ }
2335
+ });
2336
+
2337
+ // src/mcp/tools/task.ts
2338
+ import { z as z4 } from "zod";
2339
+ function createTaskTools(container) {
2340
+ return [
2341
+ {
2342
+ definition: { name: "list_tasks", description: "List all tasks for a project.", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name" } }, required: ["project"] } },
2343
+ schema: ProjectSchema,
2344
+ handler: async (args) => {
2345
+ const { project } = args;
2346
+ const tasks = container.tasks.list(project);
2347
+ return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
2348
+ }
2349
+ },
2350
+ {
2351
+ definition: { name: "get_task", description: "Get details of a specific task.", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name" }, task_slug: { type: "string", description: "Task slug" } }, required: ["project", "task_slug"] } },
2352
+ schema: TaskSchema,
2353
+ handler: async (args) => {
2354
+ const { project, task_slug } = args;
2355
+ const task = container.tasks.get(project, task_slug);
2356
+ if (!task) return { content: [{ type: "text", text: `Task '${task_slug}' not found.` }], isError: true };
2357
+ return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
2358
+ }
2359
+ },
2360
+ {
2361
+ definition: { name: "create_task", description: "Create a new task.", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name" }, task_slug: { type: "string", description: "Task slug (kebab-case)" }, title: { type: "string", description: "Task title" }, summary: { type: "string", description: "Brief summary" } }, required: ["project", "task_slug"] } },
2362
+ schema: CreateTaskSchema,
2363
+ handler: async (args) => {
2364
+ const { project, task_slug, title, summary } = args;
2365
+ const task = container.tasks.create(project, task_slug, { title, summary });
2366
+ return { content: [{ type: "text", text: `Task '${task_slug}' created.
2367
+ ${JSON.stringify(task, null, 2)}` }] };
2368
+ }
2369
+ },
2370
+ {
2371
+ definition: { name: "update_task", description: "Update an existing task.", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name" }, task_slug: { type: "string", description: "Task slug" }, updates: { type: "object", description: "Fields to update" } }, required: ["project", "task_slug", "updates"] } },
2372
+ schema: UpdateTaskSchema,
2373
+ handler: async (args) => {
2374
+ const { project, task_slug, updates } = args;
2375
+ const task = container.tasks.update(project, task_slug, updates);
2376
+ if (!task) return { content: [{ type: "text", text: `Task '${task_slug}' not found.` }], isError: true };
2377
+ return { content: [{ type: "text", text: `Task '${task_slug}' updated.
2378
+ ${JSON.stringify(task, null, 2)}` }] };
2379
+ }
2380
+ },
2381
+ {
2382
+ definition: { name: "delete_task", description: "Delete a task.", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name" }, task_slug: { type: "string", description: "Task slug" } }, required: ["project", "task_slug"] } },
2383
+ schema: TaskSchema,
2384
+ handler: async (args) => {
2385
+ const { project, task_slug } = args;
2386
+ const ok = container.tasks.delete(project, task_slug);
2387
+ return { content: [{ type: "text", text: ok ? `Task '${task_slug}' deleted.` : `Failed to delete '${task_slug}'.` }] };
2388
+ }
2389
+ },
2390
+ {
2391
+ definition: { name: "search_tasks", description: "Search tasks by keyword, status, agent, or date.", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name" }, keyword: { type: "string" }, status: { type: "string" }, agent: { type: "string" }, since: { type: "string" }, limit: { type: "number" } }, required: ["project"] } },
2392
+ schema: SearchTasksSchema,
2393
+ handler: async (args) => {
2394
+ const params = args;
2395
+ const results = container.tasks.search(params.project, params);
2396
+ return { content: [{ type: "text", text: JSON.stringify({ count: results.length, tasks: results }, null, 2) }] };
2397
+ }
2398
+ },
2399
+ {
2400
+ definition: { name: "validate_phase", description: "Check if a task phase has prerequisites complete.", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name" }, task_slug: { type: "string", description: "Task slug" }, phase: { type: "string", description: "Phase to validate" } }, required: ["project", "task_slug", "phase"] } },
2401
+ schema: ValidatePhaseSchema,
2402
+ handler: async (args) => {
2403
+ const { project, task_slug, phase } = args;
2404
+ const result = container.tasks.validatePhase(project, task_slug, phase);
2405
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2406
+ }
2407
+ }
2408
+ ];
2409
+ }
2410
+ var ProjectSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, SearchTasksSchema, ValidatePhaseSchema;
2411
+ var init_task = __esm({
2412
+ "src/mcp/tools/task.ts"() {
2413
+ "use strict";
2414
+ ProjectSchema = z4.object({ project: z4.string() });
2415
+ TaskSchema = z4.object({ project: z4.string(), task_slug: z4.string() });
2416
+ CreateTaskSchema = z4.object({ project: z4.string(), task_slug: z4.string(), title: z4.string().optional(), summary: z4.string().optional() });
2417
+ UpdateTaskSchema = z4.object({ project: z4.string(), task_slug: z4.string(), updates: z4.record(z4.string(), z4.unknown()) });
2418
+ SearchTasksSchema = z4.object({ project: z4.string(), keyword: z4.string().optional(), status: z4.string().optional(), agent: z4.string().optional(), since: z4.string().optional(), limit: z4.number().optional() });
2419
+ ValidatePhaseSchema = z4.object({ project: z4.string(), task_slug: z4.string(), phase: z4.enum(["research", "planning", "execution", "documentation"]) });
2420
+ }
2421
+ });
2422
+
2423
+ // src/mcp/tools/session.ts
2424
+ import { z as z5 } from "zod";
2425
+ function createSessionTools(container) {
2426
+ return [
2427
+ {
2428
+ definition: {
2429
+ name: "start_session",
2430
+ description: "Start an agent session for task tracking.",
2431
+ inputSchema: { type: "object", properties: { project: { type: "string" }, task_slug: { type: "string" }, agent: { type: "string" }, phase: { type: "string" } }, required: ["project", "task_slug", "agent", "phase"] }
2432
+ },
2433
+ schema: StartSessionSchema,
2434
+ handler: async (args) => {
2435
+ const { project, task_slug, agent, phase } = args;
2436
+ const result = container.sessions.start(project, task_slug, agent, phase);
2437
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
2438
+ }
2439
+ },
2440
+ {
2441
+ definition: {
2442
+ name: "end_session",
2443
+ description: "End an agent session.",
2444
+ inputSchema: { type: "object", properties: { project: { type: "string" }, task_slug: { type: "string" } }, required: ["project", "task_slug"] }
2445
+ },
2446
+ schema: EndSessionSchema,
2447
+ handler: async (args) => {
2448
+ const { project, task_slug } = args;
2449
+ const result = container.sessions.end(project, task_slug);
2450
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
2451
+ }
2452
+ },
2453
+ {
2454
+ definition: {
2455
+ name: "update_agent_todos",
2456
+ description: "Update the agent todo list for a task.",
2457
+ inputSchema: {
2458
+ type: "object",
2459
+ properties: {
2460
+ project: { type: "string" },
2461
+ task_slug: { type: "string" },
2462
+ phase: { type: "string" },
2463
+ agent: { type: "string" },
2464
+ items: { type: "array", description: "Todo items", items: { type: "object", properties: { id: { type: "string" }, content: { type: "string" }, status: { type: "string" }, priority: { type: "string" } }, required: ["id", "content", "status", "priority"] } }
2465
+ },
2466
+ required: ["project", "task_slug", "phase", "agent", "items"]
2467
+ }
2468
+ },
2469
+ schema: UpdateTodosSchema,
2470
+ handler: async (args) => {
2471
+ const { project, task_slug, phase, agent, items } = args;
2472
+ const result = container.sessions.updateTodos(project, task_slug, phase, agent, items);
2473
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
2474
+ }
2475
+ }
2476
+ ];
2477
+ }
2478
+ var StartSessionSchema, EndSessionSchema, UpdateTodosSchema;
2479
+ var init_session = __esm({
2480
+ "src/mcp/tools/session.ts"() {
2481
+ "use strict";
2482
+ StartSessionSchema = z5.object({
2483
+ project: z5.string(),
2484
+ task_slug: z5.string(),
2485
+ agent: z5.string(),
2486
+ phase: z5.string()
2487
+ });
2488
+ EndSessionSchema = z5.object({ project: z5.string(), task_slug: z5.string() });
2489
+ UpdateTodosSchema = z5.object({
2490
+ project: z5.string(),
2491
+ task_slug: z5.string(),
2492
+ phase: z5.string(),
2493
+ agent: z5.string(),
2494
+ items: z5.array(z5.object({
2495
+ id: z5.string(),
2496
+ content: z5.string(),
2497
+ status: z5.enum(["pending", "in_progress", "completed"]),
2498
+ priority: z5.enum(["high", "medium", "low"])
2499
+ }))
2500
+ });
2501
+ }
2502
+ });
2503
+
2504
+ // src/mcp/tools/agent.ts
2505
+ import { z as z6 } from "zod";
2506
+ function createAgentTools(container) {
2507
+ return [
2508
+ {
2509
+ definition: {
2510
+ name: "list_agents",
2511
+ description: "List available agents and their arguments.",
2512
+ inputSchema: { type: "object", properties: {} }
2513
+ },
2514
+ handler: async () => {
2515
+ const prompts = container.prompts.getAllPrompts();
2516
+ return {
2517
+ content: [{
2518
+ type: "text",
2519
+ text: JSON.stringify(prompts.map((p) => ({
2520
+ name: p.name,
2521
+ id: p.id,
2522
+ description: p.description,
2523
+ arguments: p.arguments
2524
+ })), null, 2) + "\n\nTip: Use `get_agent_prompt` with the agent name or ID."
2525
+ }]
2526
+ };
2527
+ }
2528
+ },
2529
+ {
2530
+ definition: {
2531
+ name: "get_agent_prompt",
2532
+ description: "Get the system prompt for a specific agent.",
2533
+ inputSchema: {
2534
+ type: "object",
2535
+ properties: {
2536
+ agent: { type: "string", description: "Agent name or ID" },
2537
+ args: { type: "object", description: "Arguments for the prompt" }
2538
+ },
2539
+ required: ["agent"]
2540
+ }
2541
+ },
2542
+ schema: GetAgentPromptSchema,
2543
+ handler: async (args) => {
2544
+ const { agent, args: promptArgs } = args;
2545
+ const promptDef = container.prompts.getPrompt(agent);
2546
+ if (!promptDef) {
2547
+ const available = container.prompts.getAllPrompts().map((p) => `${p.name} (id: ${p.id})`).join(", ");
2548
+ return { content: [{ type: "text", text: `Agent not found: ${agent}. Available: ${available}` }], isError: true };
2549
+ }
2550
+ const activeProject = container.projects.findClosest();
2551
+ const context = {
2552
+ dataPath: activeProject ? getDataPath(activeProject.name) + "/" : ".ce-workflow/",
2553
+ home: getHome(),
2554
+ workspaceRoot: activeProject?.sourcePath || activeProject?.path || process.cwd(),
2555
+ workspaceName: activeProject?.name || "current-project"
2556
+ };
2557
+ const { rendered } = container.prompts.renderWithContext(
2558
+ promptDef.content,
2559
+ promptArgs || {},
2560
+ context
2561
+ );
2562
+ return { content: [{ type: "text", text: rendered }] };
2563
+ }
2564
+ }
2565
+ ];
2566
+ }
2567
+ var GetAgentPromptSchema;
2568
+ var init_agent = __esm({
2569
+ "src/mcp/tools/agent.ts"() {
2570
+ "use strict";
2571
+ init_paths();
2572
+ GetAgentPromptSchema = z6.object({
2573
+ agent: z6.string(),
2574
+ args: z6.record(z6.string(), z6.string()).optional()
2575
+ });
2576
+ }
2577
+ });
2578
+
2579
+ // src/mcp/tools/cleanup.ts
2580
+ import { z as z7 } from "zod";
2581
+ import * as fs12 from "fs";
2582
+ import * as path12 from "path";
2583
+ function createCleanupTools(container) {
2584
+ return [
2585
+ {
2586
+ definition: {
2587
+ name: "cleanup_task",
2588
+ description: "Cleanup task(s) by extracting knowledge and deleting artifacts.",
2589
+ inputSchema: {
2590
+ type: "object",
2591
+ properties: {
2592
+ project: { type: "string", description: "Project name" },
2593
+ task_slug: { type: "string", description: "Single task slug" },
2594
+ task_slugs: { type: "array", items: { type: "string" }, description: "Multiple task slugs" },
2595
+ cleanup_all: { type: "boolean", description: "Cleanup all tasks" }
2596
+ },
2597
+ required: ["project"]
2598
+ }
2599
+ },
2600
+ schema: CleanupTaskSchema,
2601
+ handler: async (args) => {
2602
+ const { project, task_slug, task_slugs, cleanup_all } = args;
2603
+ let slugs = [];
2604
+ if (cleanup_all) {
2605
+ slugs = container.tasks.list(project).map((t) => t.task_slug);
2606
+ } else if (task_slugs?.length) {
2607
+ slugs = task_slugs;
2608
+ } else if (task_slug) {
2609
+ slugs = [task_slug];
2610
+ }
2611
+ if (slugs.length === 0) {
2612
+ return { content: [{ type: "text", text: "No tasks specified for cleanup." }], isError: true };
2613
+ }
2614
+ if (slugs.length > 10) {
2615
+ return { content: [{ type: "text", text: `Too many tasks (${slugs.length}). Max 10 per batch.` }], isError: true };
2616
+ }
2617
+ let successCount = 0;
2618
+ const errors = [];
2619
+ for (const slug of slugs) {
2620
+ const task = container.tasks.get(project, slug);
2621
+ if (!task) {
2622
+ errors.push(`Task '${slug}' not found.`);
2623
+ continue;
2624
+ }
2625
+ const taskDir = path12.join(getTasksPath(project), slug);
2626
+ const artifacts = [];
2627
+ for (const phase of ["research", "planning", "execution", "docs"]) {
2628
+ const artPath = path12.join(taskDir, phase, `${slug}-${phase}.md`);
2629
+ if (fs12.existsSync(artPath)) artifacts.push(phase);
2630
+ }
2631
+ successCount++;
2632
+ }
2633
+ let msg = `Loaded ${successCount} task(s) for cleanup.`;
2634
+ if (errors.length > 0) msg += `
2635
+ Errors: ${errors.join("; ")}`;
2636
+ msg += "\n\nThe cleanup agent will now extract knowledge, check for duplicates, and delete task directories.";
2637
+ return { content: [{ type: "text", text: msg }] };
2638
+ }
2639
+ }
2640
+ ];
2641
+ }
2642
+ var CleanupTaskSchema;
2643
+ var init_cleanup = __esm({
2644
+ "src/mcp/tools/cleanup.ts"() {
2645
+ "use strict";
2646
+ init_paths();
2647
+ CleanupTaskSchema = z7.object({
2648
+ project: z7.string(),
2649
+ task_slug: z7.string().optional(),
2650
+ task_slugs: z7.array(z7.string()).optional(),
2651
+ cleanup_all: z7.boolean().optional()
2652
+ });
2653
+ }
2654
+ });
2655
+
2656
+ // src/mcp/tools/index.ts
2657
+ function createAllTools(container) {
2658
+ return [
2659
+ ...createProjectTools(container),
2660
+ ...createSearchTools(container),
2661
+ ...createTaskTools(container),
2662
+ ...createSessionTools(container),
2663
+ ...createAgentTools(container),
2664
+ ...createCleanupTools(container)
2665
+ ];
2666
+ }
2667
+ var init_tools = __esm({
2668
+ "src/mcp/tools/index.ts"() {
2669
+ "use strict";
2670
+ init_project();
2671
+ init_search();
2672
+ init_task();
2673
+ init_session();
2674
+ init_agent();
2675
+ init_cleanup();
2676
+ }
2677
+ });
2678
+
2679
+ // src/mcp/handlers/resources.ts
2680
+ import "@modelcontextprotocol/sdk/server/index.js";
2681
+ import {
2682
+ ListResourcesRequestSchema,
2683
+ ReadResourceRequestSchema
2684
+ } from "@modelcontextprotocol/sdk/types.js";
2685
+ import * as fs13 from "fs";
2686
+ import * as path13 from "path";
2687
+ function registerResourceHandlers(server, container) {
2688
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
2689
+ container.logger.debug("Listing resources");
2690
+ const config = container.config.load();
2691
+ const projects = container.projects.scan();
2692
+ const resources = [];
2693
+ resources.push({
2694
+ uri: "ce://projects",
2695
+ name: "Project List",
2696
+ description: "List of all projects exposed via MCP",
2697
+ mimeType: "application/json"
2698
+ });
2699
+ for (const project of projects) {
2700
+ if (!container.config.isProjectExposed(config, project.name, project.path)) continue;
2701
+ const permissions = container.config.getProjectPermissions(config, project.name, project.path);
2702
+ if (permissions.knowledge) {
2703
+ resources.push({
2704
+ uri: `ce://projects/${project.name}/context`,
2705
+ name: `${project.name} - Project Context`,
2706
+ description: `Project context and architecture for ${project.name}`,
2707
+ mimeType: "text/markdown"
2708
+ });
2709
+ }
2710
+ if (permissions.tasks) {
2711
+ resources.push({
2712
+ uri: `ce://projects/${project.name}/tasks`,
2713
+ name: `${project.name} - Tasks`,
2714
+ description: `Task list and status for ${project.name}`,
2715
+ mimeType: "application/json"
2716
+ });
2717
+ }
2718
+ }
2719
+ return { resources };
2720
+ });
2721
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2722
+ const { uri } = request.params;
2723
+ container.logger.info(`Reading resource: ${uri}`);
2724
+ try {
2725
+ if (uri === "ce://projects") {
2726
+ const config = container.config.load();
2727
+ const projects = container.projects.scan().filter((p) => container.config.isProjectExposed(config, p.name, p.path));
2728
+ return {
2729
+ contents: [{
2730
+ uri,
2731
+ mimeType: "application/json",
2732
+ text: JSON.stringify(projects.map((p) => ({
2733
+ name: p.name,
2734
+ source: p.source,
2735
+ path: p.path
2736
+ })), null, 2)
2737
+ }]
2738
+ };
2739
+ }
2740
+ const projectMatch = uri.match(/^ce:\/\/projects\/([^/]+)\/(.+)$/);
2741
+ if (projectMatch && projectMatch[1] && projectMatch[2]) {
2742
+ const projectName = projectMatch[1];
2743
+ const resourceType = projectMatch[2];
2744
+ const content = resourceType === "context" ? readProjectContext(projectName) : JSON.stringify(container.tasks.list(projectName), null, 2);
2745
+ if (content === null) throw new Error(`Resource not found: ${uri}`);
2746
+ return {
2747
+ contents: [{
2748
+ uri,
2749
+ mimeType: resourceType === "tasks" ? "application/json" : "text/markdown",
2750
+ text: content
2751
+ }]
2752
+ };
2753
+ }
2754
+ throw new Error(`Unknown resource: ${uri}`);
2755
+ } catch (error) {
2756
+ container.logger.error(`Failed to read resource: ${uri}`, error);
2757
+ throw error;
2758
+ }
2759
+ });
2760
+ }
2761
+ function readProjectContext(projectName) {
2762
+ const knowledgeDir = getKnowledgePath(projectName);
2763
+ if (!fs13.existsSync(knowledgeDir)) return null;
2764
+ const parts = [];
2765
+ try {
2766
+ for (const file of fs13.readdirSync(knowledgeDir)) {
2767
+ if (!file.endsWith(".md")) continue;
2768
+ const content = fs13.readFileSync(path13.join(knowledgeDir, file), "utf-8");
2769
+ parts.push(content);
2770
+ }
2771
+ } catch {
2772
+ }
2773
+ return parts.length > 0 ? parts.join("\n\n---\n\n") : null;
2774
+ }
2775
+ var init_resources = __esm({
2776
+ "src/mcp/handlers/resources.ts"() {
2777
+ "use strict";
2778
+ init_paths();
2779
+ }
2780
+ });
2781
+
2782
+ // src/mcp/handlers/prompts.ts
2783
+ import "@modelcontextprotocol/sdk/server/index.js";
2784
+ import {
2785
+ ListPromptsRequestSchema,
2786
+ GetPromptRequestSchema
2787
+ } from "@modelcontextprotocol/sdk/types.js";
2788
+ function registerPromptHandlers(server, container) {
2789
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2790
+ container.logger.debug("Listing prompts");
2791
+ const prompts = container.prompts.getAllPrompts();
2792
+ return {
2793
+ prompts: prompts.map((p) => ({
2794
+ name: p.name,
2795
+ description: p.description,
2796
+ arguments: p.arguments.map((a) => ({
2797
+ name: a.name,
2798
+ description: a.description,
2799
+ required: a.required
2800
+ }))
2801
+ }))
2802
+ };
2803
+ });
2804
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2805
+ const { name, arguments: args } = request.params;
2806
+ container.logger.info(`Getting prompt: ${name}`, args);
2807
+ const promptDef = container.prompts.getPrompt(name);
2808
+ if (!promptDef) {
2809
+ container.logger.error(`Prompt not found: ${name}`);
2810
+ throw new Error(`Prompt not found: ${name}`);
2811
+ }
2812
+ try {
2813
+ const providedArgs = args || {};
2814
+ const missingArgs = promptDef.arguments.filter((a) => a.required && !providedArgs[a.name]).map((a) => a.name);
2815
+ if (missingArgs.length > 0) {
2816
+ throw new Error(`Missing required arguments: ${missingArgs.join(", ")}`);
2817
+ }
2818
+ const renderArgs = {};
2819
+ for (const [key, val] of Object.entries(providedArgs)) {
2820
+ renderArgs[key] = String(val);
2821
+ }
2822
+ const activeProject = container.projects.findClosest();
2823
+ const context = {
2824
+ dataPath: activeProject ? getDataPath(activeProject.name) + "/" : ".ce-workflow/",
2825
+ home: getHome(),
2826
+ workspaceRoot: activeProject?.sourcePath || activeProject?.path || process.cwd(),
2827
+ workspaceName: activeProject?.name || "current-project"
2828
+ };
2829
+ const { rendered } = container.prompts.renderWithContext(
2830
+ promptDef.content,
2831
+ renderArgs,
2832
+ context
2833
+ );
2834
+ return {
2835
+ messages: [
2836
+ {
2837
+ role: "user",
2838
+ content: {
2839
+ type: "text",
2840
+ text: rendered
2841
+ }
2842
+ }
2843
+ ]
2844
+ };
2845
+ } catch (error) {
2846
+ container.logger.error(`Failed to get prompt: ${name}`, error);
2847
+ throw error;
2848
+ }
2849
+ });
2850
+ }
2851
+ var init_prompts = __esm({
2852
+ "src/mcp/handlers/prompts.ts"() {
2853
+ "use strict";
2854
+ init_paths();
2855
+ }
2856
+ });
2857
+
2858
+ // src/mcp/server.ts
2859
+ import { Server as Server3 } from "@modelcontextprotocol/sdk/server/index.js";
2860
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2861
+ import * as fs14 from "fs";
2862
+ import {
2863
+ CallToolRequestSchema,
2864
+ ListToolsRequestSchema
2865
+ } from "@modelcontextprotocol/sdk/types.js";
2866
+ var MCPServer;
2867
+ var init_server = __esm({
2868
+ "src/mcp/server.ts"() {
2869
+ "use strict";
2870
+ init_registry();
2871
+ init_tools();
2872
+ init_resources();
2873
+ init_prompts();
2874
+ MCPServer = class {
2875
+ constructor(container) {
2876
+ this.container = container;
2877
+ this.registry = new ToolRegistry();
2878
+ }
2879
+ server = null;
2880
+ registry;
2881
+ configWatcher = null;
2882
+ running = false;
2883
+ async start(options = {}) {
2884
+ const logger = this.container.logger;
2885
+ try {
2886
+ logger.info("Starting MCP Server...");
2887
+ process.on("uncaughtException", (error) => {
2888
+ logger.error("Uncaught Exception", error);
2889
+ });
2890
+ process.on("unhandledRejection", (reason) => {
2891
+ logger.error("Unhandled Rejection", reason);
2892
+ });
2893
+ const config = this.container.config.load();
2894
+ const tools = createAllTools(this.container);
2895
+ this.registry.registerMany(tools);
2896
+ this.server = new Server3(
2897
+ { name: "ce-mcp-hub", version: "0.4.0" },
2898
+ { capabilities: { resources: {}, tools: {}, prompts: {} } }
2899
+ );
2900
+ this.server.onerror = (error) => {
2901
+ logger.error("MCP Server Error", error);
2902
+ };
2903
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
2904
+ tools: this.registry.listTools()
2905
+ }));
2906
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
2907
+ const { name, arguments: args, _meta } = request.params;
2908
+ const resolvedArgs = this.injectCallerContext(args ?? {}, _meta);
2909
+ logger.info(`Tool call: ${name}`, resolvedArgs);
2910
+ return this.registry.dispatch(name, resolvedArgs);
2911
+ });
2912
+ registerResourceHandlers(this.server, this.container);
2913
+ registerPromptHandlers(this.server, this.container);
2914
+ if (!options.interactive) {
2915
+ const configPath = this.container.config.getConfigFilePath();
2916
+ try {
2917
+ if (fs14.existsSync(configPath)) {
2918
+ this.configWatcher = fs14.watch(configPath, () => {
2919
+ this.container.config.invalidate();
2920
+ logger.info("Config file changed, refreshed");
2921
+ });
2922
+ logger.info("Watching config file for changes", { configPath });
2923
+ }
2924
+ } catch (err) {
2925
+ logger.warn("Failed to watch config file", { error: String(err) });
2926
+ }
2927
+ }
2928
+ if (!options.interactive) {
2929
+ const transport = new StdioServerTransport();
2930
+ await this.server.connect(transport);
2931
+ } else {
2932
+ logger.info("Running in interactive mode (stdio transport detached)");
2933
+ }
2934
+ this.running = true;
2935
+ const projects = this.container.projects.scan();
2936
+ const exposed = projects.filter((p) => this.container.config.isProjectExposed(config, p.name, p.path)).map((p) => p.name).join(", ");
2937
+ logger.info(`CE MCP Hub started (pid: ${process.pid})`, { exposedProjects: exposed });
2938
+ if (!options.interactive) {
2939
+ console.error(`CE MCP Hub started (pid: ${process.pid})`);
2940
+ console.error(`Exposed projects: ${exposed}`);
2941
+ }
2942
+ return { port: config.server.port, pid: process.pid };
2943
+ } catch (error) {
2944
+ logger.error("Failed to start MCP server", error);
2945
+ throw error;
2946
+ }
2947
+ }
2948
+ stop() {
2949
+ if (this.configWatcher) {
2950
+ this.configWatcher.close();
2951
+ this.configWatcher = null;
2952
+ }
2953
+ if (this.server) {
2954
+ this.container.logger.info("Stopping MCP Server...");
2955
+ this.server.close();
2956
+ this.server = null;
2957
+ }
2958
+ this.running = false;
2959
+ this.container.logger.info("CE MCP Hub stopped");
2960
+ }
2961
+ isRunning() {
2962
+ return this.running;
2963
+ }
2964
+ getRegistry() {
2965
+ return this.registry;
2966
+ }
2967
+ /**
2968
+ * Auto-inject project from caller metadata when not explicitly provided.
2969
+ * MCP clients may pass _meta with workspaceRoot or cwd.
2970
+ */
2971
+ injectCallerContext(args, meta) {
2972
+ if (args["project"] || !meta) return args;
2973
+ const cwd = meta["workspaceRoot"] ?? meta["cwd"] ?? meta["rootUri"];
2974
+ if (!cwd) return args;
2975
+ const project = this.container.projects.findClosest(cwd);
2976
+ if (!project) return args;
2977
+ return { ...args, project: project.name };
2978
+ }
2979
+ };
2980
+ }
2981
+ });
2982
+
2983
+ // src/ui/components/header.tsx
2984
+ import "react";
2985
+ import { Box, Text } from "ink";
2986
+ import { jsx } from "react/jsx-runtime";
2987
+ var Header;
2988
+ var init_header = __esm({
2989
+ "src/ui/components/header.tsx"() {
2990
+ "use strict";
2991
+ Header = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingBottom: 1, children: /* @__PURE__ */ jsx(Box, { borderStyle: "double", borderColor: "white", paddingX: 2, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: " CE MCP Hub " }) }) });
2992
+ }
2993
+ });
2994
+
2995
+ // src/lib/drift-service.ts
2996
+ import * as fs15 from "fs";
2997
+ import * as path14 from "path";
2998
+ import * as crypto from "crypto";
2999
+ var DriftService;
3000
+ var init_drift_service = __esm({
3001
+ "src/lib/drift-service.ts"() {
3002
+ "use strict";
3003
+ DriftService = class {
3004
+ static CHECKSUM_FILENAME = ".ce-checksums.json";
3005
+ static calculateHash(filePath) {
3006
+ const content = fs15.readFileSync(filePath);
3007
+ return crypto.createHash("md5").update(content).digest("hex");
3008
+ }
3009
+ static getManifestPath(projectPath) {
3010
+ return path14.join(projectPath, this.CHECKSUM_FILENAME);
3011
+ }
3012
+ static loadManifest(projectPath) {
3013
+ const manifestPath = this.getManifestPath(projectPath);
3014
+ if (!fs15.existsSync(manifestPath)) return {};
3015
+ try {
3016
+ return JSON.parse(fs15.readFileSync(manifestPath, "utf8"));
3017
+ } catch {
3018
+ return {};
3019
+ }
3020
+ }
3021
+ static saveManifest(projectPath, manifest) {
3022
+ fs15.writeFileSync(this.getManifestPath(projectPath), JSON.stringify(manifest, null, 2));
3023
+ }
3024
+ static generateManifest(projectPath, files) {
3025
+ const manifest = {};
3026
+ for (const file of files) {
3027
+ const fullPath = path14.join(projectPath, file);
3028
+ if (fs15.existsSync(fullPath)) {
3029
+ const stats = fs15.statSync(fullPath);
3030
+ manifest[file] = {
3031
+ hash: this.calculateHash(fullPath),
3032
+ mtime: stats.mtimeMs
3033
+ };
3034
+ }
3035
+ }
3036
+ return manifest;
3037
+ }
3038
+ static detectModifiedFiles(projectPath) {
3039
+ const manifest = this.loadManifest(projectPath);
3040
+ const modified = [];
3041
+ const deleted = [];
3042
+ for (const [relPath, entry] of Object.entries(manifest)) {
3043
+ const fullPath = path14.join(projectPath, relPath);
3044
+ if (!fs15.existsSync(fullPath)) {
3045
+ deleted.push(relPath);
3046
+ continue;
3047
+ }
3048
+ const stats = fs15.statSync(fullPath);
3049
+ if (stats.mtimeMs === entry.mtime) continue;
3050
+ const currentHash = this.calculateHash(fullPath);
3051
+ if (currentHash !== entry.hash) {
3052
+ modified.push(relPath);
3053
+ }
3054
+ }
3055
+ return { modified, deleted };
3056
+ }
3057
+ static checkDrift(projectPath, currentVersion, runningVersion) {
3058
+ const { modified, deleted } = this.detectModifiedFiles(projectPath);
3059
+ let type = "none";
3060
+ let hasDrift = false;
3061
+ if (currentVersion !== runningVersion) {
3062
+ hasDrift = true;
3063
+ type = "version";
3064
+ } else if (modified.length > 0 || deleted.length > 0) {
3065
+ hasDrift = true;
3066
+ type = "modified";
3067
+ }
3068
+ return {
3069
+ hasDrift,
3070
+ type,
3071
+ modifiedFiles: modified,
3072
+ deletedFiles: deleted,
3073
+ version: {
3074
+ current: currentVersion || "0.0.0",
3075
+ running: runningVersion
3076
+ }
3077
+ };
3078
+ }
3079
+ };
3080
+ }
3081
+ });
3082
+
3083
+ // src/ui/providers/service-provider.tsx
3084
+ import { createContext, useContext } from "react";
3085
+ import { jsx as jsx2 } from "react/jsx-runtime";
3086
+ function useServices() {
3087
+ const context = useContext(ServiceContext);
3088
+ if (!context) throw new Error("useServices must be used within a ServiceProvider");
3089
+ return context;
3090
+ }
3091
+ var ServiceContext, ServiceProvider;
3092
+ var init_service_provider = __esm({
3093
+ "src/ui/providers/service-provider.tsx"() {
3094
+ "use strict";
3095
+ ServiceContext = createContext(null);
3096
+ ServiceProvider = ({ container, children }) => {
3097
+ return /* @__PURE__ */ jsx2(ServiceContext.Provider, { value: container, children });
3098
+ };
3099
+ }
3100
+ });
3101
+
3102
+ // src/ui/providers/config-provider.tsx
3103
+ import { createContext as createContext2, useContext as useContext2, useState, useCallback, useMemo, useEffect } from "react";
3104
+ import { jsx as jsx3 } from "react/jsx-runtime";
3105
+ function useConfig() {
3106
+ const context = useContext2(ConfigContext);
3107
+ if (!context) throw new Error("useConfig must be used within a ConfigProvider");
3108
+ return context;
3109
+ }
3110
+ var ConfigContext, ConfigProvider;
3111
+ var init_config_provider = __esm({
3112
+ "src/ui/providers/config-provider.tsx"() {
3113
+ "use strict";
3114
+ init_drift_service();
3115
+ init_service_provider();
3116
+ ConfigContext = createContext2(null);
3117
+ ConfigProvider = ({ children }) => {
3118
+ const container = useServices();
3119
+ const [config, setConfig] = useState(() => container.config.load());
3120
+ const [projects, setProjects] = useState(() => container.projects.scan());
3121
+ const [driftReports, setDriftReports] = useState({});
3122
+ const refresh = useCallback(() => {
3123
+ container.config.invalidate();
3124
+ setConfig(container.config.load());
3125
+ setProjects(container.projects.refresh());
3126
+ }, [container]);
3127
+ const checkAllDrift = useCallback(() => {
3128
+ const reports = {};
3129
+ for (const project of projects) {
3130
+ const projectConfig = container.config.findProject(config, { name: project.name, path: project.path });
3131
+ const currentVersion = projectConfig?.last_synced_version;
3132
+ reports[project.path] = DriftService.checkDrift(project.dataPath, currentVersion, "0.4.0");
3133
+ }
3134
+ setDriftReports(reports);
3135
+ }, [projects, config, container]);
3136
+ useEffect(() => {
3137
+ checkAllDrift();
3138
+ }, [checkAllDrift]);
3139
+ const exposedProjects = useMemo(
3140
+ () => projects.filter((p) => container.config.isProjectExposed(config, p.name, p.path)),
3141
+ [projects, config, container]
3142
+ );
3143
+ const value = useMemo(() => ({
3144
+ config,
3145
+ projects,
3146
+ exposedProjects,
3147
+ driftReports,
3148
+ refresh,
3149
+ checkAllDrift
3150
+ }), [config, projects, exposedProjects, driftReports, refresh, checkAllDrift]);
3151
+ return /* @__PURE__ */ jsx3(ConfigContext.Provider, { value, children });
3152
+ };
3153
+ }
3154
+ });
3155
+
3156
+ // src/ui/hooks/use-config.ts
3157
+ var init_use_config = __esm({
3158
+ "src/ui/hooks/use-config.ts"() {
3159
+ "use strict";
3160
+ init_config_provider();
3161
+ }
3162
+ });
3163
+
3164
+ // src/ui/hooks/use-services.ts
3165
+ var init_use_services = __esm({
3166
+ "src/ui/hooks/use-services.ts"() {
3167
+ "use strict";
3168
+ init_service_provider();
3169
+ }
3170
+ });
3171
+
3172
+ // src/ui/lib/ui-helpers.ts
3173
+ var getStatusIcon, getStatusColor, getChecklistProgress, getCheckbox, getProgressBar, getFolderIcon, getAgentStatusIcon, getPhaseIcon, getTreeBranch, formatRelativeTime, getTodoStatusIcon;
3174
+ var init_ui_helpers = __esm({
3175
+ "src/ui/lib/ui-helpers.ts"() {
3176
+ "use strict";
3177
+ getStatusIcon = (status) => {
3178
+ const icons = {
3179
+ pending: "\u23F3",
3180
+ in_progress: "\u{1F504}",
3181
+ blocked: "\u{1F6AB}",
3182
+ complete: "\u2705"
3183
+ };
3184
+ return icons[status] || "\u25CB";
3185
+ };
3186
+ getStatusColor = (status) => {
3187
+ const colors = {
3188
+ pending: "yellow",
3189
+ in_progress: "yellow",
3190
+ blocked: "red",
3191
+ complete: "green"
3192
+ };
3193
+ return colors[status] || "white";
3194
+ };
3195
+ getChecklistProgress = (checklist) => {
3196
+ if (!checklist || checklist.length === 0) return { completed: 0, total: 0, percentage: 0 };
3197
+ const completed = checklist.filter((item) => item.status === "done").length;
3198
+ return { completed, total: checklist.length, percentage: Math.round(completed / checklist.length * 100) };
3199
+ };
3200
+ getCheckbox = (status) => status === "done" ? "\u2611" : "\u2610";
3201
+ getProgressBar = (percentage, length = 10) => {
3202
+ const filled = Math.floor(percentage / 100 * length);
3203
+ return "\u2588".repeat(filled) + "\u2591".repeat(length - filled);
3204
+ };
3205
+ getFolderIcon = (isOpen) => isOpen ? "\u{1F4C2}" : "\u{1F4C1}";
3206
+ getAgentStatusIcon = (status) => {
3207
+ const icons = { complete: "\u2713", in_progress: "\u27F3", pending: "\u25CB", blocked: "\u2715" };
3208
+ return icons[status] || "\u2014";
3209
+ };
3210
+ getPhaseIcon = (agent) => {
3211
+ const icons = { research: "\u{1F52C}", planning: "\u{1F4DD}", executor: "\u26A1", documentation: "\u{1F4DA}" };
3212
+ return icons[agent] || "\u{1F527}";
3213
+ };
3214
+ getTreeBranch = (isLast) => isLast ? "\u2514\u2500" : "\u251C\u2500";
3215
+ formatRelativeTime = (dateString) => {
3216
+ if (!dateString) return "\u2014";
3217
+ const date = Date.parse(dateString);
3218
+ if (isNaN(date)) return "\u2014";
3219
+ const diffMs = Date.now() - date;
3220
+ const diffMins = Math.floor(diffMs / 6e4);
3221
+ const diffHours = Math.floor(diffMs / 36e5);
3222
+ const diffDays = Math.floor(diffMs / 864e5);
3223
+ if (diffMins < 1) return "just now";
3224
+ if (diffMins < 60) return `${diffMins}m ago`;
3225
+ if (diffHours < 24) return `${diffHours}h ago`;
3226
+ if (diffDays < 7) return `${diffDays}d ago`;
3227
+ return `${Math.floor(diffDays / 7)}w ago`;
3228
+ };
3229
+ getTodoStatusIcon = (status) => {
3230
+ const icons = { completed: "\u2713", in_progress: "\u27F3", pending: "\u25CB" };
3231
+ return icons[status] || "\u25CB";
3232
+ };
3233
+ }
3234
+ });
3235
+
3236
+ // src/ui/views/overview.tsx
3237
+ import { useMemo as useMemo2, memo } from "react";
3238
+ import { Box as Box2, Text as Text2 } from "ink";
3239
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
3240
+ var getElapsedTime, Overview;
3241
+ var init_overview = __esm({
3242
+ "src/ui/views/overview.tsx"() {
3243
+ "use strict";
3244
+ init_header();
3245
+ init_use_config();
3246
+ init_use_services();
3247
+ init_ui_helpers();
3248
+ getElapsedTime = (startedAt) => {
3249
+ const start = Date.parse(startedAt);
3250
+ if (isNaN(start)) return "";
3251
+ const elapsed = Date.now() - start;
3252
+ const mins = Math.floor(elapsed / 6e4);
3253
+ if (mins < 60) return `${mins}m`;
3254
+ const hours = Math.floor(mins / 60);
3255
+ return `${hours}h ${mins % 60}m`;
3256
+ };
3257
+ Overview = memo(({ serverStatus, stats, logs }) => {
3258
+ const { projects } = useConfig();
3259
+ const container = useServices();
3260
+ const activeTasks = useMemo2(() => {
3261
+ const active = [];
3262
+ for (const p of projects) {
3263
+ const tasks = container.tasks.list(p.name);
3264
+ const inProgress = tasks.filter((t) => t.status === "in_progress");
3265
+ for (const t of inProgress) {
3266
+ const session = container.sessions.getSession(p.name, t.task_slug);
3267
+ const todos = container.sessions.getTodos(p.name, t.task_slug);
3268
+ active.push({
3269
+ project: p.name,
3270
+ title: t.title || t.task_slug,
3271
+ slug: t.task_slug,
3272
+ session,
3273
+ todos
3274
+ });
3275
+ }
3276
+ }
3277
+ return active;
3278
+ }, [projects, container]);
3279
+ const recentLogs = useMemo2(() => logs.slice(-5).reverse(), [logs]);
3280
+ return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", flexGrow: 1, children: [
3281
+ /* @__PURE__ */ jsx4(Header, {}),
3282
+ /* @__PURE__ */ jsxs(Box2, { borderStyle: "round", paddingX: 1, borderColor: "white", flexDirection: "column", flexGrow: 1, children: [
3283
+ /* @__PURE__ */ jsxs(Box2, { justifyContent: "space-between", children: [
3284
+ /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", width: "50%", children: [
3285
+ /* @__PURE__ */ jsx4(Text2, { bold: true, color: "cyan", children: "System Cockpit" }),
3286
+ /* @__PURE__ */ jsxs(Box2, { marginTop: 1, children: [
3287
+ /* @__PURE__ */ jsx4(Text2, { children: "MCP Server: " }),
3288
+ /* @__PURE__ */ jsx4(Text2, { color: "green", children: "Running" }),
3289
+ /* @__PURE__ */ jsxs(Text2, { color: "dim", children: [
3290
+ " (Port: ",
3291
+ serverStatus.port,
3292
+ ")"
3293
+ ] })
3294
+ ] }),
3295
+ /* @__PURE__ */ jsxs(Box2, { children: [
3296
+ /* @__PURE__ */ jsx4(Text2, { children: "Projects: " }),
3297
+ /* @__PURE__ */ jsxs(Text2, { children: [
3298
+ stats.exposedProjects,
3299
+ " / ",
3300
+ stats.totalProjects,
3301
+ " exposed"
3302
+ ] })
3303
+ ] })
3304
+ ] }),
3305
+ /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", width: "50%", paddingLeft: 2, children: [
3306
+ /* @__PURE__ */ jsx4(Text2, { bold: true, color: "magenta", children: "Slash Commands" }),
3307
+ /* @__PURE__ */ jsxs(Box2, { marginTop: 1, flexDirection: "column", children: [
3308
+ /* @__PURE__ */ jsxs(Text2, { color: "cyan", children: [
3309
+ "/ce_init ",
3310
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "- Setup workspace" })
3311
+ ] }),
3312
+ /* @__PURE__ */ jsxs(Text2, { color: "cyan", children: [
3313
+ "/ce_research ",
3314
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "- Clarify requirements" })
3315
+ ] }),
3316
+ /* @__PURE__ */ jsxs(Text2, { color: "cyan", children: [
3317
+ "/ce_plan ",
3318
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "- Generate plan" })
3319
+ ] }),
3320
+ /* @__PURE__ */ jsxs(Text2, { color: "cyan", children: [
3321
+ "/ce_execute ",
3322
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "- Run executor" })
3323
+ ] })
3324
+ ] })
3325
+ ] })
3326
+ ] }),
3327
+ /* @__PURE__ */ jsxs(Box2, { marginTop: 1, borderStyle: "single", borderColor: "blue", flexDirection: "column", paddingX: 1, children: [
3328
+ /* @__PURE__ */ jsx4(Text2, { bold: true, color: "blue", children: "Active Tasks" }),
3329
+ activeTasks.length === 0 ? /* @__PURE__ */ jsx4(Box2, { paddingY: 1, children: /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "No tasks currently in progress." }) }) : activeTasks.slice(0, 3).map((t, i) => /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", marginTop: i === 0 ? 0 : 1, children: [
3330
+ /* @__PURE__ */ jsxs(Box2, { children: [
3331
+ /* @__PURE__ */ jsx4(Text2, { color: "yellow", children: "* " }),
3332
+ /* @__PURE__ */ jsx4(Text2, { bold: true, color: "white", children: t.project }),
3333
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: ": " }),
3334
+ /* @__PURE__ */ jsx4(Text2, { color: "white", children: t.title })
3335
+ ] }),
3336
+ t.session && /* @__PURE__ */ jsxs(Box2, { marginLeft: 3, children: [
3337
+ /* @__PURE__ */ jsxs(Text2, { children: [
3338
+ getPhaseIcon(t.session.agent),
3339
+ " "
3340
+ ] }),
3341
+ /* @__PURE__ */ jsx4(Text2, { color: "cyan", children: t.session.agent }),
3342
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: " - " }),
3343
+ /* @__PURE__ */ jsx4(Text2, { color: "white", children: t.session.phase }),
3344
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: " - " }),
3345
+ /* @__PURE__ */ jsxs(Text2, { color: "green", children: [
3346
+ getElapsedTime(t.session.started_at),
3347
+ " elapsed"
3348
+ ] })
3349
+ ] }),
3350
+ t.todos && t.todos.items.length > 0 && /* @__PURE__ */ jsx4(Box2, { flexDirection: "column", marginLeft: 3, children: t.todos.items.slice(0, 3).map((item, idx) => /* @__PURE__ */ jsxs(Box2, { children: [
3351
+ /* @__PURE__ */ jsxs(Text2, { color: "dim", children: [
3352
+ getTreeBranch(idx === Math.min(2, t.todos.items.length - 1)),
3353
+ " "
3354
+ ] }),
3355
+ /* @__PURE__ */ jsx4(Text2, { color: item.status === "completed" ? "green" : item.status === "in_progress" ? "yellow" : "dim", children: getTodoStatusIcon(item.status) }),
3356
+ /* @__PURE__ */ jsxs(Text2, { color: item.status === "completed" ? "dim" : "white", children: [
3357
+ " ",
3358
+ item.content.length > 40 ? item.content.substring(0, 37) + "..." : item.content
3359
+ ] })
3360
+ ] }, item.id)) })
3361
+ ] }, `${t.project}-${t.slug}`)),
3362
+ activeTasks.length > 3 && /* @__PURE__ */ jsxs(Text2, { color: "dim", children: [
3363
+ " ...and ",
3364
+ activeTasks.length - 3,
3365
+ " more"
3366
+ ] })
3367
+ ] }),
3368
+ /* @__PURE__ */ jsxs(Box2, { marginTop: 1, borderStyle: "single", borderColor: "dim", flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
3369
+ /* @__PURE__ */ jsx4(Text2, { bold: true, color: "dim", children: "Recent Activity" }),
3370
+ /* @__PURE__ */ jsx4(Box2, { flexDirection: "column", marginTop: 0, children: recentLogs.length === 0 ? /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "Waiting for activity..." }) : recentLogs.map((log, i) => /* @__PURE__ */ jsx4(Text2, { color: "white", wrap: "truncate-end", children: log.length > 80 ? log.substring(0, 77) + "..." : log }, i)) })
3371
+ ] }),
3372
+ /* @__PURE__ */ jsxs(Box2, { marginTop: 0, justifyContent: "space-between", children: [
3373
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "r:Restart q:Quit 1-4/arrows:Tabs" }),
3374
+ /* @__PURE__ */ jsx4(Text2, { color: "dim", children: "CE MCP Hub v0.4.0" })
3375
+ ] })
3376
+ ] })
3377
+ ] });
3378
+ });
3379
+ }
3380
+ });
3381
+
3382
+ // src/ui/views/projects-view.tsx
3383
+ import { useState as useState2, memo as memo2 } from "react";
3384
+ import { Box as Box3, Text as Text3, useInput } from "ink";
3385
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
3386
+ var ProjectsView;
3387
+ var init_projects_view = __esm({
3388
+ "src/ui/views/projects-view.tsx"() {
3389
+ "use strict";
3390
+ init_use_config();
3391
+ init_use_services();
3392
+ ProjectsView = memo2(({ config: initialConfig, projects, onConfigChange, workspacePath }) => {
3393
+ const { driftReports, checkAllDrift } = useConfig();
3394
+ const container = useServices();
3395
+ const [config, setConfig] = useState2(initialConfig);
3396
+ const [selectedIndex, setSelectedIndex] = useState2(0);
3397
+ const [selected, setSelected] = useState2(() => {
3398
+ const exposed = /* @__PURE__ */ new Set();
3399
+ for (const p of projects) {
3400
+ if (container.config.isProjectExposed(config, p.name, p.path)) {
3401
+ exposed.add(p.path);
3402
+ }
3403
+ }
3404
+ return exposed;
3405
+ });
3406
+ useInput((input, key) => {
3407
+ if (input === "u") {
3408
+ checkAllDrift();
3409
+ return;
3410
+ }
3411
+ if (key.upArrow) {
3412
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
3413
+ return;
3414
+ }
3415
+ if (key.downArrow) {
3416
+ setSelectedIndex((prev) => Math.min(projects.length - 1, prev + 1));
3417
+ return;
3418
+ }
3419
+ if (input === " ") {
3420
+ const p = projects[selectedIndex];
3421
+ if (!p) return;
3422
+ setSelected((prev) => {
3423
+ const next = new Set(prev);
3424
+ if (next.has(p.path)) next.delete(p.path);
3425
+ else next.add(p.path);
3426
+ return next;
3427
+ });
3428
+ return;
3429
+ }
3430
+ if (key.return) {
3431
+ let newConfig = { ...config };
3432
+ for (const p of projects) {
3433
+ const expose = selected.has(p.path);
3434
+ newConfig = container.config.setProject(newConfig, p.name, expose, void 0, p.path);
3435
+ }
3436
+ container.config.save(newConfig);
3437
+ setConfig(newConfig);
3438
+ onConfigChange?.();
3439
+ return;
3440
+ }
3441
+ if (input === "a") {
3442
+ const newConfig = {
3443
+ ...config,
3444
+ defaults: { ...config.defaults, includeNew: !config.defaults.includeNew }
3445
+ };
3446
+ container.config.save(newConfig);
3447
+ setConfig(newConfig);
3448
+ onConfigChange?.();
3449
+ }
3450
+ });
3451
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: "white", flexGrow: 1, children: [
3452
+ /* @__PURE__ */ jsxs2(Box3, { paddingX: 1, justifyContent: "space-between", children: [
3453
+ /* @__PURE__ */ jsxs2(Box3, { children: [
3454
+ /* @__PURE__ */ jsx5(Text3, { bold: true, color: "cyan", children: "Projects" }),
3455
+ /* @__PURE__ */ jsx5(Text3, { color: "dim", children: " | " }),
3456
+ /* @__PURE__ */ jsxs2(Text3, { color: config.defaults.includeNew ? "green" : "red", children: [
3457
+ "Auto-expose: ",
3458
+ config.defaults.includeNew ? "ON" : "OFF"
3459
+ ] })
3460
+ ] }),
3461
+ /* @__PURE__ */ jsx5(Text3, { color: "dim", children: "v0.4.0" })
3462
+ ] }),
3463
+ /* @__PURE__ */ jsx5(Box3, { marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx5(Text3, { color: "dim", children: "Manage which projects are exposed to the MCP server." }) }),
3464
+ /* @__PURE__ */ jsx5(Box3, { marginTop: 1, paddingX: 1, flexDirection: "column", flexGrow: 1, children: projects.map((p, i) => {
3465
+ const isActive = i === selectedIndex;
3466
+ const isChecked = selected.has(p.path);
3467
+ const isCurrent = p.path === workspacePath;
3468
+ const drift = driftReports[p.path];
3469
+ return /* @__PURE__ */ jsxs2(Box3, { children: [
3470
+ /* @__PURE__ */ jsx5(Text3, { color: isActive ? "cyan" : "white", children: isActive ? "> " : " " }),
3471
+ /* @__PURE__ */ jsx5(Text3, { color: isChecked ? "green" : "gray", children: isChecked ? "[x] " : "[ ] " }),
3472
+ /* @__PURE__ */ jsx5(Text3, { color: isActive ? "cyan" : "white", children: p.name }),
3473
+ isCurrent && /* @__PURE__ */ jsx5(Text3, { color: "yellow", children: " (current)" }),
3474
+ drift?.hasDrift && /* @__PURE__ */ jsx5(Text3, { color: "magenta", children: " !" })
3475
+ ] }, p.path);
3476
+ }) }),
3477
+ /* @__PURE__ */ jsxs2(Box3, { paddingX: 1, justifyContent: "space-between", children: [
3478
+ /* @__PURE__ */ jsx5(Text3, { color: "dim", children: "Space:Select Enter:Save a:Toggle Auto u:Refresh Drift" }),
3479
+ /* @__PURE__ */ jsx5(Text3, { color: "dim", children: "q to exit" })
3480
+ ] })
3481
+ ] });
3482
+ });
3483
+ }
3484
+ });
3485
+
3486
+ // src/ui/hooks/use-tasks.ts
3487
+ import { useState as useState3, useCallback as useCallback2, useMemo as useMemo4 } from "react";
3488
+ function useTasks(projects) {
3489
+ const container = useServices();
3490
+ const [taskCache, setTaskCache] = useState3({});
3491
+ const refreshForProject = useCallback2((project) => {
3492
+ const tasks = container.tasks.list(project.name);
3493
+ setTaskCache((prev) => ({ ...prev, [project.name]: tasks }));
3494
+ }, [container]);
3495
+ const refreshAll = useCallback2(() => {
3496
+ const next = {};
3497
+ for (const p of projects) {
3498
+ next[p.name] = container.tasks.list(p.name);
3499
+ }
3500
+ setTaskCache(next);
3501
+ }, [projects, container]);
3502
+ const totalCount = useMemo4(
3503
+ () => Object.values(taskCache).reduce((sum, tasks) => sum + tasks.length, 0),
3504
+ [taskCache]
3505
+ );
3506
+ return { taskCache, refreshForProject, refreshAll, totalCount };
3507
+ }
3508
+ var init_use_tasks = __esm({
3509
+ "src/ui/hooks/use-tasks.ts"() {
3510
+ "use strict";
3511
+ init_use_services();
3512
+ }
3513
+ });
3514
+
3515
+ // src/ui/components/task-row.tsx
3516
+ import "react";
3517
+ import { Box as Box4, Text as Text4 } from "ink";
3518
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
3519
+ function getActiveAgent(task) {
3520
+ const agents = task.agents;
3521
+ if (!agents) return null;
3522
+ for (const [agent, info] of Object.entries(agents)) {
3523
+ if (info?.status === "in_progress") return { agent, status: "in_progress" };
3524
+ }
3525
+ const order = ["documentation", "executor", "planning", "research"];
3526
+ for (const agent of order) {
3527
+ if (agents[agent]?.status === "complete") return { agent, status: "complete" };
3528
+ }
3529
+ return null;
3530
+ }
3531
+ var TaskRow;
3532
+ var init_task_row = __esm({
3533
+ "src/ui/components/task-row.tsx"() {
3534
+ "use strict";
3535
+ init_ui_helpers();
3536
+ TaskRow = ({
3537
+ row,
3538
+ isSelected,
3539
+ isExpanded,
3540
+ taskCount,
3541
+ hasDrift,
3542
+ isCurrentProject = false,
3543
+ isLastTask = false
3544
+ }) => {
3545
+ if (row.kind === "project") {
3546
+ const projectColor = isSelected ? "cyan" : isCurrentProject ? "yellow" : "white";
3547
+ return /* @__PURE__ */ jsxs3(Box4, { children: [
3548
+ /* @__PURE__ */ jsx6(Text4, { color: isSelected ? "cyan" : "dim", children: isSelected ? "\u25B8 " : " " }),
3549
+ /* @__PURE__ */ jsxs3(Text4, { bold: isSelected || isCurrentProject, color: projectColor, children: [
3550
+ getFolderIcon(isExpanded),
3551
+ " ",
3552
+ row.project.name
3553
+ ] }),
3554
+ isCurrentProject && /* @__PURE__ */ jsx6(Text4, { color: "yellow", dimColor: true, children: " (current)" }),
3555
+ hasDrift && /* @__PURE__ */ jsx6(Text4, { color: "magenta", children: " \u26A0" }),
3556
+ /* @__PURE__ */ jsx6(Text4, { color: "dim", children: taskCount > 0 ? ` [${taskCount}]` : "" })
3557
+ ] });
3558
+ }
3559
+ const { task } = row;
3560
+ const branch = getTreeBranch(isLastTask);
3561
+ const isPlaceholder = task.task_slug === "__none__";
3562
+ if (isPlaceholder) {
3563
+ return /* @__PURE__ */ jsxs3(Box4, { children: [
3564
+ /* @__PURE__ */ jsxs3(Text4, { color: "dim", children: [
3565
+ " ",
3566
+ branch,
3567
+ " "
3568
+ ] }),
3569
+ /* @__PURE__ */ jsx6(Text4, { color: "dim", italic: true, children: task.title })
3570
+ ] });
3571
+ }
3572
+ const activeAgent = getActiveAgent(task);
3573
+ const progress = getChecklistProgress(task.checklist || []);
3574
+ const relativeTime = task.updated_at ? formatRelativeTime(task.updated_at) : "";
3575
+ return /* @__PURE__ */ jsxs3(Box4, { children: [
3576
+ /* @__PURE__ */ jsx6(Text4, { color: isSelected ? "cyan" : "dim", children: isSelected ? "\u25B8 " : " " }),
3577
+ /* @__PURE__ */ jsxs3(Text4, { color: "dim", children: [
3578
+ branch,
3579
+ " "
3580
+ ] }),
3581
+ /* @__PURE__ */ jsx6(Text4, { color: isSelected ? "cyan" : "white", children: "\u{1F4CB} " }),
3582
+ /* @__PURE__ */ jsx6(Box4, { flexGrow: 1, children: /* @__PURE__ */ jsx6(Text4, { bold: isSelected, color: isSelected ? "cyan" : "white", children: task.title.length > 25 ? task.title.substring(0, 22) + "..." : task.title }) }),
3583
+ activeAgent && /* @__PURE__ */ jsxs3(Text4, { children: [
3584
+ /* @__PURE__ */ jsx6(Text4, { color: "dim", children: " " }),
3585
+ /* @__PURE__ */ jsx6(Text4, { children: getPhaseIcon(activeAgent.agent) }),
3586
+ /* @__PURE__ */ jsx6(Text4, { color: activeAgent.status === "in_progress" ? "yellow" : "green", children: getAgentStatusIcon(activeAgent.status) })
3587
+ ] }),
3588
+ progress.total > 0 && /* @__PURE__ */ jsxs3(Text4, { color: "dim", children: [
3589
+ " ",
3590
+ getProgressBar(progress.percentage, 6),
3591
+ " ",
3592
+ progress.completed,
3593
+ "/",
3594
+ progress.total
3595
+ ] }),
3596
+ relativeTime && relativeTime !== "\u2014" && /* @__PURE__ */ jsxs3(Text4, { color: "dim", children: [
3597
+ " ",
3598
+ relativeTime
3599
+ ] }),
3600
+ !activeAgent && task.status && /* @__PURE__ */ jsxs3(Text4, { color: getStatusColor(task.status), children: [
3601
+ " ",
3602
+ getStatusIcon(task.status),
3603
+ " ",
3604
+ task.status
3605
+ ] })
3606
+ ] });
3607
+ };
3608
+ }
3609
+ });
3610
+
3611
+ // src/ui/components/task-tree.tsx
3612
+ import "react";
3613
+ import { Box as Box5, Text as Text5 } from "ink";
3614
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
3615
+ var TaskTree;
3616
+ var init_task_tree = __esm({
3617
+ "src/ui/components/task-tree.tsx"() {
3618
+ "use strict";
3619
+ init_task_row();
3620
+ TaskTree = ({
3621
+ flattenedRows,
3622
+ selectedIndex,
3623
+ expanded,
3624
+ taskCache,
3625
+ driftReports
3626
+ }) => /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", width: "50%", borderStyle: "single", borderColor: "dim", borderRight: true, paddingX: 1, children: flattenedRows.length === 0 ? /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: [
3627
+ /* @__PURE__ */ jsx7(Text5, { color: "dim", children: "No projects detected." }),
3628
+ /* @__PURE__ */ jsx7(Text5, { color: "dim", children: "Run the wizard to set up projects." })
3629
+ ] }) : /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", marginTop: 1, children: flattenedRows.map((row, idx) => {
3630
+ const key = row.project.name;
3631
+ return /* @__PURE__ */ jsx7(
3632
+ TaskRow,
3633
+ {
3634
+ row,
3635
+ isSelected: idx === selectedIndex,
3636
+ isExpanded: expanded.has(key),
3637
+ taskCount: (taskCache[key] || []).length,
3638
+ hasDrift: !!driftReports[row.project.path]?.hasDrift,
3639
+ isCurrentProject: row.kind === "project" ? row.isCurrentProject : false,
3640
+ isLastTask: row.kind === "task" ? row.isLastTask : false
3641
+ },
3642
+ row.kind === "project" ? `p:${key}` : `t:${key}:${row.task.task_slug}`
3643
+ );
3644
+ }) }) });
3645
+ }
3646
+ });
3647
+
3648
+ // src/ui/views/tasks-view.tsx
3649
+ import { useEffect as useEffect2, useMemo as useMemo5, useState as useState4, memo as memo3 } from "react";
3650
+ import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
3651
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
3652
+ function nextStatus(current) {
3653
+ const idx = STATUS_CYCLE.indexOf(current);
3654
+ if (idx === -1) return STATUS_CYCLE[0];
3655
+ return STATUS_CYCLE[(idx + 1) % STATUS_CYCLE.length];
3656
+ }
3657
+ var STATUS_CYCLE, TasksView, TaskDetailsPane;
3658
+ var init_tasks_view = __esm({
3659
+ "src/ui/views/tasks-view.tsx"() {
3660
+ "use strict";
3661
+ init_use_config();
3662
+ init_use_tasks();
3663
+ init_use_services();
3664
+ init_task_tree();
3665
+ init_ui_helpers();
3666
+ STATUS_CYCLE = ["pending", "in_progress", "blocked", "complete"];
3667
+ TasksView = memo3(({ projects, workspacePath }) => {
3668
+ const { driftReports } = useConfig();
3669
+ const container = useServices();
3670
+ const { taskCache, refreshForProject, refreshAll, totalCount } = useTasks(projects);
3671
+ const [expanded, setExpanded] = useState4(() => /* @__PURE__ */ new Set());
3672
+ const [selectedIndex, setSelectedIndex] = useState4(0);
3673
+ const [errorLine, setErrorLine] = useState4(null);
3674
+ useEffect2(() => {
3675
+ const current = projects.find((p) => p.path === workspacePath);
3676
+ if (current && !expanded.has(current.name)) {
3677
+ setExpanded((prev) => {
3678
+ const next = new Set(prev);
3679
+ next.add(current.name);
3680
+ return next;
3681
+ });
3682
+ refreshForProject(current);
3683
+ }
3684
+ }, [projects, workspacePath]);
3685
+ const flattenedRows = useMemo5(() => {
3686
+ const rows = [];
3687
+ for (const p of projects) {
3688
+ const isCurrentProject = p.path === workspacePath;
3689
+ rows.push({ kind: "project", project: p, isCurrentProject });
3690
+ if (!expanded.has(p.name)) continue;
3691
+ const tasks = taskCache[p.name] || [];
3692
+ tasks.forEach((t, idx) => {
3693
+ rows.push({ kind: "task", project: p, task: t, isLastTask: idx === tasks.length - 1 });
3694
+ });
3695
+ if (tasks.length === 0) {
3696
+ rows.push({
3697
+ kind: "task",
3698
+ project: p,
3699
+ task: { task_slug: "__none__", title: "(no tasks)", summary: "", status: "", created_at: "", updated_at: "" },
3700
+ isLastTask: true
3701
+ });
3702
+ }
3703
+ }
3704
+ return rows;
3705
+ }, [projects, expanded, taskCache, workspacePath]);
3706
+ useInput2((input, key) => {
3707
+ if (input === "R") {
3708
+ setErrorLine(null);
3709
+ refreshAll();
3710
+ return;
3711
+ }
3712
+ if (key.upArrow) {
3713
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
3714
+ return;
3715
+ }
3716
+ if (key.downArrow) {
3717
+ setSelectedIndex((prev) => Math.min(flattenedRows.length - 1, prev + 1));
3718
+ return;
3719
+ }
3720
+ if (key.return) {
3721
+ const row = flattenedRows[selectedIndex];
3722
+ if (row?.kind === "project") {
3723
+ const next = new Set(expanded);
3724
+ if (next.has(row.project.name)) {
3725
+ next.delete(row.project.name);
3726
+ } else {
3727
+ next.add(row.project.name);
3728
+ refreshForProject(row.project);
3729
+ }
3730
+ setExpanded(next);
3731
+ }
3732
+ return;
3733
+ }
3734
+ if (input === "s") {
3735
+ const row = flattenedRows[selectedIndex];
3736
+ if (row?.kind === "task" && row.task.task_slug !== "__none__") {
3737
+ setErrorLine(null);
3738
+ const desired = nextStatus(row.task.status);
3739
+ const result = container.tasks.update(row.project.name, row.task.task_slug, { status: desired });
3740
+ if (!result) {
3741
+ setErrorLine("Failed to update status");
3742
+ return;
3743
+ }
3744
+ refreshForProject(row.project);
3745
+ }
3746
+ }
3747
+ });
3748
+ useEffect2(() => {
3749
+ setSelectedIndex((prev) => Math.min(prev, Math.max(0, flattenedRows.length - 1)));
3750
+ }, [flattenedRows]);
3751
+ const selectedRow = flattenedRows[selectedIndex];
3752
+ const selectedTask = selectedRow?.kind === "task" && selectedRow.task.task_slug !== "__none__" ? selectedRow.task : null;
3753
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "white", flexGrow: 1, children: [
3754
+ /* @__PURE__ */ jsxs5(Box6, { paddingX: 1, justifyContent: "space-between", children: [
3755
+ /* @__PURE__ */ jsxs5(Box6, { children: [
3756
+ /* @__PURE__ */ jsx8(Text6, { bold: true, color: "cyan", children: "Tasks" }),
3757
+ /* @__PURE__ */ jsx8(Text6, { color: "dim", children: " | " }),
3758
+ /* @__PURE__ */ jsxs5(Text6, { children: [
3759
+ projects.length,
3760
+ " projects"
3761
+ ] }),
3762
+ /* @__PURE__ */ jsx8(Text6, { color: "dim", children: " - " }),
3763
+ /* @__PURE__ */ jsxs5(Text6, { children: [
3764
+ totalCount,
3765
+ " tasks"
3766
+ ] })
3767
+ ] }),
3768
+ /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "v0.4.0" })
3769
+ ] }),
3770
+ errorLine && /* @__PURE__ */ jsx8(Box6, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
3771
+ "! ",
3772
+ errorLine
3773
+ ] }) }),
3774
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", flexGrow: 1, children: [
3775
+ /* @__PURE__ */ jsx8(
3776
+ TaskTree,
3777
+ {
3778
+ flattenedRows,
3779
+ selectedIndex,
3780
+ expanded,
3781
+ taskCache,
3782
+ driftReports
3783
+ }
3784
+ ),
3785
+ /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", width: "50%", paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx8(TaskDetailsPane, { task: selectedTask }) })
3786
+ ] }),
3787
+ /* @__PURE__ */ jsxs5(Box6, { paddingX: 1, justifyContent: "space-between", children: [
3788
+ /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "Up/Down:Nav Enter:Expand s:Cycle Status R:Refresh" }),
3789
+ /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "q to exit" })
3790
+ ] })
3791
+ ] });
3792
+ });
3793
+ TaskDetailsPane = ({ task }) => {
3794
+ if (!task) {
3795
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", justifyContent: "center", alignItems: "center", flexGrow: 1, children: [
3796
+ /* @__PURE__ */ jsx8(Text6, { bold: true, color: "dim", children: "No Task Selected" }),
3797
+ /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "Use arrows to navigate" })
3798
+ ] });
3799
+ }
3800
+ const checklist = task.checklist || [];
3801
+ const progress = getChecklistProgress(checklist);
3802
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
3803
+ /* @__PURE__ */ jsx8(Text6, { bold: true, color: "cyan", children: task.title || task.task_slug }),
3804
+ task.summary && /* @__PURE__ */ jsx8(Text6, { color: "white", wrap: "wrap", children: task.summary.length > 150 ? task.summary.substring(0, 147) + "..." : task.summary }),
3805
+ /* @__PURE__ */ jsx8(Box6, { marginY: 0, children: /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "\u2500".repeat(40) }) }),
3806
+ /* @__PURE__ */ jsxs5(Box6, { justifyContent: "space-between", children: [
3807
+ /* @__PURE__ */ jsx8(Text6, { bold: true, color: "white", children: "STATUS" }),
3808
+ /* @__PURE__ */ jsx8(Text6, { color: getStatusColor(task.status || ""), children: task.status?.toUpperCase() || "UNKNOWN" })
3809
+ ] }),
3810
+ /* @__PURE__ */ jsxs5(Box6, { marginLeft: 1, children: [
3811
+ /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "Updated: " }),
3812
+ /* @__PURE__ */ jsx8(Text6, { children: formatRelativeTime(task.updated_at || "") })
3813
+ ] }),
3814
+ /* @__PURE__ */ jsx8(Box6, { marginY: 0, children: /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "\u2500".repeat(40) }) }),
3815
+ /* @__PURE__ */ jsxs5(Text6, { bold: true, color: "white", children: [
3816
+ "CHECKLIST ",
3817
+ progress.total > 0 && `${getProgressBar(progress.percentage, 8)} ${progress.completed}/${progress.total}`
3818
+ ] }),
3819
+ /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginLeft: 1, children: checklist.length === 0 ? /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "No checklist items" }) : checklist.slice(0, 10).map((c, i) => /* @__PURE__ */ jsxs5(Text6, { color: c.status === "done" ? "dim" : "white", children: [
3820
+ /* @__PURE__ */ jsxs5(Text6, { color: c.status === "done" ? "green" : "dim", children: [
3821
+ getCheckbox(c.status || "pending"),
3822
+ " "
3823
+ ] }),
3824
+ (c.label || c.id || "item").substring(0, 40)
3825
+ ] }, c.id || i)) }),
3826
+ /* @__PURE__ */ jsx8(Box6, { marginY: 0, children: /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "\u2500".repeat(40) }) }),
3827
+ /* @__PURE__ */ jsx8(Text6, { bold: true, color: "white", children: "AGENTS" }),
3828
+ /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginLeft: 1, children: (() => {
3829
+ const agents = task.agents;
3830
+ if (!agents || Object.keys(agents).length === 0) {
3831
+ return /* @__PURE__ */ jsx8(Text6, { color: "dim", children: "No agent activity yet" });
3832
+ }
3833
+ return Object.entries(agents).map(([agent, info]) => /* @__PURE__ */ jsxs5(Box6, { children: [
3834
+ /* @__PURE__ */ jsxs5(Text6, { children: [
3835
+ getPhaseIcon(agent),
3836
+ " "
3837
+ ] }),
3838
+ /* @__PURE__ */ jsxs5(Text6, { color: "dim", children: [
3839
+ agent,
3840
+ ": "
3841
+ ] }),
3842
+ /* @__PURE__ */ jsx8(Text6, { color: info?.status === "complete" ? "dim" : "white", children: info?.status || "pending" })
3843
+ ] }, agent));
3844
+ })() })
3845
+ ] });
3846
+ };
3847
+ }
3848
+ });
3849
+
3850
+ // src/ui/views/log-viewer.tsx
3851
+ import { memo as memo4 } from "react";
3852
+ import { Box as Box7, Text as Text7 } from "ink";
3853
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
3854
+ var formatLog, LogViewer;
3855
+ var init_log_viewer = __esm({
3856
+ "src/ui/views/log-viewer.tsx"() {
3857
+ "use strict";
3858
+ formatLog = (log) => {
3859
+ if (log.includes("[ERROR]")) return /* @__PURE__ */ jsx9(Text7, { color: "red", children: log });
3860
+ if (log.includes("[WARN]")) return /* @__PURE__ */ jsx9(Text7, { color: "yellow", children: log });
3861
+ if (log.includes("[INFO]")) return /* @__PURE__ */ jsx9(Text7, { color: "green", children: log });
3862
+ if (log.includes("[RAG]")) return /* @__PURE__ */ jsx9(Text7, { color: "cyan", children: log });
3863
+ return /* @__PURE__ */ jsx9(Text7, { children: log });
3864
+ };
3865
+ LogViewer = memo4(({ logs, height }) => {
3866
+ const visibleLogs = logs.slice(-height);
3867
+ const emptyLines = Math.max(0, height - visibleLogs.length);
3868
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "white", paddingX: 1, height: height + 2, flexGrow: 1, children: [
3869
+ Array.from({ length: emptyLines }, (_, i) => /* @__PURE__ */ jsx9(Text7, { children: " " }, `empty-${i}`)),
3870
+ visibleLogs.map((log, i) => /* @__PURE__ */ jsx9(Box7, { children: formatLog(log) }, `log-${i}`))
3871
+ ] });
3872
+ });
3873
+ }
3874
+ });
3875
+
3876
+ // src/ui/components/status-bar.tsx
3877
+ import "react";
3878
+ import { Box as Box8, Text as Text8 } from "ink";
3879
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
3880
+ var StatusBar;
3881
+ var init_status_bar = __esm({
3882
+ "src/ui/components/status-bar.tsx"() {
3883
+ "use strict";
3884
+ StatusBar = ({ exposedLabel, port, pid, running, hasDrift }) => /* @__PURE__ */ jsx10(Box8, { borderStyle: "single", borderColor: "white", paddingX: 1, flexGrow: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
3885
+ running ? /* @__PURE__ */ jsx10(Text8, { color: "green", children: "\u25CF RUNNING" }) : /* @__PURE__ */ jsx10(Text8, { color: "red", children: "\u25CF STOPPED" }),
3886
+ " \u2502 \u{1F4CB} ",
3887
+ /* @__PURE__ */ jsx10(Text8, { color: "yellow", children: exposedLabel }),
3888
+ " \u2502 Port: ",
3889
+ /* @__PURE__ */ jsx10(Text8, { color: "green", children: port }),
3890
+ " \u2502 PID: ",
3891
+ /* @__PURE__ */ jsx10(Text8, { color: "green", children: pid }),
3892
+ hasDrift && /* @__PURE__ */ jsx10(Text8, { color: "magenta", bold: true, children: " \u2502 UPDATE AVAILABLE" })
3893
+ ] }) });
3894
+ }
3895
+ });
3896
+
3897
+ // src/ui/components/tab-bar.tsx
3898
+ import "react";
3899
+ import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
3900
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
3901
+ var TabBar;
3902
+ var init_tab_bar = __esm({
3903
+ "src/ui/components/tab-bar.tsx"() {
3904
+ "use strict";
3905
+ TabBar = ({ tabs, activeTab, onChange }) => {
3906
+ useInput3((input, key) => {
3907
+ if (tabs.length === 0) return;
3908
+ if (key.leftArrow) {
3909
+ const index = tabs.findIndex((t) => t.id === activeTab);
3910
+ if (index !== -1) {
3911
+ const next = tabs[(index - 1 + tabs.length) % tabs.length];
3912
+ if (next) onChange(next.id);
3913
+ }
3914
+ }
3915
+ if (key.rightArrow) {
3916
+ const index = tabs.findIndex((t) => t.id === activeTab);
3917
+ if (index !== -1) {
3918
+ const next = tabs[(index + 1) % tabs.length];
3919
+ if (next) onChange(next.id);
3920
+ }
3921
+ }
3922
+ const num = parseInt(input);
3923
+ if (!isNaN(num) && num > 0 && num <= tabs.length) {
3924
+ const tab = tabs[num - 1];
3925
+ if (tab) onChange(tab.id);
3926
+ }
3927
+ });
3928
+ return /* @__PURE__ */ jsxs8(Box9, { borderStyle: "single", paddingX: 1, borderColor: "gray", children: [
3929
+ tabs.map((tab) => {
3930
+ const isActive = tab.id === activeTab;
3931
+ return /* @__PURE__ */ jsx11(Box9, { marginRight: 2, children: /* @__PURE__ */ jsx11(Text9, { color: isActive ? "cyan" : "white", bold: isActive, children: isActive ? `[ ${tab.label} ]` : ` ${tab.label} ` }) }, tab.id);
3932
+ }),
3933
+ /* @__PURE__ */ jsx11(Box9, { flexGrow: 1 }),
3934
+ /* @__PURE__ */ jsx11(Text9, { color: "dim", children: "Use arrow keys to navigate" })
3935
+ ] });
3936
+ };
3937
+ }
3938
+ });
3939
+
3940
+ // src/ui/hooks/use-log-stream.ts
3941
+ import { useState as useState5, useEffect as useEffect3 } from "react";
3942
+ function useLogStream(logger, maxLines = 100) {
3943
+ const [logs, setLogs] = useState5([]);
3944
+ useEffect3(() => {
3945
+ const handler = (line) => {
3946
+ setLogs((prev) => {
3947
+ const next = [...prev, line];
3948
+ return next.length > maxLines ? next.slice(-maxLines) : next;
3949
+ });
3950
+ };
3951
+ logger.on("line", handler);
3952
+ return () => {
3953
+ logger.off("line", handler);
3954
+ };
3955
+ }, [logger, maxLines]);
3956
+ return logs;
3957
+ }
3958
+ var init_use_log_stream = __esm({
3959
+ "src/ui/hooks/use-log-stream.ts"() {
3960
+ "use strict";
3961
+ }
3962
+ });
3963
+
3964
+ // src/ui/app.tsx
3965
+ import { useState as useState6, useMemo as useMemo6, useCallback as useCallback3, memo as memo5 } from "react";
3966
+ import { Box as Box10, useInput as useInput4, useApp } from "ink";
3967
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
3968
+ var App;
3969
+ var init_app = __esm({
3970
+ "src/ui/app.tsx"() {
3971
+ "use strict";
3972
+ init_overview();
3973
+ init_projects_view();
3974
+ init_tasks_view();
3975
+ init_log_viewer();
3976
+ init_status_bar();
3977
+ init_tab_bar();
3978
+ init_use_config();
3979
+ init_use_services();
3980
+ init_use_log_stream();
3981
+ init_server();
3982
+ init_paths();
3983
+ App = memo5(({ onExit, initialPort }) => {
3984
+ const { exit } = useApp();
3985
+ const { config, projects, exposedProjects, driftReports, refresh: refreshData } = useConfig();
3986
+ const container = useServices();
3987
+ const logs = useLogStream(container.logger);
3988
+ const [activeTab, setActiveTab] = useState6("overview");
3989
+ const [serverInfo, setServerInfo] = useState6({
3990
+ port: initialPort,
3991
+ pid: process.pid,
3992
+ running: true
3993
+ // Server started before rendering
3994
+ });
3995
+ const workspacePath = useMemo6(() => detectWorkspaceRoot(), []);
3996
+ const hasAnyDrift = useMemo6(
3997
+ () => Object.values(driftReports).some((r) => r.hasDrift),
3998
+ [driftReports]
3999
+ );
4000
+ const tabs = useMemo6(() => [
4001
+ { id: "overview", label: "Overview" },
4002
+ { id: "logs", label: "Logs" },
4003
+ { id: "tasks", label: "Tasks" },
4004
+ { id: "projects", label: "Projects" }
4005
+ ], []);
4006
+ useInput4((input, key) => {
4007
+ if (input === "q" || key.ctrl && input === "c") {
4008
+ onExit();
4009
+ exit();
4010
+ }
4011
+ });
4012
+ const termHeight = process.stdout.rows || 24;
4013
+ const contentHeight = termHeight - 8;
4014
+ const handleConfigChange = useCallback3(() => {
4015
+ refreshData();
4016
+ }, [refreshData]);
4017
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", padding: 0, height: termHeight, children: [
4018
+ /* @__PURE__ */ jsx12(TabBar, { tabs, activeTab, onChange: setActiveTab }),
4019
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexGrow: 1, children: [
4020
+ activeTab === "overview" && /* @__PURE__ */ jsx12(
4021
+ Overview,
4022
+ {
4023
+ serverStatus: serverInfo,
4024
+ stats: {
4025
+ exposedProjects: exposedProjects.length,
4026
+ totalProjects: projects.length
4027
+ },
4028
+ logs
4029
+ }
4030
+ ),
4031
+ activeTab === "logs" && /* @__PURE__ */ jsx12(LogViewer, { logs, height: contentHeight }),
4032
+ activeTab === "tasks" && /* @__PURE__ */ jsx12(TasksView, { projects, workspacePath }),
4033
+ activeTab === "projects" && /* @__PURE__ */ jsx12(ProjectsView, { config, projects, onConfigChange: handleConfigChange, workspacePath })
4034
+ ] }),
4035
+ /* @__PURE__ */ jsx12(Box10, { marginTop: 0, children: /* @__PURE__ */ jsx12(
4036
+ StatusBar,
4037
+ {
4038
+ exposedLabel: `${exposedProjects.length} / ${projects.length} projects`,
4039
+ port: serverInfo.port,
4040
+ pid: serverInfo.pid,
4041
+ running: serverInfo.running,
4042
+ hasDrift: hasAnyDrift
4043
+ }
4044
+ ) })
4045
+ ] });
4046
+ });
4047
+ }
4048
+ });
4049
+
4050
+ // src/ui/index.ts
4051
+ var ui_exports = {};
4052
+ __export(ui_exports, {
4053
+ startTUI: () => startTUI
4054
+ });
4055
+ import React13 from "react";
4056
+ import { render } from "ink";
4057
+ async function startTUI(container) {
4058
+ const config = container.config.load();
4059
+ const port = config.server.port;
4060
+ const server = new MCPServer(container);
4061
+ await server.start({ interactive: true });
4062
+ process.stdin.resume();
4063
+ const app = render(
4064
+ React13.createElement(
4065
+ ServiceProvider,
4066
+ { container },
4067
+ React13.createElement(
4068
+ ConfigProvider,
4069
+ null,
4070
+ React13.createElement(App, {
4071
+ initialPort: port,
4072
+ onExit: () => {
4073
+ server.stop();
4074
+ }
4075
+ })
4076
+ )
4077
+ ),
4078
+ { exitOnCtrlC: false }
4079
+ );
4080
+ await app.waitUntilExit();
4081
+ console.clear();
4082
+ }
4083
+ var init_ui = __esm({
4084
+ "src/ui/index.ts"() {
4085
+ "use strict";
4086
+ init_app();
4087
+ init_service_provider();
4088
+ init_config_provider();
4089
+ init_server();
4090
+ }
4091
+ });
4092
+
4093
+ // src/commands/wizard/index.ts
4094
+ var wizard_exports = {};
4095
+ __export(wizard_exports, {
4096
+ runWizard: () => runWizard
4097
+ });
4098
+ import * as readline from "readline";
4099
+ function createRL() {
4100
+ return readline.createInterface({ input: process.stdin, output: process.stdout });
4101
+ }
4102
+ function ask(rl, question) {
4103
+ return new Promise((resolve3) => rl.question(question, resolve3));
4104
+ }
4105
+ function confirm(rl, question, defaultVal = true) {
4106
+ return new Promise((resolve3) => {
4107
+ const hint = defaultVal ? "[Y/n]" : "[y/N]";
4108
+ rl.question(`${question} ${hint} `, (answer) => {
4109
+ if (answer.trim() === "") resolve3(defaultVal);
4110
+ else resolve3(answer.trim().toLowerCase().startsWith("y"));
4111
+ });
4112
+ });
4113
+ }
4114
+ async function runWizard() {
4115
+ const rl = createRL();
4116
+ console.log("\n CE-Workflow Setup Wizard\n");
4117
+ console.log(" Set up your workspace for agentic code workflows.\n");
4118
+ const container = createServices();
4119
+ const workspaceRoot = detectWorkspaceRoot();
4120
+ const workspaceName = getWorkspaceName(workspaceRoot);
4121
+ console.log(` Workspace: ${workspaceName} (${workspaceRoot})`);
4122
+ const home = getHome();
4123
+ ensureDir(home);
4124
+ console.log(` Storage: ${home}
4125
+ `);
4126
+ const shouldRegister = await confirm(rl, " Register this project?");
4127
+ if (shouldRegister) {
4128
+ const config = container.config.load();
4129
+ container.config.setProject(config, workspaceName, true, void 0, workspaceRoot);
4130
+ container.config.save(config);
4131
+ console.log(` -> Project "${workspaceName}" registered and exposed.
4132
+ `);
4133
+ }
4134
+ const enableSearch = await confirm(rl, " Enable semantic search (Local RAG)?");
4135
+ if (enableSearch) {
4136
+ const config = container.config.load();
4137
+ container.config.setProject(config, workspaceName, true, void 0, workspaceRoot, { enabled: true });
4138
+ container.config.save(config);
4139
+ console.log(" -> Semantic search enabled. First use will download the embedding model.\n");
4140
+ }
4141
+ const portAnswer = await ask(rl, " MCP server port [3200]: ");
4142
+ const port = parseInt(portAnswer.trim(), 10);
4143
+ if (!isNaN(port) && port > 0) {
4144
+ const config = container.config.load();
4145
+ config.server.port = port;
4146
+ container.config.save(config);
4147
+ console.log(` -> Port set to ${port}
4148
+ `);
4149
+ }
4150
+ const shouldStart = await confirm(rl, " Start the MCP dashboard now?");
4151
+ rl.close();
4152
+ if (shouldStart) {
4153
+ console.log(" Starting MCP dashboard...\n");
4154
+ const { startTUI: startTUI2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
4155
+ await startTUI2(container);
4156
+ } else {
4157
+ console.log('\n Setup complete! Run "ce-workflow mcp" to start the dashboard.\n');
4158
+ }
4159
+ }
4160
+ var init_wizard = __esm({
4161
+ "src/commands/wizard/index.ts"() {
4162
+ "use strict";
4163
+ init_services();
4164
+ init_paths();
4165
+ }
4166
+ });
4167
+
4168
+ // src/mcp/index.ts
4169
+ init_services();
4170
+ init_server();
4171
+ init_server();
4172
+ init_registry();
4173
+ async function runMCP(subcommand2) {
4174
+ const container = createServices();
4175
+ if (subcommand2 === "start" || !process.stdout.isTTY) {
4176
+ const server = new MCPServer(container);
4177
+ await server.start({ interactive: false });
4178
+ await new Promise(() => {
4179
+ });
4180
+ return;
4181
+ }
4182
+ const { startTUI: startTUI2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
4183
+ await startTUI2(container);
4184
+ }
4185
+
4186
+ // src/index.ts
4187
+ var command = process.argv[2];
4188
+ var subcommand = process.argv[3];
4189
+ if (!command || command === "wizard" || command === "install") {
4190
+ Promise.resolve().then(() => (init_wizard(), wizard_exports)).then((m) => m.runWizard());
4191
+ } else if (command === "mcp") {
4192
+ runMCP(subcommand);
4193
+ } else {
4194
+ console.error(`Unknown command: ${command}`);
4195
+ console.error("Usage: ce-workflow [wizard|install|mcp]");
4196
+ process.exit(1);
4197
+ }