rrce-workflow 0.3.12 → 0.3.14
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 +12 -0
- package/agent-core/prompts/_base.md +8 -1
- package/agent-core/prompts/doctor.md +2 -2
- package/agent-core/prompts/documentation.md +1 -1
- package/agent-core/prompts/executor.md +1 -1
- package/agent-core/prompts/init.md +1 -1
- package/agent-core/prompts/orchestrator.md +13 -3
- package/agent-core/prompts/planning_discussion.md +1 -1
- package/agent-core/prompts/research_discussion.md +2 -2
- package/agent-core/prompts/sync.md +1 -1
- package/dist/index.js +469 -519
- package/docs/AI_AGENT_GUIDE.md +65 -0
- package/package.json +1 -1
- package/dist/commands/selector.d.ts +0 -1
- package/dist/commands/selector.js +0 -29
- package/dist/commands/wizard/index.d.ts +0 -1
- package/dist/commands/wizard/index.js +0 -86
- package/dist/commands/wizard/link-flow.d.ts +0 -5
- package/dist/commands/wizard/link-flow.js +0 -97
- package/dist/commands/wizard/setup-flow.d.ts +0 -4
- package/dist/commands/wizard/setup-flow.js +0 -262
- package/dist/commands/wizard/sync-flow.d.ts +0 -4
- package/dist/commands/wizard/sync-flow.js +0 -67
- package/dist/commands/wizard/update-flow.d.ts +0 -4
- package/dist/commands/wizard/update-flow.js +0 -85
- package/dist/commands/wizard/utils.d.ts +0 -9
- package/dist/commands/wizard/utils.js +0 -33
- package/dist/commands/wizard/vscode.d.ts +0 -15
- package/dist/commands/wizard/vscode.js +0 -148
- package/dist/index.d.ts +0 -1
- package/dist/lib/autocomplete-prompt.d.ts +0 -14
- package/dist/lib/autocomplete-prompt.js +0 -167
- package/dist/lib/detection.d.ts +0 -44
- package/dist/lib/detection.js +0 -185
- package/dist/lib/git.d.ts +0 -12
- package/dist/lib/git.js +0 -37
- package/dist/lib/paths.d.ts +0 -108
- package/dist/lib/paths.js +0 -296
- package/dist/lib/prompts.d.ts +0 -18
- package/dist/lib/prompts.js +0 -62
- package/dist/types/prompt.d.ts +0 -54
- package/dist/types/prompt.js +0 -20
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# AI Agent Architecture Guide: RRCE-Workflow
|
|
2
|
+
|
|
3
|
+
This guide helps AI coding agents understand the codebase structure and conventions to ensure accurate implementations and prevent duplication.
|
|
4
|
+
|
|
5
|
+
## 1. Project Navigation
|
|
6
|
+
|
|
7
|
+
### Core Directories
|
|
8
|
+
- `src/mcp/`: MCP Server implementation and TUI
|
|
9
|
+
- `ui/`: React (Ink) components for the TUI
|
|
10
|
+
- `handlers/`: MCP protocol handlers (tools, prompts, resources)
|
|
11
|
+
- `services/`: Business logic (RAG, Indexing, Context Extraction)
|
|
12
|
+
- `lib/`: TUI-specific utilities
|
|
13
|
+
- `src/lib/`: Shared core utilities (detection, paths, project utils)
|
|
14
|
+
- `agent-core/`: Global agent prompts and templates
|
|
15
|
+
- `docs/`: System documentation
|
|
16
|
+
|
|
17
|
+
### Feature-to-File Mapping
|
|
18
|
+
| Feature | File(s) |
|
|
19
|
+
|---------|---------|
|
|
20
|
+
| TUI Dashboard | `src/mcp/ui/Overview.tsx` |
|
|
21
|
+
| Projects Management | `src/mcp/ui/ProjectsView.tsx` |
|
|
22
|
+
| Task Browser | `src/mcp/ui/TasksView.tsx` |
|
|
23
|
+
| MCP Tools | `src/mcp/handlers/tools.ts` |
|
|
24
|
+
| MCP Resources | `src/mcp/resources.ts` |
|
|
25
|
+
| RAG Logic | `src/mcp/services/rag.ts` |
|
|
26
|
+
| Indexing Background Jobs | `src/mcp/services/indexing-jobs.ts` |
|
|
27
|
+
| Installation Logic | `src/mcp/install.ts` |
|
|
28
|
+
|
|
29
|
+
## 2. Coding Conventions
|
|
30
|
+
|
|
31
|
+
### Single Source of Truth (SSOT)
|
|
32
|
+
Always use existing utilities instead of reimplementing logic:
|
|
33
|
+
- **Project Sorting**: Use `sortProjects` from `src/lib/project-utils.ts`.
|
|
34
|
+
- **Project Config Lookup**: Use `findProjectConfig` from `src/mcp/config-utils.ts`.
|
|
35
|
+
- **Path Resolution**: Use `resolveDataPath` or `resolveProjectPaths`.
|
|
36
|
+
|
|
37
|
+
### TUI Components
|
|
38
|
+
- **Size Limit**: Keep components under 200 lines. Extract sub-components to `src/mcp/ui/components/`.
|
|
39
|
+
- **Styling**: Use `ink` components. Follow the "Unified Cockpit" aesthetic (white borders).
|
|
40
|
+
- **Status Icons**: Use helpers in `src/mcp/ui/ui-helpers.ts`.
|
|
41
|
+
|
|
42
|
+
### Type Safety
|
|
43
|
+
- **Avoid `any`**: Use `TaskMeta`, `AgentInfo`, and `DetectedProject` types.
|
|
44
|
+
- **Interfaces**: Define clear interfaces for component props and service parameters.
|
|
45
|
+
- **Validation**: Validate input at the boundaries of the MCP tools and resources.
|
|
46
|
+
|
|
47
|
+
## 3. Implementation Patterns
|
|
48
|
+
|
|
49
|
+
### Error Handling
|
|
50
|
+
- Never use empty `catch {}` blocks.
|
|
51
|
+
- Use `logger.error(message, error)` for internal logging.
|
|
52
|
+
- Surface meaningful error messages to the user in the TUI via `errorLine` state.
|
|
53
|
+
|
|
54
|
+
### Async Operations
|
|
55
|
+
- Use `indexingJobs` for long-running background tasks.
|
|
56
|
+
- Ensure the TUI remains responsive by using cooperative yields (`setImmediate`) in tight loops.
|
|
57
|
+
|
|
58
|
+
### Metadata
|
|
59
|
+
- Task metadata is stored in `meta.json` within project task directories.
|
|
60
|
+
- Use `updateTask` in `resources.ts` to persist changes to task state.
|
|
61
|
+
|
|
62
|
+
## 4. Common Pitfalls to Avoid
|
|
63
|
+
- **Duplicating Install Logic**: All IDE configurations are handled in `install.ts` using a data-driven approach via `TARGET_CONFIGS`.
|
|
64
|
+
- **Hardcoding Paths**: Always use path resolution utilities to handle Global vs. Workspace modes.
|
|
65
|
+
- **Stale Context**: When searching code, be aware that semantic search results might be stale if indexing is in progress. Check the `indexingInProgress` flag.
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runSelector(): Promise<void>;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { intro, select, note, cancel, isCancel, outro } from '@clack/prompts';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { loadPromptsFromDir, getAgentCorePromptsDir } from '../lib/prompts';
|
|
5
|
-
export async function runSelector() {
|
|
6
|
-
const workspaceName = path.basename(process.cwd());
|
|
7
|
-
intro(pc.cyan(pc.inverse(` RRCE-Workflow | ${workspaceName} `)));
|
|
8
|
-
const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
|
|
9
|
-
if (prompts.length === 0) {
|
|
10
|
-
cancel('No agents found. Run `rrce-workflow` to set up.');
|
|
11
|
-
process.exit(0);
|
|
12
|
-
}
|
|
13
|
-
const selection = await select({
|
|
14
|
-
message: 'Select an agent:',
|
|
15
|
-
options: prompts.map(p => ({
|
|
16
|
-
value: p,
|
|
17
|
-
label: p.frontmatter.name,
|
|
18
|
-
hint: p.frontmatter.description
|
|
19
|
-
})),
|
|
20
|
-
});
|
|
21
|
-
if (isCancel(selection)) {
|
|
22
|
-
cancel('Selection cancelled.');
|
|
23
|
-
process.exit(0);
|
|
24
|
-
}
|
|
25
|
-
const prompt = selection;
|
|
26
|
-
note(`Use this agent in your IDE by invoking:
|
|
27
|
-
${pc.bold(pc.cyan(`@${prompt.frontmatter.name}`))}`, 'Agent Selected');
|
|
28
|
-
outro('Done');
|
|
29
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runWizard(): Promise<void>;
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { intro, select, spinner, note, outro, isCancel } from '@clack/prompts';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import { getGitUser } from '../../lib/git';
|
|
5
|
-
import { detectWorkspaceRoot, getWorkspaceName, listGlobalProjects, getLocalWorkspacePath, getConfigPath } from '../../lib/paths';
|
|
6
|
-
// Import flows
|
|
7
|
-
import { runSetupFlow } from './setup-flow';
|
|
8
|
-
import { runLinkProjectsFlow } from './link-flow';
|
|
9
|
-
import { runSyncToGlobalFlow } from './sync-flow';
|
|
10
|
-
import { runUpdateFlow } from './update-flow';
|
|
11
|
-
export async function runWizard() {
|
|
12
|
-
intro(pc.cyan(pc.inverse(' RRCE-Workflow Setup ')));
|
|
13
|
-
const s = spinner();
|
|
14
|
-
s.start('Detecting environment');
|
|
15
|
-
const workspacePath = detectWorkspaceRoot();
|
|
16
|
-
const workspaceName = getWorkspaceName(workspacePath);
|
|
17
|
-
const gitUser = getGitUser();
|
|
18
|
-
await new Promise(r => setTimeout(r, 800)); // Dramatic pause
|
|
19
|
-
s.stop('Environment detected');
|
|
20
|
-
note(`Git User: ${pc.bold(gitUser || '(not found)')}
|
|
21
|
-
Workspace: ${pc.bold(workspaceName)}`, 'Context');
|
|
22
|
-
// Check for existing projects in global storage
|
|
23
|
-
const existingProjects = listGlobalProjects(workspaceName);
|
|
24
|
-
// Check if already configured (using getConfigPath for new/legacy support)
|
|
25
|
-
const configFilePath = getConfigPath(workspacePath);
|
|
26
|
-
const isAlreadyConfigured = fs.existsSync(configFilePath);
|
|
27
|
-
// Check current storage mode from config
|
|
28
|
-
let currentStorageMode = null;
|
|
29
|
-
if (isAlreadyConfigured) {
|
|
30
|
-
try {
|
|
31
|
-
const configContent = fs.readFileSync(configFilePath, 'utf-8');
|
|
32
|
-
const modeMatch = configContent.match(/mode:\s*(global|workspace|both)/);
|
|
33
|
-
currentStorageMode = modeMatch?.[1] ?? null;
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
// Ignore parse errors
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// Check if workspace has local data that could be synced
|
|
40
|
-
const localDataPath = getLocalWorkspacePath(workspacePath);
|
|
41
|
-
const hasLocalData = fs.existsSync(localDataPath);
|
|
42
|
-
// If already configured, show menu
|
|
43
|
-
if (isAlreadyConfigured) {
|
|
44
|
-
const menuOptions = [];
|
|
45
|
-
// Add link option if other projects exist
|
|
46
|
-
if (existingProjects.length > 0) {
|
|
47
|
-
menuOptions.push({
|
|
48
|
-
value: 'link',
|
|
49
|
-
label: 'Link other project knowledge',
|
|
50
|
-
hint: `${existingProjects.length} projects available`
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
// Add sync to global option if using workspace-only mode
|
|
54
|
-
if (currentStorageMode === 'workspace' && hasLocalData) {
|
|
55
|
-
menuOptions.push({
|
|
56
|
-
value: 'sync-global',
|
|
57
|
-
label: 'Sync to global storage',
|
|
58
|
-
hint: 'Share knowledge with other projects'
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
menuOptions.push({ value: 'update', label: 'Update from package', hint: 'Get latest prompts & templates' });
|
|
62
|
-
menuOptions.push({ value: 'exit', label: 'Exit' });
|
|
63
|
-
const action = await select({
|
|
64
|
-
message: 'This workspace is already configured. What would you like to do?',
|
|
65
|
-
options: menuOptions,
|
|
66
|
-
});
|
|
67
|
-
if (isCancel(action) || action === 'exit') {
|
|
68
|
-
outro('Exited.');
|
|
69
|
-
process.exit(0);
|
|
70
|
-
}
|
|
71
|
-
if (action === 'link') {
|
|
72
|
-
await runLinkProjectsFlow(workspacePath, workspaceName, existingProjects);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (action === 'sync-global') {
|
|
76
|
-
await runSyncToGlobalFlow(workspacePath, workspaceName);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (action === 'update') {
|
|
80
|
-
await runUpdateFlow(workspacePath, workspaceName, currentStorageMode);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
// Run full setup flow for new workspaces
|
|
85
|
-
await runSetupFlow(workspacePath, workspaceName, existingProjects);
|
|
86
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Run the link-only flow for adding other project knowledge to an existing workspace
|
|
3
|
-
* Now supports detecting workspace-scoped sibling projects, not just global storage
|
|
4
|
-
*/
|
|
5
|
-
export declare function runLinkProjectsFlow(workspacePath: string, workspaceName: string, existingProjects?: string[]): Promise<void>;
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { multiselect, spinner, note, outro, cancel, isCancel } from '@clack/prompts';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import { getEffectiveRRCEHome, getConfigPath } from '../../lib/paths';
|
|
5
|
-
import { generateVSCodeWorkspace } from './vscode';
|
|
6
|
-
import { scanForProjects, getProjectDisplayLabel } from '../../lib/detection';
|
|
7
|
-
/**
|
|
8
|
-
* Run the link-only flow for adding other project knowledge to an existing workspace
|
|
9
|
-
* Now supports detecting workspace-scoped sibling projects, not just global storage
|
|
10
|
-
*/
|
|
11
|
-
export async function runLinkProjectsFlow(workspacePath, workspaceName, existingProjects) {
|
|
12
|
-
// Scan for projects using the new detection system
|
|
13
|
-
const detectedProjects = scanForProjects({
|
|
14
|
-
excludeWorkspace: workspaceName,
|
|
15
|
-
workspacePath: workspacePath,
|
|
16
|
-
scanSiblings: true,
|
|
17
|
-
});
|
|
18
|
-
// If legacy string array is passed, use that instead (for backwards compat)
|
|
19
|
-
const projects = existingProjects
|
|
20
|
-
? existingProjects.map(name => ({ name, source: 'global' }))
|
|
21
|
-
: detectedProjects;
|
|
22
|
-
if (projects.length === 0) {
|
|
23
|
-
outro(pc.yellow('No other projects found. Try setting up another project first.'));
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const customGlobalPath = getEffectiveRRCEHome(workspacePath);
|
|
27
|
-
// Build options with source labels
|
|
28
|
-
const linkedProjects = await multiselect({
|
|
29
|
-
message: 'Select projects to link:',
|
|
30
|
-
options: projects.map(project => ({
|
|
31
|
-
value: project.name,
|
|
32
|
-
label: project.name,
|
|
33
|
-
hint: pc.dim(getProjectDisplayLabel(project)),
|
|
34
|
-
})),
|
|
35
|
-
required: true,
|
|
36
|
-
});
|
|
37
|
-
if (isCancel(linkedProjects)) {
|
|
38
|
-
cancel('Cancelled.');
|
|
39
|
-
process.exit(0);
|
|
40
|
-
}
|
|
41
|
-
const selectedProjectNames = linkedProjects;
|
|
42
|
-
if (selectedProjectNames.length === 0) {
|
|
43
|
-
outro('No projects selected.');
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
// Get the full DetectedProject objects for selected projects
|
|
47
|
-
const selectedProjects = projects.filter(p => selectedProjectNames.includes(p.name));
|
|
48
|
-
const s = spinner();
|
|
49
|
-
s.start('Linking projects');
|
|
50
|
-
// Update config.yaml with linked projects
|
|
51
|
-
const configFilePath = getConfigPath(workspacePath);
|
|
52
|
-
let configContent = fs.readFileSync(configFilePath, 'utf-8');
|
|
53
|
-
// Check if linked_projects section exists
|
|
54
|
-
if (configContent.includes('linked_projects:')) {
|
|
55
|
-
// Append to existing section - find and update
|
|
56
|
-
const lines = configContent.split('\n');
|
|
57
|
-
const linkedIndex = lines.findIndex(l => l.trim() === 'linked_projects:');
|
|
58
|
-
if (linkedIndex !== -1) {
|
|
59
|
-
// Find where to insert new projects (after existing ones)
|
|
60
|
-
let insertIndex = linkedIndex + 1;
|
|
61
|
-
while (insertIndex < lines.length && lines[insertIndex]?.startsWith(' - ')) {
|
|
62
|
-
insertIndex++;
|
|
63
|
-
}
|
|
64
|
-
// Add new projects that aren't already there
|
|
65
|
-
for (const name of selectedProjectNames) {
|
|
66
|
-
if (!configContent.includes(` - ${name}`)) {
|
|
67
|
-
lines.splice(insertIndex, 0, ` - ${name}`);
|
|
68
|
-
insertIndex++;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
configContent = lines.join('\n');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// Add new linked_projects section
|
|
76
|
-
configContent += `\nlinked_projects:\n`;
|
|
77
|
-
selectedProjectNames.forEach(name => {
|
|
78
|
-
configContent += ` - ${name}\n`;
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
fs.writeFileSync(configFilePath, configContent);
|
|
82
|
-
// Update VSCode workspace file with full project info (includes refs, tasks)
|
|
83
|
-
generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
|
|
84
|
-
s.stop('Projects linked');
|
|
85
|
-
// Show summary with project sources
|
|
86
|
-
const workspaceFile = `${workspaceName}.code-workspace`;
|
|
87
|
-
const summary = [
|
|
88
|
-
`Linked projects:`,
|
|
89
|
-
...selectedProjects.map(p => ` ✓ ${p.name} ${pc.dim(`(${p.source})`)}`),
|
|
90
|
-
``,
|
|
91
|
-
`Workspace file: ${pc.cyan(workspaceFile)}`,
|
|
92
|
-
``,
|
|
93
|
-
pc.dim('Includes: knowledge, refs, tasks folders'),
|
|
94
|
-
];
|
|
95
|
-
note(summary.join('\n'), 'Link Summary');
|
|
96
|
-
outro(pc.green(`✓ Projects linked! Open ${pc.bold(workspaceFile)} in VSCode to access linked knowledge.`));
|
|
97
|
-
}
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { group, 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 { ensureDir, getAgentPromptPath, syncMetadataToAll, copyDirToAllStoragePaths, checkWriteAccess, getDefaultRRCEHome } from '../../lib/paths';
|
|
6
|
-
import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../../lib/prompts';
|
|
7
|
-
import { copyPromptsToDir } from './utils';
|
|
8
|
-
import { generateVSCodeWorkspace } from './vscode';
|
|
9
|
-
import { directoryAutocomplete, isCancel as isAutocompleteCancel } from '../../lib/autocomplete-prompt';
|
|
10
|
-
/**
|
|
11
|
-
* Run the full setup flow for new workspaces
|
|
12
|
-
*/
|
|
13
|
-
export async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
14
|
-
const s = spinner();
|
|
15
|
-
// Full setup flow
|
|
16
|
-
const config = await group({
|
|
17
|
-
storageMode: () => select({
|
|
18
|
-
message: 'Where should workflow data be stored?',
|
|
19
|
-
options: [
|
|
20
|
-
{ value: 'global', label: 'Global (~/.rrce-workflow/)' },
|
|
21
|
-
{ value: 'workspace', label: 'Workspace (.rrce-workflow/)' },
|
|
22
|
-
{ value: 'both', label: 'Both' },
|
|
23
|
-
],
|
|
24
|
-
initialValue: 'global',
|
|
25
|
-
}),
|
|
26
|
-
tools: () => multiselect({
|
|
27
|
-
message: 'Which AI tools do you use?',
|
|
28
|
-
options: [
|
|
29
|
-
{ value: 'copilot', label: 'GitHub Copilot', hint: 'VSCode' },
|
|
30
|
-
{ value: 'antigravity', label: 'Antigravity IDE' },
|
|
31
|
-
],
|
|
32
|
-
required: false,
|
|
33
|
-
}),
|
|
34
|
-
linkedProjects: () => {
|
|
35
|
-
// Only show if there are other projects to link
|
|
36
|
-
if (existingProjects.length === 0) {
|
|
37
|
-
return Promise.resolve([]);
|
|
38
|
-
}
|
|
39
|
-
return multiselect({
|
|
40
|
-
message: 'Link knowledge from other projects?',
|
|
41
|
-
options: existingProjects.map(name => ({
|
|
42
|
-
value: name,
|
|
43
|
-
label: name,
|
|
44
|
-
hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
|
|
45
|
-
})),
|
|
46
|
-
required: false,
|
|
47
|
-
});
|
|
48
|
-
},
|
|
49
|
-
confirm: () => confirm({
|
|
50
|
-
message: 'Create configuration?',
|
|
51
|
-
initialValue: true,
|
|
52
|
-
}),
|
|
53
|
-
}, {
|
|
54
|
-
onCancel: () => {
|
|
55
|
-
cancel('Setup process cancelled.');
|
|
56
|
-
process.exit(0);
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
if (!config.confirm) {
|
|
60
|
-
outro('Setup cancelled by user.');
|
|
61
|
-
process.exit(0);
|
|
62
|
-
}
|
|
63
|
-
// Determine global path for 'global' or 'both' modes
|
|
64
|
-
let customGlobalPath;
|
|
65
|
-
if (config.storageMode === 'global' || config.storageMode === 'both') {
|
|
66
|
-
customGlobalPath = await resolveGlobalPath();
|
|
67
|
-
if (!customGlobalPath) {
|
|
68
|
-
cancel('Setup cancelled - no writable global path available.');
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
s.start('Generating configuration');
|
|
73
|
-
try {
|
|
74
|
-
await generateConfiguration({
|
|
75
|
-
storageMode: config.storageMode,
|
|
76
|
-
globalPath: customGlobalPath,
|
|
77
|
-
tools: config.tools,
|
|
78
|
-
linkedProjects: config.linkedProjects,
|
|
79
|
-
}, workspacePath, workspaceName);
|
|
80
|
-
s.stop('Configuration generated');
|
|
81
|
-
// Show summary
|
|
82
|
-
const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, customGlobalPath);
|
|
83
|
-
const summary = [
|
|
84
|
-
`Storage: ${config.storageMode === 'both' ? 'global + workspace' : config.storageMode}`,
|
|
85
|
-
];
|
|
86
|
-
if (customGlobalPath && customGlobalPath !== getDefaultRRCEHome()) {
|
|
87
|
-
summary.push(`Global path: ${pc.cyan(customGlobalPath)}`);
|
|
88
|
-
}
|
|
89
|
-
if (dataPaths.length > 0) {
|
|
90
|
-
summary.push(`Data paths:`);
|
|
91
|
-
dataPaths.forEach(p => summary.push(` - ${p}`));
|
|
92
|
-
}
|
|
93
|
-
const selectedTools = config.tools;
|
|
94
|
-
if (selectedTools.length > 0) {
|
|
95
|
-
summary.push(`Tools: ${selectedTools.join(', ')}`);
|
|
96
|
-
}
|
|
97
|
-
const linkedProjects = config.linkedProjects;
|
|
98
|
-
if (linkedProjects.length > 0) {
|
|
99
|
-
summary.push(`Linked projects: ${linkedProjects.join(', ')}`);
|
|
100
|
-
summary.push(`Workspace file: ${pc.cyan(`${workspaceName}.code-workspace`)}`);
|
|
101
|
-
}
|
|
102
|
-
note(summary.join('\n'), 'Setup Summary');
|
|
103
|
-
// Show appropriate outro message
|
|
104
|
-
if (linkedProjects.length > 0) {
|
|
105
|
-
outro(pc.green(`✓ Setup complete! Open ${pc.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
outro(pc.green(`✓ Setup complete! Your agents are ready to use.`));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
s.stop('Error occurred');
|
|
113
|
-
cancel(`Failed to setup: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
-
process.exit(1);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Resolve global path - always prompt user to choose default or custom
|
|
119
|
-
*/
|
|
120
|
-
async function resolveGlobalPath() {
|
|
121
|
-
const defaultPath = getDefaultRRCEHome();
|
|
122
|
-
const isDefaultWritable = checkWriteAccess(defaultPath);
|
|
123
|
-
// Build options
|
|
124
|
-
const options = [];
|
|
125
|
-
// Default option
|
|
126
|
-
options.push({
|
|
127
|
-
value: 'default',
|
|
128
|
-
label: `Default (${defaultPath})`,
|
|
129
|
-
hint: isDefaultWritable ? pc.green('✓ writable') : pc.red('✗ not writable'),
|
|
130
|
-
});
|
|
131
|
-
// Custom option
|
|
132
|
-
options.push({
|
|
133
|
-
value: 'custom',
|
|
134
|
-
label: 'Custom path',
|
|
135
|
-
hint: 'Specify your own directory',
|
|
136
|
-
});
|
|
137
|
-
const choice = await select({
|
|
138
|
-
message: 'Global storage location:',
|
|
139
|
-
options,
|
|
140
|
-
initialValue: isDefaultWritable ? 'default' : 'custom',
|
|
141
|
-
});
|
|
142
|
-
if (isCancel(choice)) {
|
|
143
|
-
return undefined;
|
|
144
|
-
}
|
|
145
|
-
if (choice === 'default') {
|
|
146
|
-
// Verify it's writable
|
|
147
|
-
if (!isDefaultWritable) {
|
|
148
|
-
note(`${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.`, 'Write Access Issue');
|
|
149
|
-
// Recursively ask again
|
|
150
|
-
return resolveGlobalPath();
|
|
151
|
-
}
|
|
152
|
-
return defaultPath;
|
|
153
|
-
}
|
|
154
|
-
// Custom path input with Tab autocomplete
|
|
155
|
-
const suggestedPath = path.join(process.env.HOME || '~', '.local', 'share', 'rrce-workflow');
|
|
156
|
-
const customPath = await directoryAutocomplete({
|
|
157
|
-
message: 'Enter custom global path:',
|
|
158
|
-
initialValue: suggestedPath,
|
|
159
|
-
hint: 'Tab to autocomplete',
|
|
160
|
-
validate: (value) => {
|
|
161
|
-
if (!value.trim()) {
|
|
162
|
-
return 'Path cannot be empty';
|
|
163
|
-
}
|
|
164
|
-
// Expand ~ to home directory
|
|
165
|
-
const expandedPath = value.startsWith('~')
|
|
166
|
-
? value.replace('~', process.env.HOME || '')
|
|
167
|
-
: value;
|
|
168
|
-
if (!checkWriteAccess(expandedPath)) {
|
|
169
|
-
return `Cannot write to ${expandedPath}. Please choose a writable path.`;
|
|
170
|
-
}
|
|
171
|
-
return undefined;
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
if (isAutocompleteCancel(customPath)) {
|
|
175
|
-
return undefined;
|
|
176
|
-
}
|
|
177
|
-
// Path is already expanded by directoryAutocomplete
|
|
178
|
-
return customPath;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Generate configuration files and directories
|
|
182
|
-
*/
|
|
183
|
-
async function generateConfiguration(config, workspacePath, workspaceName) {
|
|
184
|
-
const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
|
|
185
|
-
for (const dataPath of dataPaths) {
|
|
186
|
-
ensureDir(dataPath);
|
|
187
|
-
// Create agent metadata subdirectories (data only, no prompts)
|
|
188
|
-
ensureDir(path.join(dataPath, 'knowledge'));
|
|
189
|
-
ensureDir(path.join(dataPath, 'refs'));
|
|
190
|
-
ensureDir(path.join(dataPath, 'tasks'));
|
|
191
|
-
ensureDir(path.join(dataPath, 'templates'));
|
|
192
|
-
}
|
|
193
|
-
// Get the agent-core directory path
|
|
194
|
-
const agentCoreDir = getAgentCoreDir();
|
|
195
|
-
// Sync metadata (knowledge, refs, tasks) from agent-core to all storage locations
|
|
196
|
-
syncMetadataToAll(agentCoreDir, dataPaths);
|
|
197
|
-
// Also copy templates to all storage locations
|
|
198
|
-
copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', dataPaths);
|
|
199
|
-
// Load prompts for IDE-specific locations
|
|
200
|
-
const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
|
|
201
|
-
// Copy prompts to tool-specific locations (for IDE integration)
|
|
202
|
-
if (config.tools.includes('copilot')) {
|
|
203
|
-
const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
|
|
204
|
-
ensureDir(copilotPath);
|
|
205
|
-
copyPromptsToDir(prompts, copilotPath, '.agent.md');
|
|
206
|
-
}
|
|
207
|
-
if (config.tools.includes('antigravity')) {
|
|
208
|
-
const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
|
|
209
|
-
ensureDir(antigravityPath);
|
|
210
|
-
copyPromptsToDir(prompts, antigravityPath, '.md');
|
|
211
|
-
}
|
|
212
|
-
// Create workspace config (inside .rrce-workflow folder)
|
|
213
|
-
const workspaceConfigPath = path.join(workspacePath, '.rrce-workflow', 'config.yaml');
|
|
214
|
-
ensureDir(path.dirname(workspaceConfigPath));
|
|
215
|
-
let configContent = `# RRCE-Workflow Configuration
|
|
216
|
-
version: 1
|
|
217
|
-
|
|
218
|
-
storage:
|
|
219
|
-
mode: ${config.storageMode}`;
|
|
220
|
-
// Add custom global path if different from default
|
|
221
|
-
if (config.globalPath && config.globalPath !== getDefaultRRCEHome()) {
|
|
222
|
-
configContent += `\n globalPath: "${config.globalPath}"`;
|
|
223
|
-
}
|
|
224
|
-
configContent += `
|
|
225
|
-
|
|
226
|
-
project:
|
|
227
|
-
name: "${workspaceName}"
|
|
228
|
-
|
|
229
|
-
tools:
|
|
230
|
-
copilot: ${config.tools.includes('copilot')}
|
|
231
|
-
antigravity: ${config.tools.includes('antigravity')}
|
|
232
|
-
`;
|
|
233
|
-
// Add linked projects if any
|
|
234
|
-
if (config.linkedProjects.length > 0) {
|
|
235
|
-
configContent += `\nlinked_projects:\n`;
|
|
236
|
-
config.linkedProjects.forEach(name => {
|
|
237
|
-
configContent += ` - ${name}\n`;
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
fs.writeFileSync(workspaceConfigPath, configContent);
|
|
241
|
-
// Generate VSCode workspace file if using copilot or has linked projects
|
|
242
|
-
if (config.tools.includes('copilot') || config.linkedProjects.length > 0) {
|
|
243
|
-
generateVSCodeWorkspace(workspacePath, workspaceName, config.linkedProjects, config.globalPath);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Get data paths based on storage mode and custom global path
|
|
248
|
-
*/
|
|
249
|
-
function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
|
|
250
|
-
const globalPath = path.join(customGlobalPath || getDefaultRRCEHome(), 'workspaces', workspaceName);
|
|
251
|
-
const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
|
|
252
|
-
switch (mode) {
|
|
253
|
-
case 'global':
|
|
254
|
-
return [globalPath];
|
|
255
|
-
case 'workspace':
|
|
256
|
-
return [workspacePath];
|
|
257
|
-
case 'both':
|
|
258
|
-
return [workspacePath, globalPath];
|
|
259
|
-
default:
|
|
260
|
-
return [globalPath];
|
|
261
|
-
}
|
|
262
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
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 { ensureDir, getLocalWorkspacePath, getEffectiveRRCEHome, getConfigPath } from '../../lib/paths';
|
|
6
|
-
import { copyDirRecursive } from './utils';
|
|
7
|
-
/**
|
|
8
|
-
* Sync workspace knowledge to global storage so other projects can reference it
|
|
9
|
-
*/
|
|
10
|
-
export async function runSyncToGlobalFlow(workspacePath, workspaceName) {
|
|
11
|
-
const localPath = getLocalWorkspacePath(workspacePath);
|
|
12
|
-
const customGlobalPath = getEffectiveRRCEHome(workspacePath);
|
|
13
|
-
const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
|
|
14
|
-
// Check what exists locally
|
|
15
|
-
const subdirs = ['knowledge', 'prompts', 'templates', 'tasks', 'refs'];
|
|
16
|
-
const existingDirs = subdirs.filter(dir => fs.existsSync(path.join(localPath, dir)));
|
|
17
|
-
if (existingDirs.length === 0) {
|
|
18
|
-
outro(pc.yellow('No data found in workspace storage to sync.'));
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
// Show what will be synced
|
|
22
|
-
note(`The following will be copied to global storage:\n${existingDirs.map(d => ` • ${d}/`).join('\n')}\n\nDestination: ${pc.cyan(globalPath)}`, 'Sync Preview');
|
|
23
|
-
const shouldSync = await confirm({
|
|
24
|
-
message: 'Proceed with sync to global storage?',
|
|
25
|
-
initialValue: true,
|
|
26
|
-
});
|
|
27
|
-
if (isCancel(shouldSync) || !shouldSync) {
|
|
28
|
-
outro('Sync cancelled.');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const s = spinner();
|
|
32
|
-
s.start('Syncing to global storage');
|
|
33
|
-
try {
|
|
34
|
-
// Ensure global directory exists
|
|
35
|
-
ensureDir(globalPath);
|
|
36
|
-
// Copy each directory
|
|
37
|
-
for (const dir of existingDirs) {
|
|
38
|
-
const srcDir = path.join(localPath, dir);
|
|
39
|
-
const destDir = path.join(globalPath, dir);
|
|
40
|
-
ensureDir(destDir);
|
|
41
|
-
// Copy files recursively
|
|
42
|
-
copyDirRecursive(srcDir, destDir);
|
|
43
|
-
}
|
|
44
|
-
// Update the config to reflect 'both' mode
|
|
45
|
-
const configFilePath = getConfigPath(workspacePath);
|
|
46
|
-
let configContent = fs.readFileSync(configFilePath, 'utf-8');
|
|
47
|
-
configContent = configContent.replace(/mode:\s*workspace/, 'mode: both');
|
|
48
|
-
fs.writeFileSync(configFilePath, configContent);
|
|
49
|
-
s.stop('Sync complete');
|
|
50
|
-
const summary = [
|
|
51
|
-
`Synced directories:`,
|
|
52
|
-
...existingDirs.map(d => ` ✓ ${d}/`),
|
|
53
|
-
``,
|
|
54
|
-
`Global path: ${pc.cyan(globalPath)}`,
|
|
55
|
-
`Storage mode updated to: ${pc.bold('both')}`,
|
|
56
|
-
``,
|
|
57
|
-
`Other projects can now link this knowledge!`,
|
|
58
|
-
];
|
|
59
|
-
note(summary.join('\n'), 'Sync Summary');
|
|
60
|
-
outro(pc.green('✓ Workspace knowledge synced to global storage!'));
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
s.stop('Error occurred');
|
|
64
|
-
cancel(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
}
|