rrce-workflow 0.2.17 → 0.2.19

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 +183 -174
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/commands/wizard/index.ts
2
- import { intro, select as select2, spinner as spinner5, note as note5, outro as outro5, isCancel as isCancel6 } from "@clack/prompts";
2
+ import { intro, select as select2, spinner as spinner5, note as note5, outro as outro5, isCancel as isCancel5 } from "@clack/prompts";
3
3
  import pc6 from "picocolors";
4
4
  import * as fs11 from "fs";
5
5
 
@@ -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,37 +207,49 @@ 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) {
@@ -249,7 +279,7 @@ function parseWorkspaceConfig(configPath) {
249
279
  }
250
280
 
251
281
  // src/commands/wizard/setup-flow.ts
252
- import { group, select, multiselect, confirm, spinner, note, outro, cancel, isCancel as isCancel2 } from "@clack/prompts";
282
+ import { group, select, multiselect, confirm, spinner, note, outro, cancel, isCancel } from "@clack/prompts";
253
283
  import pc2 from "picocolors";
254
284
  import * as fs7 from "fs";
255
285
  import * as path7 from "path";
@@ -434,131 +464,110 @@ function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, c
434
464
  }
435
465
 
436
466
  // src/lib/autocomplete-prompt.ts
437
- import { TextPrompt, isCancel } from "@clack/core";
438
467
  import * as fs6 from "fs";
439
468
  import * as path6 from "path";
469
+ import * as readline from "readline";
440
470
  import pc from "picocolors";
441
- async function directoryAutocomplete(opts) {
442
- let completions = [];
443
- let completionIndex = 0;
444
- let lastTabValue = "";
445
- const prompt = new TextPrompt({
446
- initialValue: opts.initialValue,
447
- validate: opts.validate,
448
- render() {
449
- const title = `${pc.cyan("\u25C6")} ${opts.message}`;
450
- const hintText = opts.hint ? pc.dim(` (${opts.hint})`) : "";
451
- let inputLine;
452
- if (this.state === "error") {
453
- inputLine = `${pc.yellow("\u25B2")} ${this.valueWithCursor}`;
454
- } else if (this.state === "submit") {
455
- inputLine = `${pc.green("\u2713")} ${pc.dim(String(this.value || ""))}`;
456
- } else {
457
- inputLine = `${pc.cyan("\u2502")} ${this.valueWithCursor || pc.dim(opts.placeholder || "")}`;
458
- }
459
- let result2 = `${title}${hintText}
460
- ${inputLine}`;
461
- if (this.state === "error" && this.error) {
462
- result2 += `
463
- ${pc.yellow("\u2502")} ${pc.yellow(this.error)}`;
464
- }
465
- if (completions.length > 1 && this.state === "active") {
466
- const remaining = completions.length - 1;
467
- result2 += `
468
- ${pc.dim("\u2502")} ${pc.dim(`+${remaining} more, press Tab again to cycle`)}`;
469
- }
470
- return result2;
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);
471
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
+ });
472
510
  });
473
- prompt.on("key", (key) => {
474
- if (key === " " || key === "tab") {
475
- handleTabCompletion(prompt);
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;
476
526
  } else {
477
- completions = [];
478
- completionIndex = 0;
479
- lastTabValue = "";
527
+ dirToScan = path6.dirname(expanded);
528
+ prefix = path6.basename(expanded).toLowerCase();
529
+ basePath = dirToScan === "/" ? "/" : dirToScan + "/";
480
530
  }
481
- });
482
- function handleTabCompletion(p) {
483
- const input = String(p.value || "");
484
- const expanded = input.startsWith("~") ? input.replace(/^~/, process.env.HOME || "") : input;
485
- if (lastTabValue === input && completions.length > 1) {
486
- completionIndex = (completionIndex + 1) % completions.length;
487
- const completion = completions[completionIndex] || "";
488
- setPromptValue(p, completion);
489
- return;
531
+ if (!fs6.existsSync(dirToScan)) {
532
+ return [[], line];
490
533
  }
491
- completions = getDirectoryCompletions(expanded);
492
- completionIndex = 0;
493
- lastTabValue = input;
494
- if (completions.length === 1) {
495
- const completion = completions[0] || "";
496
- setPromptValue(p, completion.endsWith("/") ? completion : completion + "/");
497
- completions = [];
498
- lastTabValue = "";
499
- } else if (completions.length > 1) {
500
- const commonPrefix = getCommonPrefix(completions);
501
- if (commonPrefix.length > expanded.length) {
502
- setPromptValue(p, commonPrefix);
503
- lastTabValue = formatForDisplay(commonPrefix);
504
- } else {
505
- setPromptValue(p, completions[0] || "");
506
- }
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];
507
545
  }
508
- }
509
- function setPromptValue(p, value2) {
510
- const displayValue = formatForDisplay(value2);
511
- p.value = displayValue;
512
- }
513
- function formatForDisplay(value2) {
514
- const home = process.env.HOME || "";
515
- return value2.startsWith(home) ? value2.replace(home, "~") : value2;
516
- }
517
- function getDirectoryCompletions(inputPath) {
518
- try {
519
- let dirToScan;
520
- let prefix;
521
- if (inputPath === "" || inputPath === "/") {
522
- dirToScan = inputPath || "/";
523
- prefix = "";
524
- } else if (inputPath.endsWith("/")) {
525
- dirToScan = inputPath;
526
- prefix = "";
527
- } else {
528
- dirToScan = path6.dirname(inputPath);
529
- prefix = path6.basename(inputPath).toLowerCase();
530
- }
531
- if (!fs6.existsSync(dirToScan)) {
532
- return [];
546
+ if (entries.length > 1) {
547
+ const commonPrefix = getCommonPrefix(entries);
548
+ if (commonPrefix.length > line.length) {
549
+ return [[commonPrefix], line];
533
550
  }
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) => path6.join(dirToScan, entry.name)).sort();
539
- return entries;
540
- } catch {
541
- return [];
542
551
  }
552
+ return [entries, line];
553
+ } catch {
554
+ return [[], line];
543
555
  }
544
- function getCommonPrefix(strings) {
545
- if (strings.length === 0) return "";
546
- if (strings.length === 1) return strings[0] || "";
547
- let prefix = strings[0] || "";
548
- for (let i = 1; i < strings.length; i++) {
549
- const str = strings[i] || "";
550
- while (prefix.length > 0 && !str.startsWith(prefix)) {
551
- prefix = prefix.slice(0, -1);
552
- }
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);
553
565
  }
554
- return prefix;
555
- }
556
- const result = await prompt.prompt();
557
- if (isCancel(result)) {
558
- return result;
559
566
  }
560
- const value = String(result || "");
561
- return value.startsWith("~") ? value.replace(/^~/, process.env.HOME || "") : value;
567
+ return prefix;
568
+ }
569
+ function isCancelled(value) {
570
+ return typeof value === "symbol";
562
571
  }
563
572
 
564
573
  // src/commands/wizard/setup-flow.ts
@@ -689,7 +698,7 @@ async function resolveGlobalPath() {
689
698
  options,
690
699
  initialValue: isDefaultWritable ? "default" : "custom"
691
700
  });
692
- if (isCancel2(choice)) {
701
+ if (isCancel(choice)) {
693
702
  return void 0;
694
703
  }
695
704
  if (choice === "default") {
@@ -707,25 +716,27 @@ Please choose a custom path instead.`,
707
716
  return defaultPath;
708
717
  }
709
718
  const suggestedPath = path7.join(process.env.HOME || "~", ".local", "share", "rrce-workflow");
710
- const customPath = await directoryAutocomplete({
711
- message: "Enter custom global path:",
712
- initialValue: suggestedPath,
713
- hint: "Tab to autocomplete",
719
+ const customPath = await directoryPrompt({
720
+ message: "Enter custom global path (Tab to autocomplete):",
721
+ defaultValue: suggestedPath,
714
722
  validate: (value) => {
715
723
  if (!value.trim()) {
716
724
  return "Path cannot be empty";
717
725
  }
718
- const expandedPath = value.startsWith("~") ? value.replace("~", process.env.HOME || "") : value;
719
- if (!checkWriteAccess(expandedPath)) {
720
- return `Cannot write to ${expandedPath}. Please choose a writable path.`;
726
+ if (!checkWriteAccess(value)) {
727
+ return `Cannot write to ${value}. Please choose a writable path.`;
721
728
  }
722
729
  return void 0;
723
730
  }
724
731
  });
725
- if (isCancel(customPath)) {
732
+ if (isCancelled(customPath)) {
726
733
  return void 0;
727
734
  }
728
- return customPath;
735
+ let expandedPath = customPath;
736
+ if (!expandedPath.endsWith(".rrce-workflow")) {
737
+ expandedPath = path7.join(expandedPath, ".rrce-workflow");
738
+ }
739
+ return expandedPath;
729
740
  }
730
741
  async function generateConfiguration(config, workspacePath, workspaceName, allProjects = []) {
731
742
  const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
@@ -803,14 +814,13 @@ function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
803
814
  }
804
815
 
805
816
  // src/commands/wizard/link-flow.ts
806
- import { multiselect as multiselect2, spinner as spinner2, note as note2, outro as outro2, cancel as cancel2, isCancel as isCancel3 } from "@clack/prompts";
817
+ import { multiselect as multiselect2, spinner as spinner2, note as note2, outro as outro2, cancel as cancel2, isCancel as isCancel2 } from "@clack/prompts";
807
818
  import pc3 from "picocolors";
808
819
  import * as fs8 from "fs";
809
820
  async function runLinkProjectsFlow(workspacePath, workspaceName) {
810
821
  const projects = scanForProjects({
811
822
  excludeWorkspace: workspaceName,
812
- workspacePath,
813
- scanSiblings: true
823
+ workspacePath
814
824
  });
815
825
  if (projects.length === 0) {
816
826
  outro2(pc3.yellow("No other projects found. Try setting up another project first."));
@@ -829,7 +839,7 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
829
839
  })),
830
840
  required: true
831
841
  });
832
- if (isCancel3(linkedProjects)) {
842
+ if (isCancel2(linkedProjects)) {
833
843
  cancel2("Cancelled.");
834
844
  process.exit(0);
835
845
  }
@@ -886,7 +896,7 @@ linked_projects:
886
896
  }
887
897
 
888
898
  // src/commands/wizard/sync-flow.ts
889
- import { confirm as confirm2, spinner as spinner3, note as note3, outro as outro3, cancel as cancel3, isCancel as isCancel4 } from "@clack/prompts";
899
+ import { confirm as confirm2, spinner as spinner3, note as note3, outro as outro3, cancel as cancel3, isCancel as isCancel3 } from "@clack/prompts";
890
900
  import pc4 from "picocolors";
891
901
  import * as fs9 from "fs";
892
902
  import * as path8 from "path";
@@ -913,7 +923,7 @@ Destination: ${pc4.cyan(globalPath)}`,
913
923
  message: "Proceed with sync to global storage?",
914
924
  initialValue: true
915
925
  });
916
- if (isCancel4(shouldSync) || !shouldSync) {
926
+ if (isCancel3(shouldSync) || !shouldSync) {
917
927
  outro3("Sync cancelled.");
918
928
  return;
919
929
  }
@@ -951,7 +961,7 @@ Destination: ${pc4.cyan(globalPath)}`,
951
961
  }
952
962
 
953
963
  // src/commands/wizard/update-flow.ts
954
- import { confirm as confirm3, spinner as spinner4, note as note4, outro as outro4, cancel as cancel4, isCancel as isCancel5 } from "@clack/prompts";
964
+ import { confirm as confirm3, spinner as spinner4, note as note4, outro as outro4, cancel as cancel4, isCancel as isCancel4 } from "@clack/prompts";
955
965
  import pc5 from "picocolors";
956
966
  import * as fs10 from "fs";
957
967
  import * as path9 from "path";
@@ -978,7 +988,7 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
978
988
  message: "Proceed with update?",
979
989
  initialValue: true
980
990
  });
981
- if (isCancel5(shouldUpdate) || !shouldUpdate) {
991
+ if (isCancel4(shouldUpdate) || !shouldUpdate) {
982
992
  outro4("Update cancelled.");
983
993
  return;
984
994
  }
@@ -1046,8 +1056,7 @@ Workspace: ${pc6.bold(workspaceName)}`,
1046
1056
  );
1047
1057
  const detectedProjects = scanForProjects({
1048
1058
  excludeWorkspace: workspaceName,
1049
- workspacePath,
1050
- scanSiblings: true
1059
+ workspacePath
1051
1060
  });
1052
1061
  const configFilePath = getConfigPath(workspacePath);
1053
1062
  const isAlreadyConfigured = fs11.existsSync(configFilePath);
@@ -1084,7 +1093,7 @@ Workspace: ${pc6.bold(workspaceName)}`,
1084
1093
  message: "This workspace is already configured. What would you like to do?",
1085
1094
  options: menuOptions
1086
1095
  });
1087
- if (isCancel6(action) || action === "exit") {
1096
+ if (isCancel5(action) || action === "exit") {
1088
1097
  outro5("Exited.");
1089
1098
  process.exit(0);
1090
1099
  }
@@ -1105,7 +1114,7 @@ Workspace: ${pc6.bold(workspaceName)}`,
1105
1114
  }
1106
1115
 
1107
1116
  // src/commands/selector.ts
1108
- import { intro as intro2, select as select3, note as note6, cancel as cancel6, isCancel as isCancel7, outro as outro6 } from "@clack/prompts";
1117
+ import { intro as intro2, select as select3, note as note6, cancel as cancel6, isCancel as isCancel6, outro as outro6 } from "@clack/prompts";
1109
1118
  import pc7 from "picocolors";
1110
1119
  import * as path10 from "path";
1111
1120
  async function runSelector() {
@@ -1124,7 +1133,7 @@ async function runSelector() {
1124
1133
  hint: p.frontmatter.description
1125
1134
  }))
1126
1135
  });
1127
- if (isCancel7(selection)) {
1136
+ if (isCancel6(selection)) {
1128
1137
  cancel6("Selection cancelled.");
1129
1138
  process.exit(0);
1130
1139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",