rrce-workflow 0.2.7 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,235 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import type { StorageMode } from '../types/prompt';
4
+ import { getDefaultRRCEHome } from './paths';
5
+
6
+ /**
7
+ * Detected rrce-workflow project information
8
+ */
9
+ export interface DetectedProject {
10
+ name: string;
11
+ path: string; // Absolute path to project root
12
+ dataPath: string; // Path to .rrce-workflow data directory
13
+ source: 'global' | 'sibling' | 'parent';
14
+ storageMode?: StorageMode;
15
+ knowledgePath?: string;
16
+ refsPath?: string;
17
+ tasksPath?: string;
18
+ }
19
+
20
+ interface ScanOptions {
21
+ excludeWorkspace?: string; // Current workspace name to exclude
22
+ workspacePath?: string; // Current workspace path for sibling detection
23
+ scanSiblings?: boolean; // Whether to scan sibling directories (default: true)
24
+ }
25
+
26
+ /**
27
+ * Scan for rrce-workflow projects in various locations
28
+ */
29
+ export function scanForProjects(options: ScanOptions = {}): DetectedProject[] {
30
+ const { excludeWorkspace, workspacePath, scanSiblings = true } = options;
31
+ const projects: DetectedProject[] = [];
32
+ const seenPaths = new Set<string>();
33
+
34
+ // 1. Scan global storage (~/.rrce-workflow/workspaces/)
35
+ const globalProjects = scanGlobalStorage(excludeWorkspace);
36
+ for (const project of globalProjects) {
37
+ if (!seenPaths.has(project.path)) {
38
+ seenPaths.add(project.path);
39
+ projects.push(project);
40
+ }
41
+ }
42
+
43
+ // 2. Scan sibling directories (same parent as current workspace)
44
+ if (scanSiblings && workspacePath) {
45
+ const siblingProjects = scanSiblingDirectories(workspacePath, excludeWorkspace);
46
+ for (const project of siblingProjects) {
47
+ if (!seenPaths.has(project.path)) {
48
+ seenPaths.add(project.path);
49
+ projects.push(project);
50
+ }
51
+ }
52
+ }
53
+
54
+ return projects;
55
+ }
56
+
57
+ /**
58
+ * Scan global storage for projects
59
+ */
60
+ function scanGlobalStorage(excludeWorkspace?: string): DetectedProject[] {
61
+ const rrceHome = getDefaultRRCEHome();
62
+ const workspacesDir = path.join(rrceHome, 'workspaces');
63
+ const projects: DetectedProject[] = [];
64
+
65
+ if (!fs.existsSync(workspacesDir)) {
66
+ return projects;
67
+ }
68
+
69
+ try {
70
+ const entries = fs.readdirSync(workspacesDir, { withFileTypes: true });
71
+
72
+ for (const entry of entries) {
73
+ if (!entry.isDirectory()) continue;
74
+ if (entry.name === excludeWorkspace) continue;
75
+
76
+ const projectDataPath = path.join(workspacesDir, entry.name);
77
+ const knowledgePath = path.join(projectDataPath, 'knowledge');
78
+ const refsPath = path.join(projectDataPath, 'refs');
79
+ const tasksPath = path.join(projectDataPath, 'tasks');
80
+
81
+ projects.push({
82
+ name: entry.name,
83
+ path: projectDataPath, // For global projects, path is the data path
84
+ dataPath: projectDataPath,
85
+ source: 'global',
86
+ knowledgePath: fs.existsSync(knowledgePath) ? knowledgePath : undefined,
87
+ refsPath: fs.existsSync(refsPath) ? refsPath : undefined,
88
+ tasksPath: fs.existsSync(tasksPath) ? tasksPath : undefined,
89
+ });
90
+ }
91
+ } catch {
92
+ // Ignore errors
93
+ }
94
+
95
+ return projects;
96
+ }
97
+
98
+ /**
99
+ * Scan sibling directories for workspace-scoped projects
100
+ */
101
+ function scanSiblingDirectories(workspacePath: string, excludeWorkspace?: string): DetectedProject[] {
102
+ const parentDir = path.dirname(workspacePath);
103
+ const projects: DetectedProject[] = [];
104
+
105
+ try {
106
+ const entries = fs.readdirSync(parentDir, { withFileTypes: true });
107
+
108
+ for (const entry of entries) {
109
+ if (!entry.isDirectory()) continue;
110
+
111
+ const projectPath = path.join(parentDir, entry.name);
112
+
113
+ // Skip current workspace
114
+ if (projectPath === workspacePath) continue;
115
+ if (entry.name === excludeWorkspace) continue;
116
+
117
+ // Check for .rrce-workflow/config.yaml
118
+ const configPath = path.join(projectPath, '.rrce-workflow', 'config.yaml');
119
+ if (!fs.existsSync(configPath)) continue;
120
+
121
+ // Parse config to get project details
122
+ const config = parseWorkspaceConfig(configPath);
123
+ if (!config) continue;
124
+
125
+ const dataPath = path.join(projectPath, '.rrce-workflow');
126
+ const knowledgePath = path.join(dataPath, 'knowledge');
127
+ const refsPath = path.join(dataPath, 'refs');
128
+ const tasksPath = path.join(dataPath, 'tasks');
129
+
130
+ projects.push({
131
+ name: config.name || entry.name,
132
+ path: projectPath,
133
+ dataPath,
134
+ source: 'sibling',
135
+ storageMode: config.storageMode,
136
+ knowledgePath: fs.existsSync(knowledgePath) ? knowledgePath : undefined,
137
+ refsPath: fs.existsSync(refsPath) ? refsPath : undefined,
138
+ tasksPath: fs.existsSync(tasksPath) ? tasksPath : undefined,
139
+ });
140
+ }
141
+ } catch {
142
+ // Ignore errors
143
+ }
144
+
145
+ return projects;
146
+ }
147
+
148
+ /**
149
+ * Parse a workspace config file
150
+ */
151
+ export function parseWorkspaceConfig(configPath: string): {
152
+ name: string;
153
+ storageMode: StorageMode;
154
+ linkedProjects?: string[];
155
+ } | null {
156
+ try {
157
+ const content = fs.readFileSync(configPath, 'utf-8');
158
+
159
+ // Simple YAML parsing (we don't want to add a full YAML library)
160
+ const nameMatch = content.match(/name:\s*["']?([^"'\n]+)["']?/);
161
+ const modeMatch = content.match(/mode:\s*(global|workspace|both)/);
162
+
163
+ // Parse linked projects
164
+ const linkedProjects: string[] = [];
165
+ const linkedMatch = content.match(/linked_projects:\s*\n((?:\s+-\s+[^\n]+\n?)+)/);
166
+ if (linkedMatch && linkedMatch[1]) {
167
+ const lines = linkedMatch[1].split('\n');
168
+ for (const line of lines) {
169
+ const projectMatch = line.match(/^\s+-\s+(.+)$/);
170
+ if (projectMatch && projectMatch[1]) {
171
+ linkedProjects.push(projectMatch[1].trim());
172
+ }
173
+ }
174
+ }
175
+
176
+ return {
177
+ name: nameMatch?.[1]?.trim() || path.basename(path.dirname(path.dirname(configPath))),
178
+ storageMode: (modeMatch?.[1] as StorageMode) || 'global',
179
+ linkedProjects: linkedProjects.length > 0 ? linkedProjects : undefined,
180
+ };
181
+ } catch {
182
+ return null;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Get display label for a detected project
188
+ */
189
+ export function getProjectDisplayLabel(project: DetectedProject): string {
190
+ switch (project.source) {
191
+ case 'global':
192
+ return `global: ~/.rrce-workflow/workspaces/${project.name}`;
193
+ case 'sibling':
194
+ return `sibling: ${project.path}/.rrce-workflow`;
195
+ default:
196
+ return project.dataPath;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Get all linkable folders from a detected project
202
+ */
203
+ export function getProjectFolders(project: DetectedProject): Array<{
204
+ path: string;
205
+ type: 'knowledge' | 'refs' | 'tasks';
206
+ displayName: string;
207
+ }> {
208
+ const folders: Array<{ path: string; type: 'knowledge' | 'refs' | 'tasks'; displayName: string }> = [];
209
+
210
+ if (project.knowledgePath) {
211
+ folders.push({
212
+ path: project.knowledgePath,
213
+ type: 'knowledge',
214
+ displayName: `📚 ${project.name} (knowledge)`,
215
+ });
216
+ }
217
+
218
+ if (project.refsPath) {
219
+ folders.push({
220
+ path: project.refsPath,
221
+ type: 'refs',
222
+ displayName: `📎 ${project.name} (refs)`,
223
+ });
224
+ }
225
+
226
+ if (project.tasksPath) {
227
+ folders.push({
228
+ path: project.tasksPath,
229
+ type: 'tasks',
230
+ displayName: `📋 ${project.name} (tasks)`,
231
+ });
232
+ }
233
+
234
+ return folders;
235
+ }
@@ -1,8 +1,13 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import { fileURLToPath } from 'url';
3
4
  import matter from 'gray-matter';
4
5
  import { PromptFrontmatterSchema, type ParsedPrompt } from '../types/prompt';
5
6
 
7
+ // Get __dirname equivalent for ESM (works with both npm/tsx and Bun)
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
6
11
  /**
7
12
  * Parse a prompt file and extract frontmatter + content
8
13
  */
@@ -52,10 +57,11 @@ export function loadPromptsFromDir(dirPath: string): ParsedPrompt[] {
52
57
 
53
58
  /**
54
59
  * Get the agent-core root directory
60
+ * Works with both npm/tsx and Bun
55
61
  */
56
62
  export function getAgentCoreDir(): string {
57
- // Relative to the package root
58
- return path.join(import.meta.dir, '..', '..', 'agent-core');
63
+ // Relative to this file: src/lib/prompts.ts -> ../../agent-core
64
+ return path.join(__dirname, '..', '..', 'agent-core');
59
65
  }
60
66
 
61
67
  /**
@@ -64,3 +70,4 @@ export function getAgentCoreDir(): string {
64
70
  export function getAgentCorePromptsDir(): string {
65
71
  return path.join(getAgentCoreDir(), 'prompts');
66
72
  }
73
+