rrce-workflow 0.1.5 → 0.2.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,356 @@
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 (inside .rrce-workflow folder)
294
+ const workspaceConfigPath = path.join(workspacePath, '.rrce-workflow', 'config.yaml');
295
+ ensureDir(path.dirname(workspaceConfigPath));
296
+
297
+ let configContent = `# RRCE-Workflow Configuration
298
+ version: 1
299
+
300
+ storage:
301
+ mode: ${config.storageMode}`;
302
+
303
+ // Add custom global path if different from default
304
+ if (config.globalPath && config.globalPath !== getDefaultRRCEHome()) {
305
+ configContent += `\n globalPath: "${config.globalPath}"`;
306
+ }
307
+
308
+ configContent += `
309
+
310
+ project:
311
+ name: "${workspaceName}"
312
+
313
+ tools:
314
+ copilot: ${config.tools.includes('copilot')}
315
+ antigravity: ${config.tools.includes('antigravity')}
316
+ `;
317
+
318
+ // Add linked projects if any
319
+ if (config.linkedProjects.length > 0) {
320
+ configContent += `\nlinked_projects:\n`;
321
+ config.linkedProjects.forEach(name => {
322
+ configContent += ` - ${name}\n`;
323
+ });
324
+ }
325
+
326
+ fs.writeFileSync(workspaceConfigPath, configContent);
327
+
328
+ // Generate VSCode workspace file if using copilot or has linked projects
329
+ if (config.tools.includes('copilot') || config.linkedProjects.length > 0) {
330
+ generateVSCodeWorkspace(workspacePath, workspaceName, config.linkedProjects, config.globalPath);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Get data paths based on storage mode and custom global path
336
+ */
337
+ function getDataPaths(
338
+ mode: StorageMode,
339
+ workspaceName: string,
340
+ workspaceRoot: string,
341
+ customGlobalPath?: string
342
+ ): string[] {
343
+ const globalPath = path.join(customGlobalPath || getDefaultRRCEHome(), 'workspaces', workspaceName);
344
+ const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
345
+
346
+ switch (mode) {
347
+ case 'global':
348
+ return [globalPath];
349
+ case 'workspace':
350
+ return [workspacePath];
351
+ case 'both':
352
+ return [workspacePath, globalPath];
353
+ default:
354
+ return [globalPath];
355
+ }
356
+ }
@@ -0,0 +1,93 @@
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
+ getConfigPath
11
+ } from '../../lib/paths';
12
+ import { copyDirRecursive } from './utils';
13
+
14
+ /**
15
+ * Sync workspace knowledge to global storage so other projects can reference it
16
+ */
17
+ export async function runSyncToGlobalFlow(workspacePath: string, workspaceName: string) {
18
+ const localPath = getLocalWorkspacePath(workspacePath);
19
+ const customGlobalPath = getEffectiveRRCEHome(workspacePath);
20
+ const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
21
+
22
+ // Check what exists locally
23
+ const subdirs = ['knowledge', 'prompts', 'templates', 'tasks', 'refs'];
24
+ const existingDirs = subdirs.filter(dir =>
25
+ fs.existsSync(path.join(localPath, dir))
26
+ );
27
+
28
+ if (existingDirs.length === 0) {
29
+ outro(pc.yellow('No data found in workspace storage to sync.'));
30
+ return;
31
+ }
32
+
33
+ // Show what will be synced
34
+ note(
35
+ `The following will be copied to global storage:\n${existingDirs.map(d => ` • ${d}/`).join('\n')}\n\nDestination: ${pc.cyan(globalPath)}`,
36
+ 'Sync Preview'
37
+ );
38
+
39
+ const shouldSync = await confirm({
40
+ message: 'Proceed with sync to global storage?',
41
+ initialValue: true,
42
+ });
43
+
44
+ if (isCancel(shouldSync) || !shouldSync) {
45
+ outro('Sync cancelled.');
46
+ return;
47
+ }
48
+
49
+ const s = spinner();
50
+ s.start('Syncing to global storage');
51
+
52
+ try {
53
+ // Ensure global directory exists
54
+ ensureDir(globalPath);
55
+
56
+ // Copy each directory
57
+ for (const dir of existingDirs) {
58
+ const srcDir = path.join(localPath, dir);
59
+ const destDir = path.join(globalPath, dir);
60
+ ensureDir(destDir);
61
+
62
+ // Copy files recursively
63
+ copyDirRecursive(srcDir, destDir);
64
+ }
65
+
66
+ // Update the config to reflect 'both' mode
67
+ const configFilePath = getConfigPath(workspacePath);
68
+ let configContent = fs.readFileSync(configFilePath, 'utf-8');
69
+ configContent = configContent.replace(/mode:\s*workspace/, 'mode: both');
70
+ fs.writeFileSync(configFilePath, configContent);
71
+
72
+ s.stop('Sync complete');
73
+
74
+ const summary = [
75
+ `Synced directories:`,
76
+ ...existingDirs.map(d => ` ✓ ${d}/`),
77
+ ``,
78
+ `Global path: ${pc.cyan(globalPath)}`,
79
+ `Storage mode updated to: ${pc.bold('both')}`,
80
+ ``,
81
+ `Other projects can now link this knowledge!`,
82
+ ];
83
+
84
+ note(summary.join('\n'), 'Sync Summary');
85
+
86
+ outro(pc.green('✓ Workspace knowledge synced to global storage!'));
87
+
88
+ } catch (error) {
89
+ s.stop('Error occurred');
90
+ cancel(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
91
+ process.exit(1);
92
+ }
93
+ }
@@ -0,0 +1,129 @@
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
+ getConfigPath
13
+ } from '../../lib/paths';
14
+ import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../../lib/prompts';
15
+ import { copyPromptsToDir } from './utils';
16
+
17
+ /**
18
+ * Update prompts and templates from the package without resetting config
19
+ */
20
+ export async function runUpdateFlow(
21
+ workspacePath: string,
22
+ workspaceName: string,
23
+ currentStorageMode: string | null
24
+ ) {
25
+ const s = spinner();
26
+ s.start('Checking for updates');
27
+
28
+ try {
29
+ const agentCoreDir = getAgentCoreDir();
30
+ const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
31
+
32
+ // Determine storage paths based on current mode
33
+ const mode = (currentStorageMode as StorageMode) || 'global';
34
+
35
+ // Use effective RRCE_HOME from config for path resolution
36
+ const customGlobalPath = getEffectiveRRCEHome(workspacePath);
37
+ const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
38
+
39
+ s.stop('Updates found');
40
+
41
+ // Show what will be updated
42
+ note(
43
+ `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')}`,
44
+ 'Update Preview'
45
+ );
46
+
47
+ const shouldUpdate = await confirm({
48
+ message: 'Proceed with update?',
49
+ initialValue: true,
50
+ });
51
+
52
+ if (isCancel(shouldUpdate) || !shouldUpdate) {
53
+ outro('Update cancelled.');
54
+ return;
55
+ }
56
+
57
+ s.start('Updating from package');
58
+
59
+ // Update prompts and templates in all storage locations
60
+ for (const dataPath of dataPaths) {
61
+ // Update prompts
62
+ const promptsDir = path.join(dataPath, 'prompts');
63
+ ensureDir(promptsDir);
64
+ copyPromptsToDir(prompts, promptsDir, '.md');
65
+
66
+ // Update templates
67
+ copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', [dataPath]);
68
+ }
69
+
70
+ // Also update tool-specific locations if configured
71
+ const configFilePath = getConfigPath(workspacePath);
72
+ const configContent = fs.readFileSync(configFilePath, 'utf-8');
73
+
74
+ if (configContent.includes('copilot: true')) {
75
+ const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
76
+ ensureDir(copilotPath);
77
+ copyPromptsToDir(prompts, copilotPath, '.agent.md');
78
+ }
79
+
80
+ if (configContent.includes('antigravity: true')) {
81
+ const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
82
+ ensureDir(antigravityPath);
83
+ copyPromptsToDir(prompts, antigravityPath, '.md');
84
+ }
85
+
86
+ s.stop('Update complete');
87
+
88
+ const summary = [
89
+ `Updated:`,
90
+ ` ✓ ${prompts.length} agent prompts`,
91
+ ` ✓ Output templates`,
92
+ ``,
93
+ `Your configuration and knowledge files were preserved.`,
94
+ ];
95
+
96
+ note(summary.join('\n'), 'Update Summary');
97
+
98
+ outro(pc.green('✓ Successfully updated from package!'));
99
+
100
+ } catch (error) {
101
+ s.stop('Error occurred');
102
+ cancel(`Failed to update: ${error instanceof Error ? error.message : String(error)}`);
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Resolve all data paths with custom global path support
109
+ */
110
+ function resolveAllDataPathsWithCustomGlobal(
111
+ mode: StorageMode,
112
+ workspaceName: string,
113
+ workspaceRoot: string,
114
+ customGlobalPath: string
115
+ ): string[] {
116
+ const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
117
+ const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
118
+
119
+ switch (mode) {
120
+ case 'global':
121
+ return [globalPath];
122
+ case 'workspace':
123
+ return [workspacePath];
124
+ case 'both':
125
+ return [workspacePath, globalPath];
126
+ default:
127
+ return [globalPath];
128
+ }
129
+ }
@@ -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