rrce-workflow 0.2.15 → 0.2.17

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 +200 -245
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -49,18 +49,6 @@ function getWorkspaceName(workspaceRoot) {
49
49
  function getRRCEHome() {
50
50
  return RRCE_HOME;
51
51
  }
52
- function listGlobalProjects(excludeWorkspace) {
53
- const workspacesDir = path.join(RRCE_HOME, "workspaces");
54
- if (!fs.existsSync(workspacesDir)) {
55
- return [];
56
- }
57
- try {
58
- const entries = fs.readdirSync(workspacesDir, { withFileTypes: true });
59
- return entries.filter((entry) => entry.isDirectory() && entry.name !== excludeWorkspace).map((entry) => entry.name);
60
- } catch {
61
- return [];
62
- }
63
- }
64
52
  function getLocalWorkspacePath(workspaceRoot) {
65
53
  return path.join(workspaceRoot, ".rrce-workflow");
66
54
  }
@@ -145,115 +133,9 @@ function getEffectiveRRCEHome(workspaceRoot) {
145
133
  return getDefaultRRCEHome();
146
134
  }
147
135
 
148
- // src/commands/wizard/setup-flow.ts
149
- import { group, select, multiselect, confirm, spinner, note, outro, cancel, isCancel as isCancel2 } from "@clack/prompts";
150
- import pc2 from "picocolors";
151
- import * as fs7 from "fs";
152
- import * as path7 from "path";
153
-
154
- // src/lib/prompts.ts
136
+ // src/lib/detection.ts
155
137
  import * as fs2 from "fs";
156
138
  import * as path2 from "path";
157
- import { fileURLToPath } from "url";
158
- import matter from "gray-matter";
159
-
160
- // src/types/prompt.ts
161
- import { z } from "zod";
162
- var PromptArgSchema = z.object({
163
- name: z.string(),
164
- default: z.string().optional(),
165
- prompt: z.string().optional()
166
- });
167
- var AutoIdentitySchema = z.object({
168
- user: z.string(),
169
- model: z.string()
170
- });
171
- var PromptFrontmatterSchema = z.object({
172
- name: z.string(),
173
- description: z.string(),
174
- "argument-hint": z.union([z.string(), z.array(z.string())]).optional(),
175
- tools: z.array(z.string()).optional(),
176
- "required-args": z.array(PromptArgSchema).optional(),
177
- "optional-args": z.array(PromptArgSchema).optional(),
178
- "auto-identity": AutoIdentitySchema.optional()
179
- });
180
-
181
- // src/lib/prompts.ts
182
- var __filename = fileURLToPath(import.meta.url);
183
- var __dirname = path2.dirname(__filename);
184
- function parsePromptFile(filePath) {
185
- try {
186
- const fileContent = fs2.readFileSync(filePath, "utf-8");
187
- const { data, content } = matter(fileContent);
188
- const parsed = PromptFrontmatterSchema.safeParse(data);
189
- if (!parsed.success) {
190
- console.error(`Failed to parse frontmatter in ${filePath}:`, parsed.error);
191
- return null;
192
- }
193
- return {
194
- frontmatter: parsed.data,
195
- content: content.trim(),
196
- filePath
197
- };
198
- } catch (error) {
199
- console.error(`Error reading prompt file ${filePath}:`, error);
200
- return null;
201
- }
202
- }
203
- function loadPromptsFromDir(dirPath) {
204
- if (!fs2.existsSync(dirPath)) {
205
- return [];
206
- }
207
- const files = fs2.readdirSync(dirPath).filter((f) => f.endsWith(".md"));
208
- const prompts = [];
209
- for (const file of files) {
210
- const prompt = parsePromptFile(path2.join(dirPath, file));
211
- if (prompt) {
212
- prompts.push(prompt);
213
- }
214
- }
215
- return prompts;
216
- }
217
- function getAgentCoreDir() {
218
- return path2.join(__dirname, "..", "..", "agent-core");
219
- }
220
- function getAgentCorePromptsDir() {
221
- return path2.join(getAgentCoreDir(), "prompts");
222
- }
223
-
224
- // src/commands/wizard/utils.ts
225
- import * as fs3 from "fs";
226
- import * as path3 from "path";
227
- function copyPromptsToDir(prompts, targetDir, extension) {
228
- for (const prompt of prompts) {
229
- const baseName = path3.basename(prompt.filePath, ".md");
230
- const targetName = baseName + extension;
231
- const targetPath = path3.join(targetDir, targetName);
232
- const content = fs3.readFileSync(prompt.filePath, "utf-8");
233
- fs3.writeFileSync(targetPath, content);
234
- }
235
- }
236
- function copyDirRecursive(src, dest) {
237
- const entries = fs3.readdirSync(src, { withFileTypes: true });
238
- for (const entry of entries) {
239
- const srcPath = path3.join(src, entry.name);
240
- const destPath = path3.join(dest, entry.name);
241
- if (entry.isDirectory()) {
242
- ensureDir(destPath);
243
- copyDirRecursive(srcPath, destPath);
244
- } else {
245
- fs3.copyFileSync(srcPath, destPath);
246
- }
247
- }
248
- }
249
-
250
- // src/commands/wizard/vscode.ts
251
- import * as fs5 from "fs";
252
- import * as path5 from "path";
253
-
254
- // src/lib/detection.ts
255
- import * as fs4 from "fs";
256
- import * as path4 from "path";
257
139
  function scanForProjects(options = {}) {
258
140
  const { excludeWorkspace, workspacePath, scanSiblings = true } = options;
259
141
  const projects = [];
@@ -278,29 +160,29 @@ function scanForProjects(options = {}) {
278
160
  }
279
161
  function scanGlobalStorage(excludeWorkspace) {
280
162
  const rrceHome = getDefaultRRCEHome();
281
- const workspacesDir = path4.join(rrceHome, "workspaces");
163
+ const workspacesDir = path2.join(rrceHome, "workspaces");
282
164
  const projects = [];
283
- if (!fs4.existsSync(workspacesDir)) {
165
+ if (!fs2.existsSync(workspacesDir)) {
284
166
  return projects;
285
167
  }
286
168
  try {
287
- const entries = fs4.readdirSync(workspacesDir, { withFileTypes: true });
169
+ const entries = fs2.readdirSync(workspacesDir, { withFileTypes: true });
288
170
  for (const entry of entries) {
289
171
  if (!entry.isDirectory()) continue;
290
172
  if (entry.name === excludeWorkspace) continue;
291
- const projectDataPath = path4.join(workspacesDir, entry.name);
292
- const knowledgePath = path4.join(projectDataPath, "knowledge");
293
- const refsPath = path4.join(projectDataPath, "refs");
294
- const tasksPath = path4.join(projectDataPath, "tasks");
173
+ const projectDataPath = path2.join(workspacesDir, entry.name);
174
+ const knowledgePath = path2.join(projectDataPath, "knowledge");
175
+ const refsPath = path2.join(projectDataPath, "refs");
176
+ const tasksPath = path2.join(projectDataPath, "tasks");
295
177
  projects.push({
296
178
  name: entry.name,
297
179
  path: projectDataPath,
298
180
  // For global projects, path is the data path
299
181
  dataPath: projectDataPath,
300
182
  source: "global",
301
- knowledgePath: fs4.existsSync(knowledgePath) ? knowledgePath : void 0,
302
- refsPath: fs4.existsSync(refsPath) ? refsPath : void 0,
303
- tasksPath: fs4.existsSync(tasksPath) ? tasksPath : void 0
183
+ knowledgePath: fs2.existsSync(knowledgePath) ? knowledgePath : void 0,
184
+ refsPath: fs2.existsSync(refsPath) ? refsPath : void 0,
185
+ tasksPath: fs2.existsSync(tasksPath) ? tasksPath : void 0
304
186
  });
305
187
  }
306
188
  } catch {
@@ -308,32 +190,32 @@ function scanGlobalStorage(excludeWorkspace) {
308
190
  return projects;
309
191
  }
310
192
  function scanSiblingDirectories(workspacePath, excludeWorkspace) {
311
- const parentDir = path4.dirname(workspacePath);
193
+ const parentDir = path2.dirname(workspacePath);
312
194
  const projects = [];
313
195
  try {
314
- const entries = fs4.readdirSync(parentDir, { withFileTypes: true });
196
+ const entries = fs2.readdirSync(parentDir, { withFileTypes: true });
315
197
  for (const entry of entries) {
316
198
  if (!entry.isDirectory()) continue;
317
- const projectPath = path4.join(parentDir, entry.name);
199
+ const projectPath = path2.join(parentDir, entry.name);
318
200
  if (projectPath === workspacePath) continue;
319
201
  if (entry.name === excludeWorkspace) continue;
320
- const configPath = path4.join(projectPath, ".rrce-workflow", "config.yaml");
321
- if (!fs4.existsSync(configPath)) continue;
202
+ const configPath = path2.join(projectPath, ".rrce-workflow", "config.yaml");
203
+ if (!fs2.existsSync(configPath)) continue;
322
204
  const config = parseWorkspaceConfig(configPath);
323
205
  if (!config) continue;
324
- const dataPath = path4.join(projectPath, ".rrce-workflow");
325
- const knowledgePath = path4.join(dataPath, "knowledge");
326
- const refsPath = path4.join(dataPath, "refs");
327
- const tasksPath = path4.join(dataPath, "tasks");
206
+ const dataPath = path2.join(projectPath, ".rrce-workflow");
207
+ const knowledgePath = path2.join(dataPath, "knowledge");
208
+ const refsPath = path2.join(dataPath, "refs");
209
+ const tasksPath = path2.join(dataPath, "tasks");
328
210
  projects.push({
329
211
  name: config.name || entry.name,
330
212
  path: projectPath,
331
213
  dataPath,
332
214
  source: "sibling",
333
215
  storageMode: config.storageMode,
334
- knowledgePath: fs4.existsSync(knowledgePath) ? knowledgePath : void 0,
335
- refsPath: fs4.existsSync(refsPath) ? refsPath : void 0,
336
- tasksPath: fs4.existsSync(tasksPath) ? tasksPath : void 0
216
+ knowledgePath: fs2.existsSync(knowledgePath) ? knowledgePath : void 0,
217
+ refsPath: fs2.existsSync(refsPath) ? refsPath : void 0,
218
+ tasksPath: fs2.existsSync(tasksPath) ? tasksPath : void 0
337
219
  });
338
220
  }
339
221
  } catch {
@@ -342,7 +224,7 @@ function scanSiblingDirectories(workspacePath, excludeWorkspace) {
342
224
  }
343
225
  function parseWorkspaceConfig(configPath) {
344
226
  try {
345
- const content = fs4.readFileSync(configPath, "utf-8");
227
+ const content = fs2.readFileSync(configPath, "utf-8");
346
228
  const nameMatch = content.match(/name:\s*["']?([^"'\n]+)["']?/);
347
229
  const modeMatch = content.match(/mode:\s*(global|workspace|both)/);
348
230
  const linkedProjects = [];
@@ -357,7 +239,7 @@ function parseWorkspaceConfig(configPath) {
357
239
  }
358
240
  }
359
241
  return {
360
- name: nameMatch?.[1]?.trim() || path4.basename(path4.dirname(path4.dirname(configPath))),
242
+ name: nameMatch?.[1]?.trim() || path2.basename(path2.dirname(path2.dirname(configPath))),
361
243
  storageMode: modeMatch?.[1] || "global",
362
244
  linkedProjects: linkedProjects.length > 0 ? linkedProjects : void 0
363
245
  };
@@ -365,44 +247,112 @@ function parseWorkspaceConfig(configPath) {
365
247
  return null;
366
248
  }
367
249
  }
368
- function getProjectDisplayLabel(project) {
369
- switch (project.source) {
370
- case "global":
371
- return `global: ~/.rrce-workflow/workspaces/${project.name}`;
372
- case "sibling":
373
- return `sibling: ${project.path}/.rrce-workflow`;
374
- default:
375
- return project.dataPath;
250
+
251
+ // src/commands/wizard/setup-flow.ts
252
+ import { group, select, multiselect, confirm, spinner, note, outro, cancel, isCancel as isCancel2 } from "@clack/prompts";
253
+ import pc2 from "picocolors";
254
+ import * as fs7 from "fs";
255
+ import * as path7 from "path";
256
+
257
+ // src/lib/prompts.ts
258
+ import * as fs3 from "fs";
259
+ import * as path3 from "path";
260
+ import { fileURLToPath } from "url";
261
+ import matter from "gray-matter";
262
+
263
+ // src/types/prompt.ts
264
+ import { z } from "zod";
265
+ var PromptArgSchema = z.object({
266
+ name: z.string(),
267
+ default: z.string().optional(),
268
+ prompt: z.string().optional()
269
+ });
270
+ var AutoIdentitySchema = z.object({
271
+ user: z.string(),
272
+ model: z.string()
273
+ });
274
+ var PromptFrontmatterSchema = z.object({
275
+ name: z.string(),
276
+ description: z.string(),
277
+ "argument-hint": z.union([z.string(), z.array(z.string())]).optional(),
278
+ tools: z.array(z.string()).optional(),
279
+ "required-args": z.array(PromptArgSchema).optional(),
280
+ "optional-args": z.array(PromptArgSchema).optional(),
281
+ "auto-identity": AutoIdentitySchema.optional()
282
+ });
283
+
284
+ // src/lib/prompts.ts
285
+ var __filename = fileURLToPath(import.meta.url);
286
+ var __dirname = path3.dirname(__filename);
287
+ function parsePromptFile(filePath) {
288
+ try {
289
+ const fileContent = fs3.readFileSync(filePath, "utf-8");
290
+ const { data, content } = matter(fileContent);
291
+ const parsed = PromptFrontmatterSchema.safeParse(data);
292
+ if (!parsed.success) {
293
+ console.error(`Failed to parse frontmatter in ${filePath}:`, parsed.error);
294
+ return null;
295
+ }
296
+ return {
297
+ frontmatter: parsed.data,
298
+ content: content.trim(),
299
+ filePath
300
+ };
301
+ } catch (error) {
302
+ console.error(`Error reading prompt file ${filePath}:`, error);
303
+ return null;
376
304
  }
377
305
  }
378
- function getProjectFolders(project) {
379
- const folders = [];
380
- if (project.knowledgePath) {
381
- folders.push({
382
- path: project.knowledgePath,
383
- type: "knowledge",
384
- displayName: `\u{1F4DA} ${project.name} (knowledge)`
385
- });
306
+ function loadPromptsFromDir(dirPath) {
307
+ if (!fs3.existsSync(dirPath)) {
308
+ return [];
386
309
  }
387
- if (project.refsPath) {
388
- folders.push({
389
- path: project.refsPath,
390
- type: "refs",
391
- displayName: `\u{1F4CE} ${project.name} (refs)`
392
- });
310
+ const files = fs3.readdirSync(dirPath).filter((f) => f.endsWith(".md"));
311
+ const prompts = [];
312
+ for (const file of files) {
313
+ const prompt = parsePromptFile(path3.join(dirPath, file));
314
+ if (prompt) {
315
+ prompts.push(prompt);
316
+ }
393
317
  }
394
- if (project.tasksPath) {
395
- folders.push({
396
- path: project.tasksPath,
397
- type: "tasks",
398
- displayName: `\u{1F4CB} ${project.name} (tasks)`
399
- });
318
+ return prompts;
319
+ }
320
+ function getAgentCoreDir() {
321
+ return path3.join(__dirname, "..", "..", "agent-core");
322
+ }
323
+ function getAgentCorePromptsDir() {
324
+ return path3.join(getAgentCoreDir(), "prompts");
325
+ }
326
+
327
+ // src/commands/wizard/utils.ts
328
+ import * as fs4 from "fs";
329
+ import * as path4 from "path";
330
+ function copyPromptsToDir(prompts, targetDir, extension) {
331
+ for (const prompt of prompts) {
332
+ const baseName = path4.basename(prompt.filePath, ".md");
333
+ const targetName = baseName + extension;
334
+ const targetPath = path4.join(targetDir, targetName);
335
+ const content = fs4.readFileSync(prompt.filePath, "utf-8");
336
+ fs4.writeFileSync(targetPath, content);
337
+ }
338
+ }
339
+ function copyDirRecursive(src, dest) {
340
+ const entries = fs4.readdirSync(src, { withFileTypes: true });
341
+ for (const entry of entries) {
342
+ const srcPath = path4.join(src, entry.name);
343
+ const destPath = path4.join(dest, entry.name);
344
+ if (entry.isDirectory()) {
345
+ ensureDir(destPath);
346
+ copyDirRecursive(srcPath, destPath);
347
+ } else {
348
+ fs4.copyFileSync(srcPath, destPath);
349
+ }
400
350
  }
401
- return folders;
402
351
  }
403
352
 
404
353
  // src/commands/wizard/vscode.ts
405
- var REFERENCE_GROUP_PREFIX = "\u{1F4C1} References";
354
+ import * as fs5 from "fs";
355
+ import * as path5 from "path";
406
356
  function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, customGlobalPath) {
407
357
  const workspaceFilePath = path5.join(workspacePath, `${workspaceName}.code-workspace`);
408
358
  let workspace;
@@ -419,60 +369,47 @@ function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, c
419
369
  if (!workspace.settings) {
420
370
  workspace.settings = {};
421
371
  }
422
- const existingNonReferencesFolders = workspace.folders.filter(
423
- (f) => f.path === "." || !f.name?.includes(REFERENCE_GROUP_PREFIX) && !f.name?.startsWith("\u{1F4DA}") && !f.name?.startsWith("\u{1F4CE}") && !f.name?.startsWith("\u{1F4CB}")
372
+ workspace.folders = workspace.folders.filter(
373
+ (f) => f.path === "." || !f.name?.startsWith("\u{1F4C1}") && !f.name?.startsWith("\u{1F4DA}") && !f.name?.startsWith("\u{1F4CE}") && !f.name?.startsWith("\u{1F4CB}")
424
374
  );
425
- workspace.folders = [];
426
- const mainFolder = {
427
- path: ".",
428
- name: `\u{1F3E0} ${workspaceName} (workspace)`
429
- };
430
- workspace.folders.push(mainFolder);
431
- for (const folder of existingNonReferencesFolders) {
432
- if (folder.path !== ".") {
433
- workspace.folders.push(folder);
434
- }
375
+ const mainFolderIndex = workspace.folders.findIndex((f) => f.path === ".");
376
+ if (mainFolderIndex === -1) {
377
+ workspace.folders.unshift({
378
+ path: ".",
379
+ name: `\u{1F3E0} ${workspaceName} (workspace)`
380
+ });
381
+ } else {
382
+ workspace.folders[mainFolderIndex] = {
383
+ path: ".",
384
+ name: `\u{1F3E0} ${workspaceName} (workspace)`
385
+ };
435
386
  }
436
387
  const referenceFolderPaths = [];
437
388
  const isDetectedProjects = linkedProjects.length > 0 && typeof linkedProjects[0] === "object";
438
389
  if (isDetectedProjects) {
439
390
  const projects = linkedProjects;
440
391
  for (const project of projects) {
441
- const folders = getProjectFolders(project);
442
392
  const sourceLabel = project.source === "global" ? "global" : "local";
443
- for (const folder of folders) {
444
- referenceFolderPaths.push(folder.path);
445
- const existingIndex = workspace.folders.findIndex((f) => f.path === folder.path);
446
- if (existingIndex === -1) {
447
- workspace.folders.push({
448
- path: folder.path,
449
- name: `${folder.displayName} [${sourceLabel}]`
450
- });
451
- }
393
+ const folderPath = project.dataPath;
394
+ if (fs5.existsSync(folderPath)) {
395
+ referenceFolderPaths.push(folderPath);
396
+ workspace.folders.push({
397
+ path: folderPath,
398
+ name: `\u{1F4C1} ${project.name} [${sourceLabel}]`
399
+ });
452
400
  }
453
401
  }
454
402
  } else {
455
403
  const projectNames = linkedProjects;
456
404
  const rrceHome = customGlobalPath || getRRCEHome();
457
405
  for (const projectName of projectNames) {
458
- const projectDataPath = path5.join(rrceHome, "workspaces", projectName);
459
- const folderTypes = [
460
- { subpath: "knowledge", icon: "\u{1F4DA}", type: "knowledge" },
461
- { subpath: "refs", icon: "\u{1F4CE}", type: "refs" },
462
- { subpath: "tasks", icon: "\u{1F4CB}", type: "tasks" }
463
- ];
464
- for (const { subpath, icon, type } of folderTypes) {
465
- const folderPath = path5.join(projectDataPath, subpath);
466
- if (fs5.existsSync(folderPath)) {
467
- referenceFolderPaths.push(folderPath);
468
- const existingIndex = workspace.folders.findIndex((f) => f.path === folderPath);
469
- if (existingIndex === -1) {
470
- workspace.folders.push({
471
- path: folderPath,
472
- name: `${icon} ${projectName} (${type}) [global]`
473
- });
474
- }
475
- }
406
+ const folderPath = path5.join(rrceHome, "workspaces", projectName);
407
+ if (fs5.existsSync(folderPath)) {
408
+ referenceFolderPaths.push(folderPath);
409
+ workspace.folders.push({
410
+ path: folderPath,
411
+ name: `\u{1F4C1} ${projectName} [global]`
412
+ });
476
413
  }
477
414
  }
478
415
  }
@@ -482,12 +419,17 @@ function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, c
482
419
  readonlyPatterns[`${folderPath}/**`] = true;
483
420
  }
484
421
  const existingReadonly = workspace.settings["files.readonlyInclude"] || {};
422
+ const cleanedReadonly = {};
423
+ for (const [pattern, value] of Object.entries(existingReadonly)) {
424
+ if (!pattern.includes(".rrce-workflow") && !pattern.includes("rrce-workflow/workspaces")) {
425
+ cleanedReadonly[pattern] = value;
426
+ }
427
+ }
485
428
  workspace.settings["files.readonlyInclude"] = {
486
- ...existingReadonly,
429
+ ...cleanedReadonly,
487
430
  ...readonlyPatterns
488
431
  };
489
432
  }
490
- workspace.settings["explorer.sortOrder"] = workspace.settings["explorer.sortOrder"] || "default";
491
433
  fs5.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
492
434
  }
493
435
 
@@ -647,10 +589,13 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
647
589
  }
648
590
  return multiselect({
649
591
  message: "Link knowledge from other projects?",
650
- options: existingProjects.map((name) => ({
651
- value: name,
652
- label: name,
653
- hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
592
+ options: existingProjects.map((project) => ({
593
+ value: `${project.name}:${project.source}`,
594
+ // Unique key
595
+ label: `${project.name} ${pc2.dim(`(${project.source})`)}`,
596
+ hint: pc2.dim(
597
+ project.source === "global" ? `~/.rrce-workflow/workspaces/${project.name}` : project.dataPath
598
+ )
654
599
  })),
655
600
  required: false
656
601
  });
@@ -686,7 +631,7 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
686
631
  globalPath: customGlobalPath,
687
632
  tools: config.tools,
688
633
  linkedProjects: config.linkedProjects
689
- }, workspacePath, workspaceName);
634
+ }, workspacePath, workspaceName, existingProjects);
690
635
  s.stop("Configuration generated");
691
636
  const dataPaths = getDataPaths(
692
637
  config.storageMode,
@@ -782,7 +727,7 @@ Please choose a custom path instead.`,
782
727
  }
783
728
  return customPath;
784
729
  }
785
- async function generateConfiguration(config, workspacePath, workspaceName) {
730
+ async function generateConfiguration(config, workspacePath, workspaceName, allProjects = []) {
786
731
  const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
787
732
  for (const dataPath of dataPaths) {
788
733
  ensureDir(dataPath);
@@ -836,7 +781,10 @@ linked_projects:
836
781
  }
837
782
  fs7.writeFileSync(workspaceConfigPath, configContent);
838
783
  if (config.tools.includes("copilot") || config.linkedProjects.length > 0) {
839
- generateVSCodeWorkspace(workspacePath, workspaceName, config.linkedProjects, config.globalPath);
784
+ const selectedProjects = allProjects.filter(
785
+ (p) => config.linkedProjects.includes(`${p.name}:${p.source}`)
786
+ );
787
+ generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, config.globalPath);
840
788
  }
841
789
  }
842
790
  function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
@@ -858,13 +806,12 @@ function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
858
806
  import { multiselect as multiselect2, spinner as spinner2, note as note2, outro as outro2, cancel as cancel2, isCancel as isCancel3 } from "@clack/prompts";
859
807
  import pc3 from "picocolors";
860
808
  import * as fs8 from "fs";
861
- async function runLinkProjectsFlow(workspacePath, workspaceName, existingProjects) {
862
- const detectedProjects = scanForProjects({
809
+ async function runLinkProjectsFlow(workspacePath, workspaceName) {
810
+ const projects = scanForProjects({
863
811
  excludeWorkspace: workspaceName,
864
812
  workspacePath,
865
813
  scanSiblings: true
866
814
  });
867
- const projects = existingProjects ? existingProjects.map((name) => ({ name, source: "global" })) : detectedProjects;
868
815
  if (projects.length === 0) {
869
816
  outro2(pc3.yellow("No other projects found. Try setting up another project first."));
870
817
  return;
@@ -873,9 +820,12 @@ async function runLinkProjectsFlow(workspacePath, workspaceName, existingProject
873
820
  const linkedProjects = await multiselect2({
874
821
  message: "Select projects to link:",
875
822
  options: projects.map((project) => ({
876
- value: project.name,
877
- label: project.name,
878
- hint: pc3.dim(getProjectDisplayLabel(project))
823
+ value: `${project.name}:${project.source}`,
824
+ // Unique key
825
+ label: `${project.name} ${pc3.dim(`(${project.source})`)}`,
826
+ hint: pc3.dim(
827
+ project.source === "global" ? `~/.rrce-workflow/workspaces/${project.name}` : project.dataPath
828
+ )
879
829
  })),
880
830
  required: true
881
831
  });
@@ -883,12 +833,14 @@ async function runLinkProjectsFlow(workspacePath, workspaceName, existingProject
883
833
  cancel2("Cancelled.");
884
834
  process.exit(0);
885
835
  }
886
- const selectedProjectNames = linkedProjects;
887
- if (selectedProjectNames.length === 0) {
836
+ const selectedKeys = linkedProjects;
837
+ if (selectedKeys.length === 0) {
888
838
  outro2("No projects selected.");
889
839
  return;
890
840
  }
891
- const selectedProjects = projects.filter((p) => selectedProjectNames.includes(p.name));
841
+ const selectedProjects = projects.filter(
842
+ (p) => selectedKeys.includes(`${p.name}:${p.source}`)
843
+ );
892
844
  const s = spinner2();
893
845
  s.start("Linking projects");
894
846
  const configFilePath = getConfigPath(workspacePath);
@@ -901,9 +853,10 @@ async function runLinkProjectsFlow(workspacePath, workspaceName, existingProject
901
853
  while (insertIndex < lines.length && lines[insertIndex]?.startsWith(" - ")) {
902
854
  insertIndex++;
903
855
  }
904
- for (const name of selectedProjectNames) {
905
- if (!configContent.includes(` - ${name}`)) {
906
- lines.splice(insertIndex, 0, ` - ${name}`);
856
+ for (const project of selectedProjects) {
857
+ const entry = ` - ${project.name}:${project.source}`;
858
+ if (!configContent.includes(entry)) {
859
+ lines.splice(insertIndex, 0, entry);
907
860
  insertIndex++;
908
861
  }
909
862
  }
@@ -913,8 +866,8 @@ async function runLinkProjectsFlow(workspacePath, workspaceName, existingProject
913
866
  configContent += `
914
867
  linked_projects:
915
868
  `;
916
- selectedProjectNames.forEach((name) => {
917
- configContent += ` - ${name}
869
+ selectedProjects.forEach((project) => {
870
+ configContent += ` - ${project.name}:${project.source}
918
871
  `;
919
872
  });
920
873
  }
@@ -926,12 +879,10 @@ linked_projects:
926
879
  `Linked projects:`,
927
880
  ...selectedProjects.map((p) => ` \u2713 ${p.name} ${pc3.dim(`(${p.source})`)}`),
928
881
  ``,
929
- `Workspace file: ${pc3.cyan(workspaceFile)}`,
930
- ``,
931
- pc3.dim("Includes: knowledge, refs, tasks folders")
882
+ `Workspace file: ${pc3.cyan(workspaceFile)}`
932
883
  ];
933
884
  note2(summary.join("\n"), "Link Summary");
934
- outro2(pc3.green(`\u2713 Projects linked! Open ${pc3.bold(workspaceFile)} in VSCode to access linked knowledge.`));
885
+ outro2(pc3.green(`\u2713 Projects linked! Open ${pc3.bold(workspaceFile)} in VSCode to access linked data.`));
935
886
  }
936
887
 
937
888
  // src/commands/wizard/sync-flow.ts
@@ -1093,7 +1044,11 @@ async function runWizard() {
1093
1044
  Workspace: ${pc6.bold(workspaceName)}`,
1094
1045
  "Context"
1095
1046
  );
1096
- const existingProjects = listGlobalProjects(workspaceName);
1047
+ const detectedProjects = scanForProjects({
1048
+ excludeWorkspace: workspaceName,
1049
+ workspacePath,
1050
+ scanSiblings: true
1051
+ });
1097
1052
  const configFilePath = getConfigPath(workspacePath);
1098
1053
  const isAlreadyConfigured = fs11.existsSync(configFilePath);
1099
1054
  let currentStorageMode = null;
@@ -1109,11 +1064,11 @@ Workspace: ${pc6.bold(workspaceName)}`,
1109
1064
  const hasLocalData = fs11.existsSync(localDataPath);
1110
1065
  if (isAlreadyConfigured) {
1111
1066
  const menuOptions = [];
1112
- if (existingProjects.length > 0) {
1067
+ if (detectedProjects.length > 0) {
1113
1068
  menuOptions.push({
1114
1069
  value: "link",
1115
1070
  label: "Link other project knowledge",
1116
- hint: `${existingProjects.length} projects available`
1071
+ hint: `${detectedProjects.length} projects detected (global + sibling)`
1117
1072
  });
1118
1073
  }
1119
1074
  if (currentStorageMode === "workspace" && hasLocalData) {
@@ -1134,7 +1089,7 @@ Workspace: ${pc6.bold(workspaceName)}`,
1134
1089
  process.exit(0);
1135
1090
  }
1136
1091
  if (action === "link") {
1137
- await runLinkProjectsFlow(workspacePath, workspaceName, existingProjects);
1092
+ await runLinkProjectsFlow(workspacePath, workspaceName);
1138
1093
  return;
1139
1094
  }
1140
1095
  if (action === "sync-global") {
@@ -1146,7 +1101,7 @@ Workspace: ${pc6.bold(workspaceName)}`,
1146
1101
  return;
1147
1102
  }
1148
1103
  }
1149
- await runSetupFlow(workspacePath, workspaceName, existingProjects);
1104
+ await runSetupFlow(workspacePath, workspaceName, detectedProjects);
1150
1105
  }
1151
1106
 
1152
1107
  // src/commands/selector.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",