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.
- package/agent-core/prompts/documentation.md +15 -3
- package/agent-core/prompts/executor.md +23 -3
- package/agent-core/prompts/init.md +8 -3
- package/agent-core/prompts/planning_orchestrator.md +18 -3
- package/agent-core/prompts/research_discussion.md +5 -3
- package/agent-core/prompts/sync.md +12 -3
- package/agent-core/templates/documentation_output.md +2 -2
- package/agent-core/templates/executor_output.md +2 -2
- package/agent-core/templates/init_output.md +2 -2
- package/agent-core/templates/planning_output.md +2 -2
- package/agent-core/templates/research_output.md +2 -2
- package/package.json +1 -1
- package/src/commands/wizard/index.ts +114 -0
- package/src/commands/wizard/link-flow.ts +102 -0
- package/src/commands/wizard/setup-flow.ts +354 -0
- package/src/commands/wizard/sync-flow.ts +92 -0
- package/src/commands/wizard/update-flow.ts +128 -0
- package/src/commands/wizard/utils.ts +38 -0
- package/src/commands/wizard/vscode.ts +66 -0
- package/src/index.ts +1 -1
- package/src/lib/paths.ts +105 -0
- package/src/types/prompt.ts +3 -2
- package/src/commands/wizard.ts +0 -380
|
@@ -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