rrce-workflow 0.1.4 → 0.1.6

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.
@@ -0,0 +1,354 @@
1
+ import { group, text, 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 type { StorageMode } from '../../types/prompt';
6
+ import {
7
+ ensureDir,
8
+ getAgentPromptPath,
9
+ syncMetadataToAll,
10
+ copyDirToAllStoragePaths,
11
+ checkWriteAccess,
12
+ getDefaultRRCEHome
13
+ } from '../../lib/paths';
14
+ import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../../lib/prompts';
15
+ import { copyPromptsToDir } from './utils';
16
+ import { generateVSCodeWorkspace } from './vscode';
17
+
18
+ interface SetupConfig {
19
+ storageMode: StorageMode;
20
+ globalPath?: string;
21
+ tools: string[];
22
+ linkedProjects: string[];
23
+ }
24
+
25
+ /**
26
+ * Run the full setup flow for new workspaces
27
+ */
28
+ export async function runSetupFlow(
29
+ workspacePath: string,
30
+ workspaceName: string,
31
+ existingProjects: string[]
32
+ ): Promise<void> {
33
+ const s = spinner();
34
+
35
+ // Full setup flow
36
+ const config = await group(
37
+ {
38
+ storageMode: () =>
39
+ select({
40
+ message: 'Where should workflow data be stored?',
41
+ options: [
42
+ { value: 'global', label: 'Global (~/.rrce-workflow/)' },
43
+ { value: 'workspace', label: 'Workspace (.rrce-workflow/)' },
44
+ { value: 'both', label: 'Both' },
45
+ ],
46
+ initialValue: 'global',
47
+ }),
48
+ tools: () =>
49
+ multiselect({
50
+ message: 'Which AI tools do you use?',
51
+ options: [
52
+ { value: 'copilot', label: 'GitHub Copilot', hint: 'VSCode' },
53
+ { value: 'antigravity', label: 'Antigravity IDE' },
54
+ ],
55
+ required: false,
56
+ }),
57
+ linkedProjects: () => {
58
+ // Only show if there are other projects to link
59
+ if (existingProjects.length === 0) {
60
+ return Promise.resolve([]);
61
+ }
62
+ return multiselect({
63
+ message: 'Link knowledge from other projects?',
64
+ options: existingProjects.map(name => ({
65
+ value: name,
66
+ label: name,
67
+ hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
68
+ })),
69
+ required: false,
70
+ });
71
+ },
72
+ confirm: () =>
73
+ confirm({
74
+ message: 'Create configuration?',
75
+ initialValue: true,
76
+ }),
77
+ },
78
+ {
79
+ onCancel: () => {
80
+ cancel('Setup process cancelled.');
81
+ process.exit(0);
82
+ },
83
+ }
84
+ );
85
+
86
+ if (!config.confirm) {
87
+ outro('Setup cancelled by user.');
88
+ process.exit(0);
89
+ }
90
+
91
+ // Determine global path for 'global' or 'both' modes
92
+ let customGlobalPath: string | undefined;
93
+
94
+ if (config.storageMode === 'global' || config.storageMode === 'both') {
95
+ customGlobalPath = await resolveGlobalPath();
96
+ if (!customGlobalPath) {
97
+ cancel('Setup cancelled - no writable global path available.');
98
+ process.exit(1);
99
+ }
100
+ }
101
+
102
+ s.start('Generating configuration');
103
+
104
+ try {
105
+ await generateConfiguration({
106
+ storageMode: config.storageMode as StorageMode,
107
+ globalPath: customGlobalPath,
108
+ tools: config.tools as string[],
109
+ linkedProjects: config.linkedProjects as string[],
110
+ }, workspacePath, workspaceName);
111
+
112
+ s.stop('Configuration generated');
113
+
114
+ // Show summary
115
+ const dataPaths = getDataPaths(
116
+ config.storageMode as StorageMode,
117
+ workspaceName,
118
+ workspacePath,
119
+ customGlobalPath
120
+ );
121
+
122
+ const summary = [
123
+ `Storage: ${config.storageMode === 'both' ? 'global + workspace' : config.storageMode}`,
124
+ ];
125
+
126
+ if (customGlobalPath && customGlobalPath !== getDefaultRRCEHome()) {
127
+ summary.push(`Global path: ${pc.cyan(customGlobalPath)}`);
128
+ }
129
+
130
+ if (dataPaths.length > 0) {
131
+ summary.push(`Data paths:`);
132
+ dataPaths.forEach(p => summary.push(` - ${p}`));
133
+ }
134
+
135
+ const selectedTools = config.tools as string[];
136
+ if (selectedTools.length > 0) {
137
+ summary.push(`Tools: ${selectedTools.join(', ')}`);
138
+ }
139
+
140
+ const linkedProjects = config.linkedProjects as string[];
141
+ if (linkedProjects.length > 0) {
142
+ summary.push(`Linked projects: ${linkedProjects.join(', ')}`);
143
+ summary.push(`Workspace file: ${pc.cyan(`${workspaceName}.code-workspace`)}`);
144
+ }
145
+
146
+ note(summary.join('\n'), 'Setup Summary');
147
+
148
+ // Show appropriate outro message
149
+ if (linkedProjects.length > 0) {
150
+ outro(pc.green(`✓ Setup complete! Open ${pc.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
151
+ } else {
152
+ outro(pc.green(`✓ Setup complete! Your agents are ready to use.`));
153
+ }
154
+
155
+ } catch (error) {
156
+ s.stop('Error occurred');
157
+ cancel(`Failed to setup: ${error instanceof Error ? error.message : String(error)}`);
158
+ process.exit(1);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Resolve global path - always prompt user to choose default or custom
164
+ */
165
+ async function resolveGlobalPath(): Promise<string | undefined> {
166
+ const defaultPath = getDefaultRRCEHome();
167
+ const isDefaultWritable = checkWriteAccess(defaultPath);
168
+
169
+ // Build options
170
+ const options: { value: string; label: string; hint?: string }[] = [];
171
+
172
+ // Default option
173
+ options.push({
174
+ value: 'default',
175
+ label: `Default (${defaultPath})`,
176
+ hint: isDefaultWritable ? pc.green('✓ writable') : pc.red('✗ not writable'),
177
+ });
178
+
179
+ // Custom option
180
+ options.push({
181
+ value: 'custom',
182
+ label: 'Custom path',
183
+ hint: 'Specify your own directory',
184
+ });
185
+
186
+ const choice = await select({
187
+ message: 'Global storage location:',
188
+ options,
189
+ initialValue: isDefaultWritable ? 'default' : 'custom',
190
+ });
191
+
192
+ if (isCancel(choice)) {
193
+ return undefined;
194
+ }
195
+
196
+ if (choice === 'default') {
197
+ // Verify it's writable
198
+ if (!isDefaultWritable) {
199
+ note(
200
+ `${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.`,
201
+ 'Write Access Issue'
202
+ );
203
+ // Recursively ask again
204
+ return resolveGlobalPath();
205
+ }
206
+ return defaultPath;
207
+ }
208
+
209
+ // Custom path input
210
+ const customPath = await text({
211
+ message: 'Enter custom global path:',
212
+ placeholder: path.join(process.env.HOME || '~', '.local', 'share', 'rrce-workflow'),
213
+ validate: (value) => {
214
+ if (!value.trim()) {
215
+ return 'Path cannot be empty';
216
+ }
217
+ // Expand ~ to home directory
218
+ const expandedPath = value.startsWith('~')
219
+ ? value.replace('~', process.env.HOME || '')
220
+ : value;
221
+
222
+ if (!checkWriteAccess(expandedPath)) {
223
+ return `Cannot write to ${expandedPath}. Please choose a writable path.`;
224
+ }
225
+ return undefined;
226
+ },
227
+ });
228
+
229
+ if (isCancel(customPath)) {
230
+ return undefined;
231
+ }
232
+
233
+ // Expand ~ if present
234
+ const expandedPath = (customPath as string).startsWith('~')
235
+ ? (customPath as string).replace('~', process.env.HOME || '')
236
+ : customPath as string;
237
+
238
+ return expandedPath;
239
+ }
240
+
241
+ /**
242
+ * Generate configuration files and directories
243
+ */
244
+ async function generateConfiguration(
245
+ config: SetupConfig,
246
+ workspacePath: string,
247
+ workspaceName: string
248
+ ): Promise<void> {
249
+ const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
250
+
251
+ for (const dataPath of dataPaths) {
252
+ ensureDir(dataPath);
253
+ // Create agent metadata subdirectories
254
+ ensureDir(path.join(dataPath, 'knowledge'));
255
+ ensureDir(path.join(dataPath, 'refs'));
256
+ ensureDir(path.join(dataPath, 'tasks'));
257
+ ensureDir(path.join(dataPath, 'templates'));
258
+ ensureDir(path.join(dataPath, 'prompts'));
259
+ }
260
+
261
+ // Get the agent-core directory path
262
+ const agentCoreDir = getAgentCoreDir();
263
+
264
+ // Sync metadata (knowledge, refs, tasks) from agent-core to all storage locations
265
+ syncMetadataToAll(agentCoreDir, dataPaths);
266
+
267
+ // Also copy templates to all storage locations
268
+ copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', dataPaths);
269
+
270
+ // Load prompts
271
+ const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
272
+
273
+ // Copy prompts to all storage locations (for cross-project access)
274
+ for (const dataPath of dataPaths) {
275
+ const promptsDir = path.join(dataPath, 'prompts');
276
+ ensureDir(promptsDir);
277
+ copyPromptsToDir(prompts, promptsDir, '.md');
278
+ }
279
+
280
+ // Copy prompts to tool-specific locations (for IDE integration)
281
+ if (config.tools.includes('copilot')) {
282
+ const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
283
+ ensureDir(copilotPath);
284
+ copyPromptsToDir(prompts, copilotPath, '.agent.md');
285
+ }
286
+
287
+ if (config.tools.includes('antigravity')) {
288
+ const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
289
+ ensureDir(antigravityPath);
290
+ copyPromptsToDir(prompts, antigravityPath, '.md');
291
+ }
292
+
293
+ // Create workspace config
294
+ const workspaceConfigPath = path.join(workspacePath, '.rrce-workflow.yaml');
295
+ let configContent = `# RRCE-Workflow Configuration
296
+ version: 1
297
+
298
+ storage:
299
+ mode: ${config.storageMode}`;
300
+
301
+ // Add custom global path if different from default
302
+ if (config.globalPath && config.globalPath !== getDefaultRRCEHome()) {
303
+ configContent += `\n globalPath: "${config.globalPath}"`;
304
+ }
305
+
306
+ configContent += `
307
+
308
+ project:
309
+ name: "${workspaceName}"
310
+
311
+ tools:
312
+ copilot: ${config.tools.includes('copilot')}
313
+ antigravity: ${config.tools.includes('antigravity')}
314
+ `;
315
+
316
+ // Add linked projects if any
317
+ if (config.linkedProjects.length > 0) {
318
+ configContent += `\nlinked_projects:\n`;
319
+ config.linkedProjects.forEach(name => {
320
+ configContent += ` - ${name}\n`;
321
+ });
322
+ }
323
+
324
+ fs.writeFileSync(workspaceConfigPath, configContent);
325
+
326
+ // Generate VSCode workspace file if using copilot or has linked projects
327
+ if (config.tools.includes('copilot') || config.linkedProjects.length > 0) {
328
+ generateVSCodeWorkspace(workspacePath, workspaceName, config.linkedProjects, config.globalPath);
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Get data paths based on storage mode and custom global path
334
+ */
335
+ function getDataPaths(
336
+ mode: StorageMode,
337
+ workspaceName: string,
338
+ workspaceRoot: string,
339
+ customGlobalPath?: string
340
+ ): string[] {
341
+ const globalPath = path.join(customGlobalPath || getDefaultRRCEHome(), 'workspaces', workspaceName);
342
+ const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
343
+
344
+ switch (mode) {
345
+ case 'global':
346
+ return [globalPath];
347
+ case 'workspace':
348
+ return [workspacePath];
349
+ case 'both':
350
+ return [workspacePath, globalPath];
351
+ default:
352
+ return [globalPath];
353
+ }
354
+ }
@@ -0,0 +1,92 @@
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 {
6
+ ensureDir,
7
+ getLocalWorkspacePath,
8
+ getGlobalWorkspacePath,
9
+ getEffectiveRRCEHome
10
+ } from '../../lib/paths';
11
+ import { copyDirRecursive } from './utils';
12
+
13
+ /**
14
+ * Sync workspace knowledge to global storage so other projects can reference it
15
+ */
16
+ export async function runSyncToGlobalFlow(workspacePath: string, workspaceName: string) {
17
+ const localPath = getLocalWorkspacePath(workspacePath);
18
+ const customGlobalPath = getEffectiveRRCEHome(workspacePath);
19
+ const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
20
+
21
+ // Check what exists locally
22
+ const subdirs = ['knowledge', 'prompts', 'templates', 'tasks', 'refs'];
23
+ const existingDirs = subdirs.filter(dir =>
24
+ fs.existsSync(path.join(localPath, dir))
25
+ );
26
+
27
+ if (existingDirs.length === 0) {
28
+ outro(pc.yellow('No data found in workspace storage to sync.'));
29
+ return;
30
+ }
31
+
32
+ // Show what will be synced
33
+ note(
34
+ `The following will be copied to global storage:\n${existingDirs.map(d => ` • ${d}/`).join('\n')}\n\nDestination: ${pc.cyan(globalPath)}`,
35
+ 'Sync Preview'
36
+ );
37
+
38
+ const shouldSync = await confirm({
39
+ message: 'Proceed with sync to global storage?',
40
+ initialValue: true,
41
+ });
42
+
43
+ if (isCancel(shouldSync) || !shouldSync) {
44
+ outro('Sync cancelled.');
45
+ return;
46
+ }
47
+
48
+ const s = spinner();
49
+ s.start('Syncing to global storage');
50
+
51
+ try {
52
+ // Ensure global directory exists
53
+ ensureDir(globalPath);
54
+
55
+ // Copy each directory
56
+ for (const dir of existingDirs) {
57
+ const srcDir = path.join(localPath, dir);
58
+ const destDir = path.join(globalPath, dir);
59
+ ensureDir(destDir);
60
+
61
+ // Copy files recursively
62
+ copyDirRecursive(srcDir, destDir);
63
+ }
64
+
65
+ // Update the config to reflect 'both' mode
66
+ const configFilePath = path.join(workspacePath, '.rrce-workflow.yaml');
67
+ let configContent = fs.readFileSync(configFilePath, 'utf-8');
68
+ configContent = configContent.replace(/mode:\s*workspace/, 'mode: both');
69
+ fs.writeFileSync(configFilePath, configContent);
70
+
71
+ s.stop('Sync complete');
72
+
73
+ const summary = [
74
+ `Synced directories:`,
75
+ ...existingDirs.map(d => ` ✓ ${d}/`),
76
+ ``,
77
+ `Global path: ${pc.cyan(globalPath)}`,
78
+ `Storage mode updated to: ${pc.bold('both')}`,
79
+ ``,
80
+ `Other projects can now link this knowledge!`,
81
+ ];
82
+
83
+ note(summary.join('\n'), 'Sync Summary');
84
+
85
+ outro(pc.green('✓ Workspace knowledge synced to global storage!'));
86
+
87
+ } catch (error) {
88
+ s.stop('Error occurred');
89
+ cancel(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
90
+ process.exit(1);
91
+ }
92
+ }
@@ -0,0 +1,128 @@
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 type { StorageMode } from '../../types/prompt';
6
+ import {
7
+ ensureDir,
8
+ resolveAllDataPaths,
9
+ getAgentPromptPath,
10
+ copyDirToAllStoragePaths,
11
+ getEffectiveRRCEHome
12
+ } from '../../lib/paths';
13
+ import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../../lib/prompts';
14
+ import { copyPromptsToDir } from './utils';
15
+
16
+ /**
17
+ * Update prompts and templates from the package without resetting config
18
+ */
19
+ export async function runUpdateFlow(
20
+ workspacePath: string,
21
+ workspaceName: string,
22
+ currentStorageMode: string | null
23
+ ) {
24
+ const s = spinner();
25
+ s.start('Checking for updates');
26
+
27
+ try {
28
+ const agentCoreDir = getAgentCoreDir();
29
+ const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
30
+
31
+ // Determine storage paths based on current mode
32
+ const mode = (currentStorageMode as StorageMode) || 'global';
33
+
34
+ // Use effective RRCE_HOME from config for path resolution
35
+ const customGlobalPath = getEffectiveRRCEHome(workspacePath);
36
+ const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
37
+
38
+ s.stop('Updates found');
39
+
40
+ // Show what will be updated
41
+ note(
42
+ `The following will be updated from the package:\n • prompts/ (${prompts.length} agent prompts)\n • templates/ (output templates)\n\nTarget locations:\n${dataPaths.map(p => ` • ${p}`).join('\n')}`,
43
+ 'Update Preview'
44
+ );
45
+
46
+ const shouldUpdate = await confirm({
47
+ message: 'Proceed with update?',
48
+ initialValue: true,
49
+ });
50
+
51
+ if (isCancel(shouldUpdate) || !shouldUpdate) {
52
+ outro('Update cancelled.');
53
+ return;
54
+ }
55
+
56
+ s.start('Updating from package');
57
+
58
+ // Update prompts and templates in all storage locations
59
+ for (const dataPath of dataPaths) {
60
+ // Update prompts
61
+ const promptsDir = path.join(dataPath, 'prompts');
62
+ ensureDir(promptsDir);
63
+ copyPromptsToDir(prompts, promptsDir, '.md');
64
+
65
+ // Update templates
66
+ copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', [dataPath]);
67
+ }
68
+
69
+ // Also update tool-specific locations if configured
70
+ const configFilePath = path.join(workspacePath, '.rrce-workflow.yaml');
71
+ const configContent = fs.readFileSync(configFilePath, 'utf-8');
72
+
73
+ if (configContent.includes('copilot: true')) {
74
+ const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
75
+ ensureDir(copilotPath);
76
+ copyPromptsToDir(prompts, copilotPath, '.agent.md');
77
+ }
78
+
79
+ if (configContent.includes('antigravity: true')) {
80
+ const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
81
+ ensureDir(antigravityPath);
82
+ copyPromptsToDir(prompts, antigravityPath, '.md');
83
+ }
84
+
85
+ s.stop('Update complete');
86
+
87
+ const summary = [
88
+ `Updated:`,
89
+ ` ✓ ${prompts.length} agent prompts`,
90
+ ` ✓ Output templates`,
91
+ ``,
92
+ `Your configuration and knowledge files were preserved.`,
93
+ ];
94
+
95
+ note(summary.join('\n'), 'Update Summary');
96
+
97
+ outro(pc.green('✓ Successfully updated from package!'));
98
+
99
+ } catch (error) {
100
+ s.stop('Error occurred');
101
+ cancel(`Failed to update: ${error instanceof Error ? error.message : String(error)}`);
102
+ process.exit(1);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Resolve all data paths with custom global path support
108
+ */
109
+ function resolveAllDataPathsWithCustomGlobal(
110
+ mode: StorageMode,
111
+ workspaceName: string,
112
+ workspaceRoot: string,
113
+ customGlobalPath: string
114
+ ): string[] {
115
+ const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
116
+ const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
117
+
118
+ switch (mode) {
119
+ case 'global':
120
+ return [globalPath];
121
+ case 'workspace':
122
+ return [workspacePath];
123
+ case 'both':
124
+ return [workspacePath, globalPath];
125
+ default:
126
+ return [globalPath];
127
+ }
128
+ }
@@ -0,0 +1,38 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { ensureDir } from '../../lib/paths';
4
+ import type { ParsedPrompt } from '../../types/prompt';
5
+
6
+ /**
7
+ * Copy parsed prompts to a target directory with specified extension
8
+ */
9
+ export function copyPromptsToDir(prompts: ParsedPrompt[], targetDir: string, extension: string) {
10
+ for (const prompt of prompts) {
11
+ const baseName = path.basename(prompt.filePath, '.md');
12
+ const targetName = baseName + extension;
13
+ const targetPath = path.join(targetDir, targetName);
14
+
15
+ // Read the full content including frontmatter
16
+ const content = fs.readFileSync(prompt.filePath, 'utf-8');
17
+ fs.writeFileSync(targetPath, content);
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Recursively copy a directory
23
+ */
24
+ export function copyDirRecursive(src: string, dest: string) {
25
+ const entries = fs.readdirSync(src, { withFileTypes: true });
26
+
27
+ for (const entry of entries) {
28
+ const srcPath = path.join(src, entry.name);
29
+ const destPath = path.join(dest, entry.name);
30
+
31
+ if (entry.isDirectory()) {
32
+ ensureDir(destPath);
33
+ copyDirRecursive(srcPath, destPath);
34
+ } else {
35
+ fs.copyFileSync(srcPath, destPath);
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,66 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { getRRCEHome } from '../../lib/paths';
4
+
5
+ interface VSCodeWorkspaceFolder {
6
+ path: string;
7
+ name?: string;
8
+ }
9
+
10
+ interface VSCodeWorkspace {
11
+ folders: VSCodeWorkspaceFolder[];
12
+ settings?: Record<string, unknown>;
13
+ }
14
+
15
+ /**
16
+ * Generate or update VSCode workspace file with linked project knowledge folders
17
+ */
18
+ export function generateVSCodeWorkspace(
19
+ workspacePath: string,
20
+ workspaceName: string,
21
+ linkedProjects: string[],
22
+ customGlobalPath?: string
23
+ ) {
24
+ const workspaceFilePath = path.join(workspacePath, `${workspaceName}.code-workspace`);
25
+
26
+ let workspace: VSCodeWorkspace;
27
+
28
+ // Check if workspace file already exists
29
+ if (fs.existsSync(workspaceFilePath)) {
30
+ try {
31
+ const content = fs.readFileSync(workspaceFilePath, 'utf-8');
32
+ workspace = JSON.parse(content);
33
+ } catch {
34
+ // If parse fails, create new
35
+ workspace = { folders: [] };
36
+ }
37
+ } else {
38
+ workspace = { folders: [] };
39
+ }
40
+
41
+ // Ensure main workspace folder is first
42
+ const mainFolder: VSCodeWorkspaceFolder = { path: '.' };
43
+ const existingMainIndex = workspace.folders.findIndex(f => f.path === '.');
44
+ if (existingMainIndex === -1) {
45
+ workspace.folders.unshift(mainFolder);
46
+ }
47
+
48
+ // Add linked project knowledge folders
49
+ const rrceHome = customGlobalPath || getRRCEHome();
50
+ for (const projectName of linkedProjects) {
51
+ const knowledgePath = path.join(rrceHome, 'workspaces', projectName, 'knowledge');
52
+ const folderEntry: VSCodeWorkspaceFolder = {
53
+ path: knowledgePath,
54
+ name: `📚 ${projectName} (knowledge)`
55
+ };
56
+
57
+ // Check if already exists
58
+ const existingIndex = workspace.folders.findIndex(f => f.path === knowledgePath);
59
+ if (existingIndex === -1) {
60
+ workspace.folders.push(folderEntry);
61
+ }
62
+ }
63
+
64
+ // Write workspace file
65
+ fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
66
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { runWizard } from './commands/wizard';
1
+ import { runWizard } from './commands/wizard/index';
2
2
  import { runSelector } from './commands/selector';
3
3
 
4
4
  // Get command from args