rrce-workflow 0.3.14 → 0.3.16

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 CHANGED
@@ -2524,6 +2524,234 @@ var init_dependency_graph = __esm({
2524
2524
  }
2525
2525
  });
2526
2526
 
2527
+ // src/mcp/services/symbol-extractor.ts
2528
+ function extractSymbols(content, filePath) {
2529
+ const lines = content.split("\n");
2530
+ const symbols = [];
2531
+ const exports = [];
2532
+ const imports = [];
2533
+ const language = getLanguageFromPath(filePath);
2534
+ for (let i = 0; i < lines.length; i++) {
2535
+ const line = lines[i] ?? "";
2536
+ const lineNum = i + 1;
2537
+ const trimmed = line.trim();
2538
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || !trimmed) {
2539
+ continue;
2540
+ }
2541
+ const importMatch = line.match(/^import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/);
2542
+ if (importMatch) {
2543
+ imports.push(importMatch[3] ?? "");
2544
+ continue;
2545
+ }
2546
+ const reExportMatch = line.match(/^export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
2547
+ if (reExportMatch) {
2548
+ const items = (reExportMatch[1] ?? "").split(",").map((s) => s.trim().split(" as ")[0]?.trim() ?? "");
2549
+ exports.push(...items.filter(Boolean));
2550
+ continue;
2551
+ }
2552
+ const isExported = trimmed.startsWith("export ");
2553
+ const cleanLine = isExported ? trimmed.replace(/^export\s+(default\s+)?/, "") : trimmed;
2554
+ const funcMatch = cleanLine.match(/^(?:async\s+)?function\s+(\w+)\s*(\([^)]*\))/);
2555
+ if (funcMatch && funcMatch[1]) {
2556
+ const name = funcMatch[1];
2557
+ const params = funcMatch[2] ?? "()";
2558
+ symbols.push({
2559
+ name,
2560
+ type: "function",
2561
+ line: lineNum,
2562
+ signature: `function ${name}${params}`,
2563
+ exported: isExported
2564
+ });
2565
+ if (isExported) exports.push(name);
2566
+ continue;
2567
+ }
2568
+ const arrowMatch = cleanLine.match(/^(const|let|var)\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/);
2569
+ if (arrowMatch && arrowMatch[2]) {
2570
+ const name = arrowMatch[2];
2571
+ symbols.push({
2572
+ name,
2573
+ type: "function",
2574
+ line: lineNum,
2575
+ signature: `const ${name} = (...)`,
2576
+ exported: isExported
2577
+ });
2578
+ if (isExported) exports.push(name);
2579
+ continue;
2580
+ }
2581
+ const classMatch = cleanLine.match(/^(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?/);
2582
+ if (classMatch && classMatch[1]) {
2583
+ const name = classMatch[1];
2584
+ const extendsClass = classMatch[2];
2585
+ let signature = `class ${name}`;
2586
+ if (extendsClass) signature += ` extends ${extendsClass}`;
2587
+ symbols.push({
2588
+ name,
2589
+ type: "class",
2590
+ line: lineNum,
2591
+ signature,
2592
+ exported: isExported
2593
+ });
2594
+ if (isExported) exports.push(name);
2595
+ continue;
2596
+ }
2597
+ const interfaceMatch = cleanLine.match(/^interface\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+([^{]+))?/);
2598
+ if (interfaceMatch && interfaceMatch[1]) {
2599
+ const name = interfaceMatch[1];
2600
+ symbols.push({
2601
+ name,
2602
+ type: "interface",
2603
+ line: lineNum,
2604
+ signature: `interface ${name}`,
2605
+ exported: isExported
2606
+ });
2607
+ if (isExported) exports.push(name);
2608
+ continue;
2609
+ }
2610
+ const typeMatch = cleanLine.match(/^type\s+(\w+)(?:<[^>]+>)?\s*=/);
2611
+ if (typeMatch && typeMatch[1]) {
2612
+ const name = typeMatch[1];
2613
+ symbols.push({
2614
+ name,
2615
+ type: "type",
2616
+ line: lineNum,
2617
+ signature: `type ${name}`,
2618
+ exported: isExported
2619
+ });
2620
+ if (isExported) exports.push(name);
2621
+ continue;
2622
+ }
2623
+ const enumMatch = cleanLine.match(/^(?:const\s+)?enum\s+(\w+)/);
2624
+ if (enumMatch && enumMatch[1]) {
2625
+ const name = enumMatch[1];
2626
+ symbols.push({
2627
+ name,
2628
+ type: "enum",
2629
+ line: lineNum,
2630
+ signature: `enum ${name}`,
2631
+ exported: isExported
2632
+ });
2633
+ if (isExported) exports.push(name);
2634
+ continue;
2635
+ }
2636
+ const varMatch = cleanLine.match(/^(const|let|var)\s+(\w+)\s*(?::\s*([^=]+))?\s*=/);
2637
+ if (varMatch && varMatch[2]) {
2638
+ if (line.includes("=>")) continue;
2639
+ const name = varMatch[2];
2640
+ const varType = varMatch[1];
2641
+ symbols.push({
2642
+ name,
2643
+ type: varType === "const" ? "const" : "variable",
2644
+ line: lineNum,
2645
+ signature: `${varMatch[1]} ${name}`,
2646
+ exported: isExported
2647
+ });
2648
+ if (isExported) exports.push(name);
2649
+ continue;
2650
+ }
2651
+ const defaultExportMatch = trimmed.match(/^export\s+default\s+(?:class|function)?\s*(\w+)?/);
2652
+ if (defaultExportMatch && defaultExportMatch[1]) {
2653
+ exports.push(defaultExportMatch[1]);
2654
+ }
2655
+ }
2656
+ return {
2657
+ filePath,
2658
+ language,
2659
+ symbols,
2660
+ exports: Array.from(new Set(exports)),
2661
+ imports: Array.from(new Set(imports))
2662
+ };
2663
+ }
2664
+ function fuzzyMatchScore(query, symbolName) {
2665
+ const q = query.toLowerCase();
2666
+ const s = symbolName.toLowerCase();
2667
+ if (q === s) return 1;
2668
+ if (s.startsWith(q)) return 0.9;
2669
+ if (s.includes(q)) return 0.7;
2670
+ if (q.startsWith(s)) return 0.6;
2671
+ const distance = levenshteinDistance(q, s);
2672
+ const maxLen = Math.max(q.length, s.length);
2673
+ const similarity = 1 - distance / maxLen;
2674
+ return Math.max(0, similarity * 0.5);
2675
+ }
2676
+ function levenshteinDistance(a, b) {
2677
+ const matrix = [];
2678
+ for (let i = 0; i <= b.length; i++) {
2679
+ matrix[i] = [i];
2680
+ }
2681
+ for (let j = 0; j <= a.length; j++) {
2682
+ matrix[0][j] = j;
2683
+ }
2684
+ for (let i = 1; i <= b.length; i++) {
2685
+ for (let j = 1; j <= a.length; j++) {
2686
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
2687
+ matrix[i][j] = matrix[i - 1][j - 1];
2688
+ } else {
2689
+ matrix[i][j] = Math.min(
2690
+ matrix[i - 1][j - 1] + 1,
2691
+ // substitution
2692
+ matrix[i][j - 1] + 1,
2693
+ // insertion
2694
+ matrix[i - 1][j] + 1
2695
+ // deletion
2696
+ );
2697
+ }
2698
+ }
2699
+ }
2700
+ return matrix[b.length][a.length];
2701
+ }
2702
+ function getLanguageFromPath(filePath) {
2703
+ const ext = filePath.toLowerCase().split(".").pop() ?? "";
2704
+ const langMap = {
2705
+ "ts": "typescript",
2706
+ "tsx": "typescript",
2707
+ "js": "javascript",
2708
+ "jsx": "javascript",
2709
+ "mjs": "javascript",
2710
+ "cjs": "javascript",
2711
+ "py": "python",
2712
+ "go": "go",
2713
+ "rs": "rust",
2714
+ "java": "java",
2715
+ "kt": "kotlin",
2716
+ "rb": "ruby",
2717
+ "php": "php",
2718
+ "swift": "swift",
2719
+ "c": "c",
2720
+ "cpp": "cpp",
2721
+ "h": "c",
2722
+ "hpp": "cpp",
2723
+ "cs": "csharp"
2724
+ };
2725
+ return langMap[ext] ?? "unknown";
2726
+ }
2727
+ function searchSymbols(symbolResults, query, options = {}) {
2728
+ const { type = "any", fuzzy = true, limit = 10, minScore = 0.3 } = options;
2729
+ const matches = [];
2730
+ for (const result of symbolResults) {
2731
+ for (const symbol of result.symbols) {
2732
+ if (type !== "any" && symbol.type !== type) continue;
2733
+ const score = fuzzy ? fuzzyMatchScore(query, symbol.name) : symbol.name.toLowerCase().includes(query.toLowerCase()) ? 1 : 0;
2734
+ if (score >= minScore) {
2735
+ matches.push({
2736
+ ...symbol,
2737
+ file: result.filePath,
2738
+ score
2739
+ });
2740
+ }
2741
+ }
2742
+ }
2743
+ matches.sort((a, b) => {
2744
+ if (b.score !== a.score) return b.score - a.score;
2745
+ return a.name.length - b.name.length;
2746
+ });
2747
+ return matches.slice(0, limit);
2748
+ }
2749
+ var init_symbol_extractor = __esm({
2750
+ "src/mcp/services/symbol-extractor.ts"() {
2751
+ "use strict";
2752
+ }
2753
+ });
2754
+
2527
2755
  // src/mcp/resources.ts
2528
2756
  import * as fs15 from "fs";
2529
2757
  import * as path17 from "path";
@@ -2689,7 +2917,10 @@ function getProjectTasks(projectName) {
2689
2917
  }
2690
2918
  return tasks;
2691
2919
  }
2692
- async function searchCode(query, projectFilter, limit = 10) {
2920
+ function estimateTokens(text2) {
2921
+ return Math.ceil(text2.length / 4);
2922
+ }
2923
+ async function searchCode(query, projectFilter, limit = 10, options) {
2693
2924
  const config = loadMCPConfig();
2694
2925
  const projects = getExposedProjects();
2695
2926
  const results = [];
@@ -2697,8 +2928,8 @@ async function searchCode(query, projectFilter, limit = 10) {
2697
2928
  if (projectFilter && project.name !== projectFilter) continue;
2698
2929
  const permissions = getProjectPermissions(config, project.name, project.sourcePath || project.path);
2699
2930
  if (!permissions.knowledge || !project.knowledgePath) continue;
2700
- const indexingInProgress = indexingJobs.isRunning(project.name);
2701
- const advisoryMessage = indexingInProgress ? "Indexing in progress; results may be stale/incomplete." : void 0;
2931
+ const indexingInProgress2 = indexingJobs.isRunning(project.name);
2932
+ const advisoryMessage2 = indexingInProgress2 ? "Indexing in progress; results may be stale/incomplete." : void 0;
2702
2933
  const projConfig = findProjectConfig(config, { name: project.name, path: project.sourcePath || project.path });
2703
2934
  const useRAG = projConfig?.semanticSearch?.enabled;
2704
2935
  if (!useRAG) {
@@ -2724,8 +2955,8 @@ async function searchCode(query, projectFilter, limit = 10) {
2724
2955
  context: codeChunk.context,
2725
2956
  language: codeChunk.language,
2726
2957
  score: codeChunk.score,
2727
- indexingInProgress: indexingInProgress || void 0,
2728
- advisoryMessage
2958
+ indexingInProgress: indexingInProgress2 || void 0,
2959
+ advisoryMessage: advisoryMessage2
2729
2960
  });
2730
2961
  }
2731
2962
  } catch (e) {
@@ -2733,9 +2964,56 @@ async function searchCode(query, projectFilter, limit = 10) {
2733
2964
  }
2734
2965
  }
2735
2966
  results.sort((a, b) => b.score - a.score);
2736
- return results.slice(0, limit);
2967
+ let filteredResults = results;
2968
+ if (options?.min_score !== void 0 && options.min_score > 0) {
2969
+ filteredResults = results.filter((r) => r.score >= options.min_score);
2970
+ }
2971
+ let limitedResults = filteredResults.slice(0, limit);
2972
+ let truncated = false;
2973
+ let tokenCount = 0;
2974
+ if (options?.max_tokens !== void 0 && options.max_tokens > 0) {
2975
+ const budgetedResults = [];
2976
+ for (const result of limitedResults) {
2977
+ const resultTokens = estimateTokens(result.snippet + (result.context || ""));
2978
+ if (tokenCount + resultTokens > options.max_tokens) {
2979
+ truncated = true;
2980
+ break;
2981
+ }
2982
+ budgetedResults.push(result);
2983
+ tokenCount += resultTokens;
2984
+ }
2985
+ limitedResults = budgetedResults;
2986
+ } else {
2987
+ tokenCount = limitedResults.reduce((sum, r) => sum + estimateTokens(r.snippet + (r.context || "")), 0);
2988
+ }
2989
+ let indexAgeSeconds;
2990
+ let lastIndexedAt;
2991
+ let indexingInProgress;
2992
+ let advisoryMessage;
2993
+ if (projectFilter) {
2994
+ const project = projects.find((p) => p.name === projectFilter);
2995
+ if (project) {
2996
+ indexingInProgress = indexingJobs.isRunning(project.name);
2997
+ advisoryMessage = indexingInProgress ? "Indexing in progress; results may be stale/incomplete." : void 0;
2998
+ const progress = indexingJobs.getProgress(project.name);
2999
+ if (progress.completedAt) {
3000
+ lastIndexedAt = new Date(progress.completedAt).toISOString();
3001
+ indexAgeSeconds = Math.floor((Date.now() - progress.completedAt) / 1e3);
3002
+ }
3003
+ }
3004
+ }
3005
+ const cleanResults = limitedResults.map(({ indexingInProgress: _, advisoryMessage: __, ...rest }) => rest);
3006
+ return {
3007
+ results: cleanResults,
3008
+ token_count: tokenCount,
3009
+ truncated,
3010
+ index_age_seconds: indexAgeSeconds,
3011
+ last_indexed_at: lastIndexedAt,
3012
+ indexingInProgress,
3013
+ advisoryMessage
3014
+ };
2737
3015
  }
2738
- async function searchKnowledge(query, projectFilter) {
3016
+ async function searchKnowledge(query, projectFilter, options) {
2739
3017
  const config = loadMCPConfig();
2740
3018
  const projects = getExposedProjects();
2741
3019
  const results = [];
@@ -2744,8 +3022,8 @@ async function searchKnowledge(query, projectFilter) {
2744
3022
  if (projectFilter && project.name !== projectFilter) continue;
2745
3023
  const permissions = getProjectPermissions(config, project.name, project.sourcePath || project.path);
2746
3024
  if (!permissions.knowledge || !project.knowledgePath) continue;
2747
- const indexingInProgress = indexingJobs.isRunning(project.name);
2748
- const advisoryMessage = indexingInProgress ? "Indexing in progress; results may be stale/incomplete." : void 0;
3025
+ const indexingInProgress2 = indexingJobs.isRunning(project.name);
3026
+ const advisoryMessage2 = indexingInProgress2 ? "Indexing in progress; results may be stale/incomplete." : void 0;
2749
3027
  const projConfig = findProjectConfig(config, { name: project.name, path: project.sourcePath || project.path });
2750
3028
  const useRAG = projConfig?.semanticSearch?.enabled;
2751
3029
  if (useRAG) {
@@ -2761,8 +3039,8 @@ async function searchKnowledge(query, projectFilter) {
2761
3039
  matches: [r.content],
2762
3040
  // The chunk content is the match
2763
3041
  score: r.score,
2764
- indexingInProgress: indexingInProgress || void 0,
2765
- advisoryMessage
3042
+ indexingInProgress: indexingInProgress2 || void 0,
3043
+ advisoryMessage: advisoryMessage2
2766
3044
  });
2767
3045
  }
2768
3046
  continue;
@@ -2789,8 +3067,8 @@ async function searchKnowledge(query, projectFilter) {
2789
3067
  file,
2790
3068
  matches: matches.slice(0, 5),
2791
3069
  // Limit to 5 matches per file
2792
- indexingInProgress: indexingInProgress || void 0,
2793
- advisoryMessage
3070
+ indexingInProgress: indexingInProgress2 || void 0,
3071
+ advisoryMessage: advisoryMessage2
2794
3072
  });
2795
3073
  }
2796
3074
  }
@@ -2798,7 +3076,54 @@ async function searchKnowledge(query, projectFilter) {
2798
3076
  logger.error(`[searchKnowledge] Failed to read knowledge directory ${project.knowledgePath}`, err);
2799
3077
  }
2800
3078
  }
2801
- return results;
3079
+ let filteredResults = results;
3080
+ if (options?.min_score !== void 0 && options.min_score > 0) {
3081
+ filteredResults = results.filter((r) => (r.score ?? 1) >= options.min_score);
3082
+ }
3083
+ filteredResults.sort((a, b) => (b.score ?? 1) - (a.score ?? 1));
3084
+ let truncated = false;
3085
+ let tokenCount = 0;
3086
+ let budgetedResults = filteredResults;
3087
+ if (options?.max_tokens !== void 0 && options.max_tokens > 0) {
3088
+ budgetedResults = [];
3089
+ for (const result of filteredResults) {
3090
+ const resultTokens = estimateTokens(result.matches.join("\n"));
3091
+ if (tokenCount + resultTokens > options.max_tokens) {
3092
+ truncated = true;
3093
+ break;
3094
+ }
3095
+ budgetedResults.push(result);
3096
+ tokenCount += resultTokens;
3097
+ }
3098
+ } else {
3099
+ tokenCount = filteredResults.reduce((sum, r) => sum + estimateTokens(r.matches.join("\n")), 0);
3100
+ }
3101
+ let indexAgeSeconds;
3102
+ let lastIndexedAt;
3103
+ let indexingInProgress;
3104
+ let advisoryMessage;
3105
+ if (projectFilter) {
3106
+ const project = projects.find((p) => p.name === projectFilter);
3107
+ if (project) {
3108
+ indexingInProgress = indexingJobs.isRunning(project.name);
3109
+ advisoryMessage = indexingInProgress ? "Indexing in progress; results may be stale/incomplete." : void 0;
3110
+ const progress = indexingJobs.getProgress(project.name);
3111
+ if (progress.completedAt) {
3112
+ lastIndexedAt = new Date(progress.completedAt).toISOString();
3113
+ indexAgeSeconds = Math.floor((Date.now() - progress.completedAt) / 1e3);
3114
+ }
3115
+ }
3116
+ }
3117
+ const cleanResults = budgetedResults.map(({ indexingInProgress: _, advisoryMessage: __, ...rest }) => rest);
3118
+ return {
3119
+ results: cleanResults,
3120
+ token_count: tokenCount,
3121
+ truncated,
3122
+ index_age_seconds: indexAgeSeconds,
3123
+ last_indexed_at: lastIndexedAt,
3124
+ indexingInProgress,
3125
+ advisoryMessage
3126
+ };
2802
3127
  }
2803
3128
  function getScanContext(project, scanRoot) {
2804
3129
  const gitignorePath = path17.join(scanRoot, ".gitignore");
@@ -3160,6 +3485,453 @@ async function findRelatedFiles2(filePath, projectName, options = {}) {
3160
3485
  };
3161
3486
  }
3162
3487
  }
3488
+ async function searchSymbols2(name, projectName, options = {}) {
3489
+ const config = loadMCPConfig();
3490
+ const projects = getExposedProjects();
3491
+ const project = projects.find((p) => p.name === projectName);
3492
+ if (!project) {
3493
+ return {
3494
+ success: false,
3495
+ project: projectName,
3496
+ results: [],
3497
+ message: `Project '${projectName}' not found`
3498
+ };
3499
+ }
3500
+ const projectRoot = project.sourcePath || project.path || "";
3501
+ if (!fs15.existsSync(projectRoot)) {
3502
+ return {
3503
+ success: false,
3504
+ project: projectName,
3505
+ results: [],
3506
+ message: `Project root not found: ${projectRoot}`
3507
+ };
3508
+ }
3509
+ try {
3510
+ const codeFiles = [];
3511
+ const scanDir = (dir) => {
3512
+ const entries = fs15.readdirSync(dir, { withFileTypes: true });
3513
+ for (const entry of entries) {
3514
+ const fullPath = path17.join(dir, entry.name);
3515
+ if (entry.isDirectory()) {
3516
+ if (SKIP_DIRS.includes(entry.name)) continue;
3517
+ scanDir(fullPath);
3518
+ } else if (entry.isFile()) {
3519
+ const ext = path17.extname(entry.name).toLowerCase();
3520
+ if (CODE_EXTENSIONS.includes(ext)) {
3521
+ codeFiles.push(fullPath);
3522
+ }
3523
+ }
3524
+ }
3525
+ };
3526
+ scanDir(projectRoot);
3527
+ const symbolResults = [];
3528
+ for (const file of codeFiles.slice(0, 500)) {
3529
+ try {
3530
+ const content = fs15.readFileSync(file, "utf-8");
3531
+ const result = extractSymbols(content, file);
3532
+ symbolResults.push(result);
3533
+ } catch (e) {
3534
+ }
3535
+ }
3536
+ const matches = searchSymbols(symbolResults, name, {
3537
+ type: options.type,
3538
+ fuzzy: options.fuzzy ?? true,
3539
+ limit: options.limit ?? 10,
3540
+ minScore: 0.3
3541
+ });
3542
+ const results = matches.map((m) => ({
3543
+ name: m.name,
3544
+ type: m.type,
3545
+ file: path17.relative(projectRoot, m.file),
3546
+ line: m.line,
3547
+ signature: m.signature,
3548
+ exported: m.exported,
3549
+ score: m.score
3550
+ }));
3551
+ return {
3552
+ success: true,
3553
+ project: projectName,
3554
+ results
3555
+ };
3556
+ } catch (e) {
3557
+ logger.error(`[searchSymbols] Error searching symbols in ${projectName}`, e);
3558
+ return {
3559
+ success: false,
3560
+ project: projectName,
3561
+ results: [],
3562
+ message: `Error searching symbols: ${e instanceof Error ? e.message : String(e)}`
3563
+ };
3564
+ }
3565
+ }
3566
+ async function getFileSummary(filePath, projectName) {
3567
+ const config = loadMCPConfig();
3568
+ const projects = getExposedProjects();
3569
+ const project = projects.find((p) => p.name === projectName);
3570
+ if (!project) {
3571
+ return {
3572
+ success: false,
3573
+ message: `Project '${projectName}' not found`
3574
+ };
3575
+ }
3576
+ const projectRoot = project.sourcePath || project.path || "";
3577
+ let absolutePath = filePath;
3578
+ if (!path17.isAbsolute(filePath)) {
3579
+ absolutePath = path17.resolve(projectRoot, filePath);
3580
+ }
3581
+ if (!fs15.existsSync(absolutePath)) {
3582
+ return {
3583
+ success: false,
3584
+ message: `File not found: ${filePath}`
3585
+ };
3586
+ }
3587
+ try {
3588
+ const stat = fs15.statSync(absolutePath);
3589
+ const content = fs15.readFileSync(absolutePath, "utf-8");
3590
+ const lines = content.split("\n");
3591
+ const symbolResult = extractSymbols(content, absolutePath);
3592
+ return {
3593
+ success: true,
3594
+ summary: {
3595
+ path: path17.relative(projectRoot, absolutePath),
3596
+ language: symbolResult.language,
3597
+ lines: lines.length,
3598
+ size_bytes: stat.size,
3599
+ last_modified: stat.mtime.toISOString(),
3600
+ exports: symbolResult.exports,
3601
+ imports: symbolResult.imports,
3602
+ symbols: symbolResult.symbols.map((s) => ({
3603
+ name: s.name,
3604
+ type: s.type,
3605
+ line: s.line
3606
+ }))
3607
+ }
3608
+ };
3609
+ } catch (e) {
3610
+ logger.error(`[getFileSummary] Error reading ${filePath}`, e);
3611
+ return {
3612
+ success: false,
3613
+ message: `Error reading file: ${e instanceof Error ? e.message : String(e)}`
3614
+ };
3615
+ }
3616
+ }
3617
+ async function getContextBundle(query, projectName, options = {}) {
3618
+ const maxTokens = options.max_tokens ?? 4e3;
3619
+ const include = {
3620
+ project_context: options.include?.project_context ?? true,
3621
+ knowledge: options.include?.knowledge ?? true,
3622
+ code: options.include?.code ?? true,
3623
+ related_files: options.include?.related_files ?? false
3624
+ };
3625
+ const contextBudget = Math.floor(maxTokens * 0.4);
3626
+ const knowledgeBudget = Math.floor(maxTokens * 0.3);
3627
+ const codeBudget = Math.floor(maxTokens * 0.3);
3628
+ let totalTokens = 0;
3629
+ let truncated = false;
3630
+ let indexAgeSeconds;
3631
+ let projectContext = null;
3632
+ if (include.project_context) {
3633
+ const rawContext = getProjectContext(projectName);
3634
+ if (rawContext) {
3635
+ const contextTokens = estimateTokens(rawContext);
3636
+ if (contextTokens <= contextBudget) {
3637
+ projectContext = rawContext;
3638
+ totalTokens += contextTokens;
3639
+ } else {
3640
+ const maxChars = contextBudget * 4;
3641
+ projectContext = rawContext.slice(0, maxChars) + "\n\n[truncated]";
3642
+ totalTokens += contextBudget;
3643
+ truncated = true;
3644
+ }
3645
+ }
3646
+ }
3647
+ const knowledgeResults = [];
3648
+ if (include.knowledge) {
3649
+ const knowledgeSearch = await searchKnowledge(query, projectName, { max_tokens: knowledgeBudget });
3650
+ for (const r of knowledgeSearch.results) {
3651
+ knowledgeResults.push({
3652
+ file: r.file,
3653
+ matches: r.matches,
3654
+ score: r.score
3655
+ });
3656
+ }
3657
+ totalTokens += knowledgeSearch.token_count;
3658
+ if (knowledgeSearch.truncated) truncated = true;
3659
+ if (knowledgeSearch.index_age_seconds !== void 0) {
3660
+ indexAgeSeconds = knowledgeSearch.index_age_seconds;
3661
+ }
3662
+ }
3663
+ const codeResults = [];
3664
+ if (include.code) {
3665
+ const codeSearch = await searchCode(query, projectName, 10, { max_tokens: codeBudget });
3666
+ for (const r of codeSearch.results) {
3667
+ codeResults.push({
3668
+ file: r.file,
3669
+ snippet: r.snippet,
3670
+ lineStart: r.lineStart,
3671
+ lineEnd: r.lineEnd,
3672
+ context: r.context,
3673
+ score: r.score
3674
+ });
3675
+ }
3676
+ totalTokens += codeSearch.token_count;
3677
+ if (codeSearch.truncated) truncated = true;
3678
+ if (codeSearch.index_age_seconds !== void 0 && indexAgeSeconds === void 0) {
3679
+ indexAgeSeconds = codeSearch.index_age_seconds;
3680
+ }
3681
+ }
3682
+ const relatedFiles = [];
3683
+ if (include.related_files && codeResults.length > 0) {
3684
+ const topFile = codeResults[0]?.file;
3685
+ if (topFile) {
3686
+ const related = await findRelatedFiles2(topFile, projectName, { depth: 1 });
3687
+ if (related.success) {
3688
+ for (const r of related.relationships.slice(0, 5)) {
3689
+ relatedFiles.push(r.file);
3690
+ }
3691
+ }
3692
+ }
3693
+ }
3694
+ return {
3695
+ success: true,
3696
+ project_context: projectContext,
3697
+ knowledge_results: knowledgeResults,
3698
+ code_results: codeResults,
3699
+ related_files: relatedFiles,
3700
+ token_count: totalTokens,
3701
+ truncated,
3702
+ index_age_seconds: indexAgeSeconds
3703
+ };
3704
+ }
3705
+ async function prefetchTaskContext(projectName, taskSlug, options = {}) {
3706
+ const maxTokens = options.max_tokens ?? 6e3;
3707
+ const task = getTask(projectName, taskSlug);
3708
+ if (!task) {
3709
+ return {
3710
+ success: false,
3711
+ task: null,
3712
+ project_context: null,
3713
+ referenced_files: [],
3714
+ knowledge_matches: [],
3715
+ code_matches: [],
3716
+ token_count: 0,
3717
+ truncated: false,
3718
+ message: `Task '${taskSlug}' not found in project '${projectName}'`
3719
+ };
3720
+ }
3721
+ const searchQuery = `${task.title || ""} ${task.summary || ""}`.trim();
3722
+ const bundle = await getContextBundle(searchQuery, projectName, {
3723
+ max_tokens: Math.floor(maxTokens * 0.7),
3724
+ // Reserve 30% for referenced files
3725
+ include: {
3726
+ project_context: true,
3727
+ knowledge: true,
3728
+ code: true,
3729
+ related_files: false
3730
+ }
3731
+ });
3732
+ const referencedFiles = [];
3733
+ const references = task.references;
3734
+ if (references && Array.isArray(references)) {
3735
+ for (const ref of references.slice(0, 5)) {
3736
+ const summary = await getFileSummary(ref, projectName);
3737
+ if (summary.success && summary.summary) {
3738
+ referencedFiles.push({
3739
+ path: summary.summary.path,
3740
+ language: summary.summary.language,
3741
+ lines: summary.summary.lines,
3742
+ exports: summary.summary.exports
3743
+ });
3744
+ }
3745
+ }
3746
+ }
3747
+ const taskTokens = estimateTokens(JSON.stringify(task));
3748
+ const refTokens = estimateTokens(JSON.stringify(referencedFiles));
3749
+ return {
3750
+ success: true,
3751
+ task,
3752
+ project_context: bundle.project_context,
3753
+ referenced_files: referencedFiles,
3754
+ knowledge_matches: bundle.knowledge_results,
3755
+ code_matches: bundle.code_results,
3756
+ token_count: bundle.token_count + taskTokens + refTokens,
3757
+ truncated: bundle.truncated
3758
+ };
3759
+ }
3760
+ function searchTasks(projectName, options = {}) {
3761
+ const allTasks = getProjectTasks(projectName);
3762
+ const limit = options.limit ?? 20;
3763
+ let filtered = allTasks;
3764
+ if (options.status) {
3765
+ filtered = filtered.filter((t) => t.status === options.status);
3766
+ }
3767
+ if (options.agent) {
3768
+ filtered = filtered.filter((t) => {
3769
+ const agents = t.agents;
3770
+ if (!agents) return false;
3771
+ return agents[options.agent]?.status !== void 0;
3772
+ });
3773
+ }
3774
+ if (options.since) {
3775
+ const sinceDate = new Date(options.since).getTime();
3776
+ filtered = filtered.filter((t) => {
3777
+ const updatedAt = t.updated_at;
3778
+ if (!updatedAt) return false;
3779
+ return new Date(updatedAt).getTime() >= sinceDate;
3780
+ });
3781
+ }
3782
+ if (options.keyword) {
3783
+ const kw = options.keyword.toLowerCase();
3784
+ filtered = filtered.map((t) => {
3785
+ const title = (t.title || "").toLowerCase();
3786
+ const summary = (t.summary || "").toLowerCase();
3787
+ let relevance = 0;
3788
+ if (title.includes(kw)) relevance += 2;
3789
+ if (summary.includes(kw)) relevance += 1;
3790
+ if (t.task_slug.toLowerCase().includes(kw)) relevance += 1;
3791
+ return { ...t, relevance };
3792
+ }).filter((t) => t.relevance > 0);
3793
+ filtered.sort((a, b) => (b.relevance ?? 0) - (a.relevance ?? 0));
3794
+ } else {
3795
+ filtered.sort((a, b) => {
3796
+ const aDate = new Date(a.updated_at || 0).getTime();
3797
+ const bDate = new Date(b.updated_at || 0).getTime();
3798
+ return bDate - aDate;
3799
+ });
3800
+ }
3801
+ return filtered.slice(0, limit);
3802
+ }
3803
+ function validatePhase(projectName, taskSlug, phase) {
3804
+ const task = getTask(projectName, taskSlug);
3805
+ if (!task) {
3806
+ return {
3807
+ valid: false,
3808
+ phase,
3809
+ status: "not_found",
3810
+ missing_items: ["Task does not exist"],
3811
+ suggestions: [`Create task with: create_task(project: "${projectName}", task_slug: "${taskSlug}")`]
3812
+ };
3813
+ }
3814
+ const agents = task.agents;
3815
+ const phaseData = agents?.[phase === "execution" ? "executor" : phase];
3816
+ const status = phaseData?.status || "pending";
3817
+ const missing = [];
3818
+ const suggestions = [];
3819
+ switch (phase) {
3820
+ case "research":
3821
+ if (status !== "complete") {
3822
+ missing.push("Research phase not complete");
3823
+ suggestions.push(`Run research phase: /rrce_research ${taskSlug}`);
3824
+ }
3825
+ if (!phaseData?.artifact) {
3826
+ missing.push("Research artifact not saved");
3827
+ suggestions.push("Save research brief to complete the phase");
3828
+ }
3829
+ break;
3830
+ case "planning":
3831
+ const researchStatus = agents?.research?.status;
3832
+ if (researchStatus !== "complete") {
3833
+ missing.push("Research phase not complete");
3834
+ suggestions.push(`Complete research first: /rrce_research ${taskSlug}`);
3835
+ }
3836
+ if (status !== "complete") {
3837
+ missing.push("Planning phase not complete");
3838
+ suggestions.push(`Run planning phase: /rrce_plan ${taskSlug}`);
3839
+ }
3840
+ if (!phaseData?.artifact) {
3841
+ missing.push("Planning artifact not saved");
3842
+ }
3843
+ if (!phaseData?.task_count) {
3844
+ missing.push("Task breakdown not defined");
3845
+ }
3846
+ break;
3847
+ case "execution":
3848
+ const planningStatus = agents?.planning?.status;
3849
+ if (planningStatus !== "complete") {
3850
+ missing.push("Planning phase not complete");
3851
+ suggestions.push(`Complete planning first: /rrce_plan ${taskSlug}`);
3852
+ }
3853
+ if (status !== "complete") {
3854
+ missing.push("Execution phase not complete");
3855
+ suggestions.push(`Run execution phase: /rrce_execute ${taskSlug}`);
3856
+ }
3857
+ break;
3858
+ case "documentation":
3859
+ const executorStatus = agents?.executor?.status;
3860
+ if (executorStatus !== "complete") {
3861
+ missing.push("Execution phase not complete");
3862
+ suggestions.push(`Complete execution first: /rrce_execute ${taskSlug}`);
3863
+ }
3864
+ if (status !== "complete") {
3865
+ missing.push("Documentation phase not complete");
3866
+ suggestions.push(`Run documentation phase: /rrce_docs ${taskSlug}`);
3867
+ }
3868
+ break;
3869
+ }
3870
+ return {
3871
+ valid: missing.length === 0,
3872
+ phase,
3873
+ status,
3874
+ missing_items: missing,
3875
+ suggestions
3876
+ };
3877
+ }
3878
+ function startSession(projectName, taskSlug, agent, phase) {
3879
+ const config = loadMCPConfig();
3880
+ const projects = projectService.scan();
3881
+ const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
3882
+ if (!project || !project.tasksPath) {
3883
+ return { success: false, message: `Project '${projectName}' not found or not exposed.` };
3884
+ }
3885
+ const taskDir = path17.join(project.tasksPath, taskSlug);
3886
+ if (!fs15.existsSync(taskDir)) {
3887
+ return { success: false, message: `Task '${taskSlug}' not found.` };
3888
+ }
3889
+ const session = {
3890
+ agent,
3891
+ phase,
3892
+ task_slug: taskSlug,
3893
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
3894
+ heartbeat: (/* @__PURE__ */ new Date()).toISOString()
3895
+ };
3896
+ const sessionPath = path17.join(taskDir, "session.json");
3897
+ fs15.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
3898
+ return { success: true, message: `Session started for ${agent} agent on task '${taskSlug}' (phase: ${phase})` };
3899
+ }
3900
+ function endSession(projectName, taskSlug) {
3901
+ const config = loadMCPConfig();
3902
+ const projects = projectService.scan();
3903
+ const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
3904
+ if (!project || !project.tasksPath) {
3905
+ return { success: false, message: `Project '${projectName}' not found or not exposed.` };
3906
+ }
3907
+ const sessionPath = path17.join(project.tasksPath, taskSlug, "session.json");
3908
+ if (!fs15.existsSync(sessionPath)) {
3909
+ return { success: true, message: `No active session for task '${taskSlug}'.` };
3910
+ }
3911
+ fs15.unlinkSync(sessionPath);
3912
+ return { success: true, message: `Session ended for task '${taskSlug}'.` };
3913
+ }
3914
+ function updateAgentTodos(projectName, taskSlug, phase, agent, items) {
3915
+ const config = loadMCPConfig();
3916
+ const projects = projectService.scan();
3917
+ const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
3918
+ if (!project || !project.tasksPath) {
3919
+ return { success: false, message: `Project '${projectName}' not found or not exposed.` };
3920
+ }
3921
+ const taskDir = path17.join(project.tasksPath, taskSlug);
3922
+ if (!fs15.existsSync(taskDir)) {
3923
+ fs15.mkdirSync(taskDir, { recursive: true });
3924
+ }
3925
+ const todos = {
3926
+ phase,
3927
+ agent,
3928
+ items,
3929
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3930
+ };
3931
+ const todosPath = path17.join(taskDir, "agent-todos.json");
3932
+ fs15.writeFileSync(todosPath, JSON.stringify(todos, null, 2));
3933
+ return { success: true, message: `Updated ${items.length} todo items for task '${taskSlug}'.`, count: items.length };
3934
+ }
3163
3935
  var INDEXABLE_EXTENSIONS, CODE_EXTENSIONS, SKIP_DIRS;
3164
3936
  var init_resources = __esm({
3165
3937
  "src/mcp/resources.ts"() {
@@ -3173,6 +3945,7 @@ var init_resources = __esm({
3173
3945
  init_indexing_jobs();
3174
3946
  init_context_extractor();
3175
3947
  init_dependency_graph();
3948
+ init_symbol_extractor();
3176
3949
  init_paths();
3177
3950
  INDEXABLE_EXTENSIONS = [
3178
3951
  ".ts",
@@ -3476,25 +4249,29 @@ function registerToolHandlers(server) {
3476
4249
  },
3477
4250
  {
3478
4251
  name: "search_knowledge",
3479
- description: "Search across all exposed project knowledge bases",
4252
+ description: "Search across all exposed project knowledge bases. Returns results with token count and optional truncation.",
3480
4253
  inputSchema: {
3481
4254
  type: "object",
3482
4255
  properties: {
3483
4256
  query: { type: "string", description: "Search query to find in knowledge files" },
3484
- project: { type: "string", description: "Optional: limit search to specific project name" }
4257
+ project: { type: "string", description: "Optional: limit search to specific project name" },
4258
+ max_tokens: { type: "number", description: "Optional: maximum tokens for response (truncates by relevance)" },
4259
+ min_score: { type: "number", description: "Optional: minimum relevance score threshold (0-1)" }
3485
4260
  },
3486
4261
  required: ["query"]
3487
4262
  }
3488
4263
  },
3489
4264
  {
3490
4265
  name: "search_code",
3491
- description: "Semantic search across code files. Returns code snippets with line numbers and function/class context. Use for finding implementations, patterns, or understanding code.",
4266
+ description: "Semantic search across code files. Returns code snippets with line numbers, function/class context, and token budget support.",
3492
4267
  inputSchema: {
3493
4268
  type: "object",
3494
4269
  properties: {
3495
4270
  query: { type: "string", description: 'Search query (e.g., "error handling", "authentication logic", "database connection")' },
3496
4271
  project: { type: "string", description: "Optional: limit search to specific project name" },
3497
- limit: { type: "number", description: "Maximum number of results (default 10)" }
4272
+ limit: { type: "number", description: "Maximum number of results (default 10)" },
4273
+ max_tokens: { type: "number", description: "Optional: maximum tokens for response (truncates by relevance)" },
4274
+ min_score: { type: "number", description: "Optional: minimum relevance score threshold (0-1)" }
3498
4275
  },
3499
4276
  required: ["query"]
3500
4277
  }
@@ -3514,6 +4291,99 @@ function registerToolHandlers(server) {
3514
4291
  required: ["file", "project"]
3515
4292
  }
3516
4293
  },
4294
+ {
4295
+ name: "search_symbols",
4296
+ description: "Search for code symbols (functions, classes, types, variables) by name. Uses fuzzy matching. Faster than search_code for finding definitions.",
4297
+ inputSchema: {
4298
+ type: "object",
4299
+ properties: {
4300
+ name: { type: "string", description: "Symbol name to search for" },
4301
+ project: { type: "string", description: "Name of the project" },
4302
+ type: { type: "string", enum: ["function", "class", "type", "interface", "variable", "const", "enum", "any"], description: "Filter by symbol type (default: any)" },
4303
+ fuzzy: { type: "boolean", description: "Use fuzzy matching (default: true)" },
4304
+ limit: { type: "number", description: "Maximum results (default: 10)" }
4305
+ },
4306
+ required: ["name", "project"]
4307
+ }
4308
+ },
4309
+ {
4310
+ name: "get_file_summary",
4311
+ description: "Get a quick summary of a file without reading full content. Returns: language, LOC, exports, imports, key symbols.",
4312
+ inputSchema: {
4313
+ type: "object",
4314
+ properties: {
4315
+ file: { type: "string", description: "Path to the file (absolute or project-relative)" },
4316
+ project: { type: "string", description: "Name of the project" }
4317
+ },
4318
+ required: ["file", "project"]
4319
+ }
4320
+ },
4321
+ {
4322
+ name: "get_context_bundle",
4323
+ description: "Get bundled context for a query: project context + knowledge + code in one call. Reduces multi-tool chaining. Respects token budget.",
4324
+ inputSchema: {
4325
+ type: "object",
4326
+ properties: {
4327
+ query: { type: "string", description: "Natural language query or task description" },
4328
+ project: { type: "string", description: "Name of the project" },
4329
+ task_slug: { type: "string", description: "Optional: task slug to include task-specific context" },
4330
+ max_tokens: { type: "number", description: "Max tokens for response (default: 4000)" },
4331
+ include: {
4332
+ type: "object",
4333
+ description: "What to include in the bundle",
4334
+ properties: {
4335
+ project_context: { type: "boolean", description: "Include project context (default: true)" },
4336
+ knowledge: { type: "boolean", description: "Include knowledge search results (default: true)" },
4337
+ code: { type: "boolean", description: "Include code search results (default: true)" },
4338
+ related_files: { type: "boolean", description: "Include related files (default: false)" }
4339
+ }
4340
+ }
4341
+ },
4342
+ required: ["query", "project"]
4343
+ }
4344
+ },
4345
+ {
4346
+ name: "prefetch_task_context",
4347
+ description: "Pre-gather all context for a task: task meta, referenced files, knowledge matches, code matches. Single call for task-aware context.",
4348
+ inputSchema: {
4349
+ type: "object",
4350
+ properties: {
4351
+ project: { type: "string", description: "Name of the project" },
4352
+ task_slug: { type: "string", description: "The task slug" },
4353
+ max_tokens: { type: "number", description: "Max tokens for response (default: 6000)" }
4354
+ },
4355
+ required: ["project", "task_slug"]
4356
+ }
4357
+ },
4358
+ {
4359
+ name: "search_tasks",
4360
+ description: "Search across all tasks by keyword, status, agent phase, or date. Returns matching tasks sorted by relevance.",
4361
+ inputSchema: {
4362
+ type: "object",
4363
+ properties: {
4364
+ project: { type: "string", description: "Name of the project" },
4365
+ keyword: { type: "string", description: "Search in title/summary" },
4366
+ status: { type: "string", description: "Filter by status (draft, in_progress, complete, etc.)" },
4367
+ agent: { type: "string", description: "Filter by agent phase (research, planning, executor, documentation)" },
4368
+ since: { type: "string", description: "ISO date - tasks updated after this date" },
4369
+ limit: { type: "number", description: "Max results (default: 20)" }
4370
+ },
4371
+ required: ["project"]
4372
+ }
4373
+ },
4374
+ {
4375
+ name: "validate_phase",
4376
+ description: "Check if a task phase has all prerequisites complete. Returns validation result with missing items and suggestions.",
4377
+ inputSchema: {
4378
+ type: "object",
4379
+ properties: {
4380
+ project: { type: "string", description: "Name of the project" },
4381
+ task_slug: { type: "string", description: "The task slug" },
4382
+ phase: { type: "string", enum: ["research", "planning", "execution", "documentation"], description: "Phase to validate" }
4383
+ },
4384
+ required: ["project", "task_slug", "phase"]
4385
+ }
4386
+ },
3517
4387
  {
3518
4388
  name: "index_knowledge",
3519
4389
  description: "Update the semantic search index for a specific project",
@@ -3614,7 +4484,61 @@ function registerToolHandlers(server) {
3614
4484
  project: { type: "string", description: "Name of the project" },
3615
4485
  task_slug: { type: "string", description: "The slug of the task to delete" }
3616
4486
  },
3617
- required: ["project", "task_slug"]
4487
+ required: ["project", "task_slug"]
4488
+ }
4489
+ },
4490
+ {
4491
+ name: "start_session",
4492
+ description: "Start an agent session for active task tracking. Call this when beginning work on a task phase.",
4493
+ inputSchema: {
4494
+ type: "object",
4495
+ properties: {
4496
+ project: { type: "string", description: "Name of the project" },
4497
+ task_slug: { type: "string", description: "The slug of the task" },
4498
+ agent: { type: "string", description: "Agent type: research, planning, executor, or documentation" },
4499
+ phase: { type: "string", description: 'Current phase description (e.g., "clarification", "task breakdown")' }
4500
+ },
4501
+ required: ["project", "task_slug", "agent", "phase"]
4502
+ }
4503
+ },
4504
+ {
4505
+ name: "end_session",
4506
+ description: "End an agent session. Call this before emitting completion signal.",
4507
+ inputSchema: {
4508
+ type: "object",
4509
+ properties: {
4510
+ project: { type: "string", description: "Name of the project" },
4511
+ task_slug: { type: "string", description: "The slug of the task" }
4512
+ },
4513
+ required: ["project", "task_slug"]
4514
+ }
4515
+ },
4516
+ {
4517
+ name: "update_agent_todos",
4518
+ description: "Update the agent todo list for a task. Use this to track granular work items during a phase.",
4519
+ inputSchema: {
4520
+ type: "object",
4521
+ properties: {
4522
+ project: { type: "string", description: "Name of the project" },
4523
+ task_slug: { type: "string", description: "The slug of the task" },
4524
+ phase: { type: "string", description: "Current phase" },
4525
+ agent: { type: "string", description: "Agent type" },
4526
+ items: {
4527
+ type: "array",
4528
+ description: "Todo items array",
4529
+ items: {
4530
+ type: "object",
4531
+ properties: {
4532
+ id: { type: "string" },
4533
+ content: { type: "string" },
4534
+ status: { type: "string", enum: ["pending", "in_progress", "completed"] },
4535
+ priority: { type: "string", enum: ["high", "medium", "low"] }
4536
+ },
4537
+ required: ["id", "content", "status", "priority"]
4538
+ }
4539
+ }
4540
+ },
4541
+ required: ["project", "task_slug", "phase", "agent", "items"]
3618
4542
  }
3619
4543
  }
3620
4544
  ];
@@ -3640,13 +4564,19 @@ function registerToolHandlers(server) {
3640
4564
  }
3641
4565
  case "search_knowledge": {
3642
4566
  const params = args;
3643
- const results = await searchKnowledge(params.query, params.project);
3644
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
4567
+ const result = await searchKnowledge(params.query, params.project, {
4568
+ max_tokens: params.max_tokens,
4569
+ min_score: params.min_score
4570
+ });
4571
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3645
4572
  }
3646
4573
  case "search_code": {
3647
4574
  const params = args;
3648
- const results = await searchCode(params.query, params.project, params.limit);
3649
- if (results.length === 0) {
4575
+ const result = await searchCode(params.query, params.project, params.limit, {
4576
+ max_tokens: params.max_tokens,
4577
+ min_score: params.min_score
4578
+ });
4579
+ if (result.results.length === 0) {
3650
4580
  return {
3651
4581
  content: [{
3652
4582
  type: "text",
@@ -3654,7 +4584,7 @@ function registerToolHandlers(server) {
3654
4584
  }]
3655
4585
  };
3656
4586
  }
3657
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
4587
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3658
4588
  }
3659
4589
  case "find_related_files": {
3660
4590
  const params = args;
@@ -3665,6 +4595,35 @@ function registerToolHandlers(server) {
3665
4595
  });
3666
4596
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3667
4597
  }
4598
+ case "search_symbols": {
4599
+ const params = args;
4600
+ const result = await searchSymbols2(params.name, params.project, {
4601
+ type: params.type,
4602
+ fuzzy: params.fuzzy,
4603
+ limit: params.limit
4604
+ });
4605
+ if (!result.success) {
4606
+ return { content: [{ type: "text", text: result.message || "Search failed" }], isError: true };
4607
+ }
4608
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4609
+ }
4610
+ case "get_file_summary": {
4611
+ const params = args;
4612
+ const result = await getFileSummary(params.file, params.project);
4613
+ if (!result.success) {
4614
+ return { content: [{ type: "text", text: result.message || "Failed to get file summary" }], isError: true };
4615
+ }
4616
+ return { content: [{ type: "text", text: JSON.stringify(result.summary, null, 2) }] };
4617
+ }
4618
+ case "get_context_bundle": {
4619
+ const params = args;
4620
+ const result = await getContextBundle(params.query, params.project, {
4621
+ task_slug: params.task_slug,
4622
+ max_tokens: params.max_tokens,
4623
+ include: params.include
4624
+ });
4625
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4626
+ }
3668
4627
  case "index_knowledge": {
3669
4628
  const params = args;
3670
4629
  const result = await indexKnowledge(params.project, params.force);
@@ -3756,6 +4715,47 @@ ${JSON.stringify(task, null, 2)}` }] };
3756
4715
  const success = deleteTask(params.project, params.task_slug);
3757
4716
  return { content: [{ type: "text", text: success ? `\u2713 Task '${params.task_slug}' deleted.` : `\u2717 Failed to delete '${params.task_slug}'.` }] };
3758
4717
  }
4718
+ case "start_session": {
4719
+ const params = args;
4720
+ const result = startSession(params.project, params.task_slug, params.agent, params.phase);
4721
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
4722
+ }
4723
+ case "end_session": {
4724
+ const params = args;
4725
+ const result = endSession(params.project, params.task_slug);
4726
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
4727
+ }
4728
+ case "update_agent_todos": {
4729
+ const params = args;
4730
+ const result = updateAgentTodos(params.project, params.task_slug, params.phase, params.agent, params.items);
4731
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
4732
+ }
4733
+ case "prefetch_task_context": {
4734
+ const params = args;
4735
+ const result = await prefetchTaskContext(params.project, params.task_slug, {
4736
+ max_tokens: params.max_tokens
4737
+ });
4738
+ if (!result.success) {
4739
+ return { content: [{ type: "text", text: result.message || "Failed to prefetch task context" }], isError: true };
4740
+ }
4741
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4742
+ }
4743
+ case "search_tasks": {
4744
+ const params = args;
4745
+ const results = searchTasks(params.project, {
4746
+ keyword: params.keyword,
4747
+ status: params.status,
4748
+ agent: params.agent,
4749
+ since: params.since,
4750
+ limit: params.limit
4751
+ });
4752
+ return { content: [{ type: "text", text: JSON.stringify({ count: results.length, tasks: results }, null, 2) }] };
4753
+ }
4754
+ case "validate_phase": {
4755
+ const params = args;
4756
+ const result = validatePhase(params.project, params.task_slug, params.phase);
4757
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4758
+ }
3759
4759
  case "help_setup": {
3760
4760
  const msg = `
3761
4761
  RRCE MCP Server is running, but no projects are configured/exposed.
@@ -4008,8 +5008,8 @@ Hidden projects: ${projects.length - exposedCount}`,
4008
5008
  }
4009
5009
  async function handleConfigureGlobalPath() {
4010
5010
  const { resolveGlobalPath: resolveGlobalPath2 } = await Promise.resolve().then(() => (init_tui_utils(), tui_utils_exports));
4011
- const fs26 = await import("fs");
4012
- const path25 = await import("path");
5011
+ const fs27 = await import("fs");
5012
+ const path26 = await import("path");
4013
5013
  note3(
4014
5014
  `MCP Hub requires a ${pc5.bold("global storage path")} to store its configuration
4015
5015
  and coordinate across projects.
@@ -4023,8 +5023,8 @@ locally in each project. MCP needs a central location.`,
4023
5023
  return false;
4024
5024
  }
4025
5025
  try {
4026
- if (!fs26.existsSync(resolvedPath)) {
4027
- fs26.mkdirSync(resolvedPath, { recursive: true });
5026
+ if (!fs27.existsSync(resolvedPath)) {
5027
+ fs27.mkdirSync(resolvedPath, { recursive: true });
4028
5028
  }
4029
5029
  const config = loadMCPConfig();
4030
5030
  saveMCPConfig(config);
@@ -4032,7 +5032,7 @@ locally in each project. MCP needs a central location.`,
4032
5032
  `${pc5.green("\u2713")} Global path configured: ${pc5.cyan(resolvedPath)}
4033
5033
 
4034
5034
  MCP config will be stored at:
4035
- ${path25.join(resolvedPath, "mcp.yaml")}`,
5035
+ ${path26.join(resolvedPath, "mcp.yaml")}`,
4036
5036
  "Configuration Saved"
4037
5037
  );
4038
5038
  return true;
@@ -4312,6 +5312,37 @@ var init_ConfigContext = __esm({
4312
5312
  // src/mcp/ui/lib/tasks-fs.ts
4313
5313
  import * as fs19 from "fs";
4314
5314
  import * as path21 from "path";
5315
+ function readSession(project, taskSlug) {
5316
+ const rrceData = getProjectRRCEData(project);
5317
+ const sessionPath = path21.join(rrceData, "tasks", taskSlug, "session.json");
5318
+ if (!fs19.existsSync(sessionPath)) {
5319
+ return null;
5320
+ }
5321
+ try {
5322
+ const raw = fs19.readFileSync(sessionPath, "utf-8");
5323
+ return JSON.parse(raw);
5324
+ } catch {
5325
+ return null;
5326
+ }
5327
+ }
5328
+ function isSessionStale(session, thresholdMs = SESSION_STALE_THRESHOLD_MS) {
5329
+ const heartbeatTime = Date.parse(session.heartbeat);
5330
+ if (isNaN(heartbeatTime)) return true;
5331
+ return Date.now() - heartbeatTime > thresholdMs;
5332
+ }
5333
+ function readAgentTodos(project, taskSlug) {
5334
+ const rrceData = getProjectRRCEData(project);
5335
+ const todosPath = path21.join(rrceData, "tasks", taskSlug, "agent-todos.json");
5336
+ if (!fs19.existsSync(todosPath)) {
5337
+ return null;
5338
+ }
5339
+ try {
5340
+ const raw = fs19.readFileSync(todosPath, "utf-8");
5341
+ return JSON.parse(raw);
5342
+ } catch {
5343
+ return null;
5344
+ }
5345
+ }
4315
5346
  function detectStorageModeFromConfig(workspaceRoot) {
4316
5347
  const configPath = getConfigPath(workspaceRoot);
4317
5348
  try {
@@ -4387,10 +5418,104 @@ function updateTaskStatus(project, taskSlug, status) {
4387
5418
  return { ok: false, error: String(e) };
4388
5419
  }
4389
5420
  }
5421
+ var SESSION_STALE_THRESHOLD_MS;
4390
5422
  var init_tasks_fs = __esm({
4391
5423
  "src/mcp/ui/lib/tasks-fs.ts"() {
4392
5424
  "use strict";
4393
5425
  init_paths();
5426
+ SESSION_STALE_THRESHOLD_MS = 5 * 60 * 1e3;
5427
+ }
5428
+ });
5429
+
5430
+ // src/mcp/ui/ui-helpers.ts
5431
+ var getStatusIcon, getStatusColor, getChecklistProgress, getCheckbox, getProgressBar, getFolderIcon, getAgentStatusIcon, getPhaseIcon, getTreeBranch, formatRelativeTime, getTodoStatusIcon;
5432
+ var init_ui_helpers = __esm({
5433
+ "src/mcp/ui/ui-helpers.ts"() {
5434
+ "use strict";
5435
+ getStatusIcon = (status) => {
5436
+ const icons = {
5437
+ pending: "\u23F3",
5438
+ in_progress: "\u{1F504}",
5439
+ blocked: "\u{1F6AB}",
5440
+ complete: "\u2705"
5441
+ };
5442
+ return icons[status] || "\u25CB";
5443
+ };
5444
+ getStatusColor = (status) => {
5445
+ const colors = {
5446
+ pending: "yellow",
5447
+ in_progress: "yellow",
5448
+ blocked: "red",
5449
+ complete: "green"
5450
+ };
5451
+ return colors[status] || "white";
5452
+ };
5453
+ getChecklistProgress = (checklist) => {
5454
+ if (!checklist || checklist.length === 0) {
5455
+ return { completed: 0, total: 0, percentage: 0 };
5456
+ }
5457
+ const completed = checklist.filter((item) => item.status === "done").length;
5458
+ return {
5459
+ completed,
5460
+ total: checklist.length,
5461
+ percentage: Math.round(completed / checklist.length * 100)
5462
+ };
5463
+ };
5464
+ getCheckbox = (status) => {
5465
+ return status === "done" ? "\u2611" : "\u2610";
5466
+ };
5467
+ getProgressBar = (percentage, length = 10) => {
5468
+ const filled = Math.floor(percentage / 100 * length);
5469
+ const empty = length - filled;
5470
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
5471
+ };
5472
+ getFolderIcon = (isOpen) => {
5473
+ return isOpen ? "\u{1F4C2}" : "\u{1F4C1}";
5474
+ };
5475
+ getAgentStatusIcon = (status) => {
5476
+ const icons = {
5477
+ complete: "\u2713",
5478
+ in_progress: "\u27F3",
5479
+ pending: "\u25CB",
5480
+ blocked: "\u2715"
5481
+ };
5482
+ return icons[status] || "\u2014";
5483
+ };
5484
+ getPhaseIcon = (agent) => {
5485
+ const icons = {
5486
+ research: "\u{1F52C}",
5487
+ planning: "\u{1F4DD}",
5488
+ executor: "\u26A1",
5489
+ documentation: "\u{1F4DA}"
5490
+ };
5491
+ return icons[agent] || "\u{1F527}";
5492
+ };
5493
+ getTreeBranch = (isLast) => {
5494
+ return isLast ? "\u2514\u2500" : "\u251C\u2500";
5495
+ };
5496
+ formatRelativeTime = (dateString) => {
5497
+ if (!dateString) return "\u2014";
5498
+ const date = Date.parse(dateString);
5499
+ if (isNaN(date)) return "\u2014";
5500
+ const now = Date.now();
5501
+ const diffMs = now - date;
5502
+ const diffMins = Math.floor(diffMs / (1e3 * 60));
5503
+ const diffHours = Math.floor(diffMs / (1e3 * 60 * 60));
5504
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
5505
+ if (diffMins < 1) return "just now";
5506
+ if (diffMins < 60) return `${diffMins}m ago`;
5507
+ if (diffHours < 24) return `${diffHours}h ago`;
5508
+ if (diffDays < 7) return `${diffDays}d ago`;
5509
+ return `${Math.floor(diffDays / 7)}w ago`;
5510
+ };
5511
+ getTodoStatusIcon = (status) => {
5512
+ const icons = {
5513
+ completed: "\u2713",
5514
+ in_progress: "\u27F3",
5515
+ pending: "\u25CB"
5516
+ };
5517
+ return icons[status] || "\u25CB";
5518
+ };
4394
5519
  }
4395
5520
  });
4396
5521
 
@@ -4405,6 +5530,7 @@ var init_Overview = __esm({
4405
5530
  init_Header();
4406
5531
  init_ConfigContext();
4407
5532
  init_tasks_fs();
5533
+ init_ui_helpers();
4408
5534
  Overview = ({ serverStatus, stats, logs }) => {
4409
5535
  const { projects } = useConfig();
4410
5536
  const activeTasks = useMemo2(() => {
@@ -4413,7 +5539,16 @@ var init_Overview = __esm({
4413
5539
  const { tasks } = listProjectTasks(p);
4414
5540
  const inProgress = tasks.filter((t) => t.status === "in_progress");
4415
5541
  for (const t of inProgress) {
4416
- active.push({ project: p.name, title: t.title || t.task_slug, slug: t.task_slug });
5542
+ const session = readSession(p, t.task_slug);
5543
+ const todos = readAgentTodos(p, t.task_slug);
5544
+ active.push({
5545
+ project: p.name,
5546
+ title: t.title || t.task_slug,
5547
+ slug: t.task_slug,
5548
+ session,
5549
+ todos,
5550
+ isStale: session ? isSessionStale(session) : false
5551
+ });
4417
5552
  }
4418
5553
  }
4419
5554
  return active;
@@ -4421,6 +5556,15 @@ var init_Overview = __esm({
4421
5556
  const recentLogs = useMemo2(() => {
4422
5557
  return logs.slice(-5).reverse();
4423
5558
  }, [logs]);
5559
+ const getElapsedTime = (startedAt) => {
5560
+ const start = Date.parse(startedAt);
5561
+ if (isNaN(start)) return "";
5562
+ const elapsed = Date.now() - start;
5563
+ const mins = Math.floor(elapsed / 6e4);
5564
+ if (mins < 60) return `${mins}m`;
5565
+ const hours = Math.floor(mins / 60);
5566
+ return `${hours}h ${mins % 60}m`;
5567
+ };
4424
5568
  return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", flexGrow: 1, children: [
4425
5569
  /* @__PURE__ */ jsx3(Header, {}),
4426
5570
  /* @__PURE__ */ jsxs(Box2, { borderStyle: "round", paddingX: 1, borderColor: "white", flexDirection: "column", flexGrow: 1, children: [
@@ -4477,13 +5621,35 @@ var init_Overview = __esm({
4477
5621
  ] }),
4478
5622
  /* @__PURE__ */ jsxs(Box2, { marginTop: 1, borderStyle: "single", borderColor: "blue", flexDirection: "column", paddingX: 1, children: [
4479
5623
  /* @__PURE__ */ jsx3(Text2, { bold: true, color: "blue", children: "\u{1F3C3} Active Tasks" }),
4480
- activeTasks.length === 0 ? /* @__PURE__ */ jsx3(Box2, { paddingY: 1, children: /* @__PURE__ */ jsx3(Text2, { color: "dim", children: "No tasks currently in progress." }) }) : activeTasks.slice(0, 3).map((t, i) => /* @__PURE__ */ jsxs(Box2, { marginTop: i === 0 ? 0 : 0, children: [
4481
- /* @__PURE__ */ jsxs(Text2, { color: "yellow", children: [
4482
- "\u{1F504} ",
4483
- t.project,
4484
- ": "
5624
+ activeTasks.length === 0 ? /* @__PURE__ */ jsx3(Box2, { paddingY: 1, children: /* @__PURE__ */ jsx3(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: [
5625
+ /* @__PURE__ */ jsxs(Box2, { children: [
5626
+ /* @__PURE__ */ jsx3(Text2, { color: "yellow", children: "\u{1F504} " }),
5627
+ /* @__PURE__ */ jsx3(Text2, { bold: true, color: "white", children: t.project }),
5628
+ /* @__PURE__ */ jsx3(Text2, { color: "dim", children: ": " }),
5629
+ /* @__PURE__ */ jsx3(Text2, { color: "white", children: t.title })
5630
+ ] }),
5631
+ t.session && /* @__PURE__ */ jsxs(Box2, { marginLeft: 3, children: [
5632
+ /* @__PURE__ */ jsxs(Text2, { children: [
5633
+ getPhaseIcon(t.session.agent),
5634
+ " "
5635
+ ] }),
5636
+ /* @__PURE__ */ jsx3(Text2, { color: t.isStale ? "dim" : "cyan", children: t.session.agent }),
5637
+ /* @__PURE__ */ jsx3(Text2, { color: "dim", children: " \u2022 " }),
5638
+ /* @__PURE__ */ jsx3(Text2, { color: t.isStale ? "dim" : "white", children: t.session.phase }),
5639
+ /* @__PURE__ */ jsx3(Text2, { color: "dim", children: " \u2022 " }),
5640
+ /* @__PURE__ */ jsx3(Text2, { color: t.isStale ? "red" : "green", children: t.isStale ? "stale" : `${getElapsedTime(t.session.started_at)} elapsed` })
4485
5641
  ] }),
4486
- /* @__PURE__ */ jsx3(Text2, { children: t.title })
5642
+ t.todos && t.todos.items.length > 0 && /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginLeft: 3, children: t.todos.items.slice(0, 3).map((item, idx) => /* @__PURE__ */ jsxs(Box2, { children: [
5643
+ /* @__PURE__ */ jsxs(Text2, { color: "dim", children: [
5644
+ getTreeBranch(idx === Math.min(2, t.todos.items.length - 1)),
5645
+ " "
5646
+ ] }),
5647
+ /* @__PURE__ */ jsx3(Text2, { color: item.status === "completed" ? "green" : item.status === "in_progress" ? "yellow" : "dim", children: getTodoStatusIcon(item.status) }),
5648
+ /* @__PURE__ */ jsxs(Text2, { color: item.status === "completed" ? "dim" : "white", children: [
5649
+ " ",
5650
+ item.content.length > 40 ? item.content.substring(0, 37) + "..." : item.content
5651
+ ] })
5652
+ ] }, item.id)) })
4487
5653
  ] }, `${t.project}-${t.slug}`)),
4488
5654
  activeTasks.length > 3 && /* @__PURE__ */ jsxs(Text2, { color: "dim", children: [
4489
5655
  " ...and ",
@@ -4497,7 +5663,7 @@ var init_Overview = __esm({
4497
5663
  ] }),
4498
5664
  /* @__PURE__ */ jsxs(Box2, { marginTop: 0, justifyContent: "space-between", children: [
4499
5665
  /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsx3(Text2, { color: "dim", children: "r:Restart q:Quit 1-4/\u25C4 \u25BA:Tabs" }) }),
4500
- /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsx3(Text2, { color: "dim", children: "RRCE MCP Hub v0.3.7" }) })
5666
+ /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsx3(Text2, { color: "dim", children: "RRCE MCP Hub v0.3.14" }) })
4501
5667
  ] })
4502
5668
  ] })
4503
5669
  ] });
@@ -4607,7 +5773,37 @@ var init_project_utils = __esm({
4607
5773
  // src/mcp/ui/ProjectsView.tsx
4608
5774
  import { useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
4609
5775
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
5776
+ import * as fs20 from "fs";
5777
+ import * as path22 from "path";
4610
5778
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
5779
+ function getIndexStats(project) {
5780
+ const stats = { knowledgeCount: 0, codeCount: 0, lastIndexed: null };
5781
+ try {
5782
+ const knowledgePath = project.knowledgePath;
5783
+ if (knowledgePath) {
5784
+ const embPath = path22.join(knowledgePath, "embeddings.json");
5785
+ const codeEmbPath = path22.join(knowledgePath, "code-embeddings.json");
5786
+ if (fs20.existsSync(embPath)) {
5787
+ const stat = fs20.statSync(embPath);
5788
+ stats.lastIndexed = stat.mtime.toISOString();
5789
+ try {
5790
+ const data = JSON.parse(fs20.readFileSync(embPath, "utf-8"));
5791
+ stats.knowledgeCount = Array.isArray(data) ? data.length : Object.keys(data).length;
5792
+ } catch {
5793
+ }
5794
+ }
5795
+ if (fs20.existsSync(codeEmbPath)) {
5796
+ try {
5797
+ const data = JSON.parse(fs20.readFileSync(codeEmbPath, "utf-8"));
5798
+ stats.codeCount = Array.isArray(data) ? data.length : Object.keys(data).length;
5799
+ } catch {
5800
+ }
5801
+ }
5802
+ }
5803
+ } catch {
5804
+ }
5805
+ return stats;
5806
+ }
4611
5807
  var ProjectsView;
4612
5808
  var init_ProjectsView = __esm({
4613
5809
  "src/mcp/ui/ProjectsView.tsx"() {
@@ -4618,6 +5814,7 @@ var init_ProjectsView = __esm({
4618
5814
  init_indexing_jobs();
4619
5815
  init_config_utils();
4620
5816
  init_project_utils();
5817
+ init_ui_helpers();
4621
5818
  ProjectsView = ({ config: initialConfig, projects: allProjects, onConfigChange, workspacePath }) => {
4622
5819
  const { driftReports, checkAllDrift } = useConfig();
4623
5820
  const [config, setConfig] = useState3(initialConfig);
@@ -4664,29 +5861,41 @@ var init_ProjectsView = __esm({
4664
5861
  const isExposed = projectConfig ? projectConfig.expose : config.defaults.includeNew;
4665
5862
  const drift = driftReports[p.path];
4666
5863
  const idx = indexingStats[p.name];
5864
+ const stats = getIndexStats(p);
5865
+ const isCurrentProject = p.path === workspacePath;
4667
5866
  let label = formatProjectLabel(p);
5867
+ if (isCurrentProject) {
5868
+ label += " (current)";
5869
+ }
4668
5870
  if (drift?.hasDrift) {
4669
- label += ` \u26A0`;
5871
+ label += " \u26A0";
4670
5872
  }
5873
+ let statsRow = "";
4671
5874
  if (idx?.state === "running") {
4672
- label += `
4673
- \u27F3 Indexing ${idx.itemsDone}/${idx.itemsTotal ?? "?"}`;
5875
+ statsRow = ` \u27F3 Indexing... ${idx.itemsDone}/${idx.itemsTotal ?? "?"}`;
4674
5876
  } else if (idx?.state === "failed") {
4675
- label += `
4676
- \u2715 Index Fail`;
4677
- } else if (idx?.enabled && idx?.state === "complete") {
4678
- label += `
4679
- \u2713 Indexed`;
5877
+ statsRow = " \u2715 Index failed";
5878
+ } else if (stats.knowledgeCount > 0 || stats.codeCount > 0) {
5879
+ const parts = [];
5880
+ if (stats.knowledgeCount > 0) parts.push(`\u{1F4DA} ${stats.knowledgeCount} docs`);
5881
+ if (stats.codeCount > 0) parts.push(`\u{1F4BB} ${stats.codeCount} files`);
5882
+ if (stats.lastIndexed) parts.push(`\u{1F550} ${formatRelativeTime(stats.lastIndexed)}`);
5883
+ statsRow = ` \u{1F4CA} ${parts.join(" \u2502 ")}`;
5884
+ } else if (isExposed) {
5885
+ statsRow = " \u{1F4CA} Not indexed";
4680
5886
  }
5887
+ const fullLabel = statsRow ? `${label}
5888
+ ${statsRow}` : label;
4681
5889
  return {
4682
- label,
5890
+ label: fullLabel,
4683
5891
  value: p.path,
4684
5892
  key: p.path,
4685
5893
  exposed: isExposed,
4686
- indexing: idx
5894
+ indexing: idx,
5895
+ isCurrentProject
4687
5896
  };
4688
5897
  });
4689
- }, [sortedProjects, config, driftReports, indexingStats]);
5898
+ }, [sortedProjects, config, driftReports, indexingStats, workspacePath]);
4690
5899
  const initialSelected = useMemo3(() => {
4691
5900
  return projectItems.filter((p) => p.exposed).map((p) => p.value);
4692
5901
  }, [projectItems]);
@@ -4745,87 +5954,103 @@ var init_ProjectsView = __esm({
4745
5954
  }
4746
5955
  });
4747
5956
 
4748
- // src/mcp/ui/ui-helpers.ts
4749
- var getStatusIcon, getStatusColor, getChecklistProgress, getCheckbox, getProgressBar, getFolderIcon;
4750
- var init_ui_helpers = __esm({
4751
- "src/mcp/ui/ui-helpers.ts"() {
4752
- "use strict";
4753
- getStatusIcon = (status) => {
4754
- const icons = {
4755
- pending: "\u23F3",
4756
- in_progress: "\u{1F504}",
4757
- blocked: "\u{1F6AB}",
4758
- complete: "\u2705"
4759
- };
4760
- return icons[status] || "\u25CB";
4761
- };
4762
- getStatusColor = (status) => {
4763
- const colors = {
4764
- pending: "yellow",
4765
- in_progress: "yellow",
4766
- blocked: "red",
4767
- complete: "green"
4768
- };
4769
- return colors[status] || "white";
4770
- };
4771
- getChecklistProgress = (checklist) => {
4772
- if (!checklist || checklist.length === 0) {
4773
- return { completed: 0, total: 0, percentage: 0 };
4774
- }
4775
- const completed = checklist.filter((item) => item.status === "done").length;
4776
- return {
4777
- completed,
4778
- total: checklist.length,
4779
- percentage: Math.round(completed / checklist.length * 100)
4780
- };
4781
- };
4782
- getCheckbox = (status) => {
4783
- return status === "done" ? "\u2611" : "\u2610";
4784
- };
4785
- getProgressBar = (percentage, length = 10) => {
4786
- const filled = Math.floor(percentage / 100 * length);
4787
- const empty = length - filled;
4788
- return "\u2588".repeat(filled) + "\u2591".repeat(empty);
4789
- };
4790
- getFolderIcon = (isOpen) => {
4791
- return isOpen ? "\u{1F4C2}" : "\u{1F4C1}";
4792
- };
4793
- }
4794
- });
4795
-
4796
5957
  // src/mcp/ui/components/TaskRow.tsx
4797
5958
  import "react";
4798
5959
  import { Box as Box5, Text as Text5 } from "ink";
4799
5960
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
5961
+ function getActiveAgent(task) {
5962
+ if (!task.agents) return null;
5963
+ for (const [agent, info] of Object.entries(task.agents)) {
5964
+ if (info?.status === "in_progress") {
5965
+ return { agent, status: "in_progress" };
5966
+ }
5967
+ }
5968
+ const agentOrder = ["documentation", "executor", "planning", "research"];
5969
+ for (const agent of agentOrder) {
5970
+ if (task.agents[agent]?.status === "complete") {
5971
+ return { agent, status: "complete" };
5972
+ }
5973
+ }
5974
+ return null;
5975
+ }
4800
5976
  var TaskRow;
4801
5977
  var init_TaskRow = __esm({
4802
5978
  "src/mcp/ui/components/TaskRow.tsx"() {
4803
5979
  "use strict";
4804
5980
  init_ui_helpers();
4805
5981
  init_project_utils();
4806
- TaskRow = ({ row, isSelected, isExpanded, taskCount, hasDrift }) => {
5982
+ TaskRow = ({
5983
+ row,
5984
+ isSelected,
5985
+ isExpanded,
5986
+ taskCount,
5987
+ hasDrift,
5988
+ isCurrentProject = false,
5989
+ isLastTask = false
5990
+ }) => {
4807
5991
  if (row.kind === "project") {
4808
- return /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsxs4(Box5, { children: [
4809
- /* @__PURE__ */ jsx6(Text5, { color: isSelected ? "cyan" : "white", children: isSelected ? "> " : " " }),
4810
- /* @__PURE__ */ jsxs4(Text5, { color: isSelected ? "cyan" : "white", children: [
5992
+ const projectColor = isCurrentProject ? "cyan" : isSelected ? "yellow" : "white";
5993
+ const isBold = isCurrentProject || isSelected;
5994
+ return /* @__PURE__ */ jsxs4(Box5, { children: [
5995
+ /* @__PURE__ */ jsx6(Text5, { color: isSelected ? "cyan" : "dim", children: isSelected ? "\u25B8 " : " " }),
5996
+ /* @__PURE__ */ jsxs4(Text5, { bold: isBold, color: projectColor, children: [
4811
5997
  getFolderIcon(isExpanded),
4812
5998
  " ",
4813
5999
  formatProjectLabel(row.project)
4814
6000
  ] }),
6001
+ isCurrentProject && /* @__PURE__ */ jsx6(Text5, { color: "cyan", dimColor: true, children: " (current)" }),
4815
6002
  hasDrift && /* @__PURE__ */ jsx6(Text5, { color: "magenta", children: " \u26A0" }),
4816
6003
  /* @__PURE__ */ jsxs4(Text5, { color: "dim", children: [
4817
6004
  " ",
4818
- taskCount > 0 ? `(${taskCount})` : ""
6005
+ taskCount > 0 ? `[${taskCount}]` : ""
4819
6006
  ] })
4820
- ] }) });
6007
+ ] });
6008
+ }
6009
+ const task = row.task;
6010
+ const taskLabel = task.title || task.task_slug;
6011
+ const status = task.status || "";
6012
+ const isPlaceholder = task.task_slug === "__none__";
6013
+ const branch = getTreeBranch(isLastTask);
6014
+ const activeAgent = getActiveAgent(task);
6015
+ const progress = getChecklistProgress(task.checklist || []);
6016
+ const relativeTime = task.updated_at ? formatRelativeTime(task.updated_at) : "";
6017
+ if (isPlaceholder) {
6018
+ return /* @__PURE__ */ jsxs4(Box5, { children: [
6019
+ /* @__PURE__ */ jsxs4(Text5, { color: "dim", children: [
6020
+ " ",
6021
+ branch,
6022
+ " "
6023
+ ] }),
6024
+ /* @__PURE__ */ jsx6(Text5, { color: "dim", italic: true, children: taskLabel })
6025
+ ] });
4821
6026
  }
4822
- const taskLabel = row.task.title || row.task.task_slug;
4823
- const status = row.task.status || "";
4824
6027
  return /* @__PURE__ */ jsxs4(Box5, { children: [
4825
- /* @__PURE__ */ jsx6(Text5, { color: isSelected ? "cyan" : "white", children: isSelected ? "> " : " " }),
4826
- /* @__PURE__ */ jsx6(Text5, { color: "dim", children: " - " }),
4827
- /* @__PURE__ */ jsx6(Text5, { color: isSelected ? "cyan" : "white", children: taskLabel }),
4828
- row.task.task_slug !== "__none__" && /* @__PURE__ */ jsx6(Text5, { backgroundColor: getStatusColor(status), color: "black", children: ` ${getStatusIcon(status)} ${status.toUpperCase().replace("_", " ")} ` })
6028
+ /* @__PURE__ */ jsx6(Text5, { color: isSelected ? "cyan" : "dim", children: isSelected ? "\u25B8" : " " }),
6029
+ /* @__PURE__ */ jsxs4(Text5, { color: "dim", children: [
6030
+ " ",
6031
+ branch,
6032
+ " "
6033
+ ] }),
6034
+ /* @__PURE__ */ jsx6(Text5, { color: isSelected ? "cyan" : "white", children: "\u{1F4CB} " }),
6035
+ /* @__PURE__ */ jsx6(Text5, { bold: isSelected, color: isSelected ? "cyan" : "white", children: taskLabel.length > 25 ? taskLabel.substring(0, 22) + "..." : taskLabel }),
6036
+ activeAgent && /* @__PURE__ */ jsxs4(Text5, { children: [
6037
+ /* @__PURE__ */ jsx6(Text5, { color: "dim", children: " " }),
6038
+ /* @__PURE__ */ jsx6(Text5, { children: getPhaseIcon(activeAgent.agent) }),
6039
+ /* @__PURE__ */ jsx6(Text5, { color: activeAgent.status === "in_progress" ? "yellow" : "green", children: getAgentStatusIcon(activeAgent.status) })
6040
+ ] }),
6041
+ progress.total > 0 && /* @__PURE__ */ jsxs4(Text5, { color: "dim", children: [
6042
+ " ",
6043
+ getProgressBar(progress.percentage, 6),
6044
+ " ",
6045
+ progress.completed,
6046
+ "/",
6047
+ progress.total
6048
+ ] }),
6049
+ relativeTime && relativeTime !== "\u2014" && /* @__PURE__ */ jsxs4(Text5, { color: "dim", children: [
6050
+ " ",
6051
+ relativeTime
6052
+ ] }),
6053
+ !activeAgent && status && /* @__PURE__ */ jsx6(Text5, { backgroundColor: getStatusColor(status), color: "black", children: ` ${getStatusIcon(status)} ` })
4829
6054
  ] });
4830
6055
  };
4831
6056
  }
@@ -4834,102 +6059,124 @@ var init_TaskRow = __esm({
4834
6059
  // src/mcp/ui/components/TaskDetails.tsx
4835
6060
  import "react";
4836
6061
  import { Box as Box6, Text as Text6 } from "ink";
4837
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
6062
+ import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
4838
6063
  var TaskDetails;
4839
6064
  var init_TaskDetails = __esm({
4840
6065
  "src/mcp/ui/components/TaskDetails.tsx"() {
4841
6066
  "use strict";
4842
6067
  init_ui_helpers();
4843
- TaskDetails = ({ task }) => {
6068
+ TaskDetails = ({ task, agentTodos }) => {
4844
6069
  if (!task) {
4845
6070
  return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", justifyContent: "center", alignItems: "center", gap: 1, flexGrow: 1, children: [
4846
6071
  /* @__PURE__ */ jsx7(Text6, { bold: true, color: "dim", children: "\u2500 No Task Selected \u2500" }),
4847
- /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Use \u2191/\u2193 to navigate, Enter to expand projects" }),
4848
- /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Press 's' to cycle task status" })
6072
+ /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Use \u2191/\u2193 to navigate" }),
6073
+ /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Enter to expand projects" }),
6074
+ /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "'s' to cycle task status" })
4849
6075
  ] });
4850
6076
  }
6077
+ const progress = getChecklistProgress(task.checklist || []);
4851
6078
  return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
4852
6079
  /* @__PURE__ */ jsxs5(Box6, { marginBottom: 1, flexDirection: "column", children: [
4853
6080
  /* @__PURE__ */ jsx7(Text6, { bold: true, color: "cyan", children: task.title || task.task_slug }),
4854
- task.summary && /* @__PURE__ */ jsx7(Text6, { color: "white", children: task.summary })
6081
+ task.summary && /* @__PURE__ */ jsx7(Text6, { color: "white", wrap: "wrap", children: task.summary.length > 100 ? task.summary.substring(0, 97) + "..." : task.summary })
4855
6082
  ] }),
4856
- /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
4857
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
6083
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
6084
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
4858
6085
  /* @__PURE__ */ jsx7(Text6, { bold: true, color: "white", children: "\u{1F4CB} STATUS" }),
4859
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginTop: 1, children: [
4860
- /* @__PURE__ */ jsxs5(Text6, { children: [
4861
- /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Status: " }),
4862
- " ",
6086
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginTop: 0, marginLeft: 1, children: [
6087
+ /* @__PURE__ */ jsxs5(Box6, { children: [
6088
+ /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Status: " }),
4863
6089
  /* @__PURE__ */ jsx7(Text6, { color: getStatusColor(task.status || ""), children: task.status || "unknown" })
4864
6090
  ] }),
4865
- /* @__PURE__ */ jsxs5(Text6, { children: [
4866
- /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Updated:" }),
4867
- " ",
4868
- /* @__PURE__ */ jsx7(Text6, { children: task.updated_at || "\u2014" })
6091
+ /* @__PURE__ */ jsxs5(Box6, { children: [
6092
+ /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Updated: " }),
6093
+ /* @__PURE__ */ jsx7(Text6, { children: formatRelativeTime(task.updated_at || "") })
4869
6094
  ] }),
4870
- /* @__PURE__ */ jsxs5(Text6, { children: [
4871
- /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Tags: " }),
4872
- " ",
4873
- " ",
4874
- (() => {
4875
- const tags = task.tags || [];
4876
- return tags.length > 0 ? tags.map((tag, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
4877
- /* @__PURE__ */ jsx7(Text6, { color: "cyan", children: tag }),
4878
- i < tags.length - 1 && /* @__PURE__ */ jsx7(Text6, { color: "dim", children: ", " })
4879
- ] }, tag)) : /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "\u2014" });
4880
- })()
6095
+ task.tags && task.tags.length > 0 && /* @__PURE__ */ jsxs5(Box6, { children: [
6096
+ /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "Tags: " }),
6097
+ task.tags.map((tag, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
6098
+ /* @__PURE__ */ jsx7(Text6, { color: "cyan", children: tag }),
6099
+ i < task.tags.length - 1 && /* @__PURE__ */ jsx7(Text6, { color: "dim", children: ", " })
6100
+ ] }, tag))
4881
6101
  ] })
4882
6102
  ] })
4883
6103
  ] }),
4884
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
4885
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
4886
- /* @__PURE__ */ jsx7(Text6, { bold: true, color: "white", children: "\u{1F4CB} CHECKLIST" }),
4887
- task.checklist && task.checklist.length > 0 && /* @__PURE__ */ jsx7(Box6, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsxs5(Box6, { marginBottom: 1, children: [
4888
- /* @__PURE__ */ jsx7(Text6, { backgroundColor: "white", children: getProgressBar(getChecklistProgress(task.checklist).percentage) }),
4889
- /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
4890
- " ",
6104
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
6105
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
6106
+ /* @__PURE__ */ jsxs5(Box6, { children: [
6107
+ /* @__PURE__ */ jsx7(Text6, { bold: true, color: "white", children: "\u{1F4CB} CHECKLIST " }),
6108
+ progress.total > 0 && /* @__PURE__ */ jsxs5(Text6, { color: "dim", children: [
6109
+ getProgressBar(progress.percentage, 8),
4891
6110
  " ",
4892
- getChecklistProgress(task.checklist).completed,
6111
+ progress.completed,
4893
6112
  "/",
4894
- getChecklistProgress(task.checklist).total,
4895
- " (",
4896
- getChecklistProgress(task.checklist).percentage,
4897
- "%)"
6113
+ progress.total
4898
6114
  ] })
4899
- ] }) }),
4900
- (task.checklist || []).length === 0 ? /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "\u2014" }) : (task.checklist || []).slice(0, 12).map((c, i) => {
4901
- const isDone = c.status === "done";
4902
- return /* @__PURE__ */ jsxs5(Text6, { color: isDone ? "dim" : "white", children: [
4903
- /* @__PURE__ */ jsxs5(Text6, { color: isDone ? "green" : "dim", children: [
4904
- getCheckbox(c.status || "pending"),
4905
- " "
4906
- ] }),
4907
- c.label || c.id || "item"
4908
- ] }, c.id || i);
4909
- })
6115
+ ] }),
6116
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginLeft: 1, children: [
6117
+ (task.checklist || []).length === 0 ? /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "No checklist items" }) : (task.checklist || []).slice(0, 8).map((c, i) => {
6118
+ const isDone = c.status === "done";
6119
+ return /* @__PURE__ */ jsxs5(Text6, { color: isDone ? "dim" : "white", children: [
6120
+ /* @__PURE__ */ jsxs5(Text6, { color: isDone ? "green" : "dim", children: [
6121
+ getCheckbox(c.status || "pending"),
6122
+ " "
6123
+ ] }),
6124
+ (c.label || c.id || "item").substring(0, 35)
6125
+ ] }, c.id || i);
6126
+ }),
6127
+ (task.checklist || []).length > 8 && /* @__PURE__ */ jsxs5(Text6, { color: "dim", children: [
6128
+ "...and ",
6129
+ (task.checklist || []).length - 8,
6130
+ " more"
6131
+ ] })
6132
+ ] })
6133
+ ] }),
6134
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
6135
+ agentTodos && agentTodos.items.length > 0 && /* @__PURE__ */ jsxs5(Fragment, { children: [
6136
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
6137
+ /* @__PURE__ */ jsxs5(Box6, { children: [
6138
+ /* @__PURE__ */ jsx7(Text6, { bold: true, color: "white", children: "\u{1F3AF} AGENT TODOS " }),
6139
+ /* @__PURE__ */ jsxs5(Text6, { color: "dim", children: [
6140
+ "(",
6141
+ agentTodos.agent,
6142
+ " \u2022 ",
6143
+ agentTodos.phase,
6144
+ ")"
6145
+ ] })
6146
+ ] }),
6147
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginLeft: 1, children: [
6148
+ agentTodos.items.slice(0, 5).map((item, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
6149
+ /* @__PURE__ */ jsxs5(Text6, { color: item.status === "completed" ? "green" : item.status === "in_progress" ? "yellow" : "dim", children: [
6150
+ getTodoStatusIcon(item.status),
6151
+ " "
6152
+ ] }),
6153
+ /* @__PURE__ */ jsx7(Text6, { color: item.status === "completed" ? "dim" : "white", children: item.content.substring(0, 35) })
6154
+ ] }, item.id || i)),
6155
+ agentTodos.items.length > 5 && /* @__PURE__ */ jsxs5(Text6, { color: "dim", children: [
6156
+ "...and ",
6157
+ agentTodos.items.length - 5,
6158
+ " more"
6159
+ ] })
6160
+ ] })
6161
+ ] }),
6162
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" })
4910
6163
  ] }),
4911
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
4912
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
6164
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
4913
6165
  /* @__PURE__ */ jsx7(Text6, { bold: true, color: "white", children: "\u{1F916} AGENTS" }),
4914
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, flexDirection: "column", children: !task.agents ? /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "\u2014" }) : Object.entries(task.agents).map(([agent, info]) => /* @__PURE__ */ jsxs5(Text6, { children: [
6166
+ /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginLeft: 1, children: !task.agents || Object.keys(task.agents).length === 0 ? /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "No agent activity yet" }) : Object.entries(task.agents).map(([agent, info]) => /* @__PURE__ */ jsxs5(Box6, { children: [
6167
+ /* @__PURE__ */ jsxs5(Text6, { children: [
6168
+ getPhaseIcon(agent),
6169
+ " "
6170
+ ] }),
4915
6171
  /* @__PURE__ */ jsxs5(Text6, { color: "dim", children: [
4916
- "- ",
4917
6172
  agent,
4918
6173
  ": "
4919
6174
  ] }),
4920
- info?.status === "complete" && /* @__PURE__ */ jsx7(Text6, { color: "green", children: "\u2713" }),
4921
- info?.status === "in_progress" && /* @__PURE__ */ jsx7(Text6, { color: "yellow", children: "\u27F3" }),
4922
- info?.status === "pending" && /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "\u25CB" }),
4923
- info?.blocked && /* @__PURE__ */ jsx7(Text6, { color: "red", children: "\u2715" }),
4924
- /* @__PURE__ */ jsxs5(Text6, { color: info?.status === "complete" ? "dim" : "white", children: [
4925
- " ",
4926
- info?.status || "\u2014"
4927
- ] }),
4928
- info?.artifact && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
4929
- " (",
4930
- info.artifact,
4931
- ")"
4932
- ] })
6175
+ info?.status === "complete" && /* @__PURE__ */ jsx7(Text6, { color: "green", children: "\u2713 " }),
6176
+ info?.status === "in_progress" && /* @__PURE__ */ jsx7(Text6, { color: "yellow", children: "\u27F3 " }),
6177
+ info?.status === "pending" && /* @__PURE__ */ jsx7(Text6, { color: "dim", children: "\u25CB " }),
6178
+ info?.blocked && /* @__PURE__ */ jsx7(Text6, { color: "red", children: "\u2715 " }),
6179
+ /* @__PURE__ */ jsx7(Text6, { color: info?.status === "complete" ? "dim" : "white", children: info?.status || "pending" })
4933
6180
  ] }, agent)) })
4934
6181
  ] })
4935
6182
  ] });
@@ -4993,19 +6240,30 @@ var init_TasksView = __esm({
4993
6240
  const flattenedRows = useMemo4(() => {
4994
6241
  const rows = [];
4995
6242
  for (const p of sortedProjects) {
4996
- rows.push({ kind: "project", project: p });
6243
+ const isCurrentProject = p.path === workspacePath;
6244
+ rows.push({ kind: "project", project: p, isCurrentProject });
4997
6245
  const k = projectKey(p);
4998
6246
  if (!expanded.has(k)) continue;
4999
6247
  const tasks = taskCache[k] || [];
5000
- for (const t of tasks) {
5001
- rows.push({ kind: "task", project: p, task: t });
5002
- }
5003
- if ((taskCache[k] || []).length === 0) {
5004
- rows.push({ kind: "task", project: p, task: { task_slug: "__none__", title: "(no tasks)", status: "" } });
6248
+ tasks.forEach((t, idx) => {
6249
+ rows.push({
6250
+ kind: "task",
6251
+ project: p,
6252
+ task: t,
6253
+ isLastTask: idx === tasks.length - 1
6254
+ });
6255
+ });
6256
+ if (tasks.length === 0) {
6257
+ rows.push({
6258
+ kind: "task",
6259
+ project: p,
6260
+ task: { task_slug: "__none__", title: "(no tasks)", status: "" },
6261
+ isLastTask: true
6262
+ });
5005
6263
  }
5006
6264
  }
5007
6265
  return rows;
5008
- }, [sortedProjects, expanded, taskCache]);
6266
+ }, [sortedProjects, expanded, taskCache, workspacePath]);
5009
6267
  useInput3((input, key) => {
5010
6268
  if (input === "R") {
5011
6269
  setErrorLine(null);
@@ -5037,7 +6295,7 @@ var init_TasksView = __esm({
5037
6295
  }
5038
6296
  if (input === "s") {
5039
6297
  const row = flattenedRows[selectedIndex];
5040
- if (row?.kind === "task") {
6298
+ if (row?.kind === "task" && row.task.task_slug !== "__none__") {
5041
6299
  setErrorLine(null);
5042
6300
  const desired = nextStatus(row.task.status);
5043
6301
  const result = updateTaskStatus(row.project, row.task.task_slug, desired);
@@ -5063,43 +6321,55 @@ var init_TasksView = __esm({
5063
6321
  }, [flattenedRows]);
5064
6322
  const selectedRow = flattenedRows[selectedIndex];
5065
6323
  const selectedTask = selectedRow?.kind === "task" && selectedRow.task.task_slug !== "__none__" ? selectedRow.task : null;
5066
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "white", flexGrow: 1, children: [
5067
- /* @__PURE__ */ jsxs6(Box7, { justifyContent: "space-between", children: [
6324
+ const totalTasks = Object.values(taskCache).flat().length;
6325
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "white", flexGrow: 1, children: [
6326
+ /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, justifyContent: "space-between", borderBottom: true, children: [
5068
6327
  /* @__PURE__ */ jsxs6(Box7, { children: [
5069
6328
  /* @__PURE__ */ jsx8(Text7, { bold: true, color: "cyan", children: "\u2699 Tasks" }),
5070
- /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: " \u2022 " }),
6329
+ /* @__PURE__ */ jsx8(Text7, { color: "dim", children: " \u2502 " }),
5071
6330
  /* @__PURE__ */ jsxs6(Text7, { children: [
5072
6331
  sortedProjects.length,
5073
6332
  " projects"
5074
6333
  ] }),
5075
- /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: " \u2022 " }),
6334
+ /* @__PURE__ */ jsx8(Text7, { color: "dim", children: " \u2022 " }),
5076
6335
  /* @__PURE__ */ jsxs6(Text7, { children: [
5077
- Object.values(taskCache).flat().length,
6336
+ totalTasks,
5078
6337
  " tasks"
5079
6338
  ] })
5080
6339
  ] }),
5081
- /* @__PURE__ */ jsx8(Text7, { color: "dim", children: "\u2191/\u2193:Nav Enter:Expand s:Status R:Refresh" })
6340
+ /* @__PURE__ */ jsx8(Text7, { color: "dim", children: "\u2191\u2193:Nav Enter:Expand s:Status R:Refresh" })
5082
6341
  ] }),
5083
- errorLine && /* @__PURE__ */ jsx8(Box7, { marginTop: 0, children: /* @__PURE__ */ jsx8(Text7, { color: "red", children: errorLine }) }),
5084
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "row", flexGrow: 1, children: [
5085
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", width: "55%", children: [
5086
- flattenedRows.length === 0 ? /* @__PURE__ */ jsx8(Text7, { color: "dim", children: "No projects detected." }) : flattenedRows.map((row, idx) => {
5087
- const k = projectKey(row.project);
5088
- return /* @__PURE__ */ jsx8(
5089
- TaskRow,
5090
- {
5091
- row,
5092
- isSelected: idx === selectedIndex,
5093
- isExpanded: expanded.has(k),
5094
- taskCount: (taskCache[k] || []).length,
5095
- hasDrift: !!driftReports[row.project.path]?.hasDrift
5096
- },
5097
- row.kind === "project" ? `p:${k}` : `t:${k}:${row.task.task_slug}`
5098
- );
5099
- }),
5100
- /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "gray", children: "\u25B2/\u25BC navigate \u2022 Enter expand/collapse \u2022 s cycle status \u2022 R refresh" }) })
5101
- ] }),
5102
- /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", width: "45%", paddingLeft: 2, children: /* @__PURE__ */ jsx8(TaskDetails, { task: selectedTask }) })
6342
+ errorLine && /* @__PURE__ */ jsx8(Box7, { paddingX: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
6343
+ "\u26A0 ",
6344
+ errorLine
6345
+ ] }) }),
6346
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", flexGrow: 1, paddingX: 1, paddingY: 1, children: [
6347
+ /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", width: "55%", borderStyle: "single", borderColor: "dim", borderRight: true, paddingRight: 1, children: flattenedRows.length === 0 ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: [
6348
+ /* @__PURE__ */ jsx8(Text7, { color: "dim", children: "No projects detected." }),
6349
+ /* @__PURE__ */ jsx8(Text7, { color: "dim", children: "Run the wizard to set up projects." })
6350
+ ] }) : /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", children: flattenedRows.map((row, idx) => {
6351
+ const k = projectKey(row.project);
6352
+ const isCurrentProject = row.kind === "project" ? row.isCurrentProject : false;
6353
+ const isLastTask = row.kind === "task" ? row.isLastTask : false;
6354
+ return /* @__PURE__ */ jsx8(
6355
+ TaskRow,
6356
+ {
6357
+ row,
6358
+ isSelected: idx === selectedIndex,
6359
+ isExpanded: expanded.has(k),
6360
+ taskCount: (taskCache[k] || []).length,
6361
+ hasDrift: !!driftReports[row.project.path]?.hasDrift,
6362
+ isCurrentProject,
6363
+ isLastTask
6364
+ },
6365
+ row.kind === "project" ? `p:${k}` : `t:${k}:${row.task.task_slug}`
6366
+ );
6367
+ }) }) }),
6368
+ /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", width: "45%", paddingLeft: 1, children: /* @__PURE__ */ jsx8(TaskDetails, { task: selectedTask }) })
6369
+ ] }),
6370
+ /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, justifyContent: "space-between", borderTop: true, children: [
6371
+ /* @__PURE__ */ jsx8(Text7, { color: "dim", children: "s:Cycle Status R:Refresh ?:Help" }),
6372
+ /* @__PURE__ */ jsx8(Text7, { color: "dim", children: "RRCE MCP Hub v0.3.14" })
5103
6373
  ] })
5104
6374
  ] });
5105
6375
  };
@@ -5227,7 +6497,7 @@ __export(App_exports, {
5227
6497
  });
5228
6498
  import { useState as useState5, useEffect as useEffect6, useMemo as useMemo5, useCallback as useCallback3 } from "react";
5229
6499
  import { Box as Box11, useInput as useInput5, useApp } from "ink";
5230
- import fs20 from "fs";
6500
+ import fs21 from "fs";
5231
6501
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
5232
6502
  var App;
5233
6503
  var init_App = __esm({
@@ -5300,18 +6570,18 @@ var init_App = __esm({
5300
6570
  useEffect6(() => {
5301
6571
  const logPath = getLogFilePath();
5302
6572
  let lastSize = 0;
5303
- if (fs20.existsSync(logPath)) {
5304
- const stats = fs20.statSync(logPath);
6573
+ if (fs21.existsSync(logPath)) {
6574
+ const stats = fs21.statSync(logPath);
5305
6575
  lastSize = stats.size;
5306
6576
  }
5307
6577
  const interval = setInterval(() => {
5308
- if (fs20.existsSync(logPath)) {
5309
- const stats = fs20.statSync(logPath);
6578
+ if (fs21.existsSync(logPath)) {
6579
+ const stats = fs21.statSync(logPath);
5310
6580
  if (stats.size > lastSize) {
5311
6581
  const buffer = Buffer.alloc(stats.size - lastSize);
5312
- const fd = fs20.openSync(logPath, "r");
5313
- fs20.readSync(fd, buffer, 0, buffer.length, lastSize);
5314
- fs20.closeSync(fd);
6582
+ const fd = fs21.openSync(logPath, "r");
6583
+ fs21.readSync(fd, buffer, 0, buffer.length, lastSize);
6584
+ fs21.closeSync(fd);
5315
6585
  const newContent = buffer.toString("utf-8");
5316
6586
  const newLines = newContent.split("\n").filter((l) => l.trim());
5317
6587
  setLogs((prev) => {
@@ -5536,15 +6806,15 @@ __export(update_flow_exports, {
5536
6806
  });
5537
6807
  import { confirm as confirm5, spinner as spinner2, note as note6, outro as outro2, cancel as cancel2, isCancel as isCancel7 } from "@clack/prompts";
5538
6808
  import pc8 from "picocolors";
5539
- import * as fs21 from "fs";
5540
- import * as path22 from "path";
6809
+ import * as fs22 from "fs";
6810
+ import * as path23 from "path";
5541
6811
  import { stringify as stringify2, parse } from "yaml";
5542
6812
  function backupFile(filePath) {
5543
- if (!fs21.existsSync(filePath)) return null;
6813
+ if (!fs22.existsSync(filePath)) return null;
5544
6814
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").split("T")[0] + "-" + Date.now();
5545
6815
  const backupPath = `${filePath}.${timestamp}.bak`;
5546
6816
  try {
5547
- fs21.copyFileSync(filePath, backupPath);
6817
+ fs22.copyFileSync(filePath, backupPath);
5548
6818
  return backupPath;
5549
6819
  } catch (e) {
5550
6820
  console.error(`Failed to backup ${filePath}:`, e);
@@ -5554,9 +6824,9 @@ function backupFile(filePath) {
5554
6824
  function getPackageVersion2() {
5555
6825
  try {
5556
6826
  const agentCoreDir = getAgentCoreDir();
5557
- const packageJsonPath = path22.join(path22.dirname(agentCoreDir), "package.json");
5558
- if (fs21.existsSync(packageJsonPath)) {
5559
- return JSON.parse(fs21.readFileSync(packageJsonPath, "utf8")).version;
6827
+ const packageJsonPath = path23.join(path23.dirname(agentCoreDir), "package.json");
6828
+ if (fs22.existsSync(packageJsonPath)) {
6829
+ return JSON.parse(fs22.readFileSync(packageJsonPath, "utf8")).version;
5560
6830
  }
5561
6831
  } catch (e) {
5562
6832
  }
@@ -5571,9 +6841,9 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
5571
6841
  const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
5572
6842
  const configFilePath = getConfigPath(workspacePath);
5573
6843
  let currentSyncedVersion;
5574
- if (fs21.existsSync(configFilePath)) {
6844
+ if (fs22.existsSync(configFilePath)) {
5575
6845
  try {
5576
- const content = fs21.readFileSync(configFilePath, "utf-8");
6846
+ const content = fs22.readFileSync(configFilePath, "utf-8");
5577
6847
  const config = parse(content);
5578
6848
  currentSyncedVersion = config.last_synced_version;
5579
6849
  } catch (e) {
@@ -5581,8 +6851,8 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
5581
6851
  }
5582
6852
  const driftReport = DriftService.checkDrift(dataPaths[0], currentSyncedVersion, runningVersion);
5583
6853
  const ideTargets = [];
5584
- if (fs21.existsSync(configFilePath)) {
5585
- const configContent = fs21.readFileSync(configFilePath, "utf-8");
6854
+ if (fs22.existsSync(configFilePath)) {
6855
+ const configContent = fs22.readFileSync(configFilePath, "utf-8");
5586
6856
  if (configContent.includes("opencode: true")) ideTargets.push("OpenCode agents");
5587
6857
  if (configContent.includes("copilot: true")) ideTargets.push("GitHub Copilot");
5588
6858
  if (configContent.includes("antigravity: true")) ideTargets.push("Antigravity");
@@ -5591,14 +6861,14 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
5591
6861
  const dirs = ["templates", "prompts", "docs"];
5592
6862
  const updatedFiles = [];
5593
6863
  for (const dir of dirs) {
5594
- const srcDir = path22.join(agentCoreDir, dir);
5595
- if (!fs21.existsSync(srcDir)) continue;
6864
+ const srcDir = path23.join(agentCoreDir, dir);
6865
+ if (!fs22.existsSync(srcDir)) continue;
5596
6866
  const syncFiles = (src, rel) => {
5597
- const entries = fs21.readdirSync(src, { withFileTypes: true });
6867
+ const entries = fs22.readdirSync(src, { withFileTypes: true });
5598
6868
  for (const entry of entries) {
5599
- const entrySrc = path22.join(src, entry.name);
5600
- const entryRel = path22.join(rel, entry.name);
5601
- const entryDest = path22.join(dataPath, entryRel);
6869
+ const entrySrc = path23.join(src, entry.name);
6870
+ const entryRel = path23.join(rel, entry.name);
6871
+ const entryDest = path23.join(dataPath, entryRel);
5602
6872
  if (entry.isDirectory()) {
5603
6873
  ensureDir(entryDest);
5604
6874
  syncFiles(entrySrc, entryRel);
@@ -5606,7 +6876,7 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
5606
6876
  if (driftReport.modifiedFiles.includes(entryRel)) {
5607
6877
  backupFile(entryDest);
5608
6878
  }
5609
- fs21.copyFileSync(entrySrc, entryDest);
6879
+ fs22.copyFileSync(entrySrc, entryDest);
5610
6880
  updatedFiles.push(entryRel);
5611
6881
  }
5612
6882
  }
@@ -5617,12 +6887,12 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
5617
6887
  DriftService.saveManifest(dataPath, manifest);
5618
6888
  }
5619
6889
  const rrceHome = customGlobalPath || getDefaultRRCEHome2();
5620
- ensureDir(path22.join(rrceHome, "templates"));
5621
- ensureDir(path22.join(rrceHome, "docs"));
5622
- copyDirRecursive(path22.join(agentCoreDir, "templates"), path22.join(rrceHome, "templates"));
5623
- copyDirRecursive(path22.join(agentCoreDir, "docs"), path22.join(rrceHome, "docs"));
5624
- if (fs21.existsSync(configFilePath)) {
5625
- const configContent = fs21.readFileSync(configFilePath, "utf-8");
6890
+ ensureDir(path23.join(rrceHome, "templates"));
6891
+ ensureDir(path23.join(rrceHome, "docs"));
6892
+ copyDirRecursive(path23.join(agentCoreDir, "templates"), path23.join(rrceHome, "templates"));
6893
+ copyDirRecursive(path23.join(agentCoreDir, "docs"), path23.join(rrceHome, "docs"));
6894
+ if (fs22.existsSync(configFilePath)) {
6895
+ const configContent = fs22.readFileSync(configFilePath, "utf-8");
5626
6896
  if (configContent.includes("copilot: true")) {
5627
6897
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
5628
6898
  ensureDir(copilotPath);
@@ -5644,21 +6914,21 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
5644
6914
  try {
5645
6915
  const yaml = parse(configContent);
5646
6916
  yaml.last_synced_version = runningVersion;
5647
- fs21.writeFileSync(configFilePath, stringify2(yaml));
6917
+ fs22.writeFileSync(configFilePath, stringify2(yaml));
5648
6918
  } catch (e) {
5649
6919
  console.error("Failed to update config.yaml version:", e);
5650
6920
  }
5651
6921
  }
5652
- const mcpPath = path22.join(rrceHome, "mcp.yaml");
5653
- if (fs21.existsSync(mcpPath)) {
6922
+ const mcpPath = path23.join(rrceHome, "mcp.yaml");
6923
+ if (fs22.existsSync(mcpPath)) {
5654
6924
  try {
5655
- const content = fs21.readFileSync(mcpPath, "utf-8");
6925
+ const content = fs22.readFileSync(mcpPath, "utf-8");
5656
6926
  const yaml = parse(content);
5657
6927
  if (yaml.projects) {
5658
6928
  const project = yaml.projects.find((p) => p.name === workspaceName);
5659
6929
  if (project) {
5660
6930
  project.last_synced_version = runningVersion;
5661
- fs21.writeFileSync(mcpPath, stringify2(yaml));
6931
+ fs22.writeFileSync(mcpPath, stringify2(yaml));
5662
6932
  }
5663
6933
  }
5664
6934
  } catch (e) {
@@ -5694,9 +6964,9 @@ async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
5694
6964
  const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
5695
6965
  const configFilePath = getConfigPath(workspacePath);
5696
6966
  let currentSyncedVersion;
5697
- if (fs21.existsSync(configFilePath)) {
6967
+ if (fs22.existsSync(configFilePath)) {
5698
6968
  try {
5699
- const content = fs21.readFileSync(configFilePath, "utf-8");
6969
+ const content = fs22.readFileSync(configFilePath, "utf-8");
5700
6970
  const config = parse(content);
5701
6971
  currentSyncedVersion = config.last_synced_version;
5702
6972
  } catch (e) {
@@ -5720,8 +6990,8 @@ async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
5720
6990
  ` \u2022 docs/ (documentation)`
5721
6991
  ];
5722
6992
  const ideTargets = [];
5723
- if (fs21.existsSync(configFilePath)) {
5724
- const configContent = fs21.readFileSync(configFilePath, "utf-8");
6993
+ if (fs22.existsSync(configFilePath)) {
6994
+ const configContent = fs22.readFileSync(configFilePath, "utf-8");
5725
6995
  if (configContent.includes("opencode: true")) ideTargets.push("OpenCode agents");
5726
6996
  if (configContent.includes("copilot: true")) ideTargets.push("GitHub Copilot");
5727
6997
  if (configContent.includes("antigravity: true")) ideTargets.push("Antigravity");
@@ -5750,14 +7020,14 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
5750
7020
  const dirs = ["templates", "prompts", "docs"];
5751
7021
  const updatedFiles = [];
5752
7022
  for (const dir of dirs) {
5753
- const srcDir = path22.join(agentCoreDir, dir);
5754
- if (!fs21.existsSync(srcDir)) continue;
7023
+ const srcDir = path23.join(agentCoreDir, dir);
7024
+ if (!fs22.existsSync(srcDir)) continue;
5755
7025
  const syncFiles = (src, rel) => {
5756
- const entries = fs21.readdirSync(src, { withFileTypes: true });
7026
+ const entries = fs22.readdirSync(src, { withFileTypes: true });
5757
7027
  for (const entry of entries) {
5758
- const entrySrc = path22.join(src, entry.name);
5759
- const entryRel = path22.join(rel, entry.name);
5760
- const entryDest = path22.join(dataPath, entryRel);
7028
+ const entrySrc = path23.join(src, entry.name);
7029
+ const entryRel = path23.join(rel, entry.name);
7030
+ const entryDest = path23.join(dataPath, entryRel);
5761
7031
  if (entry.isDirectory()) {
5762
7032
  ensureDir(entryDest);
5763
7033
  syncFiles(entrySrc, entryRel);
@@ -5765,7 +7035,7 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
5765
7035
  if (driftReport.modifiedFiles.includes(entryRel)) {
5766
7036
  backupFile(entryDest);
5767
7037
  }
5768
- fs21.copyFileSync(entrySrc, entryDest);
7038
+ fs22.copyFileSync(entrySrc, entryDest);
5769
7039
  updatedFiles.push(entryRel);
5770
7040
  }
5771
7041
  }
@@ -5776,12 +7046,12 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
5776
7046
  DriftService.saveManifest(dataPath, manifest);
5777
7047
  }
5778
7048
  const rrceHome = customGlobalPath || getDefaultRRCEHome2();
5779
- ensureDir(path22.join(rrceHome, "templates"));
5780
- ensureDir(path22.join(rrceHome, "docs"));
5781
- copyDirRecursive(path22.join(agentCoreDir, "templates"), path22.join(rrceHome, "templates"));
5782
- copyDirRecursive(path22.join(agentCoreDir, "docs"), path22.join(rrceHome, "docs"));
5783
- if (fs21.existsSync(configFilePath)) {
5784
- const configContent = fs21.readFileSync(configFilePath, "utf-8");
7049
+ ensureDir(path23.join(rrceHome, "templates"));
7050
+ ensureDir(path23.join(rrceHome, "docs"));
7051
+ copyDirRecursive(path23.join(agentCoreDir, "templates"), path23.join(rrceHome, "templates"));
7052
+ copyDirRecursive(path23.join(agentCoreDir, "docs"), path23.join(rrceHome, "docs"));
7053
+ if (fs22.existsSync(configFilePath)) {
7054
+ const configContent = fs22.readFileSync(configFilePath, "utf-8");
5785
7055
  if (configContent.includes("copilot: true")) {
5786
7056
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
5787
7057
  ensureDir(copilotPath);
@@ -5803,21 +7073,21 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
5803
7073
  try {
5804
7074
  const yaml = parse(configContent);
5805
7075
  yaml.last_synced_version = runningVersion;
5806
- fs21.writeFileSync(configFilePath, stringify2(yaml));
7076
+ fs22.writeFileSync(configFilePath, stringify2(yaml));
5807
7077
  } catch (e) {
5808
7078
  console.error("Failed to update config.yaml version:", e);
5809
7079
  }
5810
7080
  }
5811
- const mcpPath = path22.join(rrceHome, "mcp.yaml");
5812
- if (fs21.existsSync(mcpPath)) {
7081
+ const mcpPath = path23.join(rrceHome, "mcp.yaml");
7082
+ if (fs22.existsSync(mcpPath)) {
5813
7083
  try {
5814
- const content = fs21.readFileSync(mcpPath, "utf-8");
7084
+ const content = fs22.readFileSync(mcpPath, "utf-8");
5815
7085
  const yaml = parse(content);
5816
7086
  if (yaml.projects) {
5817
7087
  const project = yaml.projects.find((p) => p.name === workspaceName);
5818
7088
  if (project) {
5819
7089
  project.last_synced_version = runningVersion;
5820
- fs21.writeFileSync(mcpPath, stringify2(yaml));
7090
+ fs22.writeFileSync(mcpPath, stringify2(yaml));
5821
7091
  }
5822
7092
  }
5823
7093
  } catch (e) {
@@ -5852,8 +7122,8 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
5852
7122
  }
5853
7123
  }
5854
7124
  function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot, customGlobalPath) {
5855
- const globalPath = path22.join(customGlobalPath, "workspaces", workspaceName);
5856
- const workspacePath = path22.join(workspaceRoot, ".rrce-workflow");
7125
+ const globalPath = path23.join(customGlobalPath, "workspaces", workspaceName);
7126
+ const workspacePath = path23.join(workspaceRoot, ".rrce-workflow");
5857
7127
  switch (mode) {
5858
7128
  case "global":
5859
7129
  return [globalPath];
@@ -5876,8 +7146,8 @@ var init_update_flow = __esm({
5876
7146
  // src/commands/wizard/index.ts
5877
7147
  import { intro as intro2, select as select5, spinner as spinner7, note as note11, outro as outro7, isCancel as isCancel12, confirm as confirm10 } from "@clack/prompts";
5878
7148
  import pc13 from "picocolors";
5879
- import * as fs25 from "fs";
5880
- import * as path24 from "path";
7149
+ import * as fs26 from "fs";
7150
+ import * as path25 from "path";
5881
7151
  import { parse as parse2 } from "yaml";
5882
7152
 
5883
7153
  // src/lib/git.ts
@@ -6554,7 +7824,7 @@ async function handlePostSetup(config, workspacePath, workspaceName, linkedProje
6554
7824
  init_paths();
6555
7825
  import { multiselect as multiselect4, spinner as spinner4, note as note8, outro as outro4, cancel as cancel4, isCancel as isCancel9, confirm as confirm7 } from "@clack/prompts";
6556
7826
  import pc10 from "picocolors";
6557
- import * as fs22 from "fs";
7827
+ import * as fs23 from "fs";
6558
7828
  init_detection();
6559
7829
  async function runLinkProjectsFlow(workspacePath, workspaceName) {
6560
7830
  const projects = scanForProjects({
@@ -6593,7 +7863,7 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
6593
7863
  const s = spinner4();
6594
7864
  s.start("Linking projects");
6595
7865
  const configFilePath = getConfigPath(workspacePath);
6596
- let configContent = fs22.readFileSync(configFilePath, "utf-8");
7866
+ let configContent = fs23.readFileSync(configFilePath, "utf-8");
6597
7867
  if (configContent.includes("linked_projects:")) {
6598
7868
  const lines = configContent.split("\n");
6599
7869
  const linkedIndex = lines.findIndex((l) => l.trim() === "linked_projects:");
@@ -6620,7 +7890,7 @@ linked_projects:
6620
7890
  `;
6621
7891
  });
6622
7892
  }
6623
- fs22.writeFileSync(configFilePath, configContent);
7893
+ fs23.writeFileSync(configFilePath, configContent);
6624
7894
  generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
6625
7895
  s.stop("Projects linked");
6626
7896
  const workspaceFile = `${workspaceName}.code-workspace`;
@@ -6656,15 +7926,15 @@ init_paths();
6656
7926
  init_utils();
6657
7927
  import { confirm as confirm8, spinner as spinner5, note as note9, outro as outro5, cancel as cancel5, isCancel as isCancel10 } from "@clack/prompts";
6658
7928
  import pc11 from "picocolors";
6659
- import * as fs23 from "fs";
6660
- import * as path23 from "path";
7929
+ import * as fs24 from "fs";
7930
+ import * as path24 from "path";
6661
7931
  async function runSyncToGlobalFlow(workspacePath, workspaceName) {
6662
7932
  const localPath = getLocalWorkspacePath(workspacePath);
6663
7933
  const customGlobalPath = getEffectiveRRCEHome(workspacePath);
6664
- const globalPath = path23.join(customGlobalPath, "workspaces", workspaceName);
7934
+ const globalPath = path24.join(customGlobalPath, "workspaces", workspaceName);
6665
7935
  const subdirs = ["knowledge", "prompts", "templates", "tasks", "refs"];
6666
7936
  const existingDirs = subdirs.filter(
6667
- (dir) => fs23.existsSync(path23.join(localPath, dir))
7937
+ (dir) => fs24.existsSync(path24.join(localPath, dir))
6668
7938
  );
6669
7939
  if (existingDirs.length === 0) {
6670
7940
  outro5(pc11.yellow("No data found in workspace storage to sync."));
@@ -6690,8 +7960,8 @@ Destination: ${pc11.cyan(globalPath)}`,
6690
7960
  try {
6691
7961
  ensureDir(globalPath);
6692
7962
  for (const dir of existingDirs) {
6693
- const srcDir = path23.join(localPath, dir);
6694
- const destDir = path23.join(globalPath, dir);
7963
+ const srcDir = path24.join(localPath, dir);
7964
+ const destDir = path24.join(globalPath, dir);
6695
7965
  ensureDir(destDir);
6696
7966
  copyDirRecursive(srcDir, destDir);
6697
7967
  }
@@ -6719,7 +7989,7 @@ init_update_flow();
6719
7989
  // src/commands/wizard/delete-flow.ts
6720
7990
  import { multiselect as multiselect5, confirm as confirm9, spinner as spinner6, note as note10, cancel as cancel6, isCancel as isCancel11 } from "@clack/prompts";
6721
7991
  import pc12 from "picocolors";
6722
- import * as fs24 from "fs";
7992
+ import * as fs25 from "fs";
6723
7993
  init_detection();
6724
7994
  init_config();
6725
7995
  async function runDeleteGlobalProjectFlow(availableProjects) {
@@ -6763,8 +8033,8 @@ Are you sure?`,
6763
8033
  for (const projectName of projectsToDelete) {
6764
8034
  const project = globalProjects.find((p) => p.name === projectName);
6765
8035
  if (!project) continue;
6766
- if (fs24.existsSync(project.dataPath)) {
6767
- fs24.rmSync(project.dataPath, { recursive: true, force: true });
8036
+ if (fs25.existsSync(project.dataPath)) {
8037
+ fs25.rmSync(project.dataPath, { recursive: true, force: true });
6768
8038
  }
6769
8039
  const newConfig = removeProjectConfig(mcpConfig, projectName);
6770
8040
  configChanged = true;
@@ -6786,9 +8056,9 @@ init_config();
6786
8056
  function getPackageVersion3() {
6787
8057
  try {
6788
8058
  const agentCoreDir = getAgentCoreDir();
6789
- const packageJsonPath = path24.join(path24.dirname(agentCoreDir), "package.json");
6790
- if (fs25.existsSync(packageJsonPath)) {
6791
- return JSON.parse(fs25.readFileSync(packageJsonPath, "utf8")).version;
8059
+ const packageJsonPath = path25.join(path25.dirname(agentCoreDir), "package.json");
8060
+ if (fs26.existsSync(packageJsonPath)) {
8061
+ return JSON.parse(fs26.readFileSync(packageJsonPath, "utf8")).version;
6792
8062
  }
6793
8063
  } catch (e) {
6794
8064
  }
@@ -6796,9 +8066,9 @@ function getPackageVersion3() {
6796
8066
  }
6797
8067
  function getLastSyncedVersion(workspacePath, workspaceName) {
6798
8068
  const configFilePath = getConfigPath(workspacePath);
6799
- if (fs25.existsSync(configFilePath)) {
8069
+ if (fs26.existsSync(configFilePath)) {
6800
8070
  try {
6801
- const content = fs25.readFileSync(configFilePath, "utf-8");
8071
+ const content = fs26.readFileSync(configFilePath, "utf-8");
6802
8072
  const config = parse2(content);
6803
8073
  if (config.last_synced_version) {
6804
8074
  return config.last_synced_version;
@@ -6807,10 +8077,10 @@ function getLastSyncedVersion(workspacePath, workspaceName) {
6807
8077
  }
6808
8078
  }
6809
8079
  const rrceHome = getEffectiveRRCEHome(workspacePath) || getDefaultRRCEHome2();
6810
- const mcpPath = path24.join(rrceHome, "mcp.yaml");
6811
- if (fs25.existsSync(mcpPath)) {
8080
+ const mcpPath = path25.join(rrceHome, "mcp.yaml");
8081
+ if (fs26.existsSync(mcpPath)) {
6812
8082
  try {
6813
- const content = fs25.readFileSync(mcpPath, "utf-8");
8083
+ const content = fs26.readFileSync(mcpPath, "utf-8");
6814
8084
  const config = parse2(content);
6815
8085
  const project = config.projects?.find((p) => p.name === workspaceName);
6816
8086
  if (project?.last_synced_version) {
@@ -6880,11 +8150,11 @@ Workspace: ${pc13.bold(workspaceName)}`,
6880
8150
  workspacePath
6881
8151
  });
6882
8152
  const configFilePath = getConfigPath(workspacePath);
6883
- let isAlreadyConfigured = fs25.existsSync(configFilePath);
8153
+ let isAlreadyConfigured = fs26.existsSync(configFilePath);
6884
8154
  let currentStorageMode = null;
6885
8155
  if (isAlreadyConfigured) {
6886
8156
  try {
6887
- const configContent = fs25.readFileSync(configFilePath, "utf-8");
8157
+ const configContent = fs26.readFileSync(configFilePath, "utf-8");
6888
8158
  const modeMatch = configContent.match(/mode:\s*(global|workspace)/);
6889
8159
  currentStorageMode = modeMatch?.[1] ?? null;
6890
8160
  } catch {
@@ -6901,7 +8171,7 @@ Workspace: ${pc13.bold(workspaceName)}`,
6901
8171
  }
6902
8172
  }
6903
8173
  const localDataPath = getLocalWorkspacePath(workspacePath);
6904
- const hasLocalData = fs25.existsSync(localDataPath);
8174
+ const hasLocalData = fs26.existsSync(localDataPath);
6905
8175
  if (isAlreadyConfigured) {
6906
8176
  const continueToMenu = await checkAndPromptUpdate(workspacePath, workspaceName, currentStorageMode);
6907
8177
  if (!continueToMenu) {