rrce-workflow 0.2.18 → 0.2.20

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 +310 -134
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/commands/wizard/index.ts
2
2
  import { intro, select as select2, spinner as spinner5, note as note5, outro as outro5, isCancel as isCancel5 } from "@clack/prompts";
3
- import pc5 from "picocolors";
4
- import * as fs10 from "fs";
3
+ import pc6 from "picocolors";
4
+ import * as fs11 from "fs";
5
5
 
6
6
  // src/lib/git.ts
7
7
  import { execSync } from "child_process";
@@ -136,24 +136,42 @@ function getEffectiveRRCEHome(workspaceRoot) {
136
136
  // src/lib/detection.ts
137
137
  import * as fs2 from "fs";
138
138
  import * as path2 from "path";
139
+ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
140
+ "node_modules",
141
+ ".git",
142
+ ".cache",
143
+ ".npm",
144
+ ".yarn",
145
+ ".pnpm",
146
+ ".local",
147
+ ".config",
148
+ ".vscode",
149
+ ".vscode-server",
150
+ "Library",
151
+ "Applications",
152
+ ".Trash",
153
+ "snap",
154
+ ".cargo",
155
+ ".rustup",
156
+ ".go",
157
+ ".docker"
158
+ ]);
139
159
  function scanForProjects(options = {}) {
140
- const { excludeWorkspace, workspacePath, scanSiblings = true } = options;
160
+ const { excludeWorkspace, workspacePath } = options;
141
161
  const projects = [];
142
162
  const seenPaths = /* @__PURE__ */ new Set();
143
163
  const globalProjects = scanGlobalStorage(excludeWorkspace);
144
164
  for (const project of globalProjects) {
145
- if (!seenPaths.has(project.path)) {
146
- seenPaths.add(project.path);
165
+ if (!seenPaths.has(project.dataPath)) {
166
+ seenPaths.add(project.dataPath);
147
167
  projects.push(project);
148
168
  }
149
169
  }
150
- if (scanSiblings && workspacePath) {
151
- const siblingProjects = scanSiblingDirectories(workspacePath, excludeWorkspace);
152
- for (const project of siblingProjects) {
153
- if (!seenPaths.has(project.path)) {
154
- seenPaths.add(project.path);
155
- projects.push(project);
156
- }
170
+ const homeProjects = scanHomeDirectory(workspacePath);
171
+ for (const project of homeProjects) {
172
+ if (!seenPaths.has(project.dataPath)) {
173
+ seenPaths.add(project.dataPath);
174
+ projects.push(project);
157
175
  }
158
176
  }
159
177
  return projects;
@@ -189,44 +207,56 @@ function scanGlobalStorage(excludeWorkspace) {
189
207
  }
190
208
  return projects;
191
209
  }
192
- function scanSiblingDirectories(workspacePath, excludeWorkspace) {
193
- const parentDir = path2.dirname(workspacePath);
210
+ function scanHomeDirectory(excludePath) {
211
+ const home = process.env.HOME;
212
+ if (!home) return [];
194
213
  const projects = [];
195
- try {
196
- const entries = fs2.readdirSync(parentDir, { withFileTypes: true });
197
- for (const entry of entries) {
198
- if (!entry.isDirectory()) continue;
199
- const projectPath = path2.join(parentDir, entry.name);
200
- if (projectPath === workspacePath) continue;
201
- if (entry.name === excludeWorkspace) continue;
202
- const configPath = path2.join(projectPath, ".rrce-workflow", "config.yaml");
203
- if (!fs2.existsSync(configPath)) continue;
204
- const config = parseWorkspaceConfig(configPath);
205
- if (!config) continue;
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");
210
- projects.push({
211
- name: config.name || entry.name,
212
- path: projectPath,
213
- dataPath,
214
- source: "sibling",
215
- storageMode: config.storageMode,
216
- knowledgePath: fs2.existsSync(knowledgePath) ? knowledgePath : void 0,
217
- refsPath: fs2.existsSync(refsPath) ? refsPath : void 0,
218
- tasksPath: fs2.existsSync(tasksPath) ? tasksPath : void 0
219
- });
214
+ const maxDepth = 5;
215
+ function scanDir(dirPath, depth) {
216
+ if (depth > maxDepth) return;
217
+ try {
218
+ const entries = fs2.readdirSync(dirPath, { withFileTypes: true });
219
+ for (const entry of entries) {
220
+ if (!entry.isDirectory()) continue;
221
+ const fullPath = path2.join(dirPath, entry.name);
222
+ if (excludePath && fullPath === excludePath) continue;
223
+ if (entry.name === ".rrce-workflow") {
224
+ const configPath = path2.join(fullPath, "config.yaml");
225
+ if (fs2.existsSync(configPath)) {
226
+ const projectPath = dirPath;
227
+ const projectName = path2.basename(projectPath);
228
+ const config = parseWorkspaceConfig(configPath);
229
+ const knowledgePath = path2.join(fullPath, "knowledge");
230
+ const refsPath = path2.join(fullPath, "refs");
231
+ const tasksPath = path2.join(fullPath, "tasks");
232
+ projects.push({
233
+ name: config?.name || projectName,
234
+ path: projectPath,
235
+ dataPath: fullPath,
236
+ source: "local",
237
+ storageMode: config?.storageMode,
238
+ knowledgePath: fs2.existsSync(knowledgePath) ? knowledgePath : void 0,
239
+ refsPath: fs2.existsSync(refsPath) ? refsPath : void 0,
240
+ tasksPath: fs2.existsSync(tasksPath) ? tasksPath : void 0
241
+ });
242
+ }
243
+ continue;
244
+ }
245
+ if (SKIP_DIRECTORIES.has(entry.name)) continue;
246
+ if (entry.name.startsWith(".") && entry.name !== ".rrce-workflow") continue;
247
+ scanDir(fullPath, depth + 1);
248
+ }
249
+ } catch {
220
250
  }
221
- } catch {
222
251
  }
252
+ scanDir(home, 0);
223
253
  return projects;
224
254
  }
225
255
  function parseWorkspaceConfig(configPath) {
226
256
  try {
227
257
  const content = fs2.readFileSync(configPath, "utf-8");
228
258
  const nameMatch = content.match(/name:\s*["']?([^"'\n]+)["']?/);
229
- const modeMatch = content.match(/mode:\s*(global|workspace|both)/);
259
+ const modeMatch = content.match(/mode:\s*(global|workspace)/);
230
260
  const linkedProjects = [];
231
261
  const linkedMatch = content.match(/linked_projects:\s*\n((?:\s+-\s+[^\n]+\n?)+)/);
232
262
  if (linkedMatch && linkedMatch[1]) {
@@ -249,10 +279,10 @@ function parseWorkspaceConfig(configPath) {
249
279
  }
250
280
 
251
281
  // src/commands/wizard/setup-flow.ts
252
- import { group, text, select, multiselect, confirm, spinner, note, outro, cancel, isCancel } from "@clack/prompts";
253
- import pc from "picocolors";
254
- import * as fs6 from "fs";
255
- import * as path6 from "path";
282
+ import { group, select, multiselect, confirm, spinner, note, outro, cancel, isCancel } from "@clack/prompts";
283
+ import pc2 from "picocolors";
284
+ import * as fs7 from "fs";
285
+ import * as path7 from "path";
256
286
 
257
287
  // src/lib/prompts.ts
258
288
  import * as fs3 from "fs";
@@ -433,6 +463,113 @@ function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, c
433
463
  fs5.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
434
464
  }
435
465
 
466
+ // src/lib/autocomplete-prompt.ts
467
+ import * as fs6 from "fs";
468
+ import * as path6 from "path";
469
+ import * as readline from "readline";
470
+ import pc from "picocolors";
471
+ function directoryPrompt(opts) {
472
+ return new Promise((resolve) => {
473
+ process.stdout.write(`${pc.cyan("\u25C6")} ${opts.message}
474
+ `);
475
+ process.stdout.write(`${pc.cyan("\u2502")} `);
476
+ const rl = readline.createInterface({
477
+ input: process.stdin,
478
+ output: process.stdout,
479
+ completer: completeDirectory,
480
+ terminal: true
481
+ });
482
+ if (opts.defaultValue) {
483
+ rl.write(opts.defaultValue);
484
+ }
485
+ rl.on("line", (input) => {
486
+ const value = input.trim();
487
+ const expandedPath = value.startsWith("~") ? value.replace(/^~/, process.env.HOME || "") : value;
488
+ if (opts.validate) {
489
+ const error = opts.validate(expandedPath);
490
+ if (error) {
491
+ process.stdout.write(`${pc.yellow("\u2502")} ${pc.yellow(error)}
492
+ `);
493
+ process.stdout.write(`${pc.cyan("\u2502")} `);
494
+ rl.write(value);
495
+ return;
496
+ }
497
+ }
498
+ rl.close();
499
+ process.stdout.write(`${pc.green("\u2713")} ${pc.dim(expandedPath)}
500
+ `);
501
+ resolve(expandedPath);
502
+ });
503
+ rl.on("close", () => {
504
+ });
505
+ rl.on("SIGINT", () => {
506
+ rl.close();
507
+ process.stdout.write("\n");
508
+ resolve(/* @__PURE__ */ Symbol("cancel"));
509
+ });
510
+ });
511
+ }
512
+ function completeDirectory(line) {
513
+ const expanded = line.startsWith("~") ? line.replace(/^~/, process.env.HOME || "") : line;
514
+ try {
515
+ let dirToScan;
516
+ let prefix;
517
+ let basePath;
518
+ if (expanded === "" || expanded === "/") {
519
+ dirToScan = expanded || "/";
520
+ prefix = "";
521
+ basePath = expanded;
522
+ } else if (expanded.endsWith("/")) {
523
+ dirToScan = expanded;
524
+ prefix = "";
525
+ basePath = expanded;
526
+ } else {
527
+ dirToScan = path6.dirname(expanded);
528
+ prefix = path6.basename(expanded).toLowerCase();
529
+ basePath = dirToScan === "/" ? "/" : dirToScan + "/";
530
+ }
531
+ if (!fs6.existsSync(dirToScan)) {
532
+ return [[], line];
533
+ }
534
+ const entries = fs6.readdirSync(dirToScan, { withFileTypes: true }).filter((entry) => {
535
+ if (!entry.isDirectory()) return false;
536
+ if (entry.name.startsWith(".") && !prefix.startsWith(".")) return false;
537
+ return prefix === "" || entry.name.toLowerCase().startsWith(prefix);
538
+ }).map((entry) => {
539
+ const fullPath = path6.join(dirToScan, entry.name);
540
+ const displayPath = fullPath.startsWith(process.env.HOME || "") ? fullPath.replace(process.env.HOME || "", "~") : fullPath;
541
+ return displayPath + "/";
542
+ }).sort();
543
+ if (entries.length === 1) {
544
+ return [entries, line];
545
+ }
546
+ if (entries.length > 1) {
547
+ const commonPrefix = getCommonPrefix(entries);
548
+ if (commonPrefix.length > line.length) {
549
+ return [[commonPrefix], line];
550
+ }
551
+ }
552
+ return [entries, line];
553
+ } catch {
554
+ return [[], line];
555
+ }
556
+ }
557
+ function getCommonPrefix(strings) {
558
+ if (strings.length === 0) return "";
559
+ if (strings.length === 1) return strings[0] || "";
560
+ let prefix = strings[0] || "";
561
+ for (let i = 1; i < strings.length; i++) {
562
+ const str = strings[i] || "";
563
+ while (prefix.length > 0 && !str.startsWith(prefix)) {
564
+ prefix = prefix.slice(0, -1);
565
+ }
566
+ }
567
+ return prefix;
568
+ }
569
+ function isCancelled(value) {
570
+ return typeof value === "symbol";
571
+ }
572
+
436
573
  // src/commands/wizard/setup-flow.ts
437
574
  async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
438
575
  const s = spinner();
@@ -441,9 +578,8 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
441
578
  storageMode: () => select({
442
579
  message: "Where should workflow data be stored?",
443
580
  options: [
444
- { value: "global", label: "Global (~/.rrce-workflow/)" },
445
- { value: "workspace", label: "Workspace (.rrce-workflow/)" },
446
- { value: "both", label: "Both" }
581
+ { value: "global", label: "Global (~/.rrce-workflow/)", hint: "Cross-project access, clean workspace" },
582
+ { value: "workspace", label: "Workspace (.rrce-workflow/)", hint: "Self-contained, version with repo" }
447
583
  ],
448
584
  initialValue: "global"
449
585
  }),
@@ -464,14 +600,18 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
464
600
  options: existingProjects.map((project) => ({
465
601
  value: `${project.name}:${project.source}`,
466
602
  // Unique key
467
- label: `${project.name} ${pc.dim(`(${project.source})`)}`,
468
- hint: pc.dim(
603
+ label: `${project.name} ${pc2.dim(`(${project.source})`)}`,
604
+ hint: pc2.dim(
469
605
  project.source === "global" ? `~/.rrce-workflow/workspaces/${project.name}` : project.dataPath
470
606
  )
471
607
  })),
472
608
  required: false
473
609
  });
474
610
  },
611
+ addToGitignore: () => confirm({
612
+ message: "Add generated folders to .gitignore?",
613
+ initialValue: true
614
+ }),
475
615
  confirm: () => confirm({
476
616
  message: "Create configuration?",
477
617
  initialValue: true
@@ -489,7 +629,7 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
489
629
  process.exit(0);
490
630
  }
491
631
  let customGlobalPath;
492
- if (config.storageMode === "global" || config.storageMode === "both") {
632
+ if (config.storageMode === "global") {
493
633
  customGlobalPath = await resolveGlobalPath();
494
634
  if (!customGlobalPath) {
495
635
  cancel("Setup cancelled - no writable global path available.");
@@ -502,7 +642,8 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
502
642
  storageMode: config.storageMode,
503
643
  globalPath: customGlobalPath,
504
644
  tools: config.tools,
505
- linkedProjects: config.linkedProjects
645
+ linkedProjects: config.linkedProjects,
646
+ addToGitignore: config.addToGitignore
506
647
  }, workspacePath, workspaceName, existingProjects);
507
648
  s.stop("Configuration generated");
508
649
  const dataPaths = getDataPaths(
@@ -512,10 +653,10 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
512
653
  customGlobalPath
513
654
  );
514
655
  const summary = [
515
- `Storage: ${config.storageMode === "both" ? "global + workspace" : config.storageMode}`
656
+ `Storage: ${config.storageMode}`
516
657
  ];
517
658
  if (customGlobalPath && customGlobalPath !== getDefaultRRCEHome()) {
518
- summary.push(`Global path: ${pc.cyan(customGlobalPath)}`);
659
+ summary.push(`Global path: ${pc2.cyan(customGlobalPath)}`);
519
660
  }
520
661
  if (dataPaths.length > 0) {
521
662
  summary.push(`Data paths:`);
@@ -528,13 +669,13 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
528
669
  const linkedProjects = config.linkedProjects;
529
670
  if (linkedProjects.length > 0) {
530
671
  summary.push(`Linked projects: ${linkedProjects.join(", ")}`);
531
- summary.push(`Workspace file: ${pc.cyan(`${workspaceName}.code-workspace`)}`);
672
+ summary.push(`Workspace file: ${pc2.cyan(`${workspaceName}.code-workspace`)}`);
532
673
  }
533
674
  note(summary.join("\n"), "Setup Summary");
534
675
  if (linkedProjects.length > 0) {
535
- outro(pc.green(`\u2713 Setup complete! Open ${pc.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
676
+ outro(pc2.green(`\u2713 Setup complete! Open ${pc2.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
536
677
  } else {
537
- outro(pc.green(`\u2713 Setup complete! Your agents are ready to use.`));
678
+ outro(pc2.green(`\u2713 Setup complete! Your agents are ready to use.`));
538
679
  }
539
680
  } catch (error) {
540
681
  s.stop("Error occurred");
@@ -549,7 +690,7 @@ async function resolveGlobalPath() {
549
690
  options.push({
550
691
  value: "default",
551
692
  label: `Default (${defaultPath})`,
552
- hint: isDefaultWritable ? pc.green("\u2713 writable") : pc.red("\u2717 not writable")
693
+ hint: isDefaultWritable ? pc2.green("\u2713 writable") : pc2.red("\u2717 not writable")
553
694
  });
554
695
  options.push({
555
696
  value: "custom",
@@ -567,8 +708,8 @@ async function resolveGlobalPath() {
567
708
  if (choice === "default") {
568
709
  if (!isDefaultWritable) {
569
710
  note(
570
- `${pc.yellow("\u26A0")} Cannot write to default path:
571
- ${pc.dim(defaultPath)}
711
+ `${pc2.yellow("\u26A0")} Cannot write to default path:
712
+ ${pc2.dim(defaultPath)}
572
713
 
573
714
  This can happen when running via npx/bunx in restricted environments.
574
715
  Please choose a custom path instead.`,
@@ -578,28 +719,26 @@ Please choose a custom path instead.`,
578
719
  }
579
720
  return defaultPath;
580
721
  }
581
- const suggestedPath = path6.join(process.env.HOME || "~", ".local", "share", "rrce-workflow");
582
- const customPath = await text({
583
- message: "Enter custom global path:",
722
+ const suggestedPath = path7.join(process.env.HOME || "~", ".local", "share", "rrce-workflow");
723
+ const customPath = await directoryPrompt({
724
+ message: "Enter custom global path (Tab to autocomplete):",
584
725
  defaultValue: suggestedPath,
585
- placeholder: suggestedPath,
586
726
  validate: (value) => {
587
727
  if (!value.trim()) {
588
728
  return "Path cannot be empty";
589
729
  }
590
- const expandedPath2 = value.startsWith("~") ? value.replace("~", process.env.HOME || "") : value;
591
- if (!checkWriteAccess(expandedPath2)) {
592
- return `Cannot write to ${expandedPath2}. Please choose a writable path.`;
730
+ if (!checkWriteAccess(value)) {
731
+ return `Cannot write to ${value}. Please choose a writable path.`;
593
732
  }
594
733
  return void 0;
595
734
  }
596
735
  });
597
- if (isCancel(customPath)) {
736
+ if (isCancelled(customPath)) {
598
737
  return void 0;
599
738
  }
600
- let expandedPath = customPath.startsWith("~") ? customPath.replace("~", process.env.HOME || "") : customPath;
739
+ let expandedPath = customPath;
601
740
  if (!expandedPath.endsWith(".rrce-workflow")) {
602
- expandedPath = path6.join(expandedPath, ".rrce-workflow");
741
+ expandedPath = path7.join(expandedPath, ".rrce-workflow");
603
742
  }
604
743
  return expandedPath;
605
744
  }
@@ -607,14 +746,14 @@ async function generateConfiguration(config, workspacePath, workspaceName, allPr
607
746
  const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
608
747
  for (const dataPath of dataPaths) {
609
748
  ensureDir(dataPath);
610
- ensureDir(path6.join(dataPath, "knowledge"));
611
- ensureDir(path6.join(dataPath, "refs"));
612
- ensureDir(path6.join(dataPath, "tasks"));
613
- ensureDir(path6.join(dataPath, "templates"));
749
+ ensureDir(path7.join(dataPath, "knowledge"));
750
+ ensureDir(path7.join(dataPath, "refs"));
751
+ ensureDir(path7.join(dataPath, "tasks"));
752
+ ensureDir(path7.join(dataPath, "templates"));
614
753
  }
615
754
  const agentCoreDir = getAgentCoreDir();
616
755
  syncMetadataToAll(agentCoreDir, dataPaths);
617
- copyDirToAllStoragePaths(path6.join(agentCoreDir, "templates"), "templates", dataPaths);
756
+ copyDirToAllStoragePaths(path7.join(agentCoreDir, "templates"), "templates", dataPaths);
618
757
  const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
619
758
  if (config.tools.includes("copilot")) {
620
759
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
@@ -626,8 +765,8 @@ async function generateConfiguration(config, workspacePath, workspaceName, allPr
626
765
  ensureDir(antigravityPath);
627
766
  copyPromptsToDir(prompts, antigravityPath, ".md");
628
767
  }
629
- const workspaceConfigPath = path6.join(workspacePath, ".rrce-workflow", "config.yaml");
630
- ensureDir(path6.dirname(workspaceConfigPath));
768
+ const workspaceConfigPath = path7.join(workspacePath, ".rrce-workflow", "config.yaml");
769
+ ensureDir(path7.dirname(workspaceConfigPath));
631
770
  let configContent = `# RRCE-Workflow Configuration
632
771
  version: 1
633
772
 
@@ -655,7 +794,10 @@ linked_projects:
655
794
  `;
656
795
  });
657
796
  }
658
- fs6.writeFileSync(workspaceConfigPath, configContent);
797
+ fs7.writeFileSync(workspaceConfigPath, configContent);
798
+ if (config.addToGitignore) {
799
+ updateGitignore(workspacePath, config.storageMode, config.tools);
800
+ }
659
801
  if (config.tools.includes("copilot") || config.linkedProjects.length > 0) {
660
802
  const selectedProjects = allProjects.filter(
661
803
  (p) => config.linkedProjects.includes(`${p.name}:${p.source}`)
@@ -664,32 +806,74 @@ linked_projects:
664
806
  }
665
807
  }
666
808
  function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
667
- const globalPath = path6.join(customGlobalPath || getDefaultRRCEHome(), "workspaces", workspaceName);
668
- const workspacePath = path6.join(workspaceRoot, ".rrce-workflow");
809
+ const globalPath = path7.join(customGlobalPath || getDefaultRRCEHome(), "workspaces", workspaceName);
810
+ const workspacePath = path7.join(workspaceRoot, ".rrce-workflow");
669
811
  switch (mode) {
670
812
  case "global":
671
813
  return [globalPath];
672
814
  case "workspace":
673
815
  return [workspacePath];
674
- case "both":
675
- return [workspacePath, globalPath];
676
816
  default:
677
817
  return [globalPath];
678
818
  }
679
819
  }
820
+ function updateGitignore(workspacePath, storageMode, tools) {
821
+ const gitignorePath = path7.join(workspacePath, ".gitignore");
822
+ const entries = [];
823
+ if (storageMode === "workspace") {
824
+ entries.push(".rrce-workflow/");
825
+ }
826
+ if (tools.includes("copilot")) {
827
+ entries.push(".github/agents/");
828
+ }
829
+ if (tools.includes("antigravity")) {
830
+ entries.push(".agent/");
831
+ }
832
+ if (entries.length === 0) {
833
+ return false;
834
+ }
835
+ try {
836
+ let content = "";
837
+ if (fs7.existsSync(gitignorePath)) {
838
+ content = fs7.readFileSync(gitignorePath, "utf-8");
839
+ }
840
+ const lines = content.split("\n").map((line) => line.trim());
841
+ const newEntries = [];
842
+ for (const entry of entries) {
843
+ const entryWithoutSlash = entry.replace(/\/$/, "");
844
+ if (!lines.some((line) => line === entry || line === entryWithoutSlash)) {
845
+ newEntries.push(entry);
846
+ }
847
+ }
848
+ if (newEntries.length === 0) {
849
+ return false;
850
+ }
851
+ let newContent = content;
852
+ if (!newContent.endsWith("\n") && newContent !== "") {
853
+ newContent += "\n";
854
+ }
855
+ if (newContent === "" || !content.includes("# rrce-workflow")) {
856
+ newContent += "\n# rrce-workflow generated folders\n";
857
+ }
858
+ newContent += newEntries.join("\n") + "\n";
859
+ fs7.writeFileSync(gitignorePath, newContent);
860
+ return true;
861
+ } catch {
862
+ return false;
863
+ }
864
+ }
680
865
 
681
866
  // src/commands/wizard/link-flow.ts
682
867
  import { multiselect as multiselect2, spinner as spinner2, note as note2, outro as outro2, cancel as cancel2, isCancel as isCancel2 } from "@clack/prompts";
683
- import pc2 from "picocolors";
684
- import * as fs7 from "fs";
868
+ import pc3 from "picocolors";
869
+ import * as fs8 from "fs";
685
870
  async function runLinkProjectsFlow(workspacePath, workspaceName) {
686
871
  const projects = scanForProjects({
687
872
  excludeWorkspace: workspaceName,
688
- workspacePath,
689
- scanSiblings: true
873
+ workspacePath
690
874
  });
691
875
  if (projects.length === 0) {
692
- outro2(pc2.yellow("No other projects found. Try setting up another project first."));
876
+ outro2(pc3.yellow("No other projects found. Try setting up another project first."));
693
877
  return;
694
878
  }
695
879
  const customGlobalPath = getEffectiveRRCEHome(workspacePath);
@@ -698,8 +882,8 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
698
882
  options: projects.map((project) => ({
699
883
  value: `${project.name}:${project.source}`,
700
884
  // Unique key
701
- label: `${project.name} ${pc2.dim(`(${project.source})`)}`,
702
- hint: pc2.dim(
885
+ label: `${project.name} ${pc3.dim(`(${project.source})`)}`,
886
+ hint: pc3.dim(
703
887
  project.source === "global" ? `~/.rrce-workflow/workspaces/${project.name}` : project.dataPath
704
888
  )
705
889
  })),
@@ -720,7 +904,7 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
720
904
  const s = spinner2();
721
905
  s.start("Linking projects");
722
906
  const configFilePath = getConfigPath(workspacePath);
723
- let configContent = fs7.readFileSync(configFilePath, "utf-8");
907
+ let configContent = fs8.readFileSync(configFilePath, "utf-8");
724
908
  if (configContent.includes("linked_projects:")) {
725
909
  const lines = configContent.split("\n");
726
910
  const linkedIndex = lines.findIndex((l) => l.trim() === "linked_projects:");
@@ -747,42 +931,42 @@ linked_projects:
747
931
  `;
748
932
  });
749
933
  }
750
- fs7.writeFileSync(configFilePath, configContent);
934
+ fs8.writeFileSync(configFilePath, configContent);
751
935
  generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
752
936
  s.stop("Projects linked");
753
937
  const workspaceFile = `${workspaceName}.code-workspace`;
754
938
  const summary = [
755
939
  `Linked projects:`,
756
- ...selectedProjects.map((p) => ` \u2713 ${p.name} ${pc2.dim(`(${p.source})`)}`),
940
+ ...selectedProjects.map((p) => ` \u2713 ${p.name} ${pc3.dim(`(${p.source})`)}`),
757
941
  ``,
758
- `Workspace file: ${pc2.cyan(workspaceFile)}`
942
+ `Workspace file: ${pc3.cyan(workspaceFile)}`
759
943
  ];
760
944
  note2(summary.join("\n"), "Link Summary");
761
- outro2(pc2.green(`\u2713 Projects linked! Open ${pc2.bold(workspaceFile)} in VSCode to access linked data.`));
945
+ outro2(pc3.green(`\u2713 Projects linked! Open ${pc3.bold(workspaceFile)} in VSCode to access linked data.`));
762
946
  }
763
947
 
764
948
  // src/commands/wizard/sync-flow.ts
765
949
  import { confirm as confirm2, spinner as spinner3, note as note3, outro as outro3, cancel as cancel3, isCancel as isCancel3 } from "@clack/prompts";
766
- import pc3 from "picocolors";
767
- import * as fs8 from "fs";
768
- import * as path7 from "path";
950
+ import pc4 from "picocolors";
951
+ import * as fs9 from "fs";
952
+ import * as path8 from "path";
769
953
  async function runSyncToGlobalFlow(workspacePath, workspaceName) {
770
954
  const localPath = getLocalWorkspacePath(workspacePath);
771
955
  const customGlobalPath = getEffectiveRRCEHome(workspacePath);
772
- const globalPath = path7.join(customGlobalPath, "workspaces", workspaceName);
956
+ const globalPath = path8.join(customGlobalPath, "workspaces", workspaceName);
773
957
  const subdirs = ["knowledge", "prompts", "templates", "tasks", "refs"];
774
958
  const existingDirs = subdirs.filter(
775
- (dir) => fs8.existsSync(path7.join(localPath, dir))
959
+ (dir) => fs9.existsSync(path8.join(localPath, dir))
776
960
  );
777
961
  if (existingDirs.length === 0) {
778
- outro3(pc3.yellow("No data found in workspace storage to sync."));
962
+ outro3(pc4.yellow("No data found in workspace storage to sync."));
779
963
  return;
780
964
  }
781
965
  note3(
782
966
  `The following will be copied to global storage:
783
967
  ${existingDirs.map((d) => ` \u2022 ${d}/`).join("\n")}
784
968
 
785
- Destination: ${pc3.cyan(globalPath)}`,
969
+ Destination: ${pc4.cyan(globalPath)}`,
786
970
  "Sync Preview"
787
971
  );
788
972
  const shouldSync = await confirm2({
@@ -798,27 +982,22 @@ Destination: ${pc3.cyan(globalPath)}`,
798
982
  try {
799
983
  ensureDir(globalPath);
800
984
  for (const dir of existingDirs) {
801
- const srcDir = path7.join(localPath, dir);
802
- const destDir = path7.join(globalPath, dir);
985
+ const srcDir = path8.join(localPath, dir);
986
+ const destDir = path8.join(globalPath, dir);
803
987
  ensureDir(destDir);
804
988
  copyDirRecursive(srcDir, destDir);
805
989
  }
806
- const configFilePath = getConfigPath(workspacePath);
807
- let configContent = fs8.readFileSync(configFilePath, "utf-8");
808
- configContent = configContent.replace(/mode:\s*workspace/, "mode: both");
809
- fs8.writeFileSync(configFilePath, configContent);
810
990
  s.stop("Sync complete");
811
991
  const summary = [
812
992
  `Synced directories:`,
813
993
  ...existingDirs.map((d) => ` \u2713 ${d}/`),
814
994
  ``,
815
- `Global path: ${pc3.cyan(globalPath)}`,
816
- `Storage mode updated to: ${pc3.bold("both")}`,
995
+ `Global path: ${pc4.cyan(globalPath)}`,
817
996
  ``,
818
997
  `Other projects can now link this knowledge!`
819
998
  ];
820
999
  note3(summary.join("\n"), "Sync Summary");
821
- outro3(pc3.green("\u2713 Workspace knowledge synced to global storage!"));
1000
+ outro3(pc4.green("\u2713 Workspace knowledge synced to global storage!"));
822
1001
  } catch (error) {
823
1002
  s.stop("Error occurred");
824
1003
  cancel3(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
@@ -828,9 +1007,9 @@ Destination: ${pc3.cyan(globalPath)}`,
828
1007
 
829
1008
  // src/commands/wizard/update-flow.ts
830
1009
  import { confirm as confirm3, spinner as spinner4, note as note4, outro as outro4, cancel as cancel4, isCancel as isCancel4 } from "@clack/prompts";
831
- import pc4 from "picocolors";
832
- import * as fs9 from "fs";
833
- import * as path8 from "path";
1010
+ import pc5 from "picocolors";
1011
+ import * as fs10 from "fs";
1012
+ import * as path9 from "path";
834
1013
  async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
835
1014
  const s = spinner4();
836
1015
  s.start("Checking for updates");
@@ -860,10 +1039,10 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
860
1039
  }
861
1040
  s.start("Updating from package");
862
1041
  for (const dataPath of dataPaths) {
863
- copyDirToAllStoragePaths(path8.join(agentCoreDir, "templates"), "templates", [dataPath]);
1042
+ copyDirToAllStoragePaths(path9.join(agentCoreDir, "templates"), "templates", [dataPath]);
864
1043
  }
865
1044
  const configFilePath = getConfigPath(workspacePath);
866
- const configContent = fs9.readFileSync(configFilePath, "utf-8");
1045
+ const configContent = fs10.readFileSync(configFilePath, "utf-8");
867
1046
  if (configContent.includes("copilot: true")) {
868
1047
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
869
1048
  ensureDir(copilotPath);
@@ -883,7 +1062,7 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
883
1062
  `Your configuration and knowledge files were preserved.`
884
1063
  ];
885
1064
  note4(summary.join("\n"), "Update Summary");
886
- outro4(pc4.green("\u2713 Successfully updated from package!"));
1065
+ outro4(pc5.green("\u2713 Successfully updated from package!"));
887
1066
  } catch (error) {
888
1067
  s.stop("Error occurred");
889
1068
  cancel4(`Failed to update: ${error instanceof Error ? error.message : String(error)}`);
@@ -891,15 +1070,13 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
891
1070
  }
892
1071
  }
893
1072
  function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot, customGlobalPath) {
894
- const globalPath = path8.join(customGlobalPath, "workspaces", workspaceName);
895
- const workspacePath = path8.join(workspaceRoot, ".rrce-workflow");
1073
+ const globalPath = path9.join(customGlobalPath, "workspaces", workspaceName);
1074
+ const workspacePath = path9.join(workspaceRoot, ".rrce-workflow");
896
1075
  switch (mode) {
897
1076
  case "global":
898
1077
  return [globalPath];
899
1078
  case "workspace":
900
1079
  return [workspacePath];
901
- case "both":
902
- return [workspacePath, globalPath];
903
1080
  default:
904
1081
  return [globalPath];
905
1082
  }
@@ -907,7 +1084,7 @@ function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot,
907
1084
 
908
1085
  // src/commands/wizard/index.ts
909
1086
  async function runWizard() {
910
- intro(pc5.cyan(pc5.inverse(" RRCE-Workflow Setup ")));
1087
+ intro(pc6.cyan(pc6.inverse(" RRCE-Workflow Setup ")));
911
1088
  const s = spinner5();
912
1089
  s.start("Detecting environment");
913
1090
  const workspacePath = detectWorkspaceRoot();
@@ -916,28 +1093,27 @@ async function runWizard() {
916
1093
  await new Promise((r) => setTimeout(r, 800));
917
1094
  s.stop("Environment detected");
918
1095
  note5(
919
- `Git User: ${pc5.bold(gitUser || "(not found)")}
920
- Workspace: ${pc5.bold(workspaceName)}`,
1096
+ `Git User: ${pc6.bold(gitUser || "(not found)")}
1097
+ Workspace: ${pc6.bold(workspaceName)}`,
921
1098
  "Context"
922
1099
  );
923
1100
  const detectedProjects = scanForProjects({
924
1101
  excludeWorkspace: workspaceName,
925
- workspacePath,
926
- scanSiblings: true
1102
+ workspacePath
927
1103
  });
928
1104
  const configFilePath = getConfigPath(workspacePath);
929
- const isAlreadyConfigured = fs10.existsSync(configFilePath);
1105
+ const isAlreadyConfigured = fs11.existsSync(configFilePath);
930
1106
  let currentStorageMode = null;
931
1107
  if (isAlreadyConfigured) {
932
1108
  try {
933
- const configContent = fs10.readFileSync(configFilePath, "utf-8");
934
- const modeMatch = configContent.match(/mode:\s*(global|workspace|both)/);
1109
+ const configContent = fs11.readFileSync(configFilePath, "utf-8");
1110
+ const modeMatch = configContent.match(/mode:\s*(global|workspace)/);
935
1111
  currentStorageMode = modeMatch?.[1] ?? null;
936
1112
  } catch {
937
1113
  }
938
1114
  }
939
1115
  const localDataPath = getLocalWorkspacePath(workspacePath);
940
- const hasLocalData = fs10.existsSync(localDataPath);
1116
+ const hasLocalData = fs11.existsSync(localDataPath);
941
1117
  if (isAlreadyConfigured) {
942
1118
  const menuOptions = [];
943
1119
  if (detectedProjects.length > 0) {
@@ -982,11 +1158,11 @@ Workspace: ${pc5.bold(workspaceName)}`,
982
1158
 
983
1159
  // src/commands/selector.ts
984
1160
  import { intro as intro2, select as select3, note as note6, cancel as cancel6, isCancel as isCancel6, outro as outro6 } from "@clack/prompts";
985
- import pc6 from "picocolors";
986
- import * as path9 from "path";
1161
+ import pc7 from "picocolors";
1162
+ import * as path10 from "path";
987
1163
  async function runSelector() {
988
- const workspaceName = path9.basename(process.cwd());
989
- intro2(pc6.cyan(pc6.inverse(` RRCE-Workflow | ${workspaceName} `)));
1164
+ const workspaceName = path10.basename(process.cwd());
1165
+ intro2(pc7.cyan(pc7.inverse(` RRCE-Workflow | ${workspaceName} `)));
990
1166
  const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
991
1167
  if (prompts.length === 0) {
992
1168
  cancel6("No agents found. Run `rrce-workflow` to set up.");
@@ -1007,7 +1183,7 @@ async function runSelector() {
1007
1183
  const prompt = selection;
1008
1184
  note6(
1009
1185
  `Use this agent in your IDE by invoking:
1010
- ${pc6.bold(pc6.cyan(`@${prompt.frontmatter.name}`))}`,
1186
+ ${pc7.bold(pc7.cyan(`@${prompt.frontmatter.name}`))}`,
1011
1187
  "Agent Selected"
1012
1188
  );
1013
1189
  outro6("Done");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",