rrce-workflow 0.3.30 → 0.3.32

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 (3) hide show
  1. package/README.md +109 -45
  2. package/dist/index.js +233 -117
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,8 +11,10 @@ RRCE-Workflow transforms your AI coding assistant (GitHub Copilot, OpenCode, Cla
11
11
  - **Global Knowledge Base**: Centralized context management across all your projects (`~/.rrce-workflow/`).
12
12
  - **MCP Hub**: A Model Context Protocol server exposing tools, resources, and prompts to any MCP-compatible client.
13
13
  - **Semantic Search (RAG)**: Local, privacy-first vector indexing powered by `@xenova/transformers` for deep codebase understanding.
14
- - **Structured Agent Pipelines**: 5 specialized agents (Init, Design, Develop, Docs, Sync, Doctor) for end-to-end development workflows.
15
- - **Task Management**: Built-in CRUD operations for tracking high-level tasks via MCP tools.
14
+ - **4-Phase Workflow**: Init Design (research+planning merged) Develop Document for streamlined development.
15
+ - **Slash Commands**: In-context execution (`/rrce_*`) with ~60% token efficiency over subagent delegation.
16
+ - **Task Management**: Built-in CRUD operations for tracking high-level tasks via MCP tools, including knowledge extraction and cleanup.
17
+ - **Agent Session Tracking**: Real-time task progress visualization in MCP TUI with OpenCode Todo sidebar sync.
16
18
 
17
19
  ---
18
20
 
@@ -71,14 +73,19 @@ RRCE-Workflow uses the [Model Context Protocol](https://modelcontextprotocol.io/
71
73
  | `search_knowledge` | Semantic search across project knowledge bases |
72
74
  | `search_code` | Semantic search across code files (snippets + line numbers + context) |
73
75
  | `find_related_files` | Find imports/imported-by relationships for a file |
74
- | `index_knowledge` | Start (or query) the semantic indexing job for a project |
75
- | `list_agents` | List available RRCE agents and their arguments |
76
- | `get_agent_prompt` | Get the system prompt for a specific agent (with context injection) |
77
- | `list_tasks` | List all tasks for a project |
78
- | `get_task` | Get details of a task |
79
- | `create_task` | Create a task |
80
- | `update_task` | Update a task (`meta.json`) |
76
+ | `index_knowledge` | Start (or query) the semantic indexing job for a project. Supports `force` (re-hash) and `clean` (wipe/rebuild) parameters |
77
+ | `list_tasks` | List all tasks for a project (supports filtering by status, keyword, date) |
78
+ | `get_task` | Get details of a task (including phase status, checklist, metadata) |
79
+ | `create_task` | Create a new task in the project |
80
+ | `update_task` | Update task metadata (`meta.json`) |
81
81
  | `delete_task` | Delete a task |
82
+ | `search_tasks` | Search tasks by keyword, status, agent phase, or date |
83
+ | `validate_phase` | Check if a task phase has all prerequisites complete |
84
+ | `cleanup_task` | Extract valuable knowledge from tasks and delete artifacts. Supports single, bulk, or --all mode |
85
+ | `start_session` | Start an agent session for active task tracking (TUI visibility) |
86
+ | `end_session` | End an agent session before completion signal |
87
+ | `update_agent_todos` | Update agent todo list for granular work tracking (TUI display) |
88
+ | `get_agent_prompt` | Get the system prompt for a specific agent or slash command |
82
89
 
83
90
  ### Connecting Your IDE
84
91
 
@@ -103,14 +110,15 @@ RRCE-Workflow integrates with OpenCode both as an MCP server and by providing a
103
110
  ```
104
111
 
105
112
  2. **Install Agents**: Run `npx rrce-workflow` and select **OpenCode** as a tool. This generates:
106
- - **Primary Agent (`rrce`)**: Orchestrates the complete workflow lifecycle (tab-switchable)
107
- - **Subagents** (`@rrce_*`): Specialized agents for each phase (expert mode)
113
+ - **Primary Agent (`rrce`)**: Phase Coordinator orchestrating the complete workflow (tab-switchable)
114
+ - **Subagents** (`@rrce_*`): Specialized agents for isolated execution (expert mode)
108
115
  - **Auto-configuration**: Hides OpenCode's native plan agent to avoid confusion
109
116
 
110
117
  3. **Usage**:
111
118
  - Press `Tab` to cycle to the RRCE agent for structured workflows
119
+ - Use slash commands (`/rrce_init`, `/rrce_design`, `/rrce_develop`) for in-context execution (60% more efficient)
120
+ - Direct subagent access via `@rrce_init`, `@rrce_design`, etc. for isolated execution
112
121
  - Build agent can automatically delegate to RRCE for complex tasks
113
- - Direct subagent access via `@rrce_init`, `@rrce_research`, etc.
114
122
 
115
123
  See [OpenCode Guide](docs/opencode-guide.md) for detailed usage instructions.
116
124
 
@@ -179,45 +187,65 @@ Stores everything in a `.rrce-workflow` folder inside your project root.
179
187
 
180
188
  ---
181
189
 
182
- ## The Agent Pipeline
190
+ ## 4-Phase Workflow
183
191
 
184
- RRCE provides two ways to work with agents, depending on your workflow needs:
192
+ RRCE uses a streamlined 4-phase pipeline for end-to-end development:
185
193
 
186
- ### Primary Orchestrator (Recommended for OpenCode)
194
+ ```
195
+ ┌─────────────────────────────────────────────────────────────────┐
196
+ │ 1. Init → 2. Design → 3. Develop → 4. Document │
197
+ │ /rrce_init /rrce_design /rrce_develop /rrce_docs │
198
+ └─────────────────────────────────────────────────────────────────┘
199
+ ```
200
+
201
+ | Phase | Slash Command | Purpose | Prerequisite |
202
+ |-------|---------------|---------|--------------|
203
+ | **Init** | `/rrce_init` | Project setup, context extraction, semantic indexing | None |
204
+ | **Design** | `/rrce_design task-slug "request"` | Research + Planning (merged for efficiency) | Init complete |
205
+ | **Develop** | `/rrce_develop task-slug` | Code implementation based on approved plan | Design complete |
206
+ | **Document** | `/rrce_docs task-slug` | Generate/update documentation | Develop complete |
187
207
 
188
- The **RRCE Orchestrator** is a primary agent that manages the complete workflow lifecycle:
208
+ ### Slash Commands (In-Context Execution)
189
209
 
190
- - **Access**: Press `Tab` in OpenCode to cycle to the RRCE agent
191
- - **Purpose**: Automatically coordinate research → planning → execution → documentation
192
- - **Delegation**: Build agent can delegate to RRCE for complex tasks
193
- - **Benefits**: Results flow back to calling agents, preventing hallucination
210
+ The primary interaction model is **in-context slash commands** (`/rrce_*`), which achieve a **60% token reduction** compared to subagent delegation.
194
211
 
195
- ### Specialized Subagents
212
+ | Command | Arguments | Purpose |
213
+ |---------|-----------|---------|
214
+ | `/rrce_init` | `[project-name]` | Initialize project context and semantic index |
215
+ | `/rrce_design` | `task-slug "request"` | Research and plan in single session |
216
+ | `/rrce_develop` | `task-slug` | Execute code implementation |
217
+ | `/rrce_docs` | `doc-type [task-slug]` | Generate documentation |
218
+ | `/rrce_cleanup` | `task-slug` \| `--all` | Extract knowledge and delete tasks |
219
+ | `/rrce_sync` | `[scope]` | Sync knowledge base with codebase |
220
+ | `/rrce_doctor` | `[focus-area]` | Analyze codebase health |
196
221
 
197
- For expert control, invoke subagents directly via your AI assistant or MCP tools:
222
+ ### Subagents (Isolated Execution)
223
+
224
+ For fully autonomous, non-interactive execution, use subagents via `@mentions`:
198
225
 
199
226
  | Agent | Invoke With | Purpose | Key Arguments |
200
227
  |-------|-------------|---------|---------------|
201
- | **Init** | `@rrce_init` | Analyze codebase, establish project context and semantic index | `PROJECT_NAME` (optional) |
202
- | **Research** | `@rrce_research_discussion` | Interactive requirements clarification through dialogue | `TASK_SLUG`, `REQUEST` |
203
- | **Planning** | `@rrce_planning_discussion` | Transform research into actionable execution plan | `TASK_SLUG` |
204
- | **Executor** | `@rrce_executor` | Implement the plan - the ONLY agent authorized to modify code | `TASK_SLUG`, `BRANCH` |
205
- | **Docs** | `@rrce_documentation` | Generate project documentation (API, architecture, changelog) | `DOC_TYPE`, `TASK_SLUG` |
228
+ | **Init** | `@rrce_init` | Analyze codebase, establish project context | `PROJECT_NAME` (optional) |
229
+ | **Design** | `@rrce_design` | Research + planning for isolated execution | `TASK_SLUG`, `REQUEST` |
230
+ | **Develop** | `@rrce_develop` | Implement the plan - ONLY agent authorized to modify code | `TASK_SLUG` |
231
+ | **Docs** | `@rrce_docs` | Generate project documentation | `DOC_TYPE`, `TASK_SLUG` |
206
232
  | **Sync** | `@rrce_sync` | Reconcile knowledge base with current codebase state | `SCOPE` (optional) |
207
- | **Doctor** | `@rrce_doctor` | Analyze codebase health, identify issues, recommend improvements | `PROJECT_NAME`, `FOCUS_AREA` |
233
+ | **Doctor** | `@rrce_doctor` | Analyze codebase health, recommend improvements | `PROJECT_NAME`, `FOCUS_AREA` |
234
+
235
+ ### OpenCode Integration
208
236
 
209
- ### Workflow Comparison
237
+ OpenCode provides specialized UX optimizations:
210
238
 
211
- | Approach | When to Use | Example |
212
- |----------|-------------|---------|
213
- | **Orchestrator** | Complex features, want automatic flow | Switch to RRCE agent: "Add user authentication" → Auto-orchestrates all phases |
214
- | **Subagents** | Expert control, specific phase needed | `@rrce_executor TASK_SLUG=user-auth` → Direct execution |
215
- | **Build Delegation** | Want build's help but need structure | Stay in build: "Implement caching" → Build delegates to RRCE |
239
+ - **Tool Name Stabilization**: Standard tools (`read`, `write`) use no `rrce_` prefix, aligning with native IDE capabilities
240
+ - **Checklist Sync**: Agents automatically push their task checklist to the OpenCode Todo sidebar
241
+ - **Hybrid Delegation**: Orchestrator uses a mix of `@mention` text and interactive confirmation suggestions
216
242
 
217
243
  ### Recommended Workflow
218
- 1. **`@rrce_init`** (or orchestrator): "Analyze this codebase." → Creates `project-context.md` and semantic index.
219
- 2. **RRCE Orchestrator**: "I need to add user auth." Runs research, planning, execution phases automatically.
220
- 3. **`@rrce_sync`**: "Update knowledge." → Refreshes context for the next task.
244
+ 1. **`/rrce_init`**: "Analyze this codebase." → Creates `project-context.md` and semantic index
245
+ 2. **`/rrce_design my-feature "Add user authentication"`**: Research + planning in one session
246
+ 3. **`/rrce_develop my-feature`**: Execute the implementation
247
+ 4. **`/rrce_docs my-feature`**: Generate/update documentation
248
+ 5. **`/rrce_cleanup my-feature`**: (Optional) Extract insights and delete task artifacts
221
249
 
222
250
  ---
223
251
 
@@ -226,23 +254,59 @@ For expert control, invoke subagents directly via your AI assistant or MCP tools
226
254
  RRCE-Workflow includes a local, embedding-based search engine powered by `@xenova/transformers`.
227
255
 
228
256
  - **Privacy First**: All embeddings are calculated locally. No code leaves your machine.
229
- - **Full Codebase Indexing**: The `index_knowledge` tool scans your entire source tree (respecting skip lists like `node_modules`, `.git`).
257
+ - **Full Codebase Indexing**: The `index_knowledge` tool scans your entire source tree (respecting `.gitignore` rules).
258
+ - **Background Jobs**: Non-blocking indexing with progress tracking via the MCP Dashboard.
259
+ - **Automatic Cleanup**: DriftService detects and removes embeddings for deleted files during reindexing.
260
+ - **Dual Index**: Separate indices for knowledge (`embeddings.json`) and code (`code-embeddings.json`).
230
261
  - **Smart Fallback**: If RAG fails or isn't enabled, `search_knowledge` performs line-by-line text matching.
231
262
  - **Model**: Uses `Xenova/all-MiniLM-L6-v2` by default (configurable per-project).
232
263
 
264
+ ### Reindexing Guidance
265
+
266
+ | Scenario | Tool Argument | Rationale |
267
+ |----------|---------------|-----------|
268
+ | Routine updates | `{ "project": "name" }` | Incremental (fastest). Only updates changed files |
269
+ | Major refactors | `{ "project": "name", "force": true }` | Forces re-calculation of hashes for all files without wiping |
270
+ | Corrupt index / Stale vectors | `{ "project": "name", "clean": true }` | Wipes index files and rebuilds from scratch. Resolves vector drift |
271
+
233
272
  RAG is enabled by default in Express Setup. You can toggle it per-project in the MCP Dashboard or via `config.yaml`.
234
273
 
235
274
  ---
236
275
 
237
- ## 🛠 AI Agent Effectiveness & Code Health
276
+ ## MCP Dashboard (TUI)
277
+
278
+ The **MCP Dashboard** provides a cockpit-style interface for managing your RRCE workflow:
238
279
 
239
- We've optimized the codebase to be **highly navigatable for AI coding agents**:
280
+ ### Tabs Overview
281
+ 1. **Overview (System Cockpit)**: Dashboard snapshot of server health, recent activity, and active task tracking
282
+ 2. **Logs**: Real-time tailing of the MCP hub server logs
283
+ 3. **Tasks**: Priority view for task management with current project auto-pinned and expanded
284
+ 4. **Projects**: Configuration hub for project exposure with real-time indexing progress (indented row)
285
+
286
+ ### Key Features
287
+ - **Unified Cockpit Aesthetic**: White borders, high-density information display
288
+ - **Active Task Tracking**: Real-time progress visualization with phase indicators
289
+ - **Session Management**: Agent todo list display showing granular work items
290
+ - **Project Prioritization**: Current workspace automatically pinned and expanded in Tasks tab
291
+ - **Background Indexing**: Non-blocking indexing with progress reporting in Projects tab
292
+
293
+ ## 🛠 AI Agent Effectiveness & Code Health
240
294
 
241
- ### Codebase Optimization (v0.3.13)
242
- - **Deduplication**: Consolidated install/uninstall logic and project sorting.
243
- - **Function Extraction**: Split monolithic functions (like `index_knowledge`) into manageable helpers.
244
- - **Improved Type Safety**: Replaced `any` types with structured interfaces (`TaskMeta`, `AgentInfo`).
245
- - **AI Agent Guide**: Added [AI Agent Architecture Guide](docs/AI_AGENT_GUIDE.md) to help coding assistants navigate the project.
295
+ The codebase has been optimized for **highly navigatable AI coding agents**:
296
+
297
+ ### Codebase Optimizations
298
+ - **Modular Architecture**: Domain-specific tool handlers split into separate files (`tools/project.ts`, `tools/task.ts`, etc.)
299
+ - **Component Refactoring**: TUI views split into modular sub-components (<200 LOC per file)
300
+ - **Type Safety**: Strict typing with `TaskMeta`, `AgentInfo`, and `DetectedProject` interfaces (no `any` types)
301
+ - **Efficiency**: ~65% token reduction via prompt condensation, session reuse, and hybrid research
302
+ - **Testing**: 207 tests passing across 18 test files using Vitest
303
+
304
+ ### AI Agent Guide (AGENTS.md)
305
+ All AI coding agents MUST consult `AGENTS.md` for technical ground truth, including:
306
+ - Build/test commands (dev mode: `npm run dev`, test: `npm test`)
307
+ - Naming conventions (kebab-case files, camelCase functions, PascalCase classes)
308
+ - Implementation patterns (`WorkflowError` error handling, SSOT principles)
309
+ - MCP & RAG patterns (background jobs, semantic search)
246
310
 
247
311
  ---
248
312
 
package/dist/index.js CHANGED
@@ -1365,15 +1365,90 @@ var init_config_utils = __esm({
1365
1365
  }
1366
1366
  });
1367
1367
 
1368
+ // src/mcp/config-service.ts
1369
+ import { EventEmitter } from "events";
1370
+ function onConfigChange(listener) {
1371
+ configEmitter.on("change", listener);
1372
+ }
1373
+ function offConfigChange(listener) {
1374
+ configEmitter.off("change", listener);
1375
+ }
1376
+ var configEmitter, MCPConfigService, configService;
1377
+ var init_config_service = __esm({
1378
+ "src/mcp/config-service.ts"() {
1379
+ "use strict";
1380
+ init_config();
1381
+ configEmitter = new EventEmitter();
1382
+ MCPConfigService = class {
1383
+ cache = null;
1384
+ cacheTime = 0;
1385
+ TTL = 5e3;
1386
+ // 5 seconds cache TTL
1387
+ /**
1388
+ * Load config with caching
1389
+ * Returns cached version if still valid, otherwise reads from disk
1390
+ */
1391
+ load() {
1392
+ const now = Date.now();
1393
+ if (this.cache && now - this.cacheTime < this.TTL) {
1394
+ return this.cache;
1395
+ }
1396
+ this.cache = loadMCPConfig();
1397
+ this.cacheTime = now;
1398
+ return this.cache;
1399
+ }
1400
+ /**
1401
+ * Save config, update cache, and emit change event
1402
+ */
1403
+ save(config) {
1404
+ const oldConfig = this.cache;
1405
+ saveMCPConfig(config);
1406
+ this.cache = config;
1407
+ this.cacheTime = Date.now();
1408
+ configEmitter.emit("change", oldConfig || loadMCPConfig(), config);
1409
+ }
1410
+ /**
1411
+ * Invalidate cache, forcing next load to read from disk
1412
+ */
1413
+ invalidate() {
1414
+ this.cache = null;
1415
+ }
1416
+ /**
1417
+ * Check if cache is currently valid
1418
+ */
1419
+ isCacheValid() {
1420
+ return this.cache !== null && Date.now() - this.cacheTime < this.TTL;
1421
+ }
1422
+ /**
1423
+ * Get current cache age in milliseconds
1424
+ */
1425
+ getCacheAge() {
1426
+ return this.cache ? Date.now() - this.cacheTime : Infinity;
1427
+ }
1428
+ /**
1429
+ * Get the config file path
1430
+ */
1431
+ getConfigPath() {
1432
+ return getMCPConfigPath();
1433
+ }
1434
+ };
1435
+ configService = new MCPConfigService();
1436
+ }
1437
+ });
1438
+
1368
1439
  // src/mcp/config.ts
1369
1440
  var config_exports = {};
1370
1441
  __export(config_exports, {
1371
1442
  cleanStaleProjects: () => cleanStaleProjects,
1443
+ configEmitter: () => configEmitter,
1444
+ configService: () => configService,
1372
1445
  ensureMCPGlobalPath: () => ensureMCPGlobalPath,
1373
1446
  getMCPConfigPath: () => getMCPConfigPath,
1374
1447
  getProjectPermissions: () => getProjectPermissions,
1375
1448
  isProjectExposed: () => isProjectExposed,
1376
1449
  loadMCPConfig: () => loadMCPConfig,
1450
+ offConfigChange: () => offConfigChange,
1451
+ onConfigChange: () => onConfigChange,
1377
1452
  removeProjectConfig: () => removeProjectConfig,
1378
1453
  saveMCPConfig: () => saveMCPConfig,
1379
1454
  setProjectConfig: () => setProjectConfig
@@ -1560,6 +1635,8 @@ var init_config = __esm({
1560
1635
  init_paths();
1561
1636
  init_types();
1562
1637
  init_config_utils();
1638
+ init_config_service();
1639
+ init_config_service();
1563
1640
  }
1564
1641
  });
1565
1642
 
@@ -1815,7 +1892,7 @@ var init_utils2 = __esm({
1815
1892
  import * as fs15 from "fs";
1816
1893
  import * as path17 from "path";
1817
1894
  function resolveProjectPaths(project, pathInput) {
1818
- const config = loadMCPConfig();
1895
+ const config = configService.load();
1819
1896
  let workspaceRoot = pathInput;
1820
1897
  let workspaceName = project;
1821
1898
  if (!workspaceRoot && project) {
@@ -1886,7 +1963,7 @@ var init_paths2 = __esm({
1886
1963
  import * as fs16 from "fs";
1887
1964
  import * as path18 from "path";
1888
1965
  function getExposedProjects() {
1889
- const config = loadMCPConfig();
1966
+ const config = configService.load();
1890
1967
  const knownProjects = config.projects.filter((p) => !!p.path).map((p) => ({ name: p.name, path: p.path }));
1891
1968
  const allProjects = projectService.scan({ knownProjects });
1892
1969
  const activeProject = detectActiveProject(allProjects);
@@ -1933,7 +2010,7 @@ function getExposedProjects() {
1933
2010
  function detectActiveProject(knownProjects) {
1934
2011
  let scanList = knownProjects;
1935
2012
  if (!scanList) {
1936
- const config = loadMCPConfig();
2013
+ const config = configService.load();
1937
2014
  const knownProjectsMap = config.projects.filter((p) => !!p.path).map((p) => ({ name: p.name, path: p.path }));
1938
2015
  const all = projectService.scan({ knownProjects: knownProjectsMap });
1939
2016
  scanList = all.filter((project) => isProjectExposed(config, project.name, project.sourcePath || project.path));
@@ -1941,7 +2018,7 @@ function detectActiveProject(knownProjects) {
1941
2018
  return findClosestProject(scanList);
1942
2019
  }
1943
2020
  function getProjectContext(projectName) {
1944
- const config = loadMCPConfig();
2021
+ const config = configService.load();
1945
2022
  const projects = projectService.scan();
1946
2023
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
1947
2024
  if (!project) {
@@ -1980,7 +2057,7 @@ import * as path19 from "path";
1980
2057
  import * as os3 from "os";
1981
2058
  import * as crypto from "crypto";
1982
2059
  function getProjectTasks(projectName) {
1983
- const config = loadMCPConfig();
2060
+ const config = configService.load();
1984
2061
  const projects = projectService.scan();
1985
2062
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
1986
2063
  if (!project) {
@@ -2014,7 +2091,7 @@ function getProjectTasks(projectName) {
2014
2091
  return tasks;
2015
2092
  }
2016
2093
  function getTask(projectName, taskSlug) {
2017
- const config = loadMCPConfig();
2094
+ const config = configService.load();
2018
2095
  const projects = projectService.scan();
2019
2096
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
2020
2097
  if (!project || !project.tasksPath) return null;
@@ -2028,7 +2105,7 @@ function getTask(projectName, taskSlug) {
2028
2105
  }
2029
2106
  }
2030
2107
  async function createTask(projectName, taskSlug, taskData) {
2031
- const config = loadMCPConfig();
2108
+ const config = configService.load();
2032
2109
  const projects = projectService.scan();
2033
2110
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
2034
2111
  if (!project || !project.tasksPath) {
@@ -2083,7 +2160,7 @@ async function updateTask(projectName, taskSlug, taskData) {
2083
2160
  workspace: meta.workspace
2084
2161
  // Protect workspace metadata
2085
2162
  };
2086
- const config = loadMCPConfig();
2163
+ const config = configService.load();
2087
2164
  const projects = projectService.scan();
2088
2165
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
2089
2166
  if (!project || !project.tasksPath) return null;
@@ -2092,7 +2169,7 @@ async function updateTask(projectName, taskSlug, taskData) {
2092
2169
  return updatedMeta;
2093
2170
  }
2094
2171
  function deleteTask(projectName, taskSlug) {
2095
- const config = loadMCPConfig();
2172
+ const config = configService.load();
2096
2173
  const projects = projectService.scan();
2097
2174
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
2098
2175
  if (!project || !project.tasksPath) return false;
@@ -3279,7 +3356,7 @@ var init_search_utils = __esm({
3279
3356
  import * as fs20 from "fs";
3280
3357
  import * as path22 from "path";
3281
3358
  async function searchCode(query, projectFilter, limit = 10, options) {
3282
- const config = loadMCPConfig();
3359
+ const config = configService.load();
3283
3360
  const projects = getExposedProjects();
3284
3361
  const results = [];
3285
3362
  for (const project of projects) {
@@ -3352,7 +3429,7 @@ async function searchCode(query, projectFilter, limit = 10, options) {
3352
3429
  };
3353
3430
  }
3354
3431
  async function searchKnowledge(query, projectFilter, options) {
3355
- const config = loadMCPConfig();
3432
+ const config = configService.load();
3356
3433
  const projects = getExposedProjects();
3357
3434
  const results = [];
3358
3435
  const queryLower = query.toLowerCase();
@@ -3439,7 +3516,7 @@ async function searchKnowledge(query, projectFilter, options) {
3439
3516
  };
3440
3517
  }
3441
3518
  async function findRelatedFiles2(filePath, projectName, options = {}) {
3442
- const config = loadMCPConfig();
3519
+ const config = configService.load();
3443
3520
  const projects = getExposedProjects();
3444
3521
  const project = projects.find((p) => p.name === projectName);
3445
3522
  if (!project) {
@@ -3495,7 +3572,7 @@ async function findRelatedFiles2(filePath, projectName, options = {}) {
3495
3572
  }
3496
3573
  }
3497
3574
  async function searchSymbols2(name, projectName, options = {}) {
3498
- const config = loadMCPConfig();
3575
+ const config = configService.load();
3499
3576
  const projects = getExposedProjects();
3500
3577
  const project = projects.find((p) => p.name === projectName);
3501
3578
  if (!project) {
@@ -3573,7 +3650,7 @@ async function searchSymbols2(name, projectName, options = {}) {
3573
3650
  }
3574
3651
  }
3575
3652
  async function getFileSummary(filePath, projectName) {
3576
- const config = loadMCPConfig();
3653
+ const config = configService.load();
3577
3654
  const projects = getExposedProjects();
3578
3655
  const project = projects.find((p) => p.name === projectName);
3579
3656
  if (!project) {
@@ -3761,7 +3838,7 @@ var init_drift_service = __esm({
3761
3838
  import * as fs22 from "fs";
3762
3839
  import * as path24 from "path";
3763
3840
  async function indexKnowledge(projectName, force = false, clean = false) {
3764
- const config = loadMCPConfig();
3841
+ const config = configService.load();
3765
3842
  const projects = getExposedProjects();
3766
3843
  const project = projects.find((p2) => p2.name === projectName || p2.path && p2.path === projectName);
3767
3844
  if (!project) {
@@ -4262,7 +4339,7 @@ var init_validation = __esm({
4262
4339
  import * as fs23 from "fs";
4263
4340
  import * as path26 from "path";
4264
4341
  function startSession(projectName, taskSlug, agent, phase) {
4265
- const config = loadMCPConfig();
4342
+ const config = configService.load();
4266
4343
  const projects = projectService.scan();
4267
4344
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
4268
4345
  if (!project || !project.tasksPath) {
@@ -4284,7 +4361,7 @@ function startSession(projectName, taskSlug, agent, phase) {
4284
4361
  return { success: true, message: `Session started for ${agent} agent on task '${taskSlug}' (phase: ${phase})` };
4285
4362
  }
4286
4363
  function endSession(projectName, taskSlug) {
4287
- const config = loadMCPConfig();
4364
+ const config = configService.load();
4288
4365
  const projects = projectService.scan();
4289
4366
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
4290
4367
  if (!project || !project.tasksPath) {
@@ -4298,7 +4375,7 @@ function endSession(projectName, taskSlug) {
4298
4375
  return { success: true, message: `Session ended for task '${taskSlug}'.` };
4299
4376
  }
4300
4377
  function updateAgentTodos(projectName, taskSlug, phase, agent, items) {
4301
- const config = loadMCPConfig();
4378
+ const config = configService.load();
4302
4379
  const projects = projectService.scan();
4303
4380
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
4304
4381
  if (!project || !project.tasksPath) {
@@ -4370,7 +4447,7 @@ function registerResourceHandlers(server) {
4370
4447
  mimeType: "application/json"
4371
4448
  });
4372
4449
  for (const project of projects) {
4373
- const config = loadMCPConfig();
4450
+ const config = configService.load();
4374
4451
  const permissions = getProjectPermissions(config, project.name, project.dataPath);
4375
4452
  if (permissions.knowledge) {
4376
4453
  resources.push({
@@ -4438,7 +4515,7 @@ var init_resources3 = __esm({
4438
4515
  // src/mcp/handlers/tools/project.ts
4439
4516
  async function handleProjectTool(name, args) {
4440
4517
  if (!args) {
4441
- if (name === "list_projects" || name === "help_setup") {
4518
+ if (name === "list_projects" || name === "help_setup" || name === "reload_config") {
4442
4519
  } else {
4443
4520
  return null;
4444
4521
  }
@@ -4487,6 +4564,19 @@ To fix this:
4487
4564
  `;
4488
4565
  return { content: [{ type: "text", text: msg }] };
4489
4566
  }
4567
+ case "reload_config": {
4568
+ configService.invalidate();
4569
+ const newConfig = configService.load();
4570
+ const exposedProjects = getExposedProjects().map((p) => p.name);
4571
+ const msg = `Config reloaded successfully.
4572
+ - Cache invalidated
4573
+ - Config file: ${configService.getConfigPath()}
4574
+ - Exposed projects: ${exposedProjects.join(", ") || "none"}
4575
+ - Total projects in config: ${newConfig.projects.length}
4576
+ `;
4577
+ logger.info("Config reloaded via reload_config tool", { exposedProjects });
4578
+ return { content: [{ type: "text", text: msg }] };
4579
+ }
4490
4580
  default:
4491
4581
  return null;
4492
4582
  }
@@ -4497,6 +4587,7 @@ var init_project = __esm({
4497
4587
  "use strict";
4498
4588
  init_resources2();
4499
4589
  init_logger();
4590
+ init_config_service();
4500
4591
  projectTools = [
4501
4592
  {
4502
4593
  name: "resolve_path",
@@ -4535,6 +4626,11 @@ var init_project = __esm({
4535
4626
  },
4536
4627
  required: ["project"]
4537
4628
  }
4629
+ },
4630
+ {
4631
+ name: "reload_config",
4632
+ description: "Reload MCP configuration from disk. Use this after manually editing mcp.yaml or when changes are not reflecting automatically.",
4633
+ inputSchema: { type: "object", properties: {} }
4538
4634
  }
4539
4635
  ];
4540
4636
  }
@@ -5218,7 +5314,7 @@ async function cleanupSingleTask(projectName, taskSlug, projectDataPath) {
5218
5314
  }
5219
5315
  }
5220
5316
  function getProjectDataPath(projectName) {
5221
- const config = loadMCPConfig();
5317
+ const config = configService.load();
5222
5318
  const projects = projectService.scan();
5223
5319
  const project = projects.find((p) => p.name === projectName && isProjectExposed(config, p.name, p.sourcePath || p.path));
5224
5320
  if (!project || !project.dataPath) {
@@ -5456,6 +5552,7 @@ var init_prompts3 = __esm({
5456
5552
  // src/mcp/server.ts
5457
5553
  import { Server as Server4 } from "@modelcontextprotocol/sdk/server/index.js";
5458
5554
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5555
+ import * as fs26 from "fs";
5459
5556
  async function startMCPServer(options = {}) {
5460
5557
  try {
5461
5558
  logger.info("Starting MCP Server...");
@@ -5465,9 +5562,8 @@ async function startMCPServer(options = {}) {
5465
5562
  });
5466
5563
  process.on("unhandledRejection", (reason) => {
5467
5564
  logger.error("Unhandled Rejection", reason);
5468
- console.error("Unhandled Rejection:", reason);
5469
5565
  });
5470
- const config = loadMCPConfig();
5566
+ const config = configService.load();
5471
5567
  mcpServer = new Server4(
5472
5568
  { name: "rrce-mcp-hub", version: "1.0.0" },
5473
5569
  { capabilities: { resources: {}, tools: {}, prompts: {} } }
@@ -5478,6 +5574,21 @@ async function startMCPServer(options = {}) {
5478
5574
  registerResourceHandlers(mcpServer);
5479
5575
  registerToolHandlers(mcpServer);
5480
5576
  registerPromptHandlers(mcpServer);
5577
+ const configPath = configService.getConfigPath();
5578
+ if (!options.interactive) {
5579
+ try {
5580
+ if (fs26.existsSync(configPath)) {
5581
+ configWatcher = fs26.watch(configPath, () => {
5582
+ configService.invalidate();
5583
+ const exposed2 = getExposedProjects().map((p) => p.name).join(", ");
5584
+ logger.info("Config file changed, refreshed exposed projects", { exposedProjects: exposed2 });
5585
+ });
5586
+ logger.info("Watching config file for changes", { configPath });
5587
+ }
5588
+ } catch (err) {
5589
+ logger.warn("Failed to watch config file", { error: String(err) });
5590
+ }
5591
+ }
5481
5592
  if (!options.interactive) {
5482
5593
  const transport = new StdioServerTransport();
5483
5594
  await mcpServer.connect(transport);
@@ -5498,6 +5609,10 @@ async function startMCPServer(options = {}) {
5498
5609
  }
5499
5610
  }
5500
5611
  function stopMCPServer() {
5612
+ if (configWatcher) {
5613
+ configWatcher.close();
5614
+ configWatcher = null;
5615
+ }
5501
5616
  if (mcpServer) {
5502
5617
  logger.info("Stopping MCP Server...");
5503
5618
  mcpServer.close();
@@ -5509,18 +5624,19 @@ function stopMCPServer() {
5509
5624
  function getMCPServerStatus() {
5510
5625
  return { ...serverState };
5511
5626
  }
5512
- var serverState, mcpServer;
5627
+ var serverState, mcpServer, configWatcher;
5513
5628
  var init_server = __esm({
5514
5629
  "src/mcp/server.ts"() {
5515
5630
  "use strict";
5516
5631
  init_logger();
5517
- init_config();
5632
+ init_config_service();
5518
5633
  init_resources2();
5519
5634
  init_resources3();
5520
5635
  init_tools();
5521
5636
  init_prompts3();
5522
5637
  serverState = { running: false };
5523
5638
  mcpServer = null;
5639
+ configWatcher = null;
5524
5640
  }
5525
5641
  });
5526
5642
 
@@ -5530,7 +5646,7 @@ import pc5 from "picocolors";
5530
5646
  async function handleConfigure() {
5531
5647
  const s = spinner();
5532
5648
  s.start("Scanning for projects...");
5533
- const config = loadMCPConfig();
5649
+ const config = configService.load();
5534
5650
  const knownPaths = config.projects.map((p) => p.path).filter((p) => !!p);
5535
5651
  const projects = scanForProjects({ knownPaths });
5536
5652
  logger.info("Configure: Loaded config", { projects: config.projects, defaultMode: config.defaults.includeNew });
@@ -5606,7 +5722,7 @@ Hidden projects: ${projects.length - exposedCount}`,
5606
5722
  }
5607
5723
  async function handleConfigureGlobalPath() {
5608
5724
  const { resolveGlobalPath: resolveGlobalPath2 } = await Promise.resolve().then(() => (init_tui_utils(), tui_utils_exports));
5609
- const fs35 = await import("fs");
5725
+ const fs36 = await import("fs");
5610
5726
  const path35 = await import("path");
5611
5727
  note3(
5612
5728
  `MCP Hub requires a ${pc5.bold("global storage path")} to store its configuration
@@ -5621,10 +5737,10 @@ locally in each project. MCP needs a central location.`,
5621
5737
  return false;
5622
5738
  }
5623
5739
  try {
5624
- if (!fs35.existsSync(resolvedPath)) {
5625
- fs35.mkdirSync(resolvedPath, { recursive: true });
5740
+ if (!fs36.existsSync(resolvedPath)) {
5741
+ fs36.mkdirSync(resolvedPath, { recursive: true });
5626
5742
  }
5627
- const config = loadMCPConfig();
5743
+ const config = configService.load();
5628
5744
  saveMCPConfig(config);
5629
5745
  note3(
5630
5746
  `${pc5.green("\u2713")} Global path configured: ${pc5.cyan(resolvedPath)}
@@ -5734,15 +5850,15 @@ __export(ConfigContext_exports, {
5734
5850
  useConfig: () => useConfig
5735
5851
  });
5736
5852
  import { createContext, useContext, useState, useCallback, useMemo, useEffect } from "react";
5737
- import * as fs26 from "fs";
5853
+ import * as fs27 from "fs";
5738
5854
  import * as path29 from "path";
5739
5855
  import { jsx as jsx2 } from "react/jsx-runtime";
5740
5856
  function getPackageVersion() {
5741
5857
  try {
5742
5858
  const agentCoreDir = getAgentCoreDir();
5743
5859
  const packageJsonPath = path29.join(path29.dirname(agentCoreDir), "package.json");
5744
- if (fs26.existsSync(packageJsonPath)) {
5745
- return JSON.parse(fs26.readFileSync(packageJsonPath, "utf8")).version;
5860
+ if (fs27.existsSync(packageJsonPath)) {
5861
+ return JSON.parse(fs27.readFileSync(packageJsonPath, "utf8")).version;
5746
5862
  }
5747
5863
  } catch (e) {
5748
5864
  }
@@ -5809,16 +5925,16 @@ var init_ConfigContext = __esm({
5809
5925
  });
5810
5926
 
5811
5927
  // src/mcp/ui/lib/tasks-fs.ts
5812
- import * as fs27 from "fs";
5928
+ import * as fs28 from "fs";
5813
5929
  import * as path30 from "path";
5814
5930
  function readSession(project, taskSlug) {
5815
5931
  const rrceData = getProjectRRCEData(project);
5816
5932
  const sessionPath = path30.join(rrceData, "tasks", taskSlug, "session.json");
5817
- if (!fs27.existsSync(sessionPath)) {
5933
+ if (!fs28.existsSync(sessionPath)) {
5818
5934
  return null;
5819
5935
  }
5820
5936
  try {
5821
- const raw = fs27.readFileSync(sessionPath, "utf-8");
5937
+ const raw = fs28.readFileSync(sessionPath, "utf-8");
5822
5938
  return JSON.parse(raw);
5823
5939
  } catch {
5824
5940
  return null;
@@ -5832,11 +5948,11 @@ function isSessionStale(session, thresholdMs = SESSION_STALE_THRESHOLD_MS) {
5832
5948
  function readAgentTodos(project, taskSlug) {
5833
5949
  const rrceData = getProjectRRCEData(project);
5834
5950
  const todosPath = path30.join(rrceData, "tasks", taskSlug, "agent-todos.json");
5835
- if (!fs27.existsSync(todosPath)) {
5951
+ if (!fs28.existsSync(todosPath)) {
5836
5952
  return null;
5837
5953
  }
5838
5954
  try {
5839
- const raw = fs27.readFileSync(todosPath, "utf-8");
5955
+ const raw = fs28.readFileSync(todosPath, "utf-8");
5840
5956
  return JSON.parse(raw);
5841
5957
  } catch {
5842
5958
  return null;
@@ -5849,8 +5965,8 @@ function detectStorageModeFromConfig(workspaceRoot) {
5849
5965
  if (configPath.startsWith(rrceHome)) {
5850
5966
  return "global";
5851
5967
  }
5852
- if (fs27.existsSync(configPath)) {
5853
- const content = fs27.readFileSync(configPath, "utf-8");
5968
+ if (fs28.existsSync(configPath)) {
5969
+ const content = fs28.readFileSync(configPath, "utf-8");
5854
5970
  if (content.includes("mode: workspace")) return "workspace";
5855
5971
  if (content.includes("mode: global")) return "global";
5856
5972
  }
@@ -5870,18 +5986,18 @@ function getProjectRRCEData(project) {
5870
5986
  function listProjectTasks(project) {
5871
5987
  const rrceData = getProjectRRCEData(project);
5872
5988
  const tasksPath = path30.join(rrceData, "tasks");
5873
- if (!fs27.existsSync(tasksPath)) {
5989
+ if (!fs28.existsSync(tasksPath)) {
5874
5990
  return { projectName: project.name, tasksPath, tasks: [] };
5875
5991
  }
5876
5992
  const tasks = [];
5877
5993
  try {
5878
- const entries = fs27.readdirSync(tasksPath, { withFileTypes: true });
5994
+ const entries = fs28.readdirSync(tasksPath, { withFileTypes: true });
5879
5995
  for (const entry of entries) {
5880
5996
  if (!entry.isDirectory()) continue;
5881
5997
  const metaPath = path30.join(tasksPath, entry.name, "meta.json");
5882
- if (!fs27.existsSync(metaPath)) continue;
5998
+ if (!fs28.existsSync(metaPath)) continue;
5883
5999
  try {
5884
- const raw = fs27.readFileSync(metaPath, "utf-8");
6000
+ const raw = fs28.readFileSync(metaPath, "utf-8");
5885
6001
  const meta = JSON.parse(raw);
5886
6002
  if (!meta.task_slug) meta.task_slug = entry.name;
5887
6003
  tasks.push(meta);
@@ -5901,17 +6017,17 @@ function listProjectTasks(project) {
5901
6017
  function updateTaskStatus(project, taskSlug, status) {
5902
6018
  const rrceData = getProjectRRCEData(project);
5903
6019
  const metaPath = path30.join(rrceData, "tasks", taskSlug, "meta.json");
5904
- if (!fs27.existsSync(metaPath)) {
6020
+ if (!fs28.existsSync(metaPath)) {
5905
6021
  return { ok: false, error: `meta.json not found for task '${taskSlug}'` };
5906
6022
  }
5907
6023
  try {
5908
- const meta = JSON.parse(fs27.readFileSync(metaPath, "utf-8"));
6024
+ const meta = JSON.parse(fs28.readFileSync(metaPath, "utf-8"));
5909
6025
  const next = {
5910
6026
  ...meta,
5911
6027
  status,
5912
6028
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
5913
6029
  };
5914
- fs27.writeFileSync(metaPath, JSON.stringify(next, null, 2));
6030
+ fs28.writeFileSync(metaPath, JSON.stringify(next, null, 2));
5915
6031
  return { ok: true, meta: next };
5916
6032
  } catch (e) {
5917
6033
  return { ok: false, error: String(e) };
@@ -6296,7 +6412,7 @@ var init_ProjectViews = __esm({
6296
6412
  });
6297
6413
 
6298
6414
  // src/mcp/ui/lib/projects.ts
6299
- import * as fs28 from "fs";
6415
+ import * as fs29 from "fs";
6300
6416
  import * as path31 from "path";
6301
6417
  function getIndexStats(project) {
6302
6418
  const stats = { knowledgeCount: 0, codeCount: 0, lastIndexed: null };
@@ -6305,18 +6421,18 @@ function getIndexStats(project) {
6305
6421
  if (knowledgePath) {
6306
6422
  const embPath = path31.join(knowledgePath, "embeddings.json");
6307
6423
  const codeEmbPath = path31.join(knowledgePath, "code-embeddings.json");
6308
- if (fs28.existsSync(embPath)) {
6309
- const stat = fs28.statSync(embPath);
6424
+ if (fs29.existsSync(embPath)) {
6425
+ const stat = fs29.statSync(embPath);
6310
6426
  stats.lastIndexed = stat.mtime.toISOString();
6311
6427
  try {
6312
- const data = JSON.parse(fs28.readFileSync(embPath, "utf-8"));
6428
+ const data = JSON.parse(fs29.readFileSync(embPath, "utf-8"));
6313
6429
  stats.knowledgeCount = Array.isArray(data) ? data.length : Object.keys(data).length;
6314
6430
  } catch {
6315
6431
  }
6316
6432
  }
6317
- if (fs28.existsSync(codeEmbPath)) {
6433
+ if (fs29.existsSync(codeEmbPath)) {
6318
6434
  try {
6319
- const data = JSON.parse(fs28.readFileSync(codeEmbPath, "utf-8"));
6435
+ const data = JSON.parse(fs29.readFileSync(codeEmbPath, "utf-8"));
6320
6436
  stats.codeCount = Array.isArray(data) ? data.length : Object.keys(data).length;
6321
6437
  } catch {
6322
6438
  }
@@ -6349,7 +6465,7 @@ var init_ProjectsView = __esm({
6349
6465
  init_ui_helpers();
6350
6466
  init_ProjectViews();
6351
6467
  init_projects2();
6352
- ProjectsView = ({ config: initialConfig, projects: allProjects, onConfigChange, workspacePath }) => {
6468
+ ProjectsView = ({ config: initialConfig, projects: allProjects, onConfigChange: onConfigChange2, workspacePath }) => {
6353
6469
  const { driftReports, checkAllDrift } = useConfig();
6354
6470
  const [config, setConfig] = useState3(initialConfig);
6355
6471
  const [indexingStats, setIndexingStats] = useState3({});
@@ -6386,7 +6502,7 @@ var init_ProjectsView = __esm({
6386
6502
  };
6387
6503
  saveMCPConfig(newConfig);
6388
6504
  setConfig(newConfig);
6389
- onConfigChange?.();
6505
+ onConfigChange2?.();
6390
6506
  }
6391
6507
  });
6392
6508
  const projectItems = useMemo3(() => {
@@ -6452,7 +6568,7 @@ ${statsRow}` : label;
6452
6568
  });
6453
6569
  saveMCPConfig(newConfig);
6454
6570
  setConfig(newConfig);
6455
- onConfigChange?.();
6571
+ onConfigChange2?.();
6456
6572
  };
6457
6573
  return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "white", flexGrow: 1, children: [
6458
6574
  /* @__PURE__ */ jsx6(ProjectsHeader, { autoExpose: config.defaults.includeNew }),
@@ -7061,7 +7177,7 @@ __export(App_exports, {
7061
7177
  });
7062
7178
  import { useState as useState5, useEffect as useEffect6, useMemo as useMemo5, useCallback as useCallback3 } from "react";
7063
7179
  import { Box as Box14, useInput as useInput5, useApp } from "ink";
7064
- import fs29 from "fs";
7180
+ import fs30 from "fs";
7065
7181
  import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
7066
7182
  var App;
7067
7183
  var init_App = __esm({
@@ -7134,18 +7250,18 @@ var init_App = __esm({
7134
7250
  useEffect6(() => {
7135
7251
  const logPath = getLogFilePath();
7136
7252
  let lastSize = 0;
7137
- if (fs29.existsSync(logPath)) {
7138
- const stats = fs29.statSync(logPath);
7253
+ if (fs30.existsSync(logPath)) {
7254
+ const stats = fs30.statSync(logPath);
7139
7255
  lastSize = stats.size;
7140
7256
  }
7141
7257
  const interval = setInterval(() => {
7142
- if (fs29.existsSync(logPath)) {
7143
- const stats = fs29.statSync(logPath);
7258
+ if (fs30.existsSync(logPath)) {
7259
+ const stats = fs30.statSync(logPath);
7144
7260
  if (stats.size > lastSize) {
7145
7261
  const buffer = Buffer.alloc(stats.size - lastSize);
7146
- const fd = fs29.openSync(logPath, "r");
7147
- fs29.readSync(fd, buffer, 0, buffer.length, lastSize);
7148
- fs29.closeSync(fd);
7262
+ const fd = fs30.openSync(logPath, "r");
7263
+ fs30.readSync(fd, buffer, 0, buffer.length, lastSize);
7264
+ fs30.closeSync(fd);
7149
7265
  const newContent = buffer.toString("utf-8");
7150
7266
  const newLines = newContent.split("\n").filter((l) => l.trim());
7151
7267
  setLogs((prev) => {
@@ -7223,7 +7339,7 @@ async function handleStartServer() {
7223
7339
  const { render } = await import("ink");
7224
7340
  const { App: App2 } = await Promise.resolve().then(() => (init_App(), App_exports));
7225
7341
  const { ConfigProvider: ConfigProvider2 } = await Promise.resolve().then(() => (init_ConfigContext(), ConfigContext_exports));
7226
- const config = loadMCPConfig();
7342
+ const config = configService.load();
7227
7343
  const projects = scanForProjects();
7228
7344
  const exposedProjects = projects.filter((p) => {
7229
7345
  const cfg = config.projects.find(
@@ -7370,15 +7486,15 @@ __export(update_flow_exports, {
7370
7486
  });
7371
7487
  import { confirm as confirm5, spinner as spinner2, note as note6, outro as outro2, cancel as cancel2, isCancel as isCancel7 } from "@clack/prompts";
7372
7488
  import pc8 from "picocolors";
7373
- import * as fs30 from "fs";
7489
+ import * as fs31 from "fs";
7374
7490
  import * as path32 from "path";
7375
7491
  import { stringify as stringify2, parse } from "yaml";
7376
7492
  function backupFile(filePath) {
7377
- if (!fs30.existsSync(filePath)) return null;
7493
+ if (!fs31.existsSync(filePath)) return null;
7378
7494
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").split("T")[0] + "-" + Date.now();
7379
7495
  const backupPath = `${filePath}.${timestamp}.bak`;
7380
7496
  try {
7381
- fs30.copyFileSync(filePath, backupPath);
7497
+ fs31.copyFileSync(filePath, backupPath);
7382
7498
  return backupPath;
7383
7499
  } catch (e) {
7384
7500
  console.error(`Failed to backup ${filePath}:`, e);
@@ -7389,8 +7505,8 @@ function getPackageVersion2() {
7389
7505
  try {
7390
7506
  const agentCoreDir = getAgentCoreDir();
7391
7507
  const packageJsonPath = path32.join(path32.dirname(agentCoreDir), "package.json");
7392
- if (fs30.existsSync(packageJsonPath)) {
7393
- return JSON.parse(fs30.readFileSync(packageJsonPath, "utf8")).version;
7508
+ if (fs31.existsSync(packageJsonPath)) {
7509
+ return JSON.parse(fs31.readFileSync(packageJsonPath, "utf8")).version;
7394
7510
  }
7395
7511
  } catch (e) {
7396
7512
  }
@@ -7405,9 +7521,9 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
7405
7521
  const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
7406
7522
  const configFilePath = getConfigPath(workspacePath);
7407
7523
  let currentSyncedVersion;
7408
- if (fs30.existsSync(configFilePath)) {
7524
+ if (fs31.existsSync(configFilePath)) {
7409
7525
  try {
7410
- const content = fs30.readFileSync(configFilePath, "utf-8");
7526
+ const content = fs31.readFileSync(configFilePath, "utf-8");
7411
7527
  const config = parse(content);
7412
7528
  currentSyncedVersion = config.last_synced_version;
7413
7529
  } catch (e) {
@@ -7415,8 +7531,8 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
7415
7531
  }
7416
7532
  const driftReport = DriftService.checkDrift(dataPaths[0], currentSyncedVersion, runningVersion);
7417
7533
  const ideTargets = [];
7418
- if (fs30.existsSync(configFilePath)) {
7419
- const configContent = fs30.readFileSync(configFilePath, "utf-8");
7534
+ if (fs31.existsSync(configFilePath)) {
7535
+ const configContent = fs31.readFileSync(configFilePath, "utf-8");
7420
7536
  if (configContent.includes("opencode: true")) ideTargets.push("OpenCode agents");
7421
7537
  if (configContent.includes("copilot: true")) ideTargets.push("GitHub Copilot");
7422
7538
  if (configContent.includes("antigravity: true")) ideTargets.push("Antigravity");
@@ -7426,9 +7542,9 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
7426
7542
  const updatedFiles = [];
7427
7543
  for (const dir of dirs) {
7428
7544
  const srcDir = path32.join(agentCoreDir, dir);
7429
- if (!fs30.existsSync(srcDir)) continue;
7545
+ if (!fs31.existsSync(srcDir)) continue;
7430
7546
  const syncFiles = (src, rel) => {
7431
- const entries = fs30.readdirSync(src, { withFileTypes: true });
7547
+ const entries = fs31.readdirSync(src, { withFileTypes: true });
7432
7548
  for (const entry of entries) {
7433
7549
  const entrySrc = path32.join(src, entry.name);
7434
7550
  const entryRel = path32.join(rel, entry.name);
@@ -7441,7 +7557,7 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
7441
7557
  backupFile(entryDest);
7442
7558
  }
7443
7559
  ensureDir(path32.dirname(entryDest));
7444
- fs30.copyFileSync(entrySrc, entryDest);
7560
+ fs31.copyFileSync(entrySrc, entryDest);
7445
7561
  updatedFiles.push(entryRel);
7446
7562
  }
7447
7563
  }
@@ -7456,8 +7572,8 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
7456
7572
  ensureDir(path32.join(rrceHome, "docs"));
7457
7573
  copyDirRecursive(path32.join(agentCoreDir, "templates"), path32.join(rrceHome, "templates"));
7458
7574
  copyDirRecursive(path32.join(agentCoreDir, "docs"), path32.join(rrceHome, "docs"));
7459
- if (fs30.existsSync(configFilePath)) {
7460
- const configContent = fs30.readFileSync(configFilePath, "utf-8");
7575
+ if (fs31.existsSync(configFilePath)) {
7576
+ const configContent = fs31.readFileSync(configFilePath, "utf-8");
7461
7577
  if (configContent.includes("copilot: true")) {
7462
7578
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
7463
7579
  ensureDir(copilotPath);
@@ -7479,21 +7595,21 @@ async function performUpdate(workspacePath, workspaceName, currentStorageMode, o
7479
7595
  try {
7480
7596
  const yaml = parse(configContent);
7481
7597
  yaml.last_synced_version = runningVersion;
7482
- fs30.writeFileSync(configFilePath, stringify2(yaml));
7598
+ fs31.writeFileSync(configFilePath, stringify2(yaml));
7483
7599
  } catch (e) {
7484
7600
  console.error("Failed to update config.yaml version:", e);
7485
7601
  }
7486
7602
  }
7487
7603
  const mcpPath = path32.join(rrceHome, "mcp.yaml");
7488
- if (fs30.existsSync(mcpPath)) {
7604
+ if (fs31.existsSync(mcpPath)) {
7489
7605
  try {
7490
- const content = fs30.readFileSync(mcpPath, "utf-8");
7606
+ const content = fs31.readFileSync(mcpPath, "utf-8");
7491
7607
  const yaml = parse(content);
7492
7608
  if (yaml.projects) {
7493
7609
  const project = yaml.projects.find((p) => p.name === workspaceName);
7494
7610
  if (project) {
7495
7611
  project.last_synced_version = runningVersion;
7496
- fs30.writeFileSync(mcpPath, stringify2(yaml));
7612
+ fs31.writeFileSync(mcpPath, stringify2(yaml));
7497
7613
  }
7498
7614
  }
7499
7615
  } catch (e) {
@@ -7529,9 +7645,9 @@ async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
7529
7645
  const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
7530
7646
  const configFilePath = getConfigPath(workspacePath);
7531
7647
  let currentSyncedVersion;
7532
- if (fs30.existsSync(configFilePath)) {
7648
+ if (fs31.existsSync(configFilePath)) {
7533
7649
  try {
7534
- const content = fs30.readFileSync(configFilePath, "utf-8");
7650
+ const content = fs31.readFileSync(configFilePath, "utf-8");
7535
7651
  const config = parse(content);
7536
7652
  currentSyncedVersion = config.last_synced_version;
7537
7653
  } catch (e) {
@@ -7555,8 +7671,8 @@ async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
7555
7671
  ` \u2022 docs/ (documentation)`
7556
7672
  ];
7557
7673
  const ideTargets = [];
7558
- if (fs30.existsSync(configFilePath)) {
7559
- const configContent = fs30.readFileSync(configFilePath, "utf-8");
7674
+ if (fs31.existsSync(configFilePath)) {
7675
+ const configContent = fs31.readFileSync(configFilePath, "utf-8");
7560
7676
  if (configContent.includes("opencode: true")) ideTargets.push("OpenCode agents");
7561
7677
  if (configContent.includes("copilot: true")) ideTargets.push("GitHub Copilot");
7562
7678
  if (configContent.includes("antigravity: true")) ideTargets.push("Antigravity");
@@ -7586,9 +7702,9 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
7586
7702
  const updatedFiles = [];
7587
7703
  for (const dir of dirs) {
7588
7704
  const srcDir = path32.join(agentCoreDir, dir);
7589
- if (!fs30.existsSync(srcDir)) continue;
7705
+ if (!fs31.existsSync(srcDir)) continue;
7590
7706
  const syncFiles = (src, rel) => {
7591
- const entries = fs30.readdirSync(src, { withFileTypes: true });
7707
+ const entries = fs31.readdirSync(src, { withFileTypes: true });
7592
7708
  for (const entry of entries) {
7593
7709
  const entrySrc = path32.join(src, entry.name);
7594
7710
  const entryRel = path32.join(rel, entry.name);
@@ -7601,7 +7717,7 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
7601
7717
  backupFile(entryDest);
7602
7718
  }
7603
7719
  ensureDir(path32.dirname(entryDest));
7604
- fs30.copyFileSync(entrySrc, entryDest);
7720
+ fs31.copyFileSync(entrySrc, entryDest);
7605
7721
  updatedFiles.push(entryRel);
7606
7722
  }
7607
7723
  }
@@ -7616,8 +7732,8 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
7616
7732
  ensureDir(path32.join(rrceHome, "docs"));
7617
7733
  copyDirRecursive(path32.join(agentCoreDir, "templates"), path32.join(rrceHome, "templates"));
7618
7734
  copyDirRecursive(path32.join(agentCoreDir, "docs"), path32.join(rrceHome, "docs"));
7619
- if (fs30.existsSync(configFilePath)) {
7620
- const configContent = fs30.readFileSync(configFilePath, "utf-8");
7735
+ if (fs31.existsSync(configFilePath)) {
7736
+ const configContent = fs31.readFileSync(configFilePath, "utf-8");
7621
7737
  if (configContent.includes("copilot: true")) {
7622
7738
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
7623
7739
  ensureDir(copilotPath);
@@ -7639,21 +7755,21 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
7639
7755
  try {
7640
7756
  const yaml = parse(configContent);
7641
7757
  yaml.last_synced_version = runningVersion;
7642
- fs30.writeFileSync(configFilePath, stringify2(yaml));
7758
+ fs31.writeFileSync(configFilePath, stringify2(yaml));
7643
7759
  } catch (e) {
7644
7760
  console.error("Failed to update config.yaml version:", e);
7645
7761
  }
7646
7762
  }
7647
7763
  const mcpPath = path32.join(rrceHome, "mcp.yaml");
7648
- if (fs30.existsSync(mcpPath)) {
7764
+ if (fs31.existsSync(mcpPath)) {
7649
7765
  try {
7650
- const content = fs30.readFileSync(mcpPath, "utf-8");
7766
+ const content = fs31.readFileSync(mcpPath, "utf-8");
7651
7767
  const yaml = parse(content);
7652
7768
  if (yaml.projects) {
7653
7769
  const project = yaml.projects.find((p) => p.name === workspaceName);
7654
7770
  if (project) {
7655
7771
  project.last_synced_version = runningVersion;
7656
- fs30.writeFileSync(mcpPath, stringify2(yaml));
7772
+ fs31.writeFileSync(mcpPath, stringify2(yaml));
7657
7773
  }
7658
7774
  }
7659
7775
  } catch (e) {
@@ -7712,7 +7828,7 @@ var init_update_flow = __esm({
7712
7828
  // src/commands/wizard/index.ts
7713
7829
  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";
7714
7830
  import pc13 from "picocolors";
7715
- import * as fs34 from "fs";
7831
+ import * as fs35 from "fs";
7716
7832
  import * as path34 from "path";
7717
7833
  import { parse as parse2 } from "yaml";
7718
7834
 
@@ -8390,7 +8506,7 @@ async function handlePostSetup(config, workspacePath, workspaceName, linkedProje
8390
8506
  init_paths();
8391
8507
  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";
8392
8508
  import pc10 from "picocolors";
8393
- import * as fs31 from "fs";
8509
+ import * as fs32 from "fs";
8394
8510
  init_detection();
8395
8511
  async function runLinkProjectsFlow(workspacePath, workspaceName) {
8396
8512
  const projects = scanForProjects({
@@ -8429,7 +8545,7 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
8429
8545
  const s = spinner4();
8430
8546
  s.start("Linking projects");
8431
8547
  const configFilePath = getConfigPath(workspacePath);
8432
- let configContent = fs31.readFileSync(configFilePath, "utf-8");
8548
+ let configContent = fs32.readFileSync(configFilePath, "utf-8");
8433
8549
  if (configContent.includes("linked_projects:")) {
8434
8550
  const lines = configContent.split("\n");
8435
8551
  const linkedIndex = lines.findIndex((l) => l.trim() === "linked_projects:");
@@ -8456,7 +8572,7 @@ linked_projects:
8456
8572
  `;
8457
8573
  });
8458
8574
  }
8459
- fs31.writeFileSync(configFilePath, configContent);
8575
+ fs32.writeFileSync(configFilePath, configContent);
8460
8576
  generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
8461
8577
  s.stop("Projects linked");
8462
8578
  const workspaceFile = `${workspaceName}.code-workspace`;
@@ -8492,7 +8608,7 @@ init_paths();
8492
8608
  init_utils();
8493
8609
  import { confirm as confirm8, spinner as spinner5, note as note9, outro as outro5, cancel as cancel5, isCancel as isCancel10 } from "@clack/prompts";
8494
8610
  import pc11 from "picocolors";
8495
- import * as fs32 from "fs";
8611
+ import * as fs33 from "fs";
8496
8612
  import * as path33 from "path";
8497
8613
  async function runSyncToGlobalFlow(workspacePath, workspaceName) {
8498
8614
  const localPath = getLocalWorkspacePath(workspacePath);
@@ -8500,7 +8616,7 @@ async function runSyncToGlobalFlow(workspacePath, workspaceName) {
8500
8616
  const globalPath = path33.join(customGlobalPath, "workspaces", workspaceName);
8501
8617
  const subdirs = ["knowledge", "prompts", "templates", "tasks", "refs"];
8502
8618
  const existingDirs = subdirs.filter(
8503
- (dir) => fs32.existsSync(path33.join(localPath, dir))
8619
+ (dir) => fs33.existsSync(path33.join(localPath, dir))
8504
8620
  );
8505
8621
  if (existingDirs.length === 0) {
8506
8622
  outro5(pc11.yellow("No data found in workspace storage to sync."));
@@ -8555,7 +8671,7 @@ init_update_flow();
8555
8671
  // src/commands/wizard/delete-flow.ts
8556
8672
  import { multiselect as multiselect5, confirm as confirm9, spinner as spinner6, note as note10, cancel as cancel6, isCancel as isCancel11 } from "@clack/prompts";
8557
8673
  import pc12 from "picocolors";
8558
- import * as fs33 from "fs";
8674
+ import * as fs34 from "fs";
8559
8675
  init_detection();
8560
8676
  init_config();
8561
8677
  async function runDeleteGlobalProjectFlow(availableProjects) {
@@ -8594,13 +8710,13 @@ Are you sure?`,
8594
8710
  const s = spinner6();
8595
8711
  s.start("Deleting projects...");
8596
8712
  try {
8597
- const mcpConfig = loadMCPConfig();
8713
+ const mcpConfig = configService.load();
8598
8714
  let configChanged = false;
8599
8715
  for (const projectName of projectsToDelete) {
8600
8716
  const project = globalProjects.find((p) => p.name === projectName);
8601
8717
  if (!project) continue;
8602
- if (fs33.existsSync(project.dataPath)) {
8603
- fs33.rmSync(project.dataPath, { recursive: true, force: true });
8718
+ if (fs34.existsSync(project.dataPath)) {
8719
+ fs34.rmSync(project.dataPath, { recursive: true, force: true });
8604
8720
  }
8605
8721
  const newConfig = removeProjectConfig(mcpConfig, projectName);
8606
8722
  configChanged = true;
@@ -8623,8 +8739,8 @@ function getPackageVersion3() {
8623
8739
  try {
8624
8740
  const agentCoreDir = getAgentCoreDir();
8625
8741
  const packageJsonPath = path34.join(path34.dirname(agentCoreDir), "package.json");
8626
- if (fs34.existsSync(packageJsonPath)) {
8627
- return JSON.parse(fs34.readFileSync(packageJsonPath, "utf8")).version;
8742
+ if (fs35.existsSync(packageJsonPath)) {
8743
+ return JSON.parse(fs35.readFileSync(packageJsonPath, "utf8")).version;
8628
8744
  }
8629
8745
  } catch (e) {
8630
8746
  }
@@ -8632,9 +8748,9 @@ function getPackageVersion3() {
8632
8748
  }
8633
8749
  function getLastSyncedVersion(workspacePath, workspaceName) {
8634
8750
  const configFilePath = getConfigPath(workspacePath);
8635
- if (fs34.existsSync(configFilePath)) {
8751
+ if (fs35.existsSync(configFilePath)) {
8636
8752
  try {
8637
- const content = fs34.readFileSync(configFilePath, "utf-8");
8753
+ const content = fs35.readFileSync(configFilePath, "utf-8");
8638
8754
  const config = parse2(content);
8639
8755
  if (config.last_synced_version) {
8640
8756
  return config.last_synced_version;
@@ -8644,9 +8760,9 @@ function getLastSyncedVersion(workspacePath, workspaceName) {
8644
8760
  }
8645
8761
  const rrceHome = getEffectiveRRCEHome(workspacePath) || getDefaultRRCEHome2();
8646
8762
  const mcpPath = path34.join(rrceHome, "mcp.yaml");
8647
- if (fs34.existsSync(mcpPath)) {
8763
+ if (fs35.existsSync(mcpPath)) {
8648
8764
  try {
8649
- const content = fs34.readFileSync(mcpPath, "utf-8");
8765
+ const content = fs35.readFileSync(mcpPath, "utf-8");
8650
8766
  const config = parse2(content);
8651
8767
  const project = config.projects?.find((p) => p.name === workspaceName);
8652
8768
  if (project?.last_synced_version) {
@@ -8697,7 +8813,7 @@ async function runWizard() {
8697
8813
  const workspaceName = getWorkspaceName(workspacePath);
8698
8814
  const gitUser = getGitUser();
8699
8815
  try {
8700
- const mcpConfig = loadMCPConfig();
8816
+ const mcpConfig = configService.load();
8701
8817
  const { config: cleanConfig, removed } = cleanStaleProjects(mcpConfig);
8702
8818
  if (removed.length > 0) {
8703
8819
  saveMCPConfig(cleanConfig);
@@ -8716,18 +8832,18 @@ Workspace: ${pc13.bold(workspaceName)}`,
8716
8832
  workspacePath
8717
8833
  });
8718
8834
  const configFilePath = getConfigPath(workspacePath);
8719
- let isAlreadyConfigured = fs34.existsSync(configFilePath);
8835
+ let isAlreadyConfigured = fs35.existsSync(configFilePath);
8720
8836
  let currentStorageMode = null;
8721
8837
  if (isAlreadyConfigured) {
8722
8838
  try {
8723
- const configContent = fs34.readFileSync(configFilePath, "utf-8");
8839
+ const configContent = fs35.readFileSync(configFilePath, "utf-8");
8724
8840
  const modeMatch = configContent.match(/mode:\s*(global|workspace)/);
8725
8841
  currentStorageMode = modeMatch?.[1] ?? null;
8726
8842
  } catch {
8727
8843
  }
8728
8844
  } else {
8729
8845
  try {
8730
- const mcpConfig = loadMCPConfig();
8846
+ const mcpConfig = configService.load();
8731
8847
  const mcpProject = mcpConfig.projects.find((p) => p.path === workspacePath);
8732
8848
  if (mcpProject) {
8733
8849
  isAlreadyConfigured = true;
@@ -8737,7 +8853,7 @@ Workspace: ${pc13.bold(workspaceName)}`,
8737
8853
  }
8738
8854
  }
8739
8855
  const localDataPath = getLocalWorkspacePath(workspacePath);
8740
- const hasLocalData = fs34.existsSync(localDataPath);
8856
+ const hasLocalData = fs35.existsSync(localDataPath);
8741
8857
  if (isAlreadyConfigured) {
8742
8858
  const continueToMenu = await checkAndPromptUpdate(workspacePath, workspaceName, currentStorageMode);
8743
8859
  if (!continueToMenu) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.3.30",
3
+ "version": "0.3.32",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",