rrce-workflow 0.2.92 → 0.2.93

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 +220 -25
  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
  });
@@ -3440,6 +3616,7 @@ var IndexingStatus;
3440
3616
  var init_IndexingStatus = __esm({
3441
3617
  "src/mcp/ui/IndexingStatus.tsx"() {
3442
3618
  "use strict";
3619
+ init_indexing_jobs();
3443
3620
  init_rag();
3444
3621
  init_resources();
3445
3622
  init_config_utils();
@@ -3456,9 +3633,14 @@ var init_IndexingStatus = __esm({
3456
3633
  }
3457
3634
  const enabled = projConfig?.semanticSearch?.enabled || project.semanticSearchEnabled || false;
3458
3635
  if (!enabled) {
3636
+ const prog = indexingJobs.getProgress(project.name);
3459
3637
  newStats.push({
3460
3638
  projectName: project.name,
3461
3639
  enabled: false,
3640
+ state: prog.state,
3641
+ itemsDone: prog.itemsDone,
3642
+ itemsTotal: prog.itemsTotal,
3643
+ currentItem: prog.currentItem,
3462
3644
  totalFiles: 0,
3463
3645
  totalChunks: 0
3464
3646
  });
@@ -3468,17 +3650,27 @@ var init_IndexingStatus = __esm({
3468
3650
  const indexPath = getRAGIndexPath(project);
3469
3651
  const rag = new RAGService(indexPath, "dummy");
3470
3652
  const s = rag.getStats();
3653
+ const prog = indexingJobs.getProgress(project.name);
3471
3654
  newStats.push({
3472
3655
  projectName: project.name,
3473
3656
  enabled: true,
3657
+ state: prog.state,
3658
+ itemsDone: prog.itemsDone,
3659
+ itemsTotal: prog.itemsTotal,
3660
+ currentItem: prog.currentItem,
3474
3661
  totalFiles: s.totalFiles,
3475
3662
  totalChunks: s.totalChunks,
3476
3663
  lastFullIndex: s.lastFullIndex
3477
3664
  });
3478
3665
  } catch (e) {
3666
+ const prog = indexingJobs.getProgress(project.name);
3479
3667
  newStats.push({
3480
3668
  projectName: project.name,
3481
3669
  enabled: true,
3670
+ state: prog.state,
3671
+ itemsDone: prog.itemsDone,
3672
+ itemsTotal: prog.itemsTotal,
3673
+ currentItem: prog.currentItem,
3482
3674
  totalFiles: 0,
3483
3675
  totalChunks: 0,
3484
3676
  error: String(e)
@@ -3501,13 +3693,16 @@ var init_IndexingStatus = __esm({
3501
3693
  /* @__PURE__ */ jsxs8(Box9, { children: [
3502
3694
  /* @__PURE__ */ jsx9(Box9, { width: 25, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Project" }) }),
3503
3695
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Status" }) }),
3696
+ /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "State" }) }),
3697
+ /* @__PURE__ */ jsx9(Box9, { width: 18, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Progress" }) }),
3504
3698
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Indexed Files" }) }),
3505
3699
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Total Chunks" }) }),
3506
3700
  /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "Last Index" }) })
3507
3701
  ] }),
3508
3702
  stats.length === 0 ? /* @__PURE__ */ jsx9(Text9, { color: "dim", children: "No exposed projects found." }) : stats.map((s) => /* @__PURE__ */ jsxs8(Box9, { marginTop: 0, children: [
3509
3703
  /* @__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" }) }),
3704
+ /* @__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" }) }),
3705
+ /* @__PURE__ */ jsx9(Box9, { width: 18, children: /* @__PURE__ */ jsx9(Text9, { children: s.state === "running" ? `${s.itemsDone}/${s.itemsTotal ?? "?"}` : "-" }) }),
3511
3706
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { children: s.enabled ? s.totalFiles : "-" }) }),
3512
3707
  /* @__PURE__ */ jsx9(Box9, { width: 15, children: /* @__PURE__ */ jsx9(Text9, { children: s.enabled ? s.totalChunks : "-" }) }),
3513
3708
  /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text9, { children: s.lastFullIndex ? new Date(s.lastFullIndex).toLocaleTimeString() : "-" }) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.2.92",
3
+ "version": "0.2.93",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",