rrce-workflow 0.2.92 → 0.2.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +629 -144
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1562,6 +1562,33 @@ var init_rag = __esm({
1562
1562
  logger.error(`[RAG] Failed to save index to ${this.indexPath}`, error);
1563
1563
  }
1564
1564
  }
1565
+ /**
1566
+ * Save index to a temp file and atomically replace
1567
+ */
1568
+ saveIndexAtomic() {
1569
+ if (!this.index) return;
1570
+ const dir = path15.dirname(this.indexPath);
1571
+ if (!fs13.existsSync(dir)) {
1572
+ fs13.mkdirSync(dir, { recursive: true });
1573
+ }
1574
+ const tmpPath = `${this.indexPath}.tmp`;
1575
+ fs13.writeFileSync(tmpPath, JSON.stringify(this.index, null, 2));
1576
+ fs13.renameSync(tmpPath, this.indexPath);
1577
+ }
1578
+ /**
1579
+ * Save index only if enough time passed since last save
1580
+ */
1581
+ maybeSaveIndex(force = false) {
1582
+ if (!this.index) return;
1583
+ const now = Date.now();
1584
+ const intervalMs = 1e3;
1585
+ const last = this.index.metadata?.lastSaveAt;
1586
+ if (force || last === void 0 || now - last >= intervalMs) {
1587
+ this.index.metadata = { ...this.index.metadata ?? {}, lastSaveAt: now };
1588
+ this.saveIndexAtomic();
1589
+ logger.info(`[RAG] Saved index (atomic) to ${this.indexPath} with ${this.index.chunks.length} chunks.`);
1590
+ }
1591
+ }
1565
1592
  /**
1566
1593
  * Generate embedding for text
1567
1594
  */
@@ -1612,7 +1639,7 @@ var init_rag = __esm({
1612
1639
  mtime: mtime ?? Date.now(),
1613
1640
  chunkCount: chunks.length
1614
1641
  };
1615
- this.saveIndex();
1642
+ this.maybeSaveIndex();
1616
1643
  return true;
1617
1644
  }
1618
1645
  /**
@@ -1628,7 +1655,7 @@ var init_rag = __esm({
1628
1655
  }
1629
1656
  if (this.index.chunks.length !== initialCount) {
1630
1657
  logger.info(`[RAG] Removed file ${filePath} from index (${initialCount - this.index.chunks.length} chunks removed)`);
1631
- this.saveIndex();
1658
+ this.maybeSaveIndex(true);
1632
1659
  }
1633
1660
  }
1634
1661
  /**
@@ -1651,7 +1678,7 @@ var init_rag = __esm({
1651
1678
  this.loadIndex();
1652
1679
  if (!this.index) return;
1653
1680
  this.index.lastFullIndex = Date.now();
1654
- this.saveIndex();
1681
+ this.maybeSaveIndex(true);
1655
1682
  }
1656
1683
  /**
1657
1684
  * Search the index
@@ -1681,9 +1708,11 @@ var init_rag = __esm({
1681
1708
  let normA = 0;
1682
1709
  let normB = 0;
1683
1710
  for (let i = 0; i < a.length; i++) {
1684
- dotProduct += a[i] * b[i];
1685
- normA += a[i] * a[i];
1686
- normB += b[i] * b[i];
1711
+ const av = a[i] ?? 0;
1712
+ const bv = b[i] ?? 0;
1713
+ dotProduct += av * bv;
1714
+ normA += av * av;
1715
+ normB += bv * bv;
1687
1716
  }
1688
1717
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
1689
1718
  }
@@ -1716,6 +1745,83 @@ var init_rag = __esm({
1716
1745
  }
1717
1746
  });
1718
1747
 
1748
+ // src/mcp/services/indexing-jobs.ts
1749
+ var IndexingJobManager, indexingJobs;
1750
+ var init_indexing_jobs = __esm({
1751
+ "src/mcp/services/indexing-jobs.ts"() {
1752
+ "use strict";
1753
+ init_logger();
1754
+ IndexingJobManager = class {
1755
+ jobs = /* @__PURE__ */ new Map();
1756
+ getProgress(project) {
1757
+ const existing = this.jobs.get(project);
1758
+ if (existing) return { ...existing.progress };
1759
+ return {
1760
+ project,
1761
+ state: "idle",
1762
+ itemsDone: 0,
1763
+ itemsTotal: void 0
1764
+ };
1765
+ }
1766
+ update(project, patch) {
1767
+ const existing = this.jobs.get(project);
1768
+ const next = {
1769
+ ...existing?.progress ?? this.getProgress(project),
1770
+ ...patch,
1771
+ project
1772
+ };
1773
+ this.jobs.set(project, { ...existing ?? { progress: next }, progress: next });
1774
+ }
1775
+ isRunning(project) {
1776
+ return this.getProgress(project).state === "running";
1777
+ }
1778
+ startOrStatus(project, runner) {
1779
+ const current = this.jobs.get(project);
1780
+ if (current?.progress.state === "running" && current.promise) {
1781
+ return {
1782
+ status: "already_running",
1783
+ state: "running",
1784
+ progress: { ...current.progress }
1785
+ };
1786
+ }
1787
+ const startedAt = Date.now();
1788
+ const initial = {
1789
+ project,
1790
+ state: "running",
1791
+ startedAt,
1792
+ itemsDone: 0,
1793
+ itemsTotal: void 0,
1794
+ currentItem: void 0,
1795
+ lastError: void 0
1796
+ };
1797
+ const job = { progress: initial };
1798
+ this.jobs.set(project, job);
1799
+ job.promise = (async () => {
1800
+ try {
1801
+ await runner();
1802
+ this.update(project, { state: "complete", completedAt: Date.now(), currentItem: void 0 });
1803
+ } catch (err) {
1804
+ const msg = err instanceof Error ? err.message : String(err);
1805
+ logger.error(`[RAG] Indexing job failed for '${project}'`, err);
1806
+ this.update(project, {
1807
+ state: "failed",
1808
+ completedAt: Date.now(),
1809
+ currentItem: void 0,
1810
+ lastError: msg
1811
+ });
1812
+ }
1813
+ })();
1814
+ return {
1815
+ status: "started",
1816
+ state: "running",
1817
+ progress: { ...this.getProgress(project) }
1818
+ };
1819
+ }
1820
+ };
1821
+ indexingJobs = new IndexingJobManager();
1822
+ }
1823
+ });
1824
+
1719
1825
  // src/mcp/resources.ts
1720
1826
  import * as fs14 from "fs";
1721
1827
  import * as path16 from "path";
@@ -1887,6 +1993,8 @@ async function searchKnowledge(query, projectFilter) {
1887
1993
  if (projectFilter && project.name !== projectFilter) continue;
1888
1994
  const permissions = getProjectPermissions(config, project.name, project.sourcePath || project.path);
1889
1995
  if (!permissions.knowledge || !project.knowledgePath) continue;
1996
+ const indexingInProgress = indexingJobs.isRunning(project.name);
1997
+ const advisoryMessage = indexingInProgress ? "Indexing in progress; results may be stale/incomplete." : void 0;
1890
1998
  const projConfig = config.projects.find(
1891
1999
  (p) => p.path && normalizeProjectPath(p.path) === normalizeProjectPath(project.sourcePath || project.path) || !p.path && p.name === project.name
1892
2000
  );
@@ -1903,7 +2011,9 @@ async function searchKnowledge(query, projectFilter) {
1903
2011
  file: path16.relative(project.knowledgePath, r.filePath),
1904
2012
  matches: [r.content],
1905
2013
  // The chunk content is the match
1906
- score: r.score
2014
+ score: r.score,
2015
+ indexingInProgress: indexingInProgress || void 0,
2016
+ advisoryMessage
1907
2017
  });
1908
2018
  }
1909
2019
  continue;
@@ -1928,8 +2038,10 @@ async function searchKnowledge(query, projectFilter) {
1928
2038
  results.push({
1929
2039
  project: project.name,
1930
2040
  file,
1931
- matches: matches.slice(0, 5)
2041
+ matches: matches.slice(0, 5),
1932
2042
  // Limit to 5 matches per file
2043
+ indexingInProgress: indexingInProgress || void 0,
2044
+ advisoryMessage
1933
2045
  });
1934
2046
  }
1935
2047
  }
@@ -1941,20 +2053,44 @@ async function searchKnowledge(query, projectFilter) {
1941
2053
  async function indexKnowledge(projectName, force = false) {
1942
2054
  const config = loadMCPConfig();
1943
2055
  const projects = getExposedProjects();
1944
- const project = projects.find((p) => p.name === projectName || p.path && p.path === projectName);
2056
+ const project = projects.find((p2) => p2.name === projectName || p2.path && p2.path === projectName);
1945
2057
  if (!project) {
1946
- return { success: false, message: `Project '${projectName}' not found`, filesIndexed: 0, filesSkipped: 0 };
2058
+ return {
2059
+ state: "failed",
2060
+ status: "failed",
2061
+ success: false,
2062
+ message: `Project '${projectName}' not found`,
2063
+ filesIndexed: 0,
2064
+ filesSkipped: 0,
2065
+ progress: { itemsDone: 0 }
2066
+ };
1947
2067
  }
1948
2068
  const projConfig = config.projects.find(
1949
- (p) => p.path && normalizeProjectPath(p.path) === normalizeProjectPath(project.sourcePath || project.path) || !p.path && p.name === project.name
2069
+ (p2) => p2.path && normalizeProjectPath(p2.path) === normalizeProjectPath(project.sourcePath || project.path) || !p2.path && p2.name === project.name
1950
2070
  ) || (project.source === "global" ? { semanticSearch: { enabled: true, model: "Xenova/all-MiniLM-L6-v2" } } : void 0);
1951
2071
  const isEnabled = projConfig?.semanticSearch?.enabled || project.semanticSearchEnabled;
1952
2072
  if (!isEnabled) {
1953
- return { success: false, message: "Semantic Search is not enabled for this project", filesIndexed: 0, filesSkipped: 0 };
2073
+ return {
2074
+ state: "failed",
2075
+ status: "failed",
2076
+ success: false,
2077
+ message: "Semantic Search is not enabled for this project",
2078
+ filesIndexed: 0,
2079
+ filesSkipped: 0,
2080
+ progress: { itemsDone: 0 }
2081
+ };
1954
2082
  }
1955
2083
  const scanRoot = project.sourcePath || project.path || project.dataPath;
1956
2084
  if (!fs14.existsSync(scanRoot)) {
1957
- return { success: false, message: "Project root not found", filesIndexed: 0, filesSkipped: 0 };
2085
+ return {
2086
+ state: "failed",
2087
+ status: "failed",
2088
+ success: false,
2089
+ message: "Project root not found",
2090
+ filesIndexed: 0,
2091
+ filesSkipped: 0,
2092
+ progress: { itemsDone: 0 }
2093
+ };
1958
2094
  }
1959
2095
  const INDEXABLE_EXTENSIONS = [
1960
2096
  ".ts",
@@ -1995,18 +2131,37 @@ async function indexKnowledge(projectName, force = false) {
1995
2131
  ".less"
1996
2132
  ];
1997
2133
  const SKIP_DIRS = ["node_modules", ".git", "dist", "build", ".next", "__pycache__", "venv", ".venv", "target", "vendor"];
1998
- try {
2134
+ const runIndexing = async () => {
1999
2135
  const indexPath = path16.join(project.knowledgePath || path16.join(scanRoot, ".rrce-workflow", "knowledge"), "embeddings.json");
2000
2136
  const model = projConfig?.semanticSearch?.model || "Xenova/all-MiniLM-L6-v2";
2001
2137
  const rag = new RAGService(indexPath, model);
2002
2138
  let indexed = 0;
2003
2139
  let skipped = 0;
2140
+ let itemsTotal = 0;
2141
+ let itemsDone = 0;
2142
+ const shouldSkipDir = (dirName) => SKIP_DIRS.includes(dirName) || dirName.startsWith(".");
2143
+ const preCount = (dir) => {
2144
+ const entries = fs14.readdirSync(dir, { withFileTypes: true });
2145
+ for (const entry of entries) {
2146
+ const fullPath = path16.join(dir, entry.name);
2147
+ if (entry.isDirectory()) {
2148
+ if (shouldSkipDir(entry.name)) continue;
2149
+ preCount(fullPath);
2150
+ } else if (entry.isFile()) {
2151
+ const ext = path16.extname(entry.name).toLowerCase();
2152
+ if (!INDEXABLE_EXTENSIONS.includes(ext)) continue;
2153
+ itemsTotal++;
2154
+ }
2155
+ }
2156
+ };
2157
+ preCount(scanRoot);
2158
+ indexingJobs.update(project.name, { itemsTotal });
2004
2159
  const scanDir = async (dir) => {
2005
2160
  const entries = fs14.readdirSync(dir, { withFileTypes: true });
2006
2161
  for (const entry of entries) {
2007
2162
  const fullPath = path16.join(dir, entry.name);
2008
2163
  if (entry.isDirectory()) {
2009
- if (SKIP_DIRS.includes(entry.name) || entry.name.startsWith(".")) {
2164
+ if (shouldSkipDir(entry.name)) {
2010
2165
  continue;
2011
2166
  }
2012
2167
  await scanDir(fullPath);
@@ -2016,6 +2171,7 @@ async function indexKnowledge(projectName, force = false) {
2016
2171
  continue;
2017
2172
  }
2018
2173
  try {
2174
+ indexingJobs.update(project.name, { currentItem: fullPath, itemsDone });
2019
2175
  const stat = fs14.statSync(fullPath);
2020
2176
  const mtime = force ? void 0 : stat.mtimeMs;
2021
2177
  const content = fs14.readFileSync(fullPath, "utf-8");
@@ -2026,6 +2182,12 @@ async function indexKnowledge(projectName, force = false) {
2026
2182
  skipped++;
2027
2183
  }
2028
2184
  } catch (err) {
2185
+ } finally {
2186
+ itemsDone++;
2187
+ indexingJobs.update(project.name, { itemsDone });
2188
+ if (itemsDone % 10 === 0) {
2189
+ await new Promise((resolve2) => setImmediate(resolve2));
2190
+ }
2029
2191
  }
2030
2192
  }
2031
2193
  }
@@ -2033,15 +2195,28 @@ async function indexKnowledge(projectName, force = false) {
2033
2195
  await scanDir(scanRoot);
2034
2196
  rag.markFullIndex();
2035
2197
  const stats = rag.getStats();
2036
- return {
2037
- success: true,
2038
- message: `Indexed ${indexed} files, skipped ${skipped} unchanged. Total: ${stats.totalChunks} chunks from ${stats.totalFiles} files.`,
2039
- filesIndexed: indexed,
2040
- filesSkipped: skipped
2041
- };
2042
- } catch (error) {
2043
- return { success: false, message: `Indexing failed: ${error}`, filesIndexed: 0, filesSkipped: 0 };
2044
- }
2198
+ const message = `Indexed ${indexed} files, skipped ${skipped} unchanged. Total: ${stats.totalChunks} chunks from ${stats.totalFiles} files.`;
2199
+ logger.info(`[RAG] ${project.name}: ${message}`);
2200
+ indexingJobs.update(project.name, { currentItem: void 0 });
2201
+ };
2202
+ const startResult = indexingJobs.startOrStatus(project.name, runIndexing);
2203
+ const p = startResult.progress;
2204
+ return {
2205
+ state: startResult.state,
2206
+ status: startResult.status,
2207
+ success: startResult.status === "started" || startResult.status === "already_running",
2208
+ message: startResult.status === "started" ? `Indexing started in background for '${project.name}'.` : `Indexing already running for '${project.name}'.`,
2209
+ filesIndexed: 0,
2210
+ filesSkipped: 0,
2211
+ progress: {
2212
+ itemsDone: p.itemsDone,
2213
+ itemsTotal: p.itemsTotal,
2214
+ currentItem: p.currentItem,
2215
+ startedAt: p.startedAt,
2216
+ completedAt: p.completedAt,
2217
+ lastError: p.lastError
2218
+ }
2219
+ };
2045
2220
  }
2046
2221
  function getContextPreamble() {
2047
2222
  const projects = getExposedProjects();
@@ -2189,6 +2364,7 @@ var init_resources = __esm({
2189
2364
  init_detection();
2190
2365
  init_detection_service();
2191
2366
  init_rag();
2367
+ init_indexing_jobs();
2192
2368
  init_paths();
2193
2369
  }
2194
2370
  });
@@ -2905,8 +3081,8 @@ Hidden projects: ${projects.length - exposedCount}`,
2905
3081
  }
2906
3082
  async function handleConfigureGlobalPath() {
2907
3083
  const { resolveGlobalPath: resolveGlobalPath2 } = await Promise.resolve().then(() => (init_tui_utils(), tui_utils_exports));
2908
- const fs22 = await import("fs");
2909
- const path20 = await import("path");
3084
+ const fs23 = await import("fs");
3085
+ const path21 = await import("path");
2910
3086
  note3(
2911
3087
  `MCP Hub requires a ${pc5.bold("global storage path")} to store its configuration
2912
3088
  and coordinate across projects.
@@ -2920,8 +3096,8 @@ locally in each project. MCP needs a central location.`,
2920
3096
  return false;
2921
3097
  }
2922
3098
  try {
2923
- if (!fs22.existsSync(resolvedPath)) {
2924
- fs22.mkdirSync(resolvedPath, { recursive: true });
3099
+ if (!fs23.existsSync(resolvedPath)) {
3100
+ fs23.mkdirSync(resolvedPath, { recursive: true });
2925
3101
  }
2926
3102
  const config = loadMCPConfig();
2927
3103
  saveMCPConfig(config);
@@ -2929,7 +3105,7 @@ locally in each project. MCP needs a central location.`,
2929
3105
  `${pc5.green("\u2713")} Global path configured: ${pc5.cyan(resolvedPath)}
2930
3106
 
2931
3107
  MCP config will be stored at:
2932
- ${path20.join(resolvedPath, "mcp.yaml")}`,
3108
+ ${path21.join(resolvedPath, "mcp.yaml")}`,
2933
3109
  "Configuration Saved"
2934
3110
  );
2935
3111
  return true;
@@ -3169,46 +3345,234 @@ var init_SimpleSelect = __esm({
3169
3345
  }
3170
3346
  });
3171
3347
 
3348
+ // src/mcp/ui/lib/tasks-fs.ts
3349
+ import * as fs16 from "fs";
3350
+ import * as path18 from "path";
3351
+ function detectStorageModeFromConfig(workspaceRoot) {
3352
+ const configPath = getConfigPath(workspaceRoot);
3353
+ try {
3354
+ const rrceHome = getEffectiveGlobalBase();
3355
+ if (configPath.startsWith(rrceHome)) {
3356
+ return "global";
3357
+ }
3358
+ if (fs16.existsSync(configPath)) {
3359
+ const content = fs16.readFileSync(configPath, "utf-8");
3360
+ if (content.includes("mode: workspace")) return "workspace";
3361
+ if (content.includes("mode: global")) return "global";
3362
+ }
3363
+ } catch {
3364
+ }
3365
+ return "global";
3366
+ }
3367
+ function getEffectiveGlobalBase() {
3368
+ const dummy = resolveDataPath("global", "__rrce_dummy__", "");
3369
+ return path18.dirname(path18.dirname(dummy));
3370
+ }
3371
+ function getProjectRRCEData(project) {
3372
+ const workspaceRoot = project.sourcePath || project.path;
3373
+ const mode = detectStorageModeFromConfig(workspaceRoot);
3374
+ return resolveDataPath(mode, project.name, workspaceRoot);
3375
+ }
3376
+ function listProjectTasks(project) {
3377
+ const rrceData = getProjectRRCEData(project);
3378
+ const tasksPath = path18.join(rrceData, "tasks");
3379
+ if (!fs16.existsSync(tasksPath)) {
3380
+ return { projectName: project.name, tasksPath, tasks: [] };
3381
+ }
3382
+ const tasks = [];
3383
+ try {
3384
+ const entries = fs16.readdirSync(tasksPath, { withFileTypes: true });
3385
+ for (const entry of entries) {
3386
+ if (!entry.isDirectory()) continue;
3387
+ const metaPath = path18.join(tasksPath, entry.name, "meta.json");
3388
+ if (!fs16.existsSync(metaPath)) continue;
3389
+ try {
3390
+ const raw = fs16.readFileSync(metaPath, "utf-8");
3391
+ const meta = JSON.parse(raw);
3392
+ if (!meta.task_slug) meta.task_slug = entry.name;
3393
+ tasks.push(meta);
3394
+ } catch {
3395
+ }
3396
+ }
3397
+ } catch {
3398
+ }
3399
+ tasks.sort((a, b) => {
3400
+ const aTime = Date.parse(a.updated_at || a.created_at || "") || 0;
3401
+ const bTime = Date.parse(b.updated_at || b.created_at || "") || 0;
3402
+ if (aTime !== bTime) return bTime - aTime;
3403
+ return String(a.task_slug).localeCompare(String(b.task_slug));
3404
+ });
3405
+ return { projectName: project.name, tasksPath, tasks };
3406
+ }
3407
+ function updateTaskStatus(project, taskSlug, status) {
3408
+ const rrceData = getProjectRRCEData(project);
3409
+ const metaPath = path18.join(rrceData, "tasks", taskSlug, "meta.json");
3410
+ if (!fs16.existsSync(metaPath)) {
3411
+ return { ok: false, error: `meta.json not found for task '${taskSlug}'` };
3412
+ }
3413
+ try {
3414
+ const meta = JSON.parse(fs16.readFileSync(metaPath, "utf-8"));
3415
+ const next = {
3416
+ ...meta,
3417
+ status,
3418
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3419
+ };
3420
+ fs16.writeFileSync(metaPath, JSON.stringify(next, null, 2));
3421
+ return { ok: true, meta: next };
3422
+ } catch (e) {
3423
+ return { ok: false, error: String(e) };
3424
+ }
3425
+ }
3426
+ var init_tasks_fs = __esm({
3427
+ "src/mcp/ui/lib/tasks-fs.ts"() {
3428
+ "use strict";
3429
+ init_paths();
3430
+ }
3431
+ });
3432
+
3172
3433
  // src/mcp/ui/ProjectsView.tsx
3173
- import { useState as useState2 } from "react";
3434
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
3174
3435
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
3175
3436
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
3176
- var ProjectsView;
3437
+ function nextStatus(current) {
3438
+ const idx = STATUS_CYCLE.indexOf(current || "");
3439
+ if (idx === -1) return STATUS_CYCLE[0];
3440
+ return STATUS_CYCLE[(idx + 1) % STATUS_CYCLE.length];
3441
+ }
3442
+ function projectKey(p) {
3443
+ return p.sourcePath ?? p.path;
3444
+ }
3445
+ function formatProjectLabel(p) {
3446
+ const root = p.sourcePath ?? p.path;
3447
+ return `${p.name} (${p.source})${root ? ` - ${root}` : ""}`;
3448
+ }
3449
+ var STATUS_CYCLE, ProjectsView;
3177
3450
  var init_ProjectsView = __esm({
3178
3451
  "src/mcp/ui/ProjectsView.tsx"() {
3179
3452
  "use strict";
3180
3453
  init_SimpleSelect();
3181
3454
  init_config();
3455
+ init_tasks_fs();
3456
+ STATUS_CYCLE = ["pending", "in_progress", "blocked", "complete"];
3182
3457
  ProjectsView = ({ config: initialConfig, projects: allProjects, onConfigChange }) => {
3183
3458
  const [config, setConfig] = useState2(initialConfig);
3184
- useInput2((input) => {
3185
- if (input === "a") {
3186
- const newConfig = {
3187
- ...config,
3188
- defaults: {
3189
- ...config.defaults,
3190
- includeNew: !config.defaults.includeNew
3459
+ const [mode, setMode] = useState2("expose");
3460
+ const [expanded, setExpanded] = useState2(() => /* @__PURE__ */ new Set());
3461
+ const [selectedIndex, setSelectedIndex] = useState2(0);
3462
+ const [taskCache, setTaskCache] = useState2({});
3463
+ const [errorLine, setErrorLine] = useState2(null);
3464
+ const sortedProjects = useMemo2(() => {
3465
+ return [...allProjects].sort((a, b) => {
3466
+ const byName = a.name.localeCompare(b.name);
3467
+ if (byName !== 0) return byName;
3468
+ return projectKey(a).localeCompare(projectKey(b));
3469
+ });
3470
+ }, [allProjects]);
3471
+ const refreshTasksForProject = (project) => {
3472
+ const res = listProjectTasks(project);
3473
+ setTaskCache((prev) => ({ ...prev, [projectKey(project)]: res.tasks }));
3474
+ };
3475
+ const refreshAllTasks = () => {
3476
+ const next = {};
3477
+ for (const p of sortedProjects) {
3478
+ next[projectKey(p)] = listProjectTasks(p).tasks;
3479
+ }
3480
+ setTaskCache(next);
3481
+ };
3482
+ useInput2((input, key) => {
3483
+ if (input === "t") {
3484
+ setErrorLine(null);
3485
+ setMode((prev) => prev === "expose" ? "tasks" : "expose");
3486
+ return;
3487
+ }
3488
+ if (mode === "expose") {
3489
+ if (input === "a") {
3490
+ const newConfig = {
3491
+ ...config,
3492
+ defaults: {
3493
+ ...config.defaults,
3494
+ includeNew: !config.defaults.includeNew
3495
+ }
3496
+ };
3497
+ saveMCPConfig(newConfig);
3498
+ setConfig(newConfig);
3499
+ onConfigChange?.();
3500
+ }
3501
+ return;
3502
+ }
3503
+ if (mode === "tasks") {
3504
+ if (input === "R") {
3505
+ setErrorLine(null);
3506
+ refreshAllTasks();
3507
+ return;
3508
+ }
3509
+ if (key.upArrow) {
3510
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : Math.max(0, flattenedRows.length - 1));
3511
+ return;
3512
+ }
3513
+ if (key.downArrow) {
3514
+ setSelectedIndex((prev) => prev < flattenedRows.length - 1 ? prev + 1 : 0);
3515
+ return;
3516
+ }
3517
+ if (key.return) {
3518
+ const row = flattenedRows[selectedIndex];
3519
+ if (row?.kind === "project") {
3520
+ const k = projectKey(row.project);
3521
+ const next = new Set(expanded);
3522
+ if (next.has(k)) {
3523
+ next.delete(k);
3524
+ } else {
3525
+ next.add(k);
3526
+ refreshTasksForProject(row.project);
3527
+ }
3528
+ setExpanded(next);
3191
3529
  }
3192
- };
3193
- saveMCPConfig(newConfig);
3194
- setConfig(newConfig);
3195
- if (onConfigChange) onConfigChange();
3530
+ return;
3531
+ }
3532
+ if (input === "s") {
3533
+ const row = flattenedRows[selectedIndex];
3534
+ if (row?.kind === "task") {
3535
+ setErrorLine(null);
3536
+ const desired = nextStatus(row.task.status);
3537
+ const result = updateTaskStatus(row.project, row.task.task_slug, desired);
3538
+ if (!result.ok) {
3539
+ setErrorLine(`Failed to update status: ${result.error}`);
3540
+ return;
3541
+ }
3542
+ setTaskCache((prev) => {
3543
+ const k = projectKey(row.project);
3544
+ const tasks = prev[k] || [];
3545
+ const updated = tasks.map((t) => t.task_slug === row.task.task_slug ? result.meta : t);
3546
+ return { ...prev, [k]: updated };
3547
+ });
3548
+ }
3549
+ return;
3550
+ }
3196
3551
  }
3197
3552
  });
3198
- const projectItems = allProjects.map((p) => {
3199
- const projectConfig = config.projects.find(
3200
- (c) => c.path && c.path === p.path || p.source === "global" && c.name === p.name || !c.path && c.name === p.name
3201
- );
3202
- const isExposed = projectConfig ? projectConfig.expose : config.defaults.includeNew;
3203
- return {
3204
- label: p.name + ` (${p.source})` + (p.path ? ` - ${p.path}` : ""),
3205
- value: p.path,
3206
- // Standardized ID: Use root path
3207
- key: p.path,
3208
- exposed: isExposed
3209
- };
3210
- });
3211
- const initialSelected = projectItems.filter((p) => p.exposed).map((p) => p.value);
3553
+ useEffect2(() => {
3554
+ setSelectedIndex((prev) => {
3555
+ if (flattenedRows.length === 0) return 0;
3556
+ return Math.min(prev, flattenedRows.length - 1);
3557
+ });
3558
+ }, [mode, allProjects, expanded, taskCache]);
3559
+ const projectItems = useMemo2(() => {
3560
+ return allProjects.map((p) => {
3561
+ const projectConfig = config.projects.find(
3562
+ (c) => c.path && c.path === p.path || p.source === "global" && c.name === p.name || !c.path && c.name === p.name
3563
+ );
3564
+ const isExposed = projectConfig ? projectConfig.expose : config.defaults.includeNew;
3565
+ return {
3566
+ label: formatProjectLabel(p),
3567
+ value: p.path,
3568
+ key: p.path,
3569
+ exposed: isExposed
3570
+ };
3571
+ });
3572
+ }, [allProjects, config]);
3573
+ const initialSelected = useMemo2(() => {
3574
+ return projectItems.filter((p) => p.exposed).map((p) => p.value);
3575
+ }, [projectItems]);
3212
3576
  const handleSubmit = (selectedIds) => {
3213
3577
  let newConfig = { ...config };
3214
3578
  projectItems.forEach((item) => {
@@ -3228,33 +3592,135 @@ var init_ProjectsView = __esm({
3228
3592
  });
3229
3593
  saveMCPConfig(newConfig);
3230
3594
  setConfig(newConfig);
3231
- if (onConfigChange) onConfigChange();
3595
+ onConfigChange?.();
3232
3596
  };
3597
+ const flattenedRows = useMemo2(() => {
3598
+ const rows = [];
3599
+ for (const p of sortedProjects) {
3600
+ rows.push({ kind: "project", project: p });
3601
+ const k = projectKey(p);
3602
+ if (!expanded.has(k)) continue;
3603
+ const tasks = taskCache[k] || [];
3604
+ for (const t of tasks) {
3605
+ rows.push({ kind: "task", project: p, task: t });
3606
+ }
3607
+ if ((taskCache[k] || []).length === 0) {
3608
+ rows.push({ kind: "task", project: p, task: { task_slug: "__none__", title: "(no tasks)", status: "" } });
3609
+ }
3610
+ }
3611
+ return rows;
3612
+ }, [sortedProjects, expanded, taskCache]);
3613
+ const selectedRow = flattenedRows[selectedIndex];
3614
+ const selectedTask = selectedRow?.kind === "task" && selectedRow.task.task_slug !== "__none__" ? selectedRow.task : null;
3615
+ if (mode === "expose") {
3616
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan", flexGrow: 1, children: [
3617
+ /* @__PURE__ */ jsxs3(Box4, { justifyContent: "space-between", children: [
3618
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: " Projects (Expose Mode) " }),
3619
+ /* @__PURE__ */ jsxs3(Box4, { children: [
3620
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Auto-expose new: " }),
3621
+ /* @__PURE__ */ jsx4(Text4, { color: config.defaults.includeNew ? "green" : "red", children: config.defaults.includeNew ? "ON" : "OFF" }),
3622
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " (Press 'a' to toggle)" })
3623
+ ] })
3624
+ ] }),
3625
+ /* @__PURE__ */ jsx4(Text4, { color: "dim", children: " Space toggles, Enter saves. Press 't' to switch to Tasks Mode." }),
3626
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx4(
3627
+ SimpleSelect,
3628
+ {
3629
+ message: "",
3630
+ items: projectItems,
3631
+ isMulti: true,
3632
+ initialSelected,
3633
+ onSelect: () => {
3634
+ },
3635
+ onSubmit: handleSubmit,
3636
+ onCancel: () => {
3637
+ }
3638
+ },
3639
+ JSON.stringify(initialSelected) + config.defaults.includeNew
3640
+ ) })
3641
+ ] });
3642
+ }
3233
3643
  return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan", flexGrow: 1, children: [
3234
3644
  /* @__PURE__ */ jsxs3(Box4, { justifyContent: "space-between", children: [
3235
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: " Exposed Projects " }),
3236
- /* @__PURE__ */ jsxs3(Box4, { children: [
3237
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Auto-expose new: " }),
3238
- /* @__PURE__ */ jsx4(Text4, { color: config.defaults.includeNew ? "green" : "red", children: config.defaults.includeNew ? "ON" : "OFF" }),
3239
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " (Press 'a' to toggle)" })
3240
- ] })
3645
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: " Projects (Tasks Mode) " }),
3646
+ /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "t:Expose Enter:Expand s:Status R:Refresh" })
3241
3647
  ] }),
3242
- /* @__PURE__ */ jsx4(Text4, { color: "dim", children: " Select projects to expose via the MCP server. Use Space to toggle, Enter to save." }),
3243
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx4(
3244
- SimpleSelect,
3245
- {
3246
- message: "",
3247
- items: projectItems,
3248
- isMulti: true,
3249
- initialSelected,
3250
- onSelect: () => {
3251
- },
3252
- onSubmit: handleSubmit,
3253
- onCancel: () => {
3254
- }
3255
- },
3256
- JSON.stringify(initialSelected) + config.defaults.includeNew
3257
- ) })
3648
+ errorLine && /* @__PURE__ */ jsx4(Box4, { marginTop: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: errorLine }) }),
3649
+ /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "row", flexGrow: 1, children: [
3650
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", width: "55%", children: [
3651
+ flattenedRows.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "No projects detected." }) : flattenedRows.map((row, idx) => {
3652
+ const isSel = idx === selectedIndex;
3653
+ if (row.kind === "project") {
3654
+ const k = projectKey(row.project);
3655
+ const isOpen = expanded.has(k);
3656
+ const count = (taskCache[k] || []).length;
3657
+ return /* @__PURE__ */ jsxs3(Box4, { children: [
3658
+ /* @__PURE__ */ jsx4(Text4, { color: isSel ? "cyan" : "white", children: isSel ? "> " : " " }),
3659
+ /* @__PURE__ */ jsxs3(Text4, { color: isSel ? "cyan" : "white", children: [
3660
+ isOpen ? "\u25BE " : "\u25B8 ",
3661
+ formatProjectLabel(row.project)
3662
+ ] }),
3663
+ /* @__PURE__ */ jsxs3(Text4, { color: "dim", children: [
3664
+ " ",
3665
+ count > 0 ? ` (tasks: ${count})` : ""
3666
+ ] })
3667
+ ] }, `p:${k}`);
3668
+ }
3669
+ const taskLabel = row.task.title || row.task.task_slug;
3670
+ const status = row.task.status || "";
3671
+ return /* @__PURE__ */ jsxs3(Box4, { children: [
3672
+ /* @__PURE__ */ jsx4(Text4, { color: isSel ? "cyan" : "white", children: isSel ? "> " : " " }),
3673
+ /* @__PURE__ */ jsx4(Text4, { color: "dim", children: " - " }),
3674
+ /* @__PURE__ */ jsx4(Text4, { color: isSel ? "cyan" : "white", children: taskLabel }),
3675
+ row.task.task_slug !== "__none__" && /* @__PURE__ */ jsx4(Text4, { color: status === "complete" ? "green" : status === "blocked" ? "red" : "yellow", children: ` [${status}]` })
3676
+ ] }, `t:${projectKey(row.project)}:${row.task.task_slug}`);
3677
+ }),
3678
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u25B2/\u25BC navigate \u2022 Enter expand/collapse \u2022 s cycle status \u2022 R refresh \u2022 t expose mode" }) })
3679
+ ] }),
3680
+ /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", width: "45%", paddingLeft: 2, children: !selectedTask ? /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "Select a task to view details." }) : /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
3681
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: selectedTask.title || selectedTask.task_slug }),
3682
+ selectedTask.summary && /* @__PURE__ */ jsx4(Text4, { children: selectedTask.summary }),
3683
+ /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
3684
+ /* @__PURE__ */ jsxs3(Text4, { children: [
3685
+ /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "Status: " }),
3686
+ /* @__PURE__ */ jsx4(Text4, { children: selectedTask.status || "unknown" })
3687
+ ] }),
3688
+ /* @__PURE__ */ jsxs3(Text4, { children: [
3689
+ /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "Updated: " }),
3690
+ /* @__PURE__ */ jsx4(Text4, { children: selectedTask.updated_at || "\u2014" })
3691
+ ] }),
3692
+ /* @__PURE__ */ jsxs3(Text4, { children: [
3693
+ /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "Tags: " }),
3694
+ /* @__PURE__ */ jsx4(Text4, { children: (selectedTask.tags || []).join(", ") || "\u2014" })
3695
+ ] })
3696
+ ] }),
3697
+ /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
3698
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Checklist" }),
3699
+ (selectedTask.checklist || []).length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "\u2014" }) : (selectedTask.checklist || []).slice(0, 12).map((c, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
3700
+ /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "- " }),
3701
+ c.label || c.id || "item",
3702
+ " ",
3703
+ /* @__PURE__ */ jsxs3(Text4, { color: "dim", children: [
3704
+ "[",
3705
+ c.status || "pending",
3706
+ "]"
3707
+ ] })
3708
+ ] }, c.id || i))
3709
+ ] }),
3710
+ /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
3711
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Agents" }),
3712
+ !selectedTask.agents ? /* @__PURE__ */ jsx4(Text4, { color: "dim", children: "\u2014" }) : Object.entries(selectedTask.agents).map(([agent, info]) => /* @__PURE__ */ jsxs3(Text4, { children: [
3713
+ /* @__PURE__ */ jsxs3(Text4, { color: "dim", children: [
3714
+ "- ",
3715
+ agent,
3716
+ ": "
3717
+ ] }),
3718
+ info?.status || "\u2014",
3719
+ info?.artifact ? ` (${info.artifact})` : ""
3720
+ ] }, agent))
3721
+ ] })
3722
+ ] }) })
3723
+ ] })
3258
3724
  ] });
3259
3725
  };
3260
3726
  }
@@ -3433,20 +3899,21 @@ var init_StatusBoard = __esm({
3433
3899
  });
3434
3900
 
3435
3901
  // src/mcp/ui/IndexingStatus.tsx
3436
- import { useState as useState4, useEffect as useEffect3 } from "react";
3902
+ import { useState as useState4, useEffect as useEffect4 } from "react";
3437
3903
  import { Box as Box9, Text as Text9 } from "ink";
3438
3904
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3439
3905
  var IndexingStatus;
3440
3906
  var init_IndexingStatus = __esm({
3441
3907
  "src/mcp/ui/IndexingStatus.tsx"() {
3442
3908
  "use strict";
3909
+ init_indexing_jobs();
3443
3910
  init_rag();
3444
3911
  init_resources();
3445
3912
  init_config_utils();
3446
3913
  IndexingStatus = ({ projects, config }) => {
3447
3914
  const [stats, setStats] = useState4([]);
3448
3915
  const [loading, setLoading] = useState4(true);
3449
- useEffect3(() => {
3916
+ useEffect4(() => {
3450
3917
  const fetchStats = async () => {
3451
3918
  const newStats = [];
3452
3919
  for (const project of projects) {
@@ -3456,9 +3923,14 @@ var init_IndexingStatus = __esm({
3456
3923
  }
3457
3924
  const enabled = projConfig?.semanticSearch?.enabled || project.semanticSearchEnabled || false;
3458
3925
  if (!enabled) {
3926
+ const prog = indexingJobs.getProgress(project.name);
3459
3927
  newStats.push({
3460
3928
  projectName: project.name,
3461
3929
  enabled: false,
3930
+ state: prog.state,
3931
+ itemsDone: prog.itemsDone,
3932
+ itemsTotal: prog.itemsTotal,
3933
+ currentItem: prog.currentItem,
3462
3934
  totalFiles: 0,
3463
3935
  totalChunks: 0
3464
3936
  });
@@ -3468,17 +3940,27 @@ var init_IndexingStatus = __esm({
3468
3940
  const indexPath = getRAGIndexPath(project);
3469
3941
  const rag = new RAGService(indexPath, "dummy");
3470
3942
  const s = rag.getStats();
3943
+ const prog = indexingJobs.getProgress(project.name);
3471
3944
  newStats.push({
3472
3945
  projectName: project.name,
3473
3946
  enabled: true,
3947
+ state: prog.state,
3948
+ itemsDone: prog.itemsDone,
3949
+ itemsTotal: prog.itemsTotal,
3950
+ currentItem: prog.currentItem,
3474
3951
  totalFiles: s.totalFiles,
3475
3952
  totalChunks: s.totalChunks,
3476
3953
  lastFullIndex: s.lastFullIndex
3477
3954
  });
3478
3955
  } catch (e) {
3956
+ const prog = indexingJobs.getProgress(project.name);
3479
3957
  newStats.push({
3480
3958
  projectName: project.name,
3481
3959
  enabled: true,
3960
+ state: prog.state,
3961
+ itemsDone: prog.itemsDone,
3962
+ itemsTotal: prog.itemsTotal,
3963
+ currentItem: prog.currentItem,
3482
3964
  totalFiles: 0,
3483
3965
  totalChunks: 0,
3484
3966
  error: String(e)
@@ -3501,13 +3983,16 @@ var init_IndexingStatus = __esm({
3501
3983
  /* @__PURE__ */ jsxs8(Box9, { children: [
3502
3984
  /* @__PURE__ */ jsx9(Box9, { width: 25, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Project" }) }),
3503
3985
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Status" }) }),
3986
+ /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "State" }) }),
3987
+ /* @__PURE__ */ jsx9(Box9, { width: 18, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Progress" }) }),
3504
3988
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Indexed Files" }) }),
3505
3989
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Total Chunks" }) }),
3506
3990
  /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Last Index" }) })
3507
3991
  ] }),
3508
3992
  stats.length === 0 ? /* @__PURE__ */ jsx9(Text9, { color: "dim", children: "No exposed projects found." }) : stats.map((s) => /* @__PURE__ */ jsxs8(Box9, { marginTop: 0, children: [
3509
3993
  /* @__PURE__ */ jsx9(Box9, { width: 25, children: /* @__PURE__ */ jsx9(Text9, { color: "white", children: s.projectName }) }),
3510
- /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { color: s.enabled ? "green" : "dim", children: s.enabled ? "Enabled" : "Disabled" }) }),
3994
+ /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { color: s.state === "running" ? "yellow" : s.state === "failed" ? "red" : s.enabled ? "green" : "dim", children: s.enabled ? s.state : "disabled" }) }),
3995
+ /* @__PURE__ */ jsx9(Box9, { width: 18, children: /* @__PURE__ */ jsx9(Text9, { children: s.state === "running" ? `${s.itemsDone}/${s.itemsTotal ?? "?"}` : "-" }) }),
3511
3996
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { children: s.enabled ? s.totalFiles : "-" }) }),
3512
3997
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { children: s.enabled ? s.totalChunks : "-" }) }),
3513
3998
  /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text9, { children: s.lastFullIndex ? new Date(s.lastFullIndex).toLocaleTimeString() : "-" }) })
@@ -3573,9 +4058,9 @@ var App_exports = {};
3573
4058
  __export(App_exports, {
3574
4059
  App: () => App
3575
4060
  });
3576
- import { useState as useState5, useEffect as useEffect4, useMemo as useMemo2, useCallback } from "react";
4061
+ import { useState as useState5, useEffect as useEffect5, useMemo as useMemo3, useCallback } from "react";
3577
4062
  import { Box as Box11, useInput as useInput4, useApp } from "ink";
3578
- import fs16 from "fs";
4063
+ import fs17 from "fs";
3579
4064
  import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3580
4065
  var App;
3581
4066
  var init_App = __esm({
@@ -3611,17 +4096,17 @@ var init_App = __esm({
3611
4096
  setConfig(loadMCPConfig());
3612
4097
  setProjects(scanForProjects());
3613
4098
  }, []);
3614
- const exposedProjects = useMemo2(
4099
+ const exposedProjects = useMemo3(
3615
4100
  () => projects.filter((p) => isProjectExposed(config, p.name, p.path)),
3616
4101
  [projects, config]
3617
4102
  );
3618
- const isRAGEnabled = useMemo2(() => {
4103
+ const isRAGEnabled = useMemo3(() => {
3619
4104
  return exposedProjects.some((p) => {
3620
4105
  const cfg = findProjectConfig(config, { name: p.name, path: p.path });
3621
4106
  return cfg?.semanticSearch?.enabled || p.semanticSearchEnabled;
3622
4107
  });
3623
4108
  }, [exposedProjects, config]);
3624
- const tabs = useMemo2(() => {
4109
+ const tabs = useMemo3(() => {
3625
4110
  const baseTabs = [
3626
4111
  { id: "overview", label: "Overview" },
3627
4112
  { id: "logs", label: "Logs" },
@@ -3641,7 +4126,7 @@ var init_App = __esm({
3641
4126
  installStatus.vscodeGlobal,
3642
4127
  installStatus.vscodeWorkspace
3643
4128
  ].filter(Boolean).length;
3644
- useEffect4(() => {
4129
+ useEffect5(() => {
3645
4130
  const start = async () => {
3646
4131
  const status = getMCPServerStatus();
3647
4132
  if (!status.running) {
@@ -3657,21 +4142,21 @@ var init_App = __esm({
3657
4142
  };
3658
4143
  start();
3659
4144
  }, []);
3660
- useEffect4(() => {
4145
+ useEffect5(() => {
3661
4146
  const logPath = getLogFilePath();
3662
4147
  let lastSize = 0;
3663
- if (fs16.existsSync(logPath)) {
3664
- const stats = fs16.statSync(logPath);
4148
+ if (fs17.existsSync(logPath)) {
4149
+ const stats = fs17.statSync(logPath);
3665
4150
  lastSize = stats.size;
3666
4151
  }
3667
4152
  const interval = setInterval(() => {
3668
- if (fs16.existsSync(logPath)) {
3669
- const stats = fs16.statSync(logPath);
4153
+ if (fs17.existsSync(logPath)) {
4154
+ const stats = fs17.statSync(logPath);
3670
4155
  if (stats.size > lastSize) {
3671
4156
  const buffer = Buffer.alloc(stats.size - lastSize);
3672
- const fd = fs16.openSync(logPath, "r");
3673
- fs16.readSync(fd, buffer, 0, buffer.length, lastSize);
3674
- fs16.closeSync(fd);
4157
+ const fd = fs17.openSync(logPath, "r");
4158
+ fs17.readSync(fd, buffer, 0, buffer.length, lastSize);
4159
+ fs17.closeSync(fd);
3675
4160
  const newContent = buffer.toString("utf-8");
3676
4161
  const newLines = newContent.split("\n").filter((l) => l.trim());
3677
4162
  setLogs((prev) => {
@@ -3882,7 +4367,7 @@ var init_mcp = __esm({
3882
4367
  // src/commands/wizard/index.ts
3883
4368
  import { intro as intro2, select as select5, spinner as spinner7, note as note11, outro as outro7, isCancel as isCancel12 } from "@clack/prompts";
3884
4369
  import pc13 from "picocolors";
3885
- import * as fs21 from "fs";
4370
+ import * as fs22 from "fs";
3886
4371
 
3887
4372
  // src/lib/git.ts
3888
4373
  import { execSync } from "child_process";
@@ -4556,7 +5041,7 @@ async function handlePostSetup(config, workspacePath, workspaceName, linkedProje
4556
5041
  init_paths();
4557
5042
  import { multiselect as multiselect4, spinner as spinner3, note as note7, outro as outro3, cancel as cancel3, isCancel as isCancel8, confirm as confirm6 } from "@clack/prompts";
4558
5043
  import pc9 from "picocolors";
4559
- import * as fs17 from "fs";
5044
+ import * as fs18 from "fs";
4560
5045
  init_detection();
4561
5046
  async function runLinkProjectsFlow(workspacePath, workspaceName) {
4562
5047
  const projects = scanForProjects({
@@ -4595,7 +5080,7 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
4595
5080
  const s = spinner3();
4596
5081
  s.start("Linking projects");
4597
5082
  const configFilePath = getConfigPath(workspacePath);
4598
- let configContent = fs17.readFileSync(configFilePath, "utf-8");
5083
+ let configContent = fs18.readFileSync(configFilePath, "utf-8");
4599
5084
  if (configContent.includes("linked_projects:")) {
4600
5085
  const lines = configContent.split("\n");
4601
5086
  const linkedIndex = lines.findIndex((l) => l.trim() === "linked_projects:");
@@ -4622,7 +5107,7 @@ linked_projects:
4622
5107
  `;
4623
5108
  });
4624
5109
  }
4625
- fs17.writeFileSync(configFilePath, configContent);
5110
+ fs18.writeFileSync(configFilePath, configContent);
4626
5111
  generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
4627
5112
  s.stop("Projects linked");
4628
5113
  const workspaceFile = `${workspaceName}.code-workspace`;
@@ -4657,15 +5142,15 @@ linked_projects:
4657
5142
  init_paths();
4658
5143
  import { confirm as confirm7, spinner as spinner4, note as note8, outro as outro4, cancel as cancel4, isCancel as isCancel9 } from "@clack/prompts";
4659
5144
  import pc10 from "picocolors";
4660
- import * as fs18 from "fs";
4661
- import * as path18 from "path";
5145
+ import * as fs19 from "fs";
5146
+ import * as path19 from "path";
4662
5147
  async function runSyncToGlobalFlow(workspacePath, workspaceName) {
4663
5148
  const localPath = getLocalWorkspacePath(workspacePath);
4664
5149
  const customGlobalPath = getEffectiveRRCEHome(workspacePath);
4665
- const globalPath = path18.join(customGlobalPath, "workspaces", workspaceName);
5150
+ const globalPath = path19.join(customGlobalPath, "workspaces", workspaceName);
4666
5151
  const subdirs = ["knowledge", "prompts", "templates", "tasks", "refs"];
4667
5152
  const existingDirs = subdirs.filter(
4668
- (dir) => fs18.existsSync(path18.join(localPath, dir))
5153
+ (dir) => fs19.existsSync(path19.join(localPath, dir))
4669
5154
  );
4670
5155
  if (existingDirs.length === 0) {
4671
5156
  outro4(pc10.yellow("No data found in workspace storage to sync."));
@@ -4691,8 +5176,8 @@ Destination: ${pc10.cyan(globalPath)}`,
4691
5176
  try {
4692
5177
  ensureDir(globalPath);
4693
5178
  for (const dir of existingDirs) {
4694
- const srcDir = path18.join(localPath, dir);
4695
- const destDir = path18.join(globalPath, dir);
5179
+ const srcDir = path19.join(localPath, dir);
5180
+ const destDir = path19.join(globalPath, dir);
4696
5181
  ensureDir(destDir);
4697
5182
  copyDirRecursive(srcDir, destDir);
4698
5183
  }
@@ -4719,8 +5204,8 @@ init_paths();
4719
5204
  init_prompts();
4720
5205
  import { confirm as confirm8, spinner as spinner5, note as note9, outro as outro5, cancel as cancel5, isCancel as isCancel10 } from "@clack/prompts";
4721
5206
  import pc11 from "picocolors";
4722
- import * as fs19 from "fs";
4723
- import * as path19 from "path";
5207
+ import * as fs20 from "fs";
5208
+ import * as path20 from "path";
4724
5209
  import { stringify as stringify3 } from "yaml";
4725
5210
  init_install();
4726
5211
  async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
@@ -4740,8 +5225,8 @@ async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
4740
5225
  ];
4741
5226
  const configFilePath = getConfigPath(workspacePath);
4742
5227
  const ideTargets = [];
4743
- if (fs19.existsSync(configFilePath)) {
4744
- const configContent = fs19.readFileSync(configFilePath, "utf-8");
5228
+ if (fs20.existsSync(configFilePath)) {
5229
+ const configContent = fs20.readFileSync(configFilePath, "utf-8");
4745
5230
  if (configContent.includes("opencode: true")) ideTargets.push("OpenCode agents");
4746
5231
  if (configContent.includes("copilot: true")) ideTargets.push("GitHub Copilot");
4747
5232
  if (configContent.includes("antigravity: true")) ideTargets.push("Antigravity");
@@ -4767,17 +5252,17 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
4767
5252
  }
4768
5253
  s.start("Updating from package");
4769
5254
  for (const dataPath of dataPaths) {
4770
- copyDirToAllStoragePaths(path19.join(agentCoreDir, "templates"), "templates", [dataPath]);
4771
- copyDirToAllStoragePaths(path19.join(agentCoreDir, "prompts"), "prompts", [dataPath]);
4772
- copyDirToAllStoragePaths(path19.join(agentCoreDir, "docs"), "docs", [dataPath]);
5255
+ copyDirToAllStoragePaths(path20.join(agentCoreDir, "templates"), "templates", [dataPath]);
5256
+ copyDirToAllStoragePaths(path20.join(agentCoreDir, "prompts"), "prompts", [dataPath]);
5257
+ copyDirToAllStoragePaths(path20.join(agentCoreDir, "docs"), "docs", [dataPath]);
4773
5258
  }
4774
5259
  const rrceHome = customGlobalPath || getDefaultRRCEHome2();
4775
- ensureDir(path19.join(rrceHome, "templates"));
4776
- ensureDir(path19.join(rrceHome, "docs"));
4777
- copyDirRecursive(path19.join(agentCoreDir, "templates"), path19.join(rrceHome, "templates"));
4778
- copyDirRecursive(path19.join(agentCoreDir, "docs"), path19.join(rrceHome, "docs"));
4779
- if (fs19.existsSync(configFilePath)) {
4780
- const configContent = fs19.readFileSync(configFilePath, "utf-8");
5260
+ ensureDir(path20.join(rrceHome, "templates"));
5261
+ ensureDir(path20.join(rrceHome, "docs"));
5262
+ copyDirRecursive(path20.join(agentCoreDir, "templates"), path20.join(rrceHome, "templates"));
5263
+ copyDirRecursive(path20.join(agentCoreDir, "docs"), path20.join(rrceHome, "docs"));
5264
+ if (fs20.existsSync(configFilePath)) {
5265
+ const configContent = fs20.readFileSync(configFilePath, "utf-8");
4781
5266
  if (configContent.includes("copilot: true")) {
4782
5267
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
4783
5268
  ensureDir(copilotPath);
@@ -4824,14 +5309,14 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
4824
5309
  function updateOpenCodeAgents(prompts, mode, primaryDataPath) {
4825
5310
  if (mode === "global") {
4826
5311
  try {
4827
- const promptsDir = path19.join(path19.dirname(OPENCODE_CONFIG), "prompts");
5312
+ const promptsDir = path20.join(path20.dirname(OPENCODE_CONFIG), "prompts");
4828
5313
  ensureDir(promptsDir);
4829
5314
  let opencodeConfig = { $schema: "https://opencode.ai/config.json" };
4830
- if (fs19.existsSync(OPENCODE_CONFIG)) {
4831
- opencodeConfig = JSON.parse(fs19.readFileSync(OPENCODE_CONFIG, "utf-8"));
5315
+ if (fs20.existsSync(OPENCODE_CONFIG)) {
5316
+ opencodeConfig = JSON.parse(fs20.readFileSync(OPENCODE_CONFIG, "utf-8"));
4832
5317
  }
4833
5318
  if (!opencodeConfig.agent) opencodeConfig.agent = {};
4834
- const currentAgentNames = prompts.map((p) => path19.basename(p.filePath, ".md"));
5319
+ const currentAgentNames = prompts.map((p) => path20.basename(p.filePath, ".md"));
4835
5320
  const existingAgentNames = Object.keys(opencodeConfig.agent);
4836
5321
  const rrceAgentPrefixes = ["init", "research", "planning", "executor", "doctor", "documentation", "sync"];
4837
5322
  for (const existingName of existingAgentNames) {
@@ -4839,30 +5324,30 @@ function updateOpenCodeAgents(prompts, mode, primaryDataPath) {
4839
5324
  const stillExists = currentAgentNames.includes(existingName);
4840
5325
  if (isRrceAgent && !stillExists) {
4841
5326
  delete opencodeConfig.agent[existingName];
4842
- const oldPromptFile = path19.join(promptsDir, `rrce-${existingName}.md`);
4843
- if (fs19.existsSync(oldPromptFile)) {
4844
- fs19.unlinkSync(oldPromptFile);
5327
+ const oldPromptFile = path20.join(promptsDir, `rrce-${existingName}.md`);
5328
+ if (fs20.existsSync(oldPromptFile)) {
5329
+ fs20.unlinkSync(oldPromptFile);
4845
5330
  }
4846
5331
  }
4847
5332
  }
4848
5333
  for (const prompt of prompts) {
4849
- const baseName = path19.basename(prompt.filePath, ".md");
5334
+ const baseName = path20.basename(prompt.filePath, ".md");
4850
5335
  const promptFileName = `rrce-${baseName}.md`;
4851
- const promptFilePath = path19.join(promptsDir, promptFileName);
4852
- fs19.writeFileSync(promptFilePath, prompt.content);
5336
+ const promptFilePath = path20.join(promptsDir, promptFileName);
5337
+ fs20.writeFileSync(promptFilePath, prompt.content);
4853
5338
  const agentConfig = convertToOpenCodeAgent(prompt, true, `./prompts/${promptFileName}`);
4854
5339
  opencodeConfig.agent[baseName] = agentConfig;
4855
5340
  }
4856
- fs19.writeFileSync(OPENCODE_CONFIG, JSON.stringify(opencodeConfig, null, 2) + "\n");
5341
+ fs20.writeFileSync(OPENCODE_CONFIG, JSON.stringify(opencodeConfig, null, 2) + "\n");
4857
5342
  } catch (e) {
4858
5343
  console.error("Failed to update global OpenCode config with agents:", e);
4859
5344
  }
4860
5345
  } else {
4861
- const opencodeBaseDir = path19.join(primaryDataPath, ".opencode", "agent");
5346
+ const opencodeBaseDir = path20.join(primaryDataPath, ".opencode", "agent");
4862
5347
  ensureDir(opencodeBaseDir);
4863
5348
  clearDirectory(opencodeBaseDir);
4864
5349
  for (const prompt of prompts) {
4865
- const baseName = path19.basename(prompt.filePath, ".md");
5350
+ const baseName = path20.basename(prompt.filePath, ".md");
4866
5351
  const agentConfig = convertToOpenCodeAgent(prompt);
4867
5352
  const content = `---
4868
5353
  ${stringify3({
@@ -4871,22 +5356,22 @@ ${stringify3({
4871
5356
  tools: agentConfig.tools
4872
5357
  })}---
4873
5358
  ${agentConfig.prompt}`;
4874
- fs19.writeFileSync(path19.join(opencodeBaseDir, `${baseName}.md`), content);
5359
+ fs20.writeFileSync(path20.join(opencodeBaseDir, `${baseName}.md`), content);
4875
5360
  }
4876
5361
  }
4877
5362
  }
4878
5363
  function clearDirectory(dirPath) {
4879
- if (!fs19.existsSync(dirPath)) return;
4880
- const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
5364
+ if (!fs20.existsSync(dirPath)) return;
5365
+ const entries = fs20.readdirSync(dirPath, { withFileTypes: true });
4881
5366
  for (const entry of entries) {
4882
5367
  if (entry.isFile()) {
4883
- fs19.unlinkSync(path19.join(dirPath, entry.name));
5368
+ fs20.unlinkSync(path20.join(dirPath, entry.name));
4884
5369
  }
4885
5370
  }
4886
5371
  }
4887
5372
  function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot, customGlobalPath) {
4888
- const globalPath = path19.join(customGlobalPath, "workspaces", workspaceName);
4889
- const workspacePath = path19.join(workspaceRoot, ".rrce-workflow");
5373
+ const globalPath = path20.join(customGlobalPath, "workspaces", workspaceName);
5374
+ const workspacePath = path20.join(workspaceRoot, ".rrce-workflow");
4890
5375
  switch (mode) {
4891
5376
  case "global":
4892
5377
  return [globalPath];
@@ -4900,7 +5385,7 @@ function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot,
4900
5385
  // src/commands/wizard/delete-flow.ts
4901
5386
  import { multiselect as multiselect5, confirm as confirm9, spinner as spinner6, note as note10, cancel as cancel6, isCancel as isCancel11 } from "@clack/prompts";
4902
5387
  import pc12 from "picocolors";
4903
- import * as fs20 from "fs";
5388
+ import * as fs21 from "fs";
4904
5389
  init_detection();
4905
5390
  init_config();
4906
5391
  async function runDeleteGlobalProjectFlow(availableProjects) {
@@ -4944,8 +5429,8 @@ Are you sure?`,
4944
5429
  for (const projectName of projectsToDelete) {
4945
5430
  const project = globalProjects.find((p) => p.name === projectName);
4946
5431
  if (!project) continue;
4947
- if (fs20.existsSync(project.dataPath)) {
4948
- fs20.rmSync(project.dataPath, { recursive: true, force: true });
5432
+ if (fs21.existsSync(project.dataPath)) {
5433
+ fs21.rmSync(project.dataPath, { recursive: true, force: true });
4949
5434
  }
4950
5435
  const newConfig = removeProjectConfig(mcpConfig, projectName);
4951
5436
  configChanged = true;
@@ -4991,11 +5476,11 @@ Workspace: ${pc13.bold(workspaceName)}`,
4991
5476
  workspacePath
4992
5477
  });
4993
5478
  const configFilePath = getConfigPath(workspacePath);
4994
- let isAlreadyConfigured = fs21.existsSync(configFilePath);
5479
+ let isAlreadyConfigured = fs22.existsSync(configFilePath);
4995
5480
  let currentStorageMode = null;
4996
5481
  if (isAlreadyConfigured) {
4997
5482
  try {
4998
- const configContent = fs21.readFileSync(configFilePath, "utf-8");
5483
+ const configContent = fs22.readFileSync(configFilePath, "utf-8");
4999
5484
  const modeMatch = configContent.match(/mode:\s*(global|workspace)/);
5000
5485
  currentStorageMode = modeMatch?.[1] ?? null;
5001
5486
  } catch {
@@ -5012,7 +5497,7 @@ Workspace: ${pc13.bold(workspaceName)}`,
5012
5497
  }
5013
5498
  }
5014
5499
  const localDataPath = getLocalWorkspacePath(workspacePath);
5015
- const hasLocalData = fs21.existsSync(localDataPath);
5500
+ const hasLocalData = fs22.existsSync(localDataPath);
5016
5501
  if (isAlreadyConfigured) {
5017
5502
  const menuOptions = [];
5018
5503
  menuOptions.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.2.92",
3
+ "version": "0.2.94",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",