rrce-workflow 0.2.14 → 0.2.16

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 (47) hide show
  1. package/bin/rrce-workflow.js +3 -33
  2. package/dist/commands/selector.d.ts +1 -0
  3. package/dist/commands/selector.js +29 -0
  4. package/dist/commands/wizard/index.d.ts +1 -0
  5. package/dist/commands/wizard/index.js +86 -0
  6. package/dist/commands/wizard/link-flow.d.ts +5 -0
  7. package/dist/commands/wizard/link-flow.js +97 -0
  8. package/dist/commands/wizard/setup-flow.d.ts +4 -0
  9. package/dist/commands/wizard/setup-flow.js +262 -0
  10. package/dist/commands/wizard/sync-flow.d.ts +4 -0
  11. package/dist/commands/wizard/sync-flow.js +67 -0
  12. package/dist/commands/wizard/update-flow.d.ts +4 -0
  13. package/dist/commands/wizard/update-flow.js +85 -0
  14. package/dist/commands/wizard/utils.d.ts +9 -0
  15. package/dist/commands/wizard/utils.js +33 -0
  16. package/dist/commands/wizard/vscode.d.ts +15 -0
  17. package/dist/commands/wizard/vscode.js +148 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +1181 -0
  20. package/dist/lib/autocomplete-prompt.d.ts +14 -0
  21. package/dist/lib/autocomplete-prompt.js +167 -0
  22. package/dist/lib/detection.d.ts +44 -0
  23. package/dist/lib/detection.js +185 -0
  24. package/dist/lib/git.d.ts +12 -0
  25. package/dist/lib/git.js +37 -0
  26. package/dist/lib/paths.d.ts +108 -0
  27. package/dist/lib/paths.js +296 -0
  28. package/dist/lib/prompts.d.ts +18 -0
  29. package/dist/lib/prompts.js +62 -0
  30. package/dist/types/prompt.d.ts +54 -0
  31. package/dist/types/prompt.js +20 -0
  32. package/package.json +10 -7
  33. package/src/commands/selector.ts +0 -42
  34. package/src/commands/wizard/index.ts +0 -114
  35. package/src/commands/wizard/link-flow.ts +0 -118
  36. package/src/commands/wizard/setup-flow.ts +0 -347
  37. package/src/commands/wizard/sync-flow.ts +0 -93
  38. package/src/commands/wizard/update-flow.ts +0 -124
  39. package/src/commands/wizard/utils.ts +0 -38
  40. package/src/commands/wizard/vscode.ts +0 -197
  41. package/src/index.ts +0 -11
  42. package/src/lib/autocomplete-prompt.ts +0 -190
  43. package/src/lib/detection.ts +0 -235
  44. package/src/lib/git.ts +0 -37
  45. package/src/lib/paths.ts +0 -332
  46. package/src/lib/prompts.ts +0 -73
  47. package/src/types/prompt.ts +0 -54
@@ -1,35 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawn } from 'node:child_process';
4
- import { fileURLToPath } from 'node:url';
5
- import { dirname, join } from 'node:path';
6
-
7
- // Get the directory of this script
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
-
11
- // Path to tsx in node_modules
12
- const tsxPath = join(__dirname, '..', 'node_modules', '.bin', 'tsx');
13
-
14
- // Path to the main TypeScript file
15
- const mainPath = join(__dirname, '..', 'src', 'index.ts');
16
-
17
- // Get command line arguments (skip node and script path)
18
- const args = process.argv.slice(2);
19
-
20
- // Spawn tsx with the main file and pass through all arguments
21
- const child = spawn(tsxPath, [mainPath, ...args], {
22
- stdio: 'inherit',
23
- cwd: process.cwd(),
24
- });
25
-
26
- // Exit with the same code as the child process
27
- child.on('exit', (code) => {
28
- process.exit(code ?? 0);
29
- });
30
-
31
- // Forward signals
32
- child.on('error', (err) => {
33
- console.error('Failed to start:', err.message);
34
- process.exit(1);
35
- });
3
+ // This is the compiled entry point for the CLI
4
+ // The source is in src/index.ts, compiled to dist/index.js
5
+ import('../dist/index.js');
@@ -0,0 +1 @@
1
+ export declare function runSelector(): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { intro, select, note, cancel, isCancel, outro } from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import * as path from 'path';
4
+ import { loadPromptsFromDir, getAgentCorePromptsDir } from '../lib/prompts';
5
+ export async function runSelector() {
6
+ const workspaceName = path.basename(process.cwd());
7
+ intro(pc.cyan(pc.inverse(` RRCE-Workflow | ${workspaceName} `)));
8
+ const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
9
+ if (prompts.length === 0) {
10
+ cancel('No agents found. Run `rrce-workflow` to set up.');
11
+ process.exit(0);
12
+ }
13
+ const selection = await select({
14
+ message: 'Select an agent:',
15
+ options: prompts.map(p => ({
16
+ value: p,
17
+ label: p.frontmatter.name,
18
+ hint: p.frontmatter.description
19
+ })),
20
+ });
21
+ if (isCancel(selection)) {
22
+ cancel('Selection cancelled.');
23
+ process.exit(0);
24
+ }
25
+ const prompt = selection;
26
+ note(`Use this agent in your IDE by invoking:
27
+ ${pc.bold(pc.cyan(`@${prompt.frontmatter.name}`))}`, 'Agent Selected');
28
+ outro('Done');
29
+ }
@@ -0,0 +1 @@
1
+ export declare function runWizard(): Promise<void>;
@@ -0,0 +1,86 @@
1
+ import { intro, select, spinner, note, outro, isCancel } from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import * as fs from 'fs';
4
+ import { getGitUser } from '../../lib/git';
5
+ import { detectWorkspaceRoot, getWorkspaceName, listGlobalProjects, getLocalWorkspacePath, getConfigPath } from '../../lib/paths';
6
+ // Import flows
7
+ import { runSetupFlow } from './setup-flow';
8
+ import { runLinkProjectsFlow } from './link-flow';
9
+ import { runSyncToGlobalFlow } from './sync-flow';
10
+ import { runUpdateFlow } from './update-flow';
11
+ export async function runWizard() {
12
+ intro(pc.cyan(pc.inverse(' RRCE-Workflow Setup ')));
13
+ const s = spinner();
14
+ s.start('Detecting environment');
15
+ const workspacePath = detectWorkspaceRoot();
16
+ const workspaceName = getWorkspaceName(workspacePath);
17
+ const gitUser = getGitUser();
18
+ await new Promise(r => setTimeout(r, 800)); // Dramatic pause
19
+ s.stop('Environment detected');
20
+ note(`Git User: ${pc.bold(gitUser || '(not found)')}
21
+ Workspace: ${pc.bold(workspaceName)}`, 'Context');
22
+ // Check for existing projects in global storage
23
+ const existingProjects = listGlobalProjects(workspaceName);
24
+ // Check if already configured (using getConfigPath for new/legacy support)
25
+ const configFilePath = getConfigPath(workspacePath);
26
+ const isAlreadyConfigured = fs.existsSync(configFilePath);
27
+ // Check current storage mode from config
28
+ let currentStorageMode = null;
29
+ if (isAlreadyConfigured) {
30
+ try {
31
+ const configContent = fs.readFileSync(configFilePath, 'utf-8');
32
+ const modeMatch = configContent.match(/mode:\s*(global|workspace|both)/);
33
+ currentStorageMode = modeMatch?.[1] ?? null;
34
+ }
35
+ catch {
36
+ // Ignore parse errors
37
+ }
38
+ }
39
+ // Check if workspace has local data that could be synced
40
+ const localDataPath = getLocalWorkspacePath(workspacePath);
41
+ const hasLocalData = fs.existsSync(localDataPath);
42
+ // If already configured, show menu
43
+ if (isAlreadyConfigured) {
44
+ const menuOptions = [];
45
+ // Add link option if other projects exist
46
+ if (existingProjects.length > 0) {
47
+ menuOptions.push({
48
+ value: 'link',
49
+ label: 'Link other project knowledge',
50
+ hint: `${existingProjects.length} projects available`
51
+ });
52
+ }
53
+ // Add sync to global option if using workspace-only mode
54
+ if (currentStorageMode === 'workspace' && hasLocalData) {
55
+ menuOptions.push({
56
+ value: 'sync-global',
57
+ label: 'Sync to global storage',
58
+ hint: 'Share knowledge with other projects'
59
+ });
60
+ }
61
+ menuOptions.push({ value: 'update', label: 'Update from package', hint: 'Get latest prompts & templates' });
62
+ menuOptions.push({ value: 'exit', label: 'Exit' });
63
+ const action = await select({
64
+ message: 'This workspace is already configured. What would you like to do?',
65
+ options: menuOptions,
66
+ });
67
+ if (isCancel(action) || action === 'exit') {
68
+ outro('Exited.');
69
+ process.exit(0);
70
+ }
71
+ if (action === 'link') {
72
+ await runLinkProjectsFlow(workspacePath, workspaceName, existingProjects);
73
+ return;
74
+ }
75
+ if (action === 'sync-global') {
76
+ await runSyncToGlobalFlow(workspacePath, workspaceName);
77
+ return;
78
+ }
79
+ if (action === 'update') {
80
+ await runUpdateFlow(workspacePath, workspaceName, currentStorageMode);
81
+ return;
82
+ }
83
+ }
84
+ // Run full setup flow for new workspaces
85
+ await runSetupFlow(workspacePath, workspaceName, existingProjects);
86
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Run the link-only flow for adding other project knowledge to an existing workspace
3
+ * Now supports detecting workspace-scoped sibling projects, not just global storage
4
+ */
5
+ export declare function runLinkProjectsFlow(workspacePath: string, workspaceName: string, existingProjects?: string[]): Promise<void>;
@@ -0,0 +1,97 @@
1
+ import { multiselect, spinner, note, outro, cancel, isCancel } from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import * as fs from 'fs';
4
+ import { getEffectiveRRCEHome, getConfigPath } from '../../lib/paths';
5
+ import { generateVSCodeWorkspace } from './vscode';
6
+ import { scanForProjects, getProjectDisplayLabel } from '../../lib/detection';
7
+ /**
8
+ * Run the link-only flow for adding other project knowledge to an existing workspace
9
+ * Now supports detecting workspace-scoped sibling projects, not just global storage
10
+ */
11
+ export async function runLinkProjectsFlow(workspacePath, workspaceName, existingProjects) {
12
+ // Scan for projects using the new detection system
13
+ const detectedProjects = scanForProjects({
14
+ excludeWorkspace: workspaceName,
15
+ workspacePath: workspacePath,
16
+ scanSiblings: true,
17
+ });
18
+ // If legacy string array is passed, use that instead (for backwards compat)
19
+ const projects = existingProjects
20
+ ? existingProjects.map(name => ({ name, source: 'global' }))
21
+ : detectedProjects;
22
+ if (projects.length === 0) {
23
+ outro(pc.yellow('No other projects found. Try setting up another project first.'));
24
+ return;
25
+ }
26
+ const customGlobalPath = getEffectiveRRCEHome(workspacePath);
27
+ // Build options with source labels
28
+ const linkedProjects = await multiselect({
29
+ message: 'Select projects to link:',
30
+ options: projects.map(project => ({
31
+ value: project.name,
32
+ label: project.name,
33
+ hint: pc.dim(getProjectDisplayLabel(project)),
34
+ })),
35
+ required: true,
36
+ });
37
+ if (isCancel(linkedProjects)) {
38
+ cancel('Cancelled.');
39
+ process.exit(0);
40
+ }
41
+ const selectedProjectNames = linkedProjects;
42
+ if (selectedProjectNames.length === 0) {
43
+ outro('No projects selected.');
44
+ return;
45
+ }
46
+ // Get the full DetectedProject objects for selected projects
47
+ const selectedProjects = projects.filter(p => selectedProjectNames.includes(p.name));
48
+ const s = spinner();
49
+ s.start('Linking projects');
50
+ // Update config.yaml with linked projects
51
+ const configFilePath = getConfigPath(workspacePath);
52
+ let configContent = fs.readFileSync(configFilePath, 'utf-8');
53
+ // Check if linked_projects section exists
54
+ if (configContent.includes('linked_projects:')) {
55
+ // Append to existing section - find and update
56
+ const lines = configContent.split('\n');
57
+ const linkedIndex = lines.findIndex(l => l.trim() === 'linked_projects:');
58
+ if (linkedIndex !== -1) {
59
+ // Find where to insert new projects (after existing ones)
60
+ let insertIndex = linkedIndex + 1;
61
+ while (insertIndex < lines.length && lines[insertIndex]?.startsWith(' - ')) {
62
+ insertIndex++;
63
+ }
64
+ // Add new projects that aren't already there
65
+ for (const name of selectedProjectNames) {
66
+ if (!configContent.includes(` - ${name}`)) {
67
+ lines.splice(insertIndex, 0, ` - ${name}`);
68
+ insertIndex++;
69
+ }
70
+ }
71
+ configContent = lines.join('\n');
72
+ }
73
+ }
74
+ else {
75
+ // Add new linked_projects section
76
+ configContent += `\nlinked_projects:\n`;
77
+ selectedProjectNames.forEach(name => {
78
+ configContent += ` - ${name}\n`;
79
+ });
80
+ }
81
+ fs.writeFileSync(configFilePath, configContent);
82
+ // Update VSCode workspace file with full project info (includes refs, tasks)
83
+ generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
84
+ s.stop('Projects linked');
85
+ // Show summary with project sources
86
+ const workspaceFile = `${workspaceName}.code-workspace`;
87
+ const summary = [
88
+ `Linked projects:`,
89
+ ...selectedProjects.map(p => ` ✓ ${p.name} ${pc.dim(`(${p.source})`)}`),
90
+ ``,
91
+ `Workspace file: ${pc.cyan(workspaceFile)}`,
92
+ ``,
93
+ pc.dim('Includes: knowledge, refs, tasks folders'),
94
+ ];
95
+ note(summary.join('\n'), 'Link Summary');
96
+ outro(pc.green(`✓ Projects linked! Open ${pc.bold(workspaceFile)} in VSCode to access linked knowledge.`));
97
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Run the full setup flow for new workspaces
3
+ */
4
+ export declare function runSetupFlow(workspacePath: string, workspaceName: string, existingProjects: string[]): Promise<void>;
@@ -0,0 +1,262 @@
1
+ import { group, select, multiselect, confirm, spinner, note, outro, cancel, isCancel } from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { ensureDir, getAgentPromptPath, syncMetadataToAll, copyDirToAllStoragePaths, checkWriteAccess, getDefaultRRCEHome } from '../../lib/paths';
6
+ import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../../lib/prompts';
7
+ import { copyPromptsToDir } from './utils';
8
+ import { generateVSCodeWorkspace } from './vscode';
9
+ import { directoryAutocomplete, isCancel as isAutocompleteCancel } from '../../lib/autocomplete-prompt';
10
+ /**
11
+ * Run the full setup flow for new workspaces
12
+ */
13
+ export async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
14
+ const s = spinner();
15
+ // Full setup flow
16
+ const config = await group({
17
+ storageMode: () => select({
18
+ message: 'Where should workflow data be stored?',
19
+ options: [
20
+ { value: 'global', label: 'Global (~/.rrce-workflow/)' },
21
+ { value: 'workspace', label: 'Workspace (.rrce-workflow/)' },
22
+ { value: 'both', label: 'Both' },
23
+ ],
24
+ initialValue: 'global',
25
+ }),
26
+ tools: () => multiselect({
27
+ message: 'Which AI tools do you use?',
28
+ options: [
29
+ { value: 'copilot', label: 'GitHub Copilot', hint: 'VSCode' },
30
+ { value: 'antigravity', label: 'Antigravity IDE' },
31
+ ],
32
+ required: false,
33
+ }),
34
+ linkedProjects: () => {
35
+ // Only show if there are other projects to link
36
+ if (existingProjects.length === 0) {
37
+ return Promise.resolve([]);
38
+ }
39
+ return multiselect({
40
+ message: 'Link knowledge from other projects?',
41
+ options: existingProjects.map(name => ({
42
+ value: name,
43
+ label: name,
44
+ hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
45
+ })),
46
+ required: false,
47
+ });
48
+ },
49
+ confirm: () => confirm({
50
+ message: 'Create configuration?',
51
+ initialValue: true,
52
+ }),
53
+ }, {
54
+ onCancel: () => {
55
+ cancel('Setup process cancelled.');
56
+ process.exit(0);
57
+ },
58
+ });
59
+ if (!config.confirm) {
60
+ outro('Setup cancelled by user.');
61
+ process.exit(0);
62
+ }
63
+ // Determine global path for 'global' or 'both' modes
64
+ let customGlobalPath;
65
+ if (config.storageMode === 'global' || config.storageMode === 'both') {
66
+ customGlobalPath = await resolveGlobalPath();
67
+ if (!customGlobalPath) {
68
+ cancel('Setup cancelled - no writable global path available.');
69
+ process.exit(1);
70
+ }
71
+ }
72
+ s.start('Generating configuration');
73
+ try {
74
+ await generateConfiguration({
75
+ storageMode: config.storageMode,
76
+ globalPath: customGlobalPath,
77
+ tools: config.tools,
78
+ linkedProjects: config.linkedProjects,
79
+ }, workspacePath, workspaceName);
80
+ s.stop('Configuration generated');
81
+ // Show summary
82
+ const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, customGlobalPath);
83
+ const summary = [
84
+ `Storage: ${config.storageMode === 'both' ? 'global + workspace' : config.storageMode}`,
85
+ ];
86
+ if (customGlobalPath && customGlobalPath !== getDefaultRRCEHome()) {
87
+ summary.push(`Global path: ${pc.cyan(customGlobalPath)}`);
88
+ }
89
+ if (dataPaths.length > 0) {
90
+ summary.push(`Data paths:`);
91
+ dataPaths.forEach(p => summary.push(` - ${p}`));
92
+ }
93
+ const selectedTools = config.tools;
94
+ if (selectedTools.length > 0) {
95
+ summary.push(`Tools: ${selectedTools.join(', ')}`);
96
+ }
97
+ const linkedProjects = config.linkedProjects;
98
+ if (linkedProjects.length > 0) {
99
+ summary.push(`Linked projects: ${linkedProjects.join(', ')}`);
100
+ summary.push(`Workspace file: ${pc.cyan(`${workspaceName}.code-workspace`)}`);
101
+ }
102
+ note(summary.join('\n'), 'Setup Summary');
103
+ // Show appropriate outro message
104
+ if (linkedProjects.length > 0) {
105
+ outro(pc.green(`✓ Setup complete! Open ${pc.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
106
+ }
107
+ else {
108
+ outro(pc.green(`✓ Setup complete! Your agents are ready to use.`));
109
+ }
110
+ }
111
+ catch (error) {
112
+ s.stop('Error occurred');
113
+ cancel(`Failed to setup: ${error instanceof Error ? error.message : String(error)}`);
114
+ process.exit(1);
115
+ }
116
+ }
117
+ /**
118
+ * Resolve global path - always prompt user to choose default or custom
119
+ */
120
+ async function resolveGlobalPath() {
121
+ const defaultPath = getDefaultRRCEHome();
122
+ const isDefaultWritable = checkWriteAccess(defaultPath);
123
+ // Build options
124
+ const options = [];
125
+ // Default option
126
+ options.push({
127
+ value: 'default',
128
+ label: `Default (${defaultPath})`,
129
+ hint: isDefaultWritable ? pc.green('✓ writable') : pc.red('✗ not writable'),
130
+ });
131
+ // Custom option
132
+ options.push({
133
+ value: 'custom',
134
+ label: 'Custom path',
135
+ hint: 'Specify your own directory',
136
+ });
137
+ const choice = await select({
138
+ message: 'Global storage location:',
139
+ options,
140
+ initialValue: isDefaultWritable ? 'default' : 'custom',
141
+ });
142
+ if (isCancel(choice)) {
143
+ return undefined;
144
+ }
145
+ if (choice === 'default') {
146
+ // Verify it's writable
147
+ if (!isDefaultWritable) {
148
+ note(`${pc.yellow('⚠')} Cannot write to default path:\n ${pc.dim(defaultPath)}\n\nThis can happen when running via npx/bunx in restricted environments.\nPlease choose a custom path instead.`, 'Write Access Issue');
149
+ // Recursively ask again
150
+ return resolveGlobalPath();
151
+ }
152
+ return defaultPath;
153
+ }
154
+ // Custom path input with Tab autocomplete
155
+ const suggestedPath = path.join(process.env.HOME || '~', '.local', 'share', 'rrce-workflow');
156
+ const customPath = await directoryAutocomplete({
157
+ message: 'Enter custom global path:',
158
+ initialValue: suggestedPath,
159
+ hint: 'Tab to autocomplete',
160
+ validate: (value) => {
161
+ if (!value.trim()) {
162
+ return 'Path cannot be empty';
163
+ }
164
+ // Expand ~ to home directory
165
+ const expandedPath = value.startsWith('~')
166
+ ? value.replace('~', process.env.HOME || '')
167
+ : value;
168
+ if (!checkWriteAccess(expandedPath)) {
169
+ return `Cannot write to ${expandedPath}. Please choose a writable path.`;
170
+ }
171
+ return undefined;
172
+ },
173
+ });
174
+ if (isAutocompleteCancel(customPath)) {
175
+ return undefined;
176
+ }
177
+ // Path is already expanded by directoryAutocomplete
178
+ return customPath;
179
+ }
180
+ /**
181
+ * Generate configuration files and directories
182
+ */
183
+ async function generateConfiguration(config, workspacePath, workspaceName) {
184
+ const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
185
+ for (const dataPath of dataPaths) {
186
+ ensureDir(dataPath);
187
+ // Create agent metadata subdirectories (data only, no prompts)
188
+ ensureDir(path.join(dataPath, 'knowledge'));
189
+ ensureDir(path.join(dataPath, 'refs'));
190
+ ensureDir(path.join(dataPath, 'tasks'));
191
+ ensureDir(path.join(dataPath, 'templates'));
192
+ }
193
+ // Get the agent-core directory path
194
+ const agentCoreDir = getAgentCoreDir();
195
+ // Sync metadata (knowledge, refs, tasks) from agent-core to all storage locations
196
+ syncMetadataToAll(agentCoreDir, dataPaths);
197
+ // Also copy templates to all storage locations
198
+ copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', dataPaths);
199
+ // Load prompts for IDE-specific locations
200
+ const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
201
+ // Copy prompts to tool-specific locations (for IDE integration)
202
+ if (config.tools.includes('copilot')) {
203
+ const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
204
+ ensureDir(copilotPath);
205
+ copyPromptsToDir(prompts, copilotPath, '.agent.md');
206
+ }
207
+ if (config.tools.includes('antigravity')) {
208
+ const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
209
+ ensureDir(antigravityPath);
210
+ copyPromptsToDir(prompts, antigravityPath, '.md');
211
+ }
212
+ // Create workspace config (inside .rrce-workflow folder)
213
+ const workspaceConfigPath = path.join(workspacePath, '.rrce-workflow', 'config.yaml');
214
+ ensureDir(path.dirname(workspaceConfigPath));
215
+ let configContent = `# RRCE-Workflow Configuration
216
+ version: 1
217
+
218
+ storage:
219
+ mode: ${config.storageMode}`;
220
+ // Add custom global path if different from default
221
+ if (config.globalPath && config.globalPath !== getDefaultRRCEHome()) {
222
+ configContent += `\n globalPath: "${config.globalPath}"`;
223
+ }
224
+ configContent += `
225
+
226
+ project:
227
+ name: "${workspaceName}"
228
+
229
+ tools:
230
+ copilot: ${config.tools.includes('copilot')}
231
+ antigravity: ${config.tools.includes('antigravity')}
232
+ `;
233
+ // Add linked projects if any
234
+ if (config.linkedProjects.length > 0) {
235
+ configContent += `\nlinked_projects:\n`;
236
+ config.linkedProjects.forEach(name => {
237
+ configContent += ` - ${name}\n`;
238
+ });
239
+ }
240
+ fs.writeFileSync(workspaceConfigPath, configContent);
241
+ // Generate VSCode workspace file if using copilot or has linked projects
242
+ if (config.tools.includes('copilot') || config.linkedProjects.length > 0) {
243
+ generateVSCodeWorkspace(workspacePath, workspaceName, config.linkedProjects, config.globalPath);
244
+ }
245
+ }
246
+ /**
247
+ * Get data paths based on storage mode and custom global path
248
+ */
249
+ function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
250
+ const globalPath = path.join(customGlobalPath || getDefaultRRCEHome(), 'workspaces', workspaceName);
251
+ const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
252
+ switch (mode) {
253
+ case 'global':
254
+ return [globalPath];
255
+ case 'workspace':
256
+ return [workspacePath];
257
+ case 'both':
258
+ return [workspacePath, globalPath];
259
+ default:
260
+ return [globalPath];
261
+ }
262
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Sync workspace knowledge to global storage so other projects can reference it
3
+ */
4
+ export declare function runSyncToGlobalFlow(workspacePath: string, workspaceName: string): Promise<void>;
@@ -0,0 +1,67 @@
1
+ import { confirm, spinner, note, outro, cancel, isCancel } from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { ensureDir, getLocalWorkspacePath, getEffectiveRRCEHome, getConfigPath } from '../../lib/paths';
6
+ import { copyDirRecursive } from './utils';
7
+ /**
8
+ * Sync workspace knowledge to global storage so other projects can reference it
9
+ */
10
+ export async function runSyncToGlobalFlow(workspacePath, workspaceName) {
11
+ const localPath = getLocalWorkspacePath(workspacePath);
12
+ const customGlobalPath = getEffectiveRRCEHome(workspacePath);
13
+ const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
14
+ // Check what exists locally
15
+ const subdirs = ['knowledge', 'prompts', 'templates', 'tasks', 'refs'];
16
+ const existingDirs = subdirs.filter(dir => fs.existsSync(path.join(localPath, dir)));
17
+ if (existingDirs.length === 0) {
18
+ outro(pc.yellow('No data found in workspace storage to sync.'));
19
+ return;
20
+ }
21
+ // Show what will be synced
22
+ note(`The following will be copied to global storage:\n${existingDirs.map(d => ` • ${d}/`).join('\n')}\n\nDestination: ${pc.cyan(globalPath)}`, 'Sync Preview');
23
+ const shouldSync = await confirm({
24
+ message: 'Proceed with sync to global storage?',
25
+ initialValue: true,
26
+ });
27
+ if (isCancel(shouldSync) || !shouldSync) {
28
+ outro('Sync cancelled.');
29
+ return;
30
+ }
31
+ const s = spinner();
32
+ s.start('Syncing to global storage');
33
+ try {
34
+ // Ensure global directory exists
35
+ ensureDir(globalPath);
36
+ // Copy each directory
37
+ for (const dir of existingDirs) {
38
+ const srcDir = path.join(localPath, dir);
39
+ const destDir = path.join(globalPath, dir);
40
+ ensureDir(destDir);
41
+ // Copy files recursively
42
+ copyDirRecursive(srcDir, destDir);
43
+ }
44
+ // Update the config to reflect 'both' mode
45
+ const configFilePath = getConfigPath(workspacePath);
46
+ let configContent = fs.readFileSync(configFilePath, 'utf-8');
47
+ configContent = configContent.replace(/mode:\s*workspace/, 'mode: both');
48
+ fs.writeFileSync(configFilePath, configContent);
49
+ s.stop('Sync complete');
50
+ const summary = [
51
+ `Synced directories:`,
52
+ ...existingDirs.map(d => ` ✓ ${d}/`),
53
+ ``,
54
+ `Global path: ${pc.cyan(globalPath)}`,
55
+ `Storage mode updated to: ${pc.bold('both')}`,
56
+ ``,
57
+ `Other projects can now link this knowledge!`,
58
+ ];
59
+ note(summary.join('\n'), 'Sync Summary');
60
+ outro(pc.green('✓ Workspace knowledge synced to global storage!'));
61
+ }
62
+ catch (error) {
63
+ s.stop('Error occurred');
64
+ cancel(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
65
+ process.exit(1);
66
+ }
67
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Update prompts and templates from the package without resetting config
3
+ */
4
+ export declare function runUpdateFlow(workspacePath: string, workspaceName: string, currentStorageMode: string | null): Promise<void>;