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.
@@ -1,612 +0,0 @@
1
- import { intro, 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 { getGitUser } from '../lib/git';
6
- import { detectWorkspaceRoot, getWorkspaceName, resolveAllDataPaths, ensureDir, getAgentPromptPath, syncMetadataToAll, copyDirToAllStoragePaths, listGlobalProjects, getGlobalProjectKnowledgePath, getRRCEHome, getGlobalWorkspacePath, getLocalWorkspacePath } from '../lib/paths';
7
- import type { StorageMode } from '../types/prompt';
8
- import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../lib/prompts';
9
-
10
- import type { ParsedPrompt } from '../types/prompt';
11
-
12
- export async function runWizard() {
13
- intro(pc.cyan(pc.inverse(' RRCE-Workflow Setup ')));
14
-
15
- const s = spinner();
16
- s.start('Detecting environment');
17
-
18
- const workspacePath = detectWorkspaceRoot();
19
- const workspaceName = getWorkspaceName(workspacePath);
20
- const gitUser = getGitUser();
21
-
22
- await new Promise(r => setTimeout(r, 800)); // Dramatic pause
23
- s.stop('Environment detected');
24
-
25
- note(
26
- `Git User: ${pc.bold(gitUser || '(not found)')}
27
- Workspace: ${pc.bold(workspaceName)}`,
28
- 'Context'
29
- );
30
-
31
- // Check for existing projects in global storage
32
- const existingProjects = listGlobalProjects(workspaceName);
33
-
34
- // Check if already configured
35
- const configFilePath = path.join(workspacePath, '.rrce-workflow.yaml');
36
- const isAlreadyConfigured = fs.existsSync(configFilePath);
37
-
38
- // Check current storage mode from config
39
- let currentStorageMode: string | null = null;
40
- if (isAlreadyConfigured) {
41
- try {
42
- const configContent = fs.readFileSync(configFilePath, 'utf-8');
43
- const modeMatch = configContent.match(/mode:\s*(global|workspace|both)/);
44
- currentStorageMode = modeMatch?.[1] ?? null;
45
- } catch {
46
- // Ignore parse errors
47
- }
48
- }
49
-
50
- // Check if workspace has local data that could be synced
51
- const localDataPath = path.join(workspacePath, '.rrce-workflow');
52
- const hasLocalData = fs.existsSync(localDataPath);
53
-
54
- // If already configured, show menu
55
- if (isAlreadyConfigured) {
56
- const menuOptions: { value: string; label: string; hint?: string }[] = [];
57
-
58
- // Add link option if other projects exist
59
- if (existingProjects.length > 0) {
60
- menuOptions.push({
61
- value: 'link',
62
- label: 'Link other project knowledge',
63
- hint: `${existingProjects.length} projects available`
64
- });
65
- }
66
-
67
- // Add sync to global option if using workspace-only mode
68
- if (currentStorageMode === 'workspace' && hasLocalData) {
69
- menuOptions.push({
70
- value: 'sync-global',
71
- label: 'Sync to global storage',
72
- hint: 'Share knowledge with other projects'
73
- });
74
- }
75
-
76
- menuOptions.push({ value: 'update', label: 'Update from package', hint: 'Get latest prompts & templates' });
77
- menuOptions.push({ value: 'exit', label: 'Exit' });
78
-
79
- const action = await select({
80
- message: 'This workspace is already configured. What would you like to do?',
81
- options: menuOptions,
82
- });
83
-
84
- if (isCancel(action) || action === 'exit') {
85
- outro('Exited.');
86
- process.exit(0);
87
- }
88
-
89
- if (action === 'link') {
90
- await runLinkProjectsFlow(workspacePath, workspaceName, existingProjects);
91
- return;
92
- }
93
-
94
- if (action === 'sync-global') {
95
- await runSyncToGlobalFlow(workspacePath, workspaceName);
96
- return;
97
- }
98
-
99
- if (action === 'update') {
100
- await runUpdateFlow(workspacePath, workspaceName, currentStorageMode);
101
- return;
102
- }
103
- }
104
-
105
- // Full setup flow
106
- const config = await group(
107
- {
108
- storageMode: () =>
109
- select({
110
- message: 'Where should workflow data be stored?',
111
- options: [
112
- { value: 'global', label: 'Global (~/.rrce-workflow/)' },
113
- { value: 'workspace', label: 'Workspace (.rrce-workflow/)' },
114
- { value: 'both', label: 'Both' },
115
- ],
116
- initialValue: 'global',
117
- }),
118
- tools: () =>
119
- multiselect({
120
- message: 'Which AI tools do you use?',
121
- options: [
122
- { value: 'copilot', label: 'GitHub Copilot', hint: 'VSCode' },
123
- { value: 'antigravity', label: 'Antigravity IDE' },
124
- ],
125
- required: false,
126
- }),
127
- linkedProjects: () => {
128
- // Only show if there are other projects to link
129
- if (existingProjects.length === 0) {
130
- return Promise.resolve([]);
131
- }
132
- return multiselect({
133
- message: 'Link knowledge from other projects?',
134
- options: existingProjects.map(name => ({
135
- value: name,
136
- label: name,
137
- hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
138
- })),
139
- required: false,
140
- });
141
- },
142
- confirm: () =>
143
- confirm({
144
- message: 'Create configuration?',
145
- initialValue: true,
146
- }),
147
- },
148
- {
149
- onCancel: () => {
150
- cancel('Setup process cancelled.');
151
- process.exit(0);
152
- },
153
- }
154
- );
155
-
156
- if (!config.confirm) {
157
- outro('Setup cancelled by user.');
158
- process.exit(0);
159
- }
160
-
161
- s.start('Generating configuration');
162
-
163
- try {
164
- // Create data directories in all storage locations
165
- const dataPaths = resolveAllDataPaths(config.storageMode as StorageMode, workspaceName, workspacePath);
166
-
167
- for (const dataPath of dataPaths) {
168
- ensureDir(dataPath);
169
- // Create agent metadata subdirectories
170
- ensureDir(path.join(dataPath, 'knowledge'));
171
- ensureDir(path.join(dataPath, 'refs'));
172
- ensureDir(path.join(dataPath, 'tasks'));
173
- ensureDir(path.join(dataPath, 'templates'));
174
- ensureDir(path.join(dataPath, 'prompts'));
175
- }
176
-
177
- // Get the agent-core directory path
178
- const agentCoreDir = getAgentCoreDir();
179
-
180
- // Sync metadata (knowledge, refs, tasks) from agent-core to all storage locations
181
- syncMetadataToAll(agentCoreDir, dataPaths);
182
-
183
- // Also copy templates to all storage locations
184
- copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', dataPaths);
185
-
186
- // Load prompts
187
- const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
188
-
189
- // Copy prompts to all storage locations (for cross-project access)
190
- for (const dataPath of dataPaths) {
191
- const promptsDir = path.join(dataPath, 'prompts');
192
- ensureDir(promptsDir);
193
- copyPromptsToDir(prompts, promptsDir, '.md');
194
- }
195
-
196
- // Copy prompts to tool-specific locations (for IDE integration)
197
- const selectedTools = config.tools as string[];
198
-
199
- if (selectedTools.includes('copilot')) {
200
- const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
201
- ensureDir(copilotPath);
202
- copyPromptsToDir(prompts, copilotPath, '.agent.md');
203
- }
204
-
205
- if (selectedTools.includes('antigravity')) {
206
- const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
207
- ensureDir(antigravityPath);
208
- copyPromptsToDir(prompts, antigravityPath, '.md');
209
- }
210
-
211
- // Create workspace config
212
- const linkedProjects = config.linkedProjects as string[];
213
- const workspaceConfigPath = path.join(workspacePath, '.rrce-workflow.yaml');
214
- let configContent = `# RRCE-Workflow Configuration
215
- version: 1
216
-
217
- storage:
218
- mode: ${config.storageMode}
219
-
220
- project:
221
- name: "${workspaceName}"
222
-
223
- tools:
224
- copilot: ${selectedTools.includes('copilot')}
225
- antigravity: ${selectedTools.includes('antigravity')}
226
- `;
227
-
228
- // Add linked projects if any
229
- if (linkedProjects.length > 0) {
230
- configContent += `\nlinked_projects:\n`;
231
- linkedProjects.forEach(name => {
232
- configContent += ` - ${name}\n`;
233
- });
234
- }
235
-
236
- fs.writeFileSync(workspaceConfigPath, configContent);
237
-
238
- // Generate VSCode workspace file if using copilot or has linked projects
239
- if (selectedTools.includes('copilot') || linkedProjects.length > 0) {
240
- generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects);
241
- }
242
-
243
- s.stop('Configuration generated');
244
-
245
- // Show summary
246
- const summary = [
247
- `Storage: ${config.storageMode === 'both' ? 'global + workspace' : config.storageMode}`,
248
- ];
249
-
250
- if (dataPaths.length > 0) {
251
- summary.push(`Data paths:`);
252
- dataPaths.forEach(p => summary.push(` - ${p}`));
253
- }
254
-
255
- if (selectedTools.length > 0) {
256
- summary.push(`Tools: ${selectedTools.join(', ')}`);
257
- }
258
-
259
- if (linkedProjects.length > 0) {
260
- summary.push(`Linked projects: ${linkedProjects.join(', ')}`);
261
- summary.push(`Workspace file: ${pc.cyan(`${workspaceName}.code-workspace`)}`);
262
- }
263
-
264
- note(summary.join('\n'), 'Setup Summary');
265
-
266
- // Show appropriate outro message
267
- if (linkedProjects.length > 0) {
268
- outro(pc.green(`✓ Setup complete! Open ${pc.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
269
- } else {
270
- outro(pc.green(`✓ Setup complete! Your agents are ready to use.`));
271
- }
272
-
273
- } catch (error) {
274
- s.stop('Error occurred');
275
- cancel(`Failed to setup: ${error instanceof Error ? error.message : String(error)}`);
276
- process.exit(1);
277
- }
278
- }
279
-
280
- function copyPromptsToDir(prompts: ParsedPrompt[], targetDir: string, extension: string) {
281
- for (const prompt of prompts) {
282
- const baseName = path.basename(prompt.filePath, '.md');
283
- const targetName = baseName + extension;
284
- const targetPath = path.join(targetDir, targetName);
285
-
286
- // Read the full content including frontmatter
287
- const content = fs.readFileSync(prompt.filePath, 'utf-8');
288
- fs.writeFileSync(targetPath, content);
289
- }
290
- }
291
-
292
- interface VSCodeWorkspaceFolder {
293
- path: string;
294
- name?: string;
295
- }
296
-
297
- interface VSCodeWorkspace {
298
- folders: VSCodeWorkspaceFolder[];
299
- settings?: Record<string, unknown>;
300
- }
301
-
302
- /**
303
- * Generate or update VSCode workspace file with linked project knowledge folders
304
- */
305
- function generateVSCodeWorkspace(workspacePath: string, workspaceName: string, linkedProjects: string[]) {
306
- const workspaceFilePath = path.join(workspacePath, `${workspaceName}.code-workspace`);
307
-
308
- let workspace: VSCodeWorkspace;
309
-
310
- // Check if workspace file already exists
311
- if (fs.existsSync(workspaceFilePath)) {
312
- try {
313
- const content = fs.readFileSync(workspaceFilePath, 'utf-8');
314
- workspace = JSON.parse(content);
315
- } catch {
316
- // If parse fails, create new
317
- workspace = { folders: [] };
318
- }
319
- } else {
320
- workspace = { folders: [] };
321
- }
322
-
323
- // Ensure main workspace folder is first
324
- const mainFolder: VSCodeWorkspaceFolder = { path: '.' };
325
- const existingMainIndex = workspace.folders.findIndex(f => f.path === '.');
326
- if (existingMainIndex === -1) {
327
- workspace.folders.unshift(mainFolder);
328
- }
329
-
330
- // Add linked project knowledge folders
331
- const rrceHome = getRRCEHome();
332
- for (const projectName of linkedProjects) {
333
- const knowledgePath = path.join(rrceHome, 'workspaces', projectName, 'knowledge');
334
- const folderEntry: VSCodeWorkspaceFolder = {
335
- path: knowledgePath,
336
- name: `📚 ${projectName} (knowledge)`
337
- };
338
-
339
- // Check if already exists
340
- const existingIndex = workspace.folders.findIndex(f => f.path === knowledgePath);
341
- if (existingIndex === -1) {
342
- workspace.folders.push(folderEntry);
343
- }
344
- }
345
-
346
- // Write workspace file
347
- fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
348
- }
349
-
350
- /**
351
- * Run the link-only flow for adding other project knowledge to an existing workspace
352
- */
353
- async function runLinkProjectsFlow(workspacePath: string, workspaceName: string, existingProjects: string[]) {
354
- const linkedProjects = await multiselect({
355
- message: 'Select projects to link:',
356
- options: existingProjects.map(name => ({
357
- value: name,
358
- label: name,
359
- hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
360
- })),
361
- required: true,
362
- });
363
-
364
- if (isCancel(linkedProjects)) {
365
- cancel('Cancelled.');
366
- process.exit(0);
367
- }
368
-
369
- const selectedProjects = linkedProjects as string[];
370
-
371
- if (selectedProjects.length === 0) {
372
- outro('No projects selected.');
373
- return;
374
- }
375
-
376
- const s = spinner();
377
- s.start('Linking projects');
378
-
379
- // Update .rrce-workflow.yaml with linked projects
380
- const configFilePath = path.join(workspacePath, '.rrce-workflow.yaml');
381
- let configContent = fs.readFileSync(configFilePath, 'utf-8');
382
-
383
- // Check if linked_projects section exists
384
- if (configContent.includes('linked_projects:')) {
385
- // Append to existing section - find and update
386
- const lines = configContent.split('\n');
387
- const linkedIndex = lines.findIndex(l => l.trim() === 'linked_projects:');
388
- if (linkedIndex !== -1) {
389
- // Find where to insert new projects (after existing ones)
390
- let insertIndex = linkedIndex + 1;
391
- while (insertIndex < lines.length && lines[insertIndex]?.startsWith(' - ')) {
392
- insertIndex++;
393
- }
394
- // Add new projects that aren't already there
395
- for (const name of selectedProjects) {
396
- if (!configContent.includes(` - ${name}`)) {
397
- lines.splice(insertIndex, 0, ` - ${name}`);
398
- insertIndex++;
399
- }
400
- }
401
- configContent = lines.join('\n');
402
- }
403
- } else {
404
- // Add new linked_projects section
405
- configContent += `\nlinked_projects:\n`;
406
- selectedProjects.forEach(name => {
407
- configContent += ` - ${name}\n`;
408
- });
409
- }
410
-
411
- fs.writeFileSync(configFilePath, configContent);
412
-
413
- // Update VSCode workspace file
414
- generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects);
415
-
416
- s.stop('Projects linked');
417
-
418
- // Show summary
419
- const workspaceFile = `${workspaceName}.code-workspace`;
420
- const summary = [
421
- `Linked projects:`,
422
- ...selectedProjects.map(p => ` ✓ ${p}`),
423
- ``,
424
- `Workspace file: ${pc.cyan(workspaceFile)}`,
425
- ];
426
-
427
- note(summary.join('\n'), 'Link Summary');
428
-
429
- outro(pc.green(`✓ Projects linked! Open ${pc.bold(workspaceFile)} in VSCode to access linked knowledge.`));
430
- }
431
-
432
- /**
433
- * Sync workspace knowledge to global storage so other projects can reference it
434
- */
435
- async function runSyncToGlobalFlow(workspacePath: string, workspaceName: string) {
436
- const localPath = getLocalWorkspacePath(workspacePath);
437
- const globalPath = getGlobalWorkspacePath(workspaceName);
438
-
439
- // Check what exists locally
440
- const subdirs = ['knowledge', 'prompts', 'templates', 'tasks', 'refs'];
441
- const existingDirs = subdirs.filter(dir =>
442
- fs.existsSync(path.join(localPath, dir))
443
- );
444
-
445
- if (existingDirs.length === 0) {
446
- outro(pc.yellow('No data found in workspace storage to sync.'));
447
- return;
448
- }
449
-
450
- // Show what will be synced
451
- note(
452
- `The following will be copied to global storage:\n${existingDirs.map(d => ` • ${d}/`).join('\n')}\n\nDestination: ${pc.cyan(globalPath)}`,
453
- 'Sync Preview'
454
- );
455
-
456
- const shouldSync = await confirm({
457
- message: 'Proceed with sync to global storage?',
458
- initialValue: true,
459
- });
460
-
461
- if (isCancel(shouldSync) || !shouldSync) {
462
- outro('Sync cancelled.');
463
- return;
464
- }
465
-
466
- const s = spinner();
467
- s.start('Syncing to global storage');
468
-
469
- try {
470
- // Ensure global directory exists
471
- ensureDir(globalPath);
472
-
473
- // Copy each directory
474
- for (const dir of existingDirs) {
475
- const srcDir = path.join(localPath, dir);
476
- const destDir = path.join(globalPath, dir);
477
- ensureDir(destDir);
478
-
479
- // Copy files recursively
480
- copyDirRecursive(srcDir, destDir);
481
- }
482
-
483
- // Update the config to reflect 'both' mode
484
- const configFilePath = path.join(workspacePath, '.rrce-workflow.yaml');
485
- let configContent = fs.readFileSync(configFilePath, 'utf-8');
486
- configContent = configContent.replace(/mode:\s*workspace/, 'mode: both');
487
- fs.writeFileSync(configFilePath, configContent);
488
-
489
- s.stop('Sync complete');
490
-
491
- const summary = [
492
- `Synced directories:`,
493
- ...existingDirs.map(d => ` ✓ ${d}/`),
494
- ``,
495
- `Global path: ${pc.cyan(globalPath)}`,
496
- `Storage mode updated to: ${pc.bold('both')}`,
497
- ``,
498
- `Other projects can now link this knowledge!`,
499
- ];
500
-
501
- note(summary.join('\n'), 'Sync Summary');
502
-
503
- outro(pc.green('✓ Workspace knowledge synced to global storage!'));
504
-
505
- } catch (error) {
506
- s.stop('Error occurred');
507
- cancel(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
508
- process.exit(1);
509
- }
510
- }
511
-
512
- /**
513
- * Recursively copy a directory
514
- */
515
- function copyDirRecursive(src: string, dest: string) {
516
- const entries = fs.readdirSync(src, { withFileTypes: true });
517
-
518
- for (const entry of entries) {
519
- const srcPath = path.join(src, entry.name);
520
- const destPath = path.join(dest, entry.name);
521
-
522
- if (entry.isDirectory()) {
523
- ensureDir(destPath);
524
- copyDirRecursive(srcPath, destPath);
525
- } else {
526
- fs.copyFileSync(srcPath, destPath);
527
- }
528
- }
529
- }
530
-
531
- /**
532
- * Update prompts and templates from the package without resetting config
533
- */
534
- async function runUpdateFlow(workspacePath: string, workspaceName: string, currentStorageMode: string | null) {
535
- const s = spinner();
536
- s.start('Checking for updates');
537
-
538
- try {
539
- const agentCoreDir = getAgentCoreDir();
540
- const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
541
-
542
- // Determine storage paths based on current mode
543
- const mode = (currentStorageMode as StorageMode) || 'global';
544
- const dataPaths = resolveAllDataPaths(mode, workspaceName, workspacePath);
545
-
546
- s.stop('Updates found');
547
-
548
- // Show what will be updated
549
- note(
550
- `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')}`,
551
- 'Update Preview'
552
- );
553
-
554
- const shouldUpdate = await confirm({
555
- message: 'Proceed with update?',
556
- initialValue: true,
557
- });
558
-
559
- if (isCancel(shouldUpdate) || !shouldUpdate) {
560
- outro('Update cancelled.');
561
- return;
562
- }
563
-
564
- s.start('Updating from package');
565
-
566
- // Update prompts and templates in all storage locations
567
- for (const dataPath of dataPaths) {
568
- // Update prompts
569
- const promptsDir = path.join(dataPath, 'prompts');
570
- ensureDir(promptsDir);
571
- copyPromptsToDir(prompts, promptsDir, '.md');
572
-
573
- // Update templates
574
- copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', [dataPath]);
575
- }
576
-
577
- // Also update tool-specific locations if configured
578
- const configFilePath = path.join(workspacePath, '.rrce-workflow.yaml');
579
- const configContent = fs.readFileSync(configFilePath, 'utf-8');
580
-
581
- if (configContent.includes('copilot: true')) {
582
- const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
583
- ensureDir(copilotPath);
584
- copyPromptsToDir(prompts, copilotPath, '.agent.md');
585
- }
586
-
587
- if (configContent.includes('antigravity: true')) {
588
- const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
589
- ensureDir(antigravityPath);
590
- copyPromptsToDir(prompts, antigravityPath, '.md');
591
- }
592
-
593
- s.stop('Update complete');
594
-
595
- const summary = [
596
- `Updated:`,
597
- ` ✓ ${prompts.length} agent prompts`,
598
- ` ✓ Output templates`,
599
- ``,
600
- `Your configuration and knowledge files were preserved.`,
601
- ];
602
-
603
- note(summary.join('\n'), 'Update Summary');
604
-
605
- outro(pc.green('✓ Successfully updated from package!'));
606
-
607
- } catch (error) {
608
- s.stop('Error occurred');
609
- cancel(`Failed to update: ${error instanceof Error ? error.message : String(error)}`);
610
- process.exit(1);
611
- }
612
- }