rrce-workflow 0.3.20 → 0.3.22

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.
@@ -33,14 +33,15 @@ Non-Negotiables
33
33
  3. Version every knowledge edit by stamping an ISO date (e.g. `Updated: 2024-11-01`) near the top of the section you modify.
34
34
  4. Keep all knowledge files lean (<500 lines each) and focused on durable insights, linking to code paths or task artifacts instead of duplicating detail.
35
35
  5. Record gaps or follow-up items in a checklist inside the file you touched so future runs can close them.
36
- 6. **Semantic Indexing (MANDATORY)**: After updating any knowledge files, run the indexer to keep search current:
37
- ```
38
- Tool: rrce_index_knowledge
39
- Args: { "project": "{{WORKSPACE_NAME}}" }
40
- ```
41
- Tool: rrce_index_knowledge
42
- Args: { "project": "{{WORKSPACE_NAME}}" }
43
- ```
36
+ 6. **Semantic Indexing (MANDATORY)**: After updating any knowledge files, run the indexer to keep search current.
37
+
38
+ ## Reindexing Guidance
39
+
40
+ | Scenario | Tool Argument | Rationale |
41
+ |----------|---------------|-----------|
42
+ | Routine updates | `{ "project": "{{WORKSPACE_NAME}}" }` | Incremental (fastest). Only updates changed files. |
43
+ | Major refactors | `{ "project": "{{WORKSPACE_NAME}}", "force": true }` | Forces re-calculation of hashes for all files without wiping. |
44
+ | Corrupt index / Stale vectors | `{ "project": "{{WORKSPACE_NAME}}", "clean": true }` | Wipes index files and rebuilds from scratch. Resolves vector drift. |
44
45
 
45
46
  Deliverable
46
47
  - Updated `{{RRCE_DATA}}/knowledge/*` files that accurately reflect the present project state, each carrying the latest `Updated:` marker and lean checklist.
package/dist/index.js CHANGED
@@ -1232,8 +1232,6 @@ ${prompt.content}` : prompt.content;
1232
1232
  if (fs7.existsSync(openCodeConfig)) {
1233
1233
  const config = JSON.parse(fs7.readFileSync(openCodeConfig, "utf8"));
1234
1234
  if (!config.agent) config.agent = {};
1235
- if (!config.agent.plan) config.agent.plan = {};
1236
- config.agent.plan.disable = true;
1237
1235
  fs7.writeFileSync(openCodeConfig, JSON.stringify(config, null, 2));
1238
1236
  }
1239
1237
  } catch (e) {
@@ -3597,10 +3595,126 @@ var init_search = __esm({
3597
3595
  }
3598
3596
  });
3599
3597
 
3600
- // src/mcp/resources/indexing.ts
3598
+ // src/lib/drift-service.ts
3601
3599
  import * as fs20 from "fs";
3602
3600
  import * as path21 from "path";
3603
- async function indexKnowledge(projectName, force = false) {
3601
+ import * as crypto2 from "crypto";
3602
+ var DriftService;
3603
+ var init_drift_service = __esm({
3604
+ "src/lib/drift-service.ts"() {
3605
+ "use strict";
3606
+ DriftService = class {
3607
+ static CHECKSUM_FILENAME = ".rrce-checksums.json";
3608
+ static calculateHash(filePath) {
3609
+ const content = fs20.readFileSync(filePath);
3610
+ return crypto2.createHash("md5").update(content).digest("hex");
3611
+ }
3612
+ static getManifestPath(projectPath) {
3613
+ return path21.join(projectPath, this.CHECKSUM_FILENAME);
3614
+ }
3615
+ static loadManifest(projectPath) {
3616
+ const manifestPath = this.getManifestPath(projectPath);
3617
+ if (!fs20.existsSync(manifestPath)) {
3618
+ return {};
3619
+ }
3620
+ try {
3621
+ return JSON.parse(fs20.readFileSync(manifestPath, "utf8"));
3622
+ } catch (e) {
3623
+ return {};
3624
+ }
3625
+ }
3626
+ static saveManifest(projectPath, manifest) {
3627
+ const manifestPath = this.getManifestPath(projectPath);
3628
+ fs20.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
3629
+ }
3630
+ /**
3631
+ * Generates a manifest for the current state of files in the project
3632
+ */
3633
+ static generateManifest(projectPath, files) {
3634
+ const manifest = {};
3635
+ for (const file of files) {
3636
+ const fullPath = path21.join(projectPath, file);
3637
+ if (fs20.existsSync(fullPath)) {
3638
+ const stats = fs20.statSync(fullPath);
3639
+ manifest[file] = {
3640
+ hash: this.calculateHash(fullPath),
3641
+ mtime: stats.mtimeMs
3642
+ };
3643
+ }
3644
+ }
3645
+ return manifest;
3646
+ }
3647
+ /**
3648
+ * Compares current files against the manifest to detect modifications and deletions
3649
+ */
3650
+ static detectModifiedFiles(projectPath) {
3651
+ const manifest = this.loadManifest(projectPath);
3652
+ const modified = [];
3653
+ const deleted = [];
3654
+ for (const [relPath, entry] of Object.entries(manifest)) {
3655
+ const fullPath = path21.join(projectPath, relPath);
3656
+ if (!fs20.existsSync(fullPath)) {
3657
+ deleted.push(relPath);
3658
+ continue;
3659
+ }
3660
+ const stats = fs20.statSync(fullPath);
3661
+ if (stats.mtimeMs === entry.mtime) {
3662
+ continue;
3663
+ }
3664
+ const currentHash = this.calculateHash(fullPath);
3665
+ if (currentHash !== entry.hash) {
3666
+ modified.push(relPath);
3667
+ }
3668
+ }
3669
+ return { modified, deleted };
3670
+ }
3671
+ /**
3672
+ * Returns array of deleted file paths from manifest
3673
+ */
3674
+ static detectDeletedFiles(projectPath) {
3675
+ const manifest = this.loadManifest(projectPath);
3676
+ const deleted = [];
3677
+ for (const relPath of Object.keys(manifest)) {
3678
+ const fullPath = path21.join(projectPath, relPath);
3679
+ if (!fs20.existsSync(fullPath)) {
3680
+ deleted.push(relPath);
3681
+ }
3682
+ }
3683
+ return deleted;
3684
+ }
3685
+ /**
3686
+ * Full drift check: version + modifications
3687
+ */
3688
+ static checkDrift(projectPath, currentVersion, runningVersion) {
3689
+ const { modified, deleted } = this.detectModifiedFiles(projectPath);
3690
+ let type = "none";
3691
+ let hasDrift = false;
3692
+ if (currentVersion !== runningVersion) {
3693
+ hasDrift = true;
3694
+ type = "version";
3695
+ } else if (modified.length > 0 || deleted.length > 0) {
3696
+ hasDrift = true;
3697
+ type = "modified";
3698
+ }
3699
+ return {
3700
+ hasDrift,
3701
+ type,
3702
+ modifiedFiles: modified,
3703
+ deletedFiles: deleted,
3704
+ version: {
3705
+ current: currentVersion || "0.0.0",
3706
+ running: runningVersion
3707
+ }
3708
+ };
3709
+ }
3710
+ };
3711
+ }
3712
+ });
3713
+
3714
+ // src/mcp/resources/indexing.ts
3715
+ import * as fs21 from "fs";
3716
+ import * as path22 from "path";
3717
+ async function indexKnowledge(projectName, force = false, clean = false) {
3604
3718
  const config = loadMCPConfig();
3605
3719
  const projects = getExposedProjects();
3606
3720
  const project = projects.find((p2) => p2.name === projectName || p2.path && p2.path === projectName);
@@ -3629,7 +3743,7 @@ async function indexKnowledge(projectName, force = false) {
3629
3743
  };
3630
3744
  }
3631
3745
  const scanRoot = project.sourcePath || project.path || project.dataPath;
3632
- if (!fs20.existsSync(scanRoot)) {
3746
+ if (!fs21.existsSync(scanRoot)) {
3633
3747
  return {
3634
3748
  state: "failed",
3635
3749
  status: "failed",
@@ -3642,8 +3756,14 @@ async function indexKnowledge(projectName, force = false) {
3642
3756
  }
3643
3757
  const runIndexing = async () => {
3644
3758
  const { shouldSkipEntryDir, shouldSkipEntryFile } = getScanContext(project, scanRoot);
3645
- const indexPath = path21.join(project.knowledgePath || path21.join(scanRoot, ".rrce-workflow", "knowledge"), "embeddings.json");
3646
- const codeIndexPath = path21.join(project.knowledgePath || path21.join(scanRoot, ".rrce-workflow", "knowledge"), "code-embeddings.json");
3759
+ const knowledgeDir = project.knowledgePath || path22.join(scanRoot, ".rrce-workflow", "knowledge");
3760
+ const indexPath = path22.join(knowledgeDir, "embeddings.json");
3761
+ const codeIndexPath = path22.join(knowledgeDir, "code-embeddings.json");
3762
+ if (clean) {
3763
+ logger.info(`[RAG] Cleaning knowledge index for ${project.name}`);
3764
+ if (fs21.existsSync(indexPath)) fs21.unlinkSync(indexPath);
3765
+ if (fs21.existsSync(codeIndexPath)) fs21.unlinkSync(codeIndexPath);
3766
+ }
3647
3767
  const model = projConfig?.semanticSearch?.model || "Xenova/all-MiniLM-L6-v2";
3648
3768
  const rag = new RAGService(indexPath, model);
3649
3769
  const codeRag = new RAGService(codeIndexPath, model);
@@ -3653,14 +3773,14 @@ async function indexKnowledge(projectName, force = false) {
3653
3773
  let itemsTotal = 0;
3654
3774
  let itemsDone = 0;
3655
3775
  const preCount = (dir) => {
3656
- const entries = fs20.readdirSync(dir, { withFileTypes: true });
3776
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
3657
3777
  for (const entry of entries) {
3658
- const fullPath = path21.join(dir, entry.name);
3778
+ const fullPath = path22.join(dir, entry.name);
3659
3779
  if (entry.isDirectory()) {
3660
3780
  if (shouldSkipEntryDir(fullPath)) continue;
3661
3781
  preCount(fullPath);
3662
3782
  } else if (entry.isFile()) {
3663
- const ext = path21.extname(entry.name).toLowerCase();
3783
+ const ext = path22.extname(entry.name).toLowerCase();
3664
3784
  if (!INDEXABLE_EXTENSIONS.includes(ext)) continue;
3665
3785
  if (shouldSkipEntryFile(fullPath)) continue;
3666
3786
  itemsTotal++;
@@ -3672,13 +3792,18 @@ async function indexKnowledge(projectName, force = false) {
3672
3792
  const cleanupIgnoredFiles = async () => {
3673
3793
  const indexedFiles = [...rag.getIndexedFiles(), ...codeRag.getIndexedFiles()];
3674
3794
  const unique = Array.from(new Set(indexedFiles));
3795
+ const deletedFiles = project.dataPath ? DriftService.detectDeletedFiles(project.dataPath) : [];
3796
+ if (deletedFiles.length > 0) {
3797
+ logger.info(`[RAG] ${project.name}: Detected ${deletedFiles.length} deleted files from manifest`);
3798
+ }
3675
3799
  for (const filePath of unique) {
3676
- if (!path21.isAbsolute(filePath)) continue;
3677
- const relFilePath = filePath.split(path21.sep).join("/");
3678
- const relScanRoot = scanRoot.split(path21.sep).join("/");
3800
+ if (!path22.isAbsolute(filePath)) continue;
3801
+ const relFilePath = filePath.split(path22.sep).join("/");
3802
+ const relScanRoot = scanRoot.split(path22.sep).join("/");
3679
3803
  const isInScanRoot = relFilePath === relScanRoot || relFilePath.startsWith(`${relScanRoot}/`);
3680
3804
  if (!isInScanRoot) continue;
3681
- if (shouldSkipEntryFile(filePath)) {
3805
+ const isDeleted = deletedFiles.some((df) => filePath.endsWith(df));
3806
+ if (shouldSkipEntryFile(filePath) || isDeleted || !fs21.existsSync(filePath)) {
3682
3807
  await rag.removeFile(filePath);
3683
3808
  await codeRag.removeFile(filePath);
3684
3809
  }
@@ -3686,21 +3811,21 @@ async function indexKnowledge(projectName, force = false) {
3686
3811
  };
3687
3812
  await cleanupIgnoredFiles();
3688
3813
  const scanDir = async (dir) => {
3689
- const entries = fs20.readdirSync(dir, { withFileTypes: true });
3814
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
3690
3815
  for (const entry of entries) {
3691
- const fullPath = path21.join(dir, entry.name);
3816
+ const fullPath = path22.join(dir, entry.name);
3692
3817
  if (entry.isDirectory()) {
3693
3818
  if (shouldSkipEntryDir(fullPath)) continue;
3694
3819
  await scanDir(fullPath);
3695
3820
  } else if (entry.isFile()) {
3696
- const ext = path21.extname(entry.name).toLowerCase();
3821
+ const ext = path22.extname(entry.name).toLowerCase();
3697
3822
  if (!INDEXABLE_EXTENSIONS.includes(ext)) continue;
3698
3823
  if (shouldSkipEntryFile(fullPath)) continue;
3699
3824
  try {
3700
3825
  indexingJobs.update(project.name, { currentItem: fullPath, itemsDone });
3701
- const stat = fs20.statSync(fullPath);
3826
+ const stat = fs21.statSync(fullPath);
3702
3827
  const mtime = force ? void 0 : stat.mtimeMs;
3703
- const content = fs20.readFileSync(fullPath, "utf-8");
3828
+ const content = fs21.readFileSync(fullPath, "utf-8");
3704
3829
  const wasIndexed = await rag.indexFile(fullPath, content, mtime);
3705
3830
  if (wasIndexed) {
3706
3831
  indexed++;
@@ -3772,11 +3897,12 @@ var init_indexing = __esm({
3772
3897
  init_projects();
3773
3898
  init_utils2();
3774
3899
  init_constants();
3900
+ init_drift_service();
3775
3901
  }
3776
3902
  });
3777
3903
 
3778
3904
  // src/mcp/resources/context.ts
3779
- import * as path22 from "path";
3905
+ import * as path23 from "path";
3780
3906
  import * as os4 from "os";
3781
3907
  function getContextPreamble() {
3782
3908
  const activeProject = detectActiveProject();
@@ -3792,7 +3918,7 @@ If the above tools fail, ask the user for clarification.
3792
3918
  ---
3793
3919
  `;
3794
3920
  }
3795
- const rrceHome = process.env.RRCE_HOME || path22.join(os4.homedir(), ".rrce-workflow");
3921
+ const rrceHome = process.env.RRCE_HOME || path23.join(os4.homedir(), ".rrce-workflow");
3796
3922
  const workspaceRoot = activeProject.sourcePath || activeProject.path || activeProject.dataPath;
3797
3923
  const rrceData = activeProject.dataPath;
3798
3924
  return `## System Context
@@ -4087,8 +4213,8 @@ var init_validation = __esm({
4087
4213
  });
4088
4214
 
4089
4215
  // src/mcp/resources/sessions.ts
4090
- import * as fs21 from "fs";
4091
- import * as path23 from "path";
4216
+ import * as fs22 from "fs";
4217
+ import * as path24 from "path";
4092
4218
  function startSession(projectName, taskSlug, agent, phase) {
4093
4219
  const config = loadMCPConfig();
4094
4220
  const projects = projectService.scan();
@@ -4096,8 +4222,8 @@ function startSession(projectName, taskSlug, agent, phase) {
4096
4222
  if (!project || !project.tasksPath) {
4097
4223
  return { success: false, message: `Project '${projectName}' not found or not exposed.` };
4098
4224
  }
4099
- const taskDir = path23.join(project.tasksPath, taskSlug);
4100
- if (!fs21.existsSync(taskDir)) {
4225
+ const taskDir = path24.join(project.tasksPath, taskSlug);
4226
+ if (!fs22.existsSync(taskDir)) {
4101
4227
  return { success: false, message: `Task '${taskSlug}' not found.` };
4102
4228
  }
4103
4229
  const session = {
@@ -4107,8 +4233,8 @@ function startSession(projectName, taskSlug, agent, phase) {
4107
4233
  started_at: (/* @__PURE__ */ new Date()).toISOString(),
4108
4234
  heartbeat: (/* @__PURE__ */ new Date()).toISOString()
4109
4235
  };
4110
- const sessionPath = path23.join(taskDir, "session.json");
4111
- fs21.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
4236
+ const sessionPath = path24.join(taskDir, "session.json");
4237
+ fs22.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
4112
4238
  return { success: true, message: `Session started for ${agent} agent on task '${taskSlug}' (phase: ${phase})` };
4113
4239
  }
4114
4240
  function endSession(projectName, taskSlug) {
@@ -4118,11 +4244,11 @@ function endSession(projectName, taskSlug) {
4118
4244
  if (!project || !project.tasksPath) {
4119
4245
  return { success: false, message: `Project '${projectName}' not found or not exposed.` };
4120
4246
  }
4121
- const sessionPath = path23.join(project.tasksPath, taskSlug, "session.json");
4122
- if (!fs21.existsSync(sessionPath)) {
4247
+ const sessionPath = path24.join(project.tasksPath, taskSlug, "session.json");
4248
+ if (!fs22.existsSync(sessionPath)) {
4123
4249
  return { success: true, message: `No active session for task '${taskSlug}'.` };
4124
4250
  }
4125
- fs21.unlinkSync(sessionPath);
4251
+ fs22.unlinkSync(sessionPath);
4126
4252
  return { success: true, message: `Session ended for task '${taskSlug}'.` };
4127
4253
  }
4128
4254
  function updateAgentTodos(projectName, taskSlug, phase, agent, items) {
@@ -4132,9 +4258,9 @@ function updateAgentTodos(projectName, taskSlug, phase, agent, items) {
4132
4258
  if (!project || !project.tasksPath) {
4133
4259
  return { success: false, message: `Project '${projectName}' not found or not exposed.` };
4134
4260
  }
4135
- const taskDir = path23.join(project.tasksPath, taskSlug);
4136
- if (!fs21.existsSync(taskDir)) {
4137
- fs21.mkdirSync(taskDir, { recursive: true });
4261
+ const taskDir = path24.join(project.tasksPath, taskSlug);
4262
+ if (!fs22.existsSync(taskDir)) {
4263
+ fs22.mkdirSync(taskDir, { recursive: true });
4138
4264
  }
4139
4265
  const todos = {
4140
4266
  phase,
@@ -4142,8 +4268,8 @@ function updateAgentTodos(projectName, taskSlug, phase, agent, items) {
4142
4268
  items,
4143
4269
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
4144
4270
  };
4145
- const todosPath = path23.join(taskDir, "agent-todos.json");
4146
- fs21.writeFileSync(todosPath, JSON.stringify(todos, null, 2));
4271
+ const todosPath = path24.join(taskDir, "agent-todos.json");
4272
+ fs22.writeFileSync(todosPath, JSON.stringify(todos, null, 2));
4147
4273
  return { success: true, message: `Updated ${items.length} todo items for task '${taskSlug}'.`, count: items.length };
4148
4274
  }
4149
4275
  var init_sessions = __esm({
@@ -4264,15 +4390,15 @@ var init_resources3 = __esm({
4264
4390
  });
4265
4391
 
4266
4392
  // src/mcp/prompts.ts
4267
- import * as path24 from "path";
4268
- import * as fs22 from "fs";
4393
+ import * as path25 from "path";
4394
+ import * as fs23 from "fs";
4269
4395
  function loadBaseProtocol2() {
4270
4396
  if (baseProtocolCache !== null) {
4271
4397
  return baseProtocolCache;
4272
4398
  }
4273
- const basePath = path24.join(getAgentCorePromptsDir(), "_base.md");
4274
- if (fs22.existsSync(basePath)) {
4275
- const content = fs22.readFileSync(basePath, "utf-8");
4399
+ const basePath = path25.join(getAgentCorePromptsDir(), "_base.md");
4400
+ if (fs23.existsSync(basePath)) {
4401
+ const content = fs23.readFileSync(basePath, "utf-8");
4276
4402
  baseProtocolCache = content.replace(/^---[\s\S]*?---\n*/, "");
4277
4403
  return baseProtocolCache;
4278
4404
  }
@@ -4335,15 +4461,15 @@ function renderPromptWithContext(content, args) {
4335
4461
  resolvedWorkspaceRoot = activeProject.sourcePath || activeProject.path || activeProject.dataPath;
4336
4462
  resolvedWorkspaceName = activeProject.name;
4337
4463
  if (activeProject.source === "global") {
4338
- const workspacesDir = path24.dirname(activeProject.dataPath);
4339
- resolvedRrceHome = path24.dirname(workspacesDir);
4464
+ const workspacesDir = path25.dirname(activeProject.dataPath);
4465
+ resolvedRrceHome = path25.dirname(workspacesDir);
4340
4466
  }
4341
4467
  } else {
4342
4468
  try {
4343
4469
  const workspaceRoot = detectWorkspaceRoot();
4344
- const workspaceName = path24.basename(workspaceRoot);
4345
- const globalWorkspacePath = path24.join(DEFAULT_RRCE_HOME, "workspaces", workspaceName);
4346
- if (fs22.existsSync(globalWorkspacePath)) {
4470
+ const workspaceName = path25.basename(workspaceRoot);
4471
+ const globalWorkspacePath = path25.join(DEFAULT_RRCE_HOME, "workspaces", workspaceName);
4472
+ if (fs23.existsSync(globalWorkspacePath)) {
4347
4473
  resolvedRrceData = globalWorkspacePath;
4348
4474
  resolvedWorkspaceRoot = workspaceRoot;
4349
4475
  resolvedWorkspaceName = workspaceName;
@@ -4555,7 +4681,8 @@ function registerToolHandlers(server) {
4555
4681
  type: "object",
4556
4682
  properties: {
4557
4683
  project: { type: "string", description: "Name of the project to index" },
4558
- force: { type: "boolean", description: "Force re-indexing of all files" }
4684
+ force: { type: "boolean", description: "Force re-indexing of all files" },
4685
+ clean: { type: "boolean", description: "Wipe existing index and rebuild from scratch" }
4559
4686
  },
4560
4687
  required: ["project"]
4561
4688
  }
@@ -4790,7 +4917,7 @@ function registerToolHandlers(server) {
4790
4917
  }
4791
4918
  case "index_knowledge": {
4792
4919
  const params = args;
4793
- const result = await indexKnowledge(params.project, params.force);
4920
+ const result = await indexKnowledge(params.project, params.force, params.clean);
4794
4921
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4795
4922
  }
4796
4923
  case "list_projects": {
@@ -5292,105 +5419,6 @@ var init_Header = __esm({
5292
5419
  }
5293
5420
  });
5294
5421
 
5295
- // src/lib/drift-service.ts
5296
- import * as fs23 from "fs";
5297
- import * as path25 from "path";
5298
- import * as crypto2 from "crypto";
5299
- var DriftService;
5300
- var init_drift_service = __esm({
5301
- "src/lib/drift-service.ts"() {
5302
- "use strict";
5303
- DriftService = class {
5304
- static CHECKSUM_FILENAME = ".rrce-checksums.json";
5305
- static calculateHash(filePath) {
5306
- const content = fs23.readFileSync(filePath);
5307
- return crypto2.createHash("md5").update(content).digest("hex");
5308
- }
5309
- static getManifestPath(projectPath) {
5310
- return path25.join(projectPath, this.CHECKSUM_FILENAME);
5311
- }
5312
- static loadManifest(projectPath) {
5313
- const manifestPath = this.getManifestPath(projectPath);
5314
- if (!fs23.existsSync(manifestPath)) {
5315
- return {};
5316
- }
5317
- try {
5318
- return JSON.parse(fs23.readFileSync(manifestPath, "utf8"));
5319
- } catch (e) {
5320
- return {};
5321
- }
5322
- }
5323
- static saveManifest(projectPath, manifest) {
5324
- const manifestPath = this.getManifestPath(projectPath);
5325
- fs23.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
5326
- }
5327
- /**
5328
- * Generates a manifest for the current state of files in the project
5329
- */
5330
- static generateManifest(projectPath, files) {
5331
- const manifest = {};
5332
- for (const file of files) {
5333
- const fullPath = path25.join(projectPath, file);
5334
- if (fs23.existsSync(fullPath)) {
5335
- const stats = fs23.statSync(fullPath);
5336
- manifest[file] = {
5337
- hash: this.calculateHash(fullPath),
5338
- mtime: stats.mtimeMs
5339
- };
5340
- }
5341
- }
5342
- return manifest;
5343
- }
5344
- /**
5345
- * Compares current files against the manifest to detect modifications
5346
- */
5347
- static detectModifiedFiles(projectPath) {
5348
- const manifest = this.loadManifest(projectPath);
5349
- const modifiedFiles = [];
5350
- for (const [relPath, entry] of Object.entries(manifest)) {
5351
- const fullPath = path25.join(projectPath, relPath);
5352
- if (!fs23.existsSync(fullPath)) {
5353
- continue;
5354
- }
5355
- const stats = fs23.statSync(fullPath);
5356
- if (stats.mtimeMs === entry.mtime) {
5357
- continue;
5358
- }
5359
- const currentHash = this.calculateHash(fullPath);
5360
- if (currentHash !== entry.hash) {
5361
- modifiedFiles.push(relPath);
5362
- }
5363
- }
5364
- return modifiedFiles;
5365
- }
5366
- /**
5367
- * Full drift check: version + modifications
5368
- */
5369
- static checkDrift(projectPath, currentVersion, runningVersion) {
5370
- const modifiedFiles = this.detectModifiedFiles(projectPath);
5371
- let type = "none";
5372
- let hasDrift = false;
5373
- if (currentVersion !== runningVersion) {
5374
- hasDrift = true;
5375
- type = "version";
5376
- } else if (modifiedFiles.length > 0) {
5377
- hasDrift = true;
5378
- type = "modified";
5379
- }
5380
- return {
5381
- hasDrift,
5382
- type,
5383
- modifiedFiles,
5384
- version: {
5385
- current: currentVersion || "0.0.0",
5386
- running: runningVersion
5387
- }
5388
- };
5389
- }
5390
- };
5391
- }
5392
- });
5393
-
5394
5422
  // src/mcp/ui/ConfigContext.tsx
5395
5423
  var ConfigContext_exports = {};
5396
5424
  __export(ConfigContext_exports, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",