rrce-workflow 0.1.2 → 0.1.4
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/README.md +17 -3
- package/agent-core/prompts/documentation.md +5 -2
- package/agent-core/prompts/executor.md +5 -2
- package/agent-core/prompts/init.md +5 -2
- package/agent-core/prompts/planning_orchestrator.md +5 -2
- package/agent-core/prompts/research_discussion.md +5 -2
- package/agent-core/prompts/sync.md +5 -2
- package/agent-core/templates/documentation_output.md +10 -0
- package/agent-core/templates/executor_output.md +10 -0
- package/agent-core/templates/init_output.md +10 -0
- package/agent-core/templates/planning_output.md +10 -0
- package/agent-core/templates/research_output.md +10 -0
- package/bin/rrce-workflow.js +1 -1
- package/package.json +1 -1
- package/src/commands/wizard.ts +258 -8
- package/src/lib/paths.ts +122 -1
- package/src/lib/prompts.ts +9 -2
package/README.md
CHANGED
|
@@ -66,9 +66,23 @@ project:
|
|
|
66
66
|
|
|
67
67
|
| Mode | Location | Use Case |
|
|
68
68
|
|------|----------|----------|
|
|
69
|
-
| `global` | `~/.rrce-workflow/workspaces/<name>/` | Non-intrusive |
|
|
70
|
-
| `workspace` | `.rrce-workflow/` | Portable with repo |
|
|
71
|
-
| `both` | Both locations | Redundancy |
|
|
69
|
+
| `global` | `~/.rrce-workflow/workspaces/<name>/` | Non-intrusive, cross-project access |
|
|
70
|
+
| `workspace` | `.rrce-workflow/` | Portable with repo, team sharing |
|
|
71
|
+
| `both` | Both locations (synced) | Redundancy + cross-project access |
|
|
72
|
+
|
|
73
|
+
When `both` is selected, data is stored in **both** locations simultaneously:
|
|
74
|
+
- Primary (for reads): `<workspace>/.rrce-workflow/`
|
|
75
|
+
- Secondary (auto-synced): `~/.rrce-workflow/workspaces/<name>/`
|
|
76
|
+
|
|
77
|
+
Each storage location contains:
|
|
78
|
+
```
|
|
79
|
+
<storage-path>/
|
|
80
|
+
├── knowledge/ # Project context and domain knowledge
|
|
81
|
+
├── prompts/ # Agent prompt files
|
|
82
|
+
├── refs/ # External references
|
|
83
|
+
├── tasks/ # Task artifacts and metadata
|
|
84
|
+
└── templates/ # Output templates
|
|
85
|
+
```
|
|
72
86
|
|
|
73
87
|
## Requirements
|
|
74
88
|
|
|
@@ -39,8 +39,11 @@ Path Resolution
|
|
|
39
39
|
- Storage mode: Determined by `.rrce-workflow.yaml` → global config → default (`global`)
|
|
40
40
|
- `global`: Data in `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
41
41
|
- `workspace`: Data in `<workspace>/.rrce-workflow/`
|
|
42
|
-
- `both`: Dual storage
|
|
43
|
-
-
|
|
42
|
+
- `both`: **Dual storage** - data stored in BOTH locations simultaneously
|
|
43
|
+
- Primary (for reads): `<workspace>/.rrce-workflow/`
|
|
44
|
+
- Secondary (auto-synced): `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
45
|
+
- When writing, always write to `{{RRCE_DATA}}` - the system ensures both locations stay in sync
|
|
46
|
+
- Data path: `{{RRCE_DATA}}` (resolves to primary storage based on mode)
|
|
44
47
|
- Global home: `{{RRCE_HOME}}` (always `~/.rrce-workflow`)
|
|
45
48
|
- Workspace root: `{{WORKSPACE_ROOT}}` (auto-detected or via `$RRCE_WORKSPACE`)
|
|
46
49
|
- Workspace name: `{{WORKSPACE_NAME}}` (from config or directory name)
|
|
@@ -35,8 +35,11 @@ Path Resolution
|
|
|
35
35
|
- Storage mode: Determined by `.rrce-workflow.yaml` → global config → default (`global`)
|
|
36
36
|
- `global`: Data in `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
37
37
|
- `workspace`: Data in `<workspace>/.rrce-workflow/`
|
|
38
|
-
- `both`: Dual storage
|
|
39
|
-
-
|
|
38
|
+
- `both`: **Dual storage** - data stored in BOTH locations simultaneously
|
|
39
|
+
- Primary (for reads): `<workspace>/.rrce-workflow/`
|
|
40
|
+
- Secondary (auto-synced): `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
41
|
+
- When writing, always write to `{{RRCE_DATA}}` - the system ensures both locations stay in sync
|
|
42
|
+
- Data path: `{{RRCE_DATA}}` (resolves to primary storage based on mode)
|
|
40
43
|
- Global home: `{{RRCE_HOME}}` (always `~/.rrce-workflow`)
|
|
41
44
|
- Workspace root: `{{WORKSPACE_ROOT}}` (auto-detected or via `$RRCE_WORKSPACE`)
|
|
42
45
|
- Workspace name: `{{WORKSPACE_NAME}}` (from config or directory name)
|
|
@@ -32,8 +32,11 @@ Path Resolution
|
|
|
32
32
|
- Storage mode: Determined by `.rrce-workflow.yaml` → global config → default (`global`)
|
|
33
33
|
- `global`: Data stored in `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
34
34
|
- `workspace`: Data stored in `<workspace>/.rrce-workflow/`
|
|
35
|
-
- `both`: Dual storage
|
|
36
|
-
-
|
|
35
|
+
- `both`: **Dual storage** - data stored in BOTH locations simultaneously
|
|
36
|
+
- Primary (for reads): `<workspace>/.rrce-workflow/`
|
|
37
|
+
- Secondary (auto-synced): `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
38
|
+
- When writing, always write to `{{RRCE_DATA}}` - the system ensures both locations stay in sync
|
|
39
|
+
- Data path: `{{RRCE_DATA}}` (resolves to primary storage based on mode)
|
|
37
40
|
- Global home: `{{RRCE_HOME}}` (always `~/.rrce-workflow`)
|
|
38
41
|
- Workspace root: `{{WORKSPACE_ROOT}}` (auto-detected or via `$RRCE_WORKSPACE`)
|
|
39
42
|
- Workspace name: `{{WORKSPACE_NAME}}` (from config or directory name)
|
|
@@ -32,8 +32,11 @@ Path Resolution
|
|
|
32
32
|
- Storage mode: Determined by `.rrce-workflow.yaml` → global config → default (`global`)
|
|
33
33
|
- `global`: Data in `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
34
34
|
- `workspace`: Data in `<workspace>/.rrce-workflow/`
|
|
35
|
-
- `both`: Dual storage
|
|
36
|
-
-
|
|
35
|
+
- `both`: **Dual storage** - data stored in BOTH locations simultaneously
|
|
36
|
+
- Primary (for reads): `<workspace>/.rrce-workflow/`
|
|
37
|
+
- Secondary (auto-synced): `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
38
|
+
- When writing, always write to `{{RRCE_DATA}}` - the system ensures both locations stay in sync
|
|
39
|
+
- Data path: `{{RRCE_DATA}}` (resolves to primary storage based on mode)
|
|
37
40
|
- Global home: `{{RRCE_HOME}}` (always `~/.rrce-workflow`)
|
|
38
41
|
- Workspace root: `{{WORKSPACE_ROOT}}` (auto-detected or via `$RRCE_WORKSPACE`)
|
|
39
42
|
- Workspace name: `{{WORKSPACE_NAME}}` (from config or directory name)
|
|
@@ -39,8 +39,11 @@ Path Resolution
|
|
|
39
39
|
- Storage mode: Determined by `.rrce-workflow.yaml` → global config → default (`global`)
|
|
40
40
|
- `global`: Data in `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
41
41
|
- `workspace`: Data in `<workspace>/.rrce-workflow/`
|
|
42
|
-
- `both`: Dual storage
|
|
43
|
-
-
|
|
42
|
+
- `both`: **Dual storage** - data stored in BOTH locations simultaneously
|
|
43
|
+
- Primary (for reads): `<workspace>/.rrce-workflow/`
|
|
44
|
+
- Secondary (auto-synced): `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
45
|
+
- When writing, always write to `{{RRCE_DATA}}` - the system ensures both locations stay in sync
|
|
46
|
+
- Data path: `{{RRCE_DATA}}` (resolves to primary storage based on mode)
|
|
44
47
|
- Global home: `{{RRCE_HOME}}` (always `~/.rrce-workflow`)
|
|
45
48
|
- Workspace root: `{{WORKSPACE_ROOT}}` (auto-detected or via `$RRCE_WORKSPACE`)
|
|
46
49
|
- Workspace name: `{{WORKSPACE_NAME}}` (from config or directory name)
|
|
@@ -32,8 +32,11 @@ Path Resolution
|
|
|
32
32
|
- Storage mode: Determined by `.rrce-workflow.yaml` → global config → default (`global`)
|
|
33
33
|
- `global`: Data in `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
34
34
|
- `workspace`: Data in `<workspace>/.rrce-workflow/`
|
|
35
|
-
- `both`: Dual storage
|
|
36
|
-
-
|
|
35
|
+
- `both`: **Dual storage** - data stored in BOTH locations simultaneously
|
|
36
|
+
- Primary (for reads): `<workspace>/.rrce-workflow/`
|
|
37
|
+
- Secondary (auto-synced): `~/.rrce-workflow/workspaces/<workspace-name>/`
|
|
38
|
+
- When writing, always write to `{{RRCE_DATA}}` - the system ensures both locations stay in sync
|
|
39
|
+
- Data path: `{{RRCE_DATA}}` (resolves to primary storage based on mode)
|
|
37
40
|
- Global home: `{{RRCE_HOME}}` (always `~/.rrce-workflow`)
|
|
38
41
|
- Workspace root: `{{WORKSPACE_ROOT}}` (auto-detected or via `$RRCE_WORKSPACE`)
|
|
39
42
|
- Workspace name: `{{WORKSPACE_NAME}}` (from config or directory name)
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RRCE Template Variables:
|
|
3
|
+
- {{RRCE_DATA}}: Primary storage path (resolves based on storage mode in .rrce-workflow.yaml)
|
|
4
|
+
- global: ~/.rrce-workflow/workspaces/<workspace-name>/
|
|
5
|
+
- workspace: <workspace>/.rrce-workflow/
|
|
6
|
+
- both: <workspace>/.rrce-workflow/ (primary, auto-synced to global)
|
|
7
|
+
- {{RRCE_HOME}}: Always ~/.rrce-workflow
|
|
8
|
+
- {{WORKSPACE_ROOT}}: Workspace root directory
|
|
9
|
+
- {{WORKSPACE_NAME}}: Workspace name from config or directory name
|
|
10
|
+
-->
|
|
1
11
|
# Handover Note – {{task_title}}
|
|
2
12
|
|
|
3
13
|
- Task ID: `{{task_id}}`
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RRCE Template Variables:
|
|
3
|
+
- {{RRCE_DATA}}: Primary storage path (resolves based on storage mode in .rrce-workflow.yaml)
|
|
4
|
+
- global: ~/.rrce-workflow/workspaces/<workspace-name>/
|
|
5
|
+
- workspace: <workspace>/.rrce-workflow/
|
|
6
|
+
- both: <workspace>/.rrce-workflow/ (primary, auto-synced to global)
|
|
7
|
+
- {{RRCE_HOME}}: Always ~/.rrce-workflow
|
|
8
|
+
- {{WORKSPACE_ROOT}}: Workspace root directory
|
|
9
|
+
- {{WORKSPACE_NAME}}: Workspace name from config or directory name
|
|
10
|
+
-->
|
|
1
11
|
# Execution Log – {{task_title}}
|
|
2
12
|
|
|
3
13
|
- Task ID: `{{task_id}}`
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RRCE Template Variables:
|
|
3
|
+
- {{RRCE_DATA}}: Primary storage path (resolves based on storage mode in .rrce-workflow.yaml)
|
|
4
|
+
- global: ~/.rrce-workflow/workspaces/<workspace-name>/
|
|
5
|
+
- workspace: <workspace>/.rrce-workflow/
|
|
6
|
+
- both: <workspace>/.rrce-workflow/ (primary, auto-synced to global)
|
|
7
|
+
- {{RRCE_HOME}}: Always ~/.rrce-workflow
|
|
8
|
+
- {{WORKSPACE_ROOT}}: Workspace root directory
|
|
9
|
+
- {{WORKSPACE_NAME}}: Workspace name from config or directory name
|
|
10
|
+
-->
|
|
1
11
|
# Project Context – {{project_name}}
|
|
2
12
|
|
|
3
13
|
- Initialized: `{{date}}`
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RRCE Template Variables:
|
|
3
|
+
- {{RRCE_DATA}}: Primary storage path (resolves based on storage mode in .rrce-workflow.yaml)
|
|
4
|
+
- global: ~/.rrce-workflow/workspaces/<workspace-name>/
|
|
5
|
+
- workspace: <workspace>/.rrce-workflow/
|
|
6
|
+
- both: <workspace>/.rrce-workflow/ (primary, auto-synced to global)
|
|
7
|
+
- {{RRCE_HOME}}: Always ~/.rrce-workflow
|
|
8
|
+
- {{WORKSPACE_ROOT}}: Workspace root directory
|
|
9
|
+
- {{WORKSPACE_NAME}}: Workspace name from config or directory name
|
|
10
|
+
-->
|
|
1
11
|
# Execution Plan – {{task_title}}
|
|
2
12
|
|
|
3
13
|
- Task ID: `{{task_id}}`
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RRCE Template Variables:
|
|
3
|
+
- {{RRCE_DATA}}: Primary storage path (resolves based on storage mode in .rrce-workflow.yaml)
|
|
4
|
+
- global: ~/.rrce-workflow/workspaces/<workspace-name>/
|
|
5
|
+
- workspace: <workspace>/.rrce-workflow/
|
|
6
|
+
- both: <workspace>/.rrce-workflow/ (primary, auto-synced to global)
|
|
7
|
+
- {{RRCE_HOME}}: Always ~/.rrce-workflow
|
|
8
|
+
- {{WORKSPACE_ROOT}}: Workspace root directory
|
|
9
|
+
- {{WORKSPACE_NAME}}: Workspace name from config or directory name
|
|
10
|
+
-->
|
|
1
11
|
# Research Brief – {{task_title}}
|
|
2
12
|
|
|
3
13
|
- Task ID: `{{task_id}}`
|
package/bin/rrce-workflow.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import "../src/index.
|
|
2
|
+
import "../src/index.ts";
|
package/package.json
CHANGED
package/src/commands/wizard.ts
CHANGED
|
@@ -3,9 +3,9 @@ import pc from 'picocolors';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { getGitUser } from '../lib/git';
|
|
6
|
-
import { detectWorkspaceRoot, getWorkspaceName,
|
|
6
|
+
import { detectWorkspaceRoot, getWorkspaceName, resolveAllDataPaths, ensureDir, getAgentPromptPath, syncMetadataToAll, copyDirToAllStoragePaths, listGlobalProjects, getGlobalProjectKnowledgePath, getRRCEHome } from '../lib/paths';
|
|
7
7
|
import type { StorageMode } from '../types/prompt';
|
|
8
|
-
import { loadPromptsFromDir, getAgentCorePromptsDir } from '../lib/prompts';
|
|
8
|
+
import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../lib/prompts';
|
|
9
9
|
|
|
10
10
|
import type { ParsedPrompt } from '../types/prompt';
|
|
11
11
|
|
|
@@ -28,6 +28,38 @@ Workspace: ${pc.bold(workspaceName)}`,
|
|
|
28
28
|
'Context'
|
|
29
29
|
);
|
|
30
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
|
+
// If already configured and there are other projects, show menu
|
|
39
|
+
if (isAlreadyConfigured && existingProjects.length > 0) {
|
|
40
|
+
const action = await select({
|
|
41
|
+
message: 'This workspace is already configured. What would you like to do?',
|
|
42
|
+
options: [
|
|
43
|
+
{ value: 'link', label: 'Link other project knowledge', hint: `${existingProjects.length} projects available` },
|
|
44
|
+
{ value: 'reconfigure', label: 'Reconfigure from scratch' },
|
|
45
|
+
{ value: 'exit', label: 'Exit' },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (isCancel(action) || action === 'exit') {
|
|
50
|
+
outro('Exited.');
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (action === 'link') {
|
|
55
|
+
// Link-only flow
|
|
56
|
+
await runLinkProjectsFlow(workspacePath, workspaceName, existingProjects);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Otherwise continue to full reconfigure flow
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Full setup flow
|
|
31
63
|
const config = await group(
|
|
32
64
|
{
|
|
33
65
|
storageMode: () =>
|
|
@@ -49,6 +81,21 @@ Workspace: ${pc.bold(workspaceName)}`,
|
|
|
49
81
|
],
|
|
50
82
|
required: false,
|
|
51
83
|
}),
|
|
84
|
+
linkedProjects: () => {
|
|
85
|
+
// Only show if there are other projects to link
|
|
86
|
+
if (existingProjects.length === 0) {
|
|
87
|
+
return Promise.resolve([]);
|
|
88
|
+
}
|
|
89
|
+
return multiselect({
|
|
90
|
+
message: 'Link knowledge from other projects?',
|
|
91
|
+
options: existingProjects.map(name => ({
|
|
92
|
+
value: name,
|
|
93
|
+
label: name,
|
|
94
|
+
hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
|
|
95
|
+
})),
|
|
96
|
+
required: false,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
52
99
|
confirm: () =>
|
|
53
100
|
confirm({
|
|
54
101
|
message: 'Create configuration?',
|
|
@@ -71,14 +118,39 @@ Workspace: ${pc.bold(workspaceName)}`,
|
|
|
71
118
|
s.start('Generating configuration');
|
|
72
119
|
|
|
73
120
|
try {
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
121
|
+
// Create data directories in all storage locations
|
|
122
|
+
const dataPaths = resolveAllDataPaths(config.storageMode as StorageMode, workspaceName, workspacePath);
|
|
123
|
+
|
|
124
|
+
for (const dataPath of dataPaths) {
|
|
125
|
+
ensureDir(dataPath);
|
|
126
|
+
// Create agent metadata subdirectories
|
|
127
|
+
ensureDir(path.join(dataPath, 'knowledge'));
|
|
128
|
+
ensureDir(path.join(dataPath, 'refs'));
|
|
129
|
+
ensureDir(path.join(dataPath, 'tasks'));
|
|
130
|
+
ensureDir(path.join(dataPath, 'templates'));
|
|
131
|
+
ensureDir(path.join(dataPath, 'prompts'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Get the agent-core directory path
|
|
135
|
+
const agentCoreDir = getAgentCoreDir();
|
|
136
|
+
|
|
137
|
+
// Sync metadata (knowledge, refs, tasks) from agent-core to all storage locations
|
|
138
|
+
syncMetadataToAll(agentCoreDir, dataPaths);
|
|
139
|
+
|
|
140
|
+
// Also copy templates to all storage locations
|
|
141
|
+
copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', dataPaths);
|
|
77
142
|
|
|
78
143
|
// Load prompts
|
|
79
144
|
const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
|
|
80
145
|
|
|
81
|
-
// Copy prompts
|
|
146
|
+
// Copy prompts to all storage locations (for cross-project access)
|
|
147
|
+
for (const dataPath of dataPaths) {
|
|
148
|
+
const promptsDir = path.join(dataPath, 'prompts');
|
|
149
|
+
ensureDir(promptsDir);
|
|
150
|
+
copyPromptsToDir(prompts, promptsDir, '.md');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Copy prompts to tool-specific locations (for IDE integration)
|
|
82
154
|
const selectedTools = config.tools as string[];
|
|
83
155
|
|
|
84
156
|
if (selectedTools.includes('copilot')) {
|
|
@@ -94,8 +166,9 @@ Workspace: ${pc.bold(workspaceName)}`,
|
|
|
94
166
|
}
|
|
95
167
|
|
|
96
168
|
// Create workspace config
|
|
169
|
+
const linkedProjects = config.linkedProjects as string[];
|
|
97
170
|
const workspaceConfigPath = path.join(workspacePath, '.rrce-workflow.yaml');
|
|
98
|
-
|
|
171
|
+
let configContent = `# RRCE-Workflow Configuration
|
|
99
172
|
version: 1
|
|
100
173
|
|
|
101
174
|
storage:
|
|
@@ -103,12 +176,50 @@ storage:
|
|
|
103
176
|
|
|
104
177
|
project:
|
|
105
178
|
name: "${workspaceName}"
|
|
179
|
+
|
|
180
|
+
tools:
|
|
181
|
+
copilot: ${selectedTools.includes('copilot')}
|
|
182
|
+
antigravity: ${selectedTools.includes('antigravity')}
|
|
106
183
|
`;
|
|
184
|
+
|
|
185
|
+
// Add linked projects if any
|
|
186
|
+
if (linkedProjects.length > 0) {
|
|
187
|
+
configContent += `\nlinked_projects:\n`;
|
|
188
|
+
linkedProjects.forEach(name => {
|
|
189
|
+
configContent += ` - ${name}\n`;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
107
193
|
fs.writeFileSync(workspaceConfigPath, configContent);
|
|
108
194
|
|
|
195
|
+
// Generate VSCode workspace file if using copilot or has linked projects
|
|
196
|
+
if (selectedTools.includes('copilot') || linkedProjects.length > 0) {
|
|
197
|
+
generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects);
|
|
198
|
+
}
|
|
199
|
+
|
|
109
200
|
s.stop('Configuration generated');
|
|
110
201
|
|
|
111
|
-
|
|
202
|
+
// Show summary
|
|
203
|
+
const summary = [
|
|
204
|
+
`Storage: ${config.storageMode === 'both' ? 'global + workspace' : config.storageMode}`,
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
if (dataPaths.length > 0) {
|
|
208
|
+
summary.push(`Data paths:`);
|
|
209
|
+
dataPaths.forEach(p => summary.push(` - ${p}`));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (selectedTools.length > 0) {
|
|
213
|
+
summary.push(`Tools: ${selectedTools.join(', ')}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (linkedProjects.length > 0) {
|
|
217
|
+
summary.push(`Linked projects: ${linkedProjects.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
note(summary.join('\n'), 'Setup Summary');
|
|
221
|
+
|
|
222
|
+
outro(pc.green(`✓ Setup complete! Your agents are ready to use.`));
|
|
112
223
|
|
|
113
224
|
} catch (error) {
|
|
114
225
|
s.stop('Error occurred');
|
|
@@ -128,3 +239,142 @@ function copyPromptsToDir(prompts: ParsedPrompt[], targetDir: string, extension:
|
|
|
128
239
|
fs.writeFileSync(targetPath, content);
|
|
129
240
|
}
|
|
130
241
|
}
|
|
242
|
+
|
|
243
|
+
interface VSCodeWorkspaceFolder {
|
|
244
|
+
path: string;
|
|
245
|
+
name?: string;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface VSCodeWorkspace {
|
|
249
|
+
folders: VSCodeWorkspaceFolder[];
|
|
250
|
+
settings?: Record<string, unknown>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generate or update VSCode workspace file with linked project knowledge folders
|
|
255
|
+
*/
|
|
256
|
+
function generateVSCodeWorkspace(workspacePath: string, workspaceName: string, linkedProjects: string[]) {
|
|
257
|
+
const workspaceFilePath = path.join(workspacePath, `${workspaceName}.code-workspace`);
|
|
258
|
+
|
|
259
|
+
let workspace: VSCodeWorkspace;
|
|
260
|
+
|
|
261
|
+
// Check if workspace file already exists
|
|
262
|
+
if (fs.existsSync(workspaceFilePath)) {
|
|
263
|
+
try {
|
|
264
|
+
const content = fs.readFileSync(workspaceFilePath, 'utf-8');
|
|
265
|
+
workspace = JSON.parse(content);
|
|
266
|
+
} catch {
|
|
267
|
+
// If parse fails, create new
|
|
268
|
+
workspace = { folders: [] };
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
workspace = { folders: [] };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Ensure main workspace folder is first
|
|
275
|
+
const mainFolder: VSCodeWorkspaceFolder = { path: '.' };
|
|
276
|
+
const existingMainIndex = workspace.folders.findIndex(f => f.path === '.');
|
|
277
|
+
if (existingMainIndex === -1) {
|
|
278
|
+
workspace.folders.unshift(mainFolder);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Add linked project knowledge folders
|
|
282
|
+
const rrceHome = getRRCEHome();
|
|
283
|
+
for (const projectName of linkedProjects) {
|
|
284
|
+
const knowledgePath = path.join(rrceHome, 'workspaces', projectName, 'knowledge');
|
|
285
|
+
const folderEntry: VSCodeWorkspaceFolder = {
|
|
286
|
+
path: knowledgePath,
|
|
287
|
+
name: `📚 ${projectName} (knowledge)`
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Check if already exists
|
|
291
|
+
const existingIndex = workspace.folders.findIndex(f => f.path === knowledgePath);
|
|
292
|
+
if (existingIndex === -1) {
|
|
293
|
+
workspace.folders.push(folderEntry);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Write workspace file
|
|
298
|
+
fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Run the link-only flow for adding other project knowledge to an existing workspace
|
|
303
|
+
*/
|
|
304
|
+
async function runLinkProjectsFlow(workspacePath: string, workspaceName: string, existingProjects: string[]) {
|
|
305
|
+
const linkedProjects = await multiselect({
|
|
306
|
+
message: 'Select projects to link:',
|
|
307
|
+
options: existingProjects.map(name => ({
|
|
308
|
+
value: name,
|
|
309
|
+
label: name,
|
|
310
|
+
hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
|
|
311
|
+
})),
|
|
312
|
+
required: true,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (isCancel(linkedProjects)) {
|
|
316
|
+
cancel('Cancelled.');
|
|
317
|
+
process.exit(0);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const selectedProjects = linkedProjects as string[];
|
|
321
|
+
|
|
322
|
+
if (selectedProjects.length === 0) {
|
|
323
|
+
outro('No projects selected.');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const s = spinner();
|
|
328
|
+
s.start('Linking projects');
|
|
329
|
+
|
|
330
|
+
// Update .rrce-workflow.yaml with linked projects
|
|
331
|
+
const configFilePath = path.join(workspacePath, '.rrce-workflow.yaml');
|
|
332
|
+
let configContent = fs.readFileSync(configFilePath, 'utf-8');
|
|
333
|
+
|
|
334
|
+
// Check if linked_projects section exists
|
|
335
|
+
if (configContent.includes('linked_projects:')) {
|
|
336
|
+
// Append to existing section - find and update
|
|
337
|
+
const lines = configContent.split('\n');
|
|
338
|
+
const linkedIndex = lines.findIndex(l => l.trim() === 'linked_projects:');
|
|
339
|
+
if (linkedIndex !== -1) {
|
|
340
|
+
// Find where to insert new projects (after existing ones)
|
|
341
|
+
let insertIndex = linkedIndex + 1;
|
|
342
|
+
while (insertIndex < lines.length && lines[insertIndex]?.startsWith(' - ')) {
|
|
343
|
+
insertIndex++;
|
|
344
|
+
}
|
|
345
|
+
// Add new projects that aren't already there
|
|
346
|
+
for (const name of selectedProjects) {
|
|
347
|
+
if (!configContent.includes(` - ${name}`)) {
|
|
348
|
+
lines.splice(insertIndex, 0, ` - ${name}`);
|
|
349
|
+
insertIndex++;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
configContent = lines.join('\n');
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
// Add new linked_projects section
|
|
356
|
+
configContent += `\nlinked_projects:\n`;
|
|
357
|
+
selectedProjects.forEach(name => {
|
|
358
|
+
configContent += ` - ${name}\n`;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
fs.writeFileSync(configFilePath, configContent);
|
|
363
|
+
|
|
364
|
+
// Update VSCode workspace file
|
|
365
|
+
generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects);
|
|
366
|
+
|
|
367
|
+
s.stop('Projects linked');
|
|
368
|
+
|
|
369
|
+
// Show summary
|
|
370
|
+
const summary = [
|
|
371
|
+
`Linked projects:`,
|
|
372
|
+
...selectedProjects.map(p => ` ✓ ${p}`),
|
|
373
|
+
``,
|
|
374
|
+
`VSCode workspace file updated: ${workspaceName}.code-workspace`,
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
note(summary.join('\n'), 'Link Summary');
|
|
378
|
+
|
|
379
|
+
outro(pc.green('✓ Projects linked successfully!'));
|
|
380
|
+
}
|
package/src/lib/paths.ts
CHANGED
|
@@ -38,7 +38,8 @@ export function getWorkspaceName(workspaceRoot: string): string {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Resolve data path based on storage mode
|
|
41
|
+
* Resolve primary data path based on storage mode
|
|
42
|
+
* Note: For 'both' mode, use resolveAllDataPaths() to get all paths
|
|
42
43
|
*/
|
|
43
44
|
export function resolveDataPath(mode: StorageMode, workspaceName: string, workspaceRoot: string): string {
|
|
44
45
|
switch (mode) {
|
|
@@ -54,6 +55,29 @@ export function resolveDataPath(mode: StorageMode, workspaceName: string, worksp
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Resolve ALL data paths based on storage mode
|
|
60
|
+
* Returns array of paths where data should be stored:
|
|
61
|
+
* - 'global': [~/.rrce-workflow/workspaces/<name>]
|
|
62
|
+
* - 'workspace': [<workspace>/.rrce-workflow]
|
|
63
|
+
* - 'both': [<workspace>/.rrce-workflow, ~/.rrce-workflow/workspaces/<name>]
|
|
64
|
+
*/
|
|
65
|
+
export function resolveAllDataPaths(mode: StorageMode, workspaceName: string, workspaceRoot: string): string[] {
|
|
66
|
+
const globalPath = path.join(RRCE_HOME, 'workspaces', workspaceName);
|
|
67
|
+
const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
|
|
68
|
+
|
|
69
|
+
switch (mode) {
|
|
70
|
+
case 'global':
|
|
71
|
+
return [globalPath];
|
|
72
|
+
case 'workspace':
|
|
73
|
+
return [workspacePath];
|
|
74
|
+
case 'both':
|
|
75
|
+
return [workspacePath, globalPath];
|
|
76
|
+
default:
|
|
77
|
+
return [globalPath];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
/**
|
|
58
82
|
* Get RRCE home directory
|
|
59
83
|
*/
|
|
@@ -61,6 +85,35 @@ export function getRRCEHome(): string {
|
|
|
61
85
|
return RRCE_HOME;
|
|
62
86
|
}
|
|
63
87
|
|
|
88
|
+
/**
|
|
89
|
+
* List all projects in global storage
|
|
90
|
+
* @param excludeWorkspace - Workspace name to exclude from the list (typically current workspace)
|
|
91
|
+
* @returns Array of project names found in ~/.rrce-workflow/workspaces/
|
|
92
|
+
*/
|
|
93
|
+
export function listGlobalProjects(excludeWorkspace?: string): string[] {
|
|
94
|
+
const workspacesDir = path.join(RRCE_HOME, 'workspaces');
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(workspacesDir)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const entries = fs.readdirSync(workspacesDir, { withFileTypes: true });
|
|
102
|
+
return entries
|
|
103
|
+
.filter(entry => entry.isDirectory() && entry.name !== excludeWorkspace)
|
|
104
|
+
.map(entry => entry.name);
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get the knowledge path for a global project
|
|
112
|
+
*/
|
|
113
|
+
export function getGlobalProjectKnowledgePath(projectName: string): string {
|
|
114
|
+
return path.join(RRCE_HOME, 'workspaces', projectName, 'knowledge');
|
|
115
|
+
}
|
|
116
|
+
|
|
64
117
|
/**
|
|
65
118
|
* Ensure directory exists
|
|
66
119
|
*/
|
|
@@ -80,3 +133,71 @@ export function getAgentPromptPath(workspaceRoot: string, tool: 'copilot' | 'ant
|
|
|
80
133
|
return path.join(workspaceRoot, '.agent', 'workflows');
|
|
81
134
|
}
|
|
82
135
|
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Copy a file to all storage paths
|
|
139
|
+
* @param sourceFile - Absolute path to source file
|
|
140
|
+
* @param relativePath - Relative path within the data directory (e.g., 'knowledge/context.md')
|
|
141
|
+
* @param dataPaths - Array of data paths from resolveAllDataPaths()
|
|
142
|
+
*/
|
|
143
|
+
export function copyToAllStoragePaths(sourceFile: string, relativePath: string, dataPaths: string[]): void {
|
|
144
|
+
const content = fs.readFileSync(sourceFile);
|
|
145
|
+
|
|
146
|
+
for (const dataPath of dataPaths) {
|
|
147
|
+
const targetPath = path.join(dataPath, relativePath);
|
|
148
|
+
ensureDir(path.dirname(targetPath));
|
|
149
|
+
fs.writeFileSync(targetPath, content);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Write content to a file in all storage paths
|
|
155
|
+
* @param content - Content to write
|
|
156
|
+
* @param relativePath - Relative path within the data directory
|
|
157
|
+
* @param dataPaths - Array of data paths from resolveAllDataPaths()
|
|
158
|
+
*/
|
|
159
|
+
export function writeToAllStoragePaths(content: string | Buffer, relativePath: string, dataPaths: string[]): void {
|
|
160
|
+
for (const dataPath of dataPaths) {
|
|
161
|
+
const targetPath = path.join(dataPath, relativePath);
|
|
162
|
+
ensureDir(path.dirname(targetPath));
|
|
163
|
+
fs.writeFileSync(targetPath, content);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Copy a directory recursively to all storage paths
|
|
169
|
+
* @param sourceDir - Absolute path to source directory
|
|
170
|
+
* @param relativeDir - Relative directory path within the data directory
|
|
171
|
+
* @param dataPaths - Array of data paths from resolveAllDataPaths()
|
|
172
|
+
*/
|
|
173
|
+
export function copyDirToAllStoragePaths(sourceDir: string, relativeDir: string, dataPaths: string[]): void {
|
|
174
|
+
if (!fs.existsSync(sourceDir)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
|
179
|
+
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
182
|
+
const relativePath = path.join(relativeDir, entry.name);
|
|
183
|
+
|
|
184
|
+
if (entry.isDirectory()) {
|
|
185
|
+
copyDirToAllStoragePaths(sourcePath, relativePath, dataPaths);
|
|
186
|
+
} else {
|
|
187
|
+
copyToAllStoragePaths(sourcePath, relativePath, dataPaths);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Sync metadata subdirectories (knowledge, refs, tasks) to all storage paths
|
|
194
|
+
* Copies from agent-core to all configured storage locations
|
|
195
|
+
*/
|
|
196
|
+
export function syncMetadataToAll(agentCorePath: string, dataPaths: string[]): void {
|
|
197
|
+
const metadataDirs = ['knowledge', 'refs', 'tasks'];
|
|
198
|
+
|
|
199
|
+
for (const dir of metadataDirs) {
|
|
200
|
+
const sourceDir = path.join(agentCorePath, dir);
|
|
201
|
+
copyDirToAllStoragePaths(sourceDir, dir, dataPaths);
|
|
202
|
+
}
|
|
203
|
+
}
|
package/src/lib/prompts.ts
CHANGED
|
@@ -50,10 +50,17 @@ export function loadPromptsFromDir(dirPath: string): ParsedPrompt[] {
|
|
|
50
50
|
return prompts;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Get the agent-core root directory
|
|
55
|
+
*/
|
|
56
|
+
export function getAgentCoreDir(): string {
|
|
57
|
+
// Relative to the package root
|
|
58
|
+
return path.join(import.meta.dir, '..', '..', 'agent-core');
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
/**
|
|
54
62
|
* Get the agent-core prompts directory
|
|
55
63
|
*/
|
|
56
64
|
export function getAgentCorePromptsDir(): string {
|
|
57
|
-
|
|
58
|
-
return path.join(import.meta.dir, '..', '..', 'agent-core', 'prompts');
|
|
65
|
+
return path.join(getAgentCoreDir(), 'prompts');
|
|
59
66
|
}
|