rrce-workflow 0.3.13 → 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.
@@ -1,85 +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, getAgentPromptPath, copyDirToAllStoragePaths, getEffectiveRRCEHome, getConfigPath } from '../../lib/paths';
6
- import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../../lib/prompts';
7
- import { copyPromptsToDir } from './utils';
8
- /**
9
- * Update prompts and templates from the package without resetting config
10
- */
11
- export async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
12
- const s = spinner();
13
- s.start('Checking for updates');
14
- try {
15
- const agentCoreDir = getAgentCoreDir();
16
- const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
17
- // Determine storage paths based on current mode
18
- const mode = currentStorageMode || 'global';
19
- // Use effective RRCE_HOME from config for path resolution
20
- const customGlobalPath = getEffectiveRRCEHome(workspacePath);
21
- const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
22
- s.stop('Updates found');
23
- // Show what will be updated
24
- note(`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')}`, 'Update Preview');
25
- const shouldUpdate = await confirm({
26
- message: 'Proceed with update?',
27
- initialValue: true,
28
- });
29
- if (isCancel(shouldUpdate) || !shouldUpdate) {
30
- outro('Update cancelled.');
31
- return;
32
- }
33
- s.start('Updating from package');
34
- // Update templates in all storage locations (no prompts in data paths)
35
- for (const dataPath of dataPaths) {
36
- // Update templates only
37
- copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', [dataPath]);
38
- }
39
- // Also update tool-specific locations if configured
40
- const configFilePath = getConfigPath(workspacePath);
41
- const configContent = fs.readFileSync(configFilePath, 'utf-8');
42
- if (configContent.includes('copilot: true')) {
43
- const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
44
- ensureDir(copilotPath);
45
- copyPromptsToDir(prompts, copilotPath, '.agent.md');
46
- }
47
- if (configContent.includes('antigravity: true')) {
48
- const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
49
- ensureDir(antigravityPath);
50
- copyPromptsToDir(prompts, antigravityPath, '.md');
51
- }
52
- s.stop('Update complete');
53
- const summary = [
54
- `Updated:`,
55
- ` ✓ ${prompts.length} agent prompts`,
56
- ` ✓ Output templates`,
57
- ``,
58
- `Your configuration and knowledge files were preserved.`,
59
- ];
60
- note(summary.join('\n'), 'Update Summary');
61
- outro(pc.green('✓ Successfully updated from package!'));
62
- }
63
- catch (error) {
64
- s.stop('Error occurred');
65
- cancel(`Failed to update: ${error instanceof Error ? error.message : String(error)}`);
66
- process.exit(1);
67
- }
68
- }
69
- /**
70
- * Resolve all data paths with custom global path support
71
- */
72
- function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot, customGlobalPath) {
73
- const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
74
- const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
75
- switch (mode) {
76
- case 'global':
77
- return [globalPath];
78
- case 'workspace':
79
- return [workspacePath];
80
- case 'both':
81
- return [workspacePath, globalPath];
82
- default:
83
- return [globalPath];
84
- }
85
- }
@@ -1,9 +0,0 @@
1
- import type { ParsedPrompt } from '../../types/prompt';
2
- /**
3
- * Copy parsed prompts to a target directory with specified extension
4
- */
5
- export declare function copyPromptsToDir(prompts: ParsedPrompt[], targetDir: string, extension: string): void;
6
- /**
7
- * Recursively copy a directory
8
- */
9
- export declare function copyDirRecursive(src: string, dest: string): void;
@@ -1,33 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { ensureDir } from '../../lib/paths';
4
- /**
5
- * Copy parsed prompts to a target directory with specified extension
6
- */
7
- export function copyPromptsToDir(prompts, targetDir, extension) {
8
- for (const prompt of prompts) {
9
- const baseName = path.basename(prompt.filePath, '.md');
10
- const targetName = baseName + extension;
11
- const targetPath = path.join(targetDir, targetName);
12
- // Read the full content including frontmatter
13
- const content = fs.readFileSync(prompt.filePath, 'utf-8');
14
- fs.writeFileSync(targetPath, content);
15
- }
16
- }
17
- /**
18
- * Recursively copy a directory
19
- */
20
- export function copyDirRecursive(src, dest) {
21
- const entries = fs.readdirSync(src, { withFileTypes: true });
22
- for (const entry of entries) {
23
- const srcPath = path.join(src, entry.name);
24
- const destPath = path.join(dest, entry.name);
25
- if (entry.isDirectory()) {
26
- ensureDir(destPath);
27
- copyDirRecursive(srcPath, destPath);
28
- }
29
- else {
30
- fs.copyFileSync(srcPath, destPath);
31
- }
32
- }
33
- }
@@ -1,15 +0,0 @@
1
- import { type DetectedProject } from '../../lib/detection';
2
- /**
3
- * Generate or update VSCode workspace file with linked project folders
4
- *
5
- * Features:
6
- * - Main workspace is clearly marked as the primary project
7
- * - Linked folders are grouped under a "References" section (via naming)
8
- * - Folders are organized by project with icons for type (📚 📎 📋)
9
- * - Reference folders are marked as readonly in workspace settings
10
- */
11
- export declare function generateVSCodeWorkspace(workspacePath: string, workspaceName: string, linkedProjects: string[] | DetectedProject[], customGlobalPath?: string): void;
12
- /**
13
- * Remove a project's folders from the workspace file
14
- */
15
- export declare function removeProjectFromWorkspace(workspacePath: string, workspaceName: string, projectName: string): void;
@@ -1,148 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { getRRCEHome } from '../../lib/paths';
4
- import { getProjectFolders } from '../../lib/detection';
5
- // Reference folder group prefix - used to visually group linked folders
6
- const REFERENCE_GROUP_PREFIX = '📁 References';
7
- /**
8
- * Generate or update VSCode workspace file with linked project folders
9
- *
10
- * Features:
11
- * - Main workspace is clearly marked as the primary project
12
- * - Linked folders are grouped under a "References" section (via naming)
13
- * - Folders are organized by project with icons for type (📚 📎 📋)
14
- * - Reference folders are marked as readonly in workspace settings
15
- */
16
- export function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, customGlobalPath) {
17
- const workspaceFilePath = path.join(workspacePath, `${workspaceName}.code-workspace`);
18
- let workspace;
19
- // Check if workspace file already exists
20
- if (fs.existsSync(workspaceFilePath)) {
21
- try {
22
- const content = fs.readFileSync(workspaceFilePath, 'utf-8');
23
- workspace = JSON.parse(content);
24
- }
25
- catch {
26
- // If parse fails, create new
27
- workspace = { folders: [], settings: {} };
28
- }
29
- }
30
- else {
31
- workspace = { folders: [], settings: {} };
32
- }
33
- // Initialize settings if not present
34
- if (!workspace.settings) {
35
- workspace.settings = {};
36
- }
37
- // Clear existing folders and rebuild (to ensure proper ordering)
38
- const existingNonReferencesFolders = workspace.folders.filter(f => f.path === '.' || (!f.name?.includes(REFERENCE_GROUP_PREFIX) && !f.name?.startsWith('📚') && !f.name?.startsWith('📎') && !f.name?.startsWith('📋')));
39
- workspace.folders = [];
40
- // 1. Add main workspace folder first with clear label
41
- const mainFolder = {
42
- path: '.',
43
- name: `🏠 ${workspaceName} (workspace)`
44
- };
45
- workspace.folders.push(mainFolder);
46
- // 2. Add any other existing non-references folders
47
- for (const folder of existingNonReferencesFolders) {
48
- if (folder.path !== '.') {
49
- workspace.folders.push(folder);
50
- }
51
- }
52
- // 3. Add reference folders grouped by project
53
- const referenceFolderPaths = [];
54
- // Determine if we're working with DetectedProject[] or string[]
55
- const isDetectedProjects = linkedProjects.length > 0 && typeof linkedProjects[0] === 'object';
56
- if (isDetectedProjects) {
57
- // New behavior: use DetectedProject[] with knowledge, refs, tasks folders
58
- const projects = linkedProjects;
59
- for (const project of projects) {
60
- const folders = getProjectFolders(project);
61
- const sourceLabel = project.source === 'global' ? 'global' : 'local';
62
- for (const folder of folders) {
63
- referenceFolderPaths.push(folder.path);
64
- // Check if already exists
65
- const existingIndex = workspace.folders.findIndex(f => f.path === folder.path);
66
- if (existingIndex === -1) {
67
- workspace.folders.push({
68
- path: folder.path,
69
- name: `${folder.displayName} [${sourceLabel}]`,
70
- });
71
- }
72
- }
73
- }
74
- }
75
- else {
76
- // Legacy behavior: string[] of project names (global storage only)
77
- const projectNames = linkedProjects;
78
- const rrceHome = customGlobalPath || getRRCEHome();
79
- for (const projectName of projectNames) {
80
- const projectDataPath = path.join(rrceHome, 'workspaces', projectName);
81
- const folderTypes = [
82
- { subpath: 'knowledge', icon: '📚', type: 'knowledge' },
83
- { subpath: 'refs', icon: '📎', type: 'refs' },
84
- { subpath: 'tasks', icon: '📋', type: 'tasks' },
85
- ];
86
- for (const { subpath, icon, type } of folderTypes) {
87
- const folderPath = path.join(projectDataPath, subpath);
88
- if (fs.existsSync(folderPath)) {
89
- referenceFolderPaths.push(folderPath);
90
- const existingIndex = workspace.folders.findIndex(f => f.path === folderPath);
91
- if (existingIndex === -1) {
92
- workspace.folders.push({
93
- path: folderPath,
94
- name: `${icon} ${projectName} (${type}) [global]`,
95
- });
96
- }
97
- }
98
- }
99
- }
100
- }
101
- // 4. Add workspace settings to mark reference folders as readonly
102
- // This uses files.readonlyInclude to make imported folders read-only
103
- if (referenceFolderPaths.length > 0) {
104
- const readonlyPatterns = {};
105
- for (const folderPath of referenceFolderPaths) {
106
- // Create a pattern that matches all files in this folder
107
- readonlyPatterns[`${folderPath}/**`] = true;
108
- }
109
- // Merge with existing readonly patterns
110
- const existingReadonly = workspace.settings['files.readonlyInclude'] || {};
111
- workspace.settings['files.readonlyInclude'] = {
112
- ...existingReadonly,
113
- ...readonlyPatterns,
114
- };
115
- }
116
- // 5. Add helpful workspace settings for multi-root experience
117
- workspace.settings['explorer.sortOrder'] = workspace.settings['explorer.sortOrder'] || 'default';
118
- // Write workspace file with nice formatting
119
- fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
120
- }
121
- /**
122
- * Remove a project's folders from the workspace file
123
- */
124
- export function removeProjectFromWorkspace(workspacePath, workspaceName, projectName) {
125
- const workspaceFilePath = path.join(workspacePath, `${workspaceName}.code-workspace`);
126
- if (!fs.existsSync(workspaceFilePath)) {
127
- return;
128
- }
129
- try {
130
- const content = fs.readFileSync(workspaceFilePath, 'utf-8');
131
- const workspace = JSON.parse(content);
132
- // Filter out folders that match the project name
133
- workspace.folders = workspace.folders.filter(f => !f.name?.includes(projectName));
134
- // Also remove readonly patterns for this project
135
- if (workspace.settings?.['files.readonlyInclude']) {
136
- const readonly = workspace.settings['files.readonlyInclude'];
137
- for (const pattern of Object.keys(readonly)) {
138
- if (pattern.includes(projectName)) {
139
- delete readonly[pattern];
140
- }
141
- }
142
- }
143
- fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
144
- }
145
- catch {
146
- // Ignore errors
147
- }
148
- }
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,14 +0,0 @@
1
- import { isCancel } from '@clack/core';
2
- interface DirectoryAutocompleteOptions {
3
- message: string;
4
- placeholder?: string;
5
- initialValue?: string;
6
- validate?: (value: string) => string | undefined;
7
- hint?: string;
8
- }
9
- /**
10
- * Custom text input with Tab-completion for directory paths
11
- * Uses @clack/core TextPrompt with custom key handling
12
- */
13
- export declare function directoryAutocomplete(opts: DirectoryAutocompleteOptions): Promise<string | symbol>;
14
- export { isCancel };
@@ -1,167 +0,0 @@
1
- import { TextPrompt, isCancel } from '@clack/core';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import pc from 'picocolors';
5
- /**
6
- * Custom text input with Tab-completion for directory paths
7
- * Uses @clack/core TextPrompt with custom key handling
8
- */
9
- export async function directoryAutocomplete(opts) {
10
- let completions = [];
11
- let completionIndex = 0;
12
- let lastTabValue = '';
13
- const prompt = new TextPrompt({
14
- initialValue: opts.initialValue,
15
- validate: opts.validate,
16
- render() {
17
- const title = `${pc.cyan('◆')} ${opts.message}`;
18
- const hintText = opts.hint ? pc.dim(` (${opts.hint})`) : '';
19
- let inputLine;
20
- if (this.state === 'error') {
21
- inputLine = `${pc.yellow('▲')} ${this.valueWithCursor}`;
22
- }
23
- else if (this.state === 'submit') {
24
- inputLine = `${pc.green('✓')} ${pc.dim(String(this.value || ''))}`;
25
- }
26
- else {
27
- inputLine = `${pc.cyan('│')} ${this.valueWithCursor || pc.dim(opts.placeholder || '')}`;
28
- }
29
- let result = `${title}${hintText}\n${inputLine}`;
30
- if (this.state === 'error' && this.error) {
31
- result += `\n${pc.yellow('│')} ${pc.yellow(this.error)}`;
32
- }
33
- // Show completion hint if multiple options
34
- if (completions.length > 1 && this.state === 'active') {
35
- const remaining = completions.length - 1;
36
- result += `\n${pc.dim('│')} ${pc.dim(`+${remaining} more, press Tab again to cycle`)}`;
37
- }
38
- return result;
39
- },
40
- });
41
- // Listen for key events - Tab key handling
42
- prompt.on('key', (key) => {
43
- if (key === '\t' || key === 'tab') {
44
- handleTabCompletion(prompt);
45
- }
46
- else {
47
- // Reset completion state on any other key
48
- completions = [];
49
- completionIndex = 0;
50
- lastTabValue = '';
51
- }
52
- });
53
- function handleTabCompletion(p) {
54
- const input = String(p.value || '');
55
- // Expand ~ to home directory
56
- const expanded = input.startsWith('~')
57
- ? input.replace(/^~/, process.env.HOME || '')
58
- : input;
59
- // If user hasn't changed input since last tab, cycle through completions
60
- if (lastTabValue === input && completions.length > 1) {
61
- completionIndex = (completionIndex + 1) % completions.length;
62
- const completion = completions[completionIndex] || '';
63
- setPromptValue(p, completion);
64
- return;
65
- }
66
- // Get new completions
67
- completions = getDirectoryCompletions(expanded);
68
- completionIndex = 0;
69
- lastTabValue = input;
70
- if (completions.length === 1) {
71
- // Single match - auto-complete with trailing slash if directory
72
- const completion = completions[0] || '';
73
- setPromptValue(p, completion.endsWith('/') ? completion : completion + '/');
74
- completions = []; // Clear so next Tab gets fresh completions
75
- lastTabValue = '';
76
- }
77
- else if (completions.length > 1) {
78
- // Multiple matches - complete common prefix and show first
79
- const commonPrefix = getCommonPrefix(completions);
80
- if (commonPrefix.length > expanded.length) {
81
- setPromptValue(p, commonPrefix);
82
- lastTabValue = formatForDisplay(commonPrefix);
83
- }
84
- else {
85
- // Show first completion
86
- setPromptValue(p, completions[0] || '');
87
- }
88
- }
89
- }
90
- function setPromptValue(p, value) {
91
- // Convert back to ~ format if in home directory for display
92
- const displayValue = formatForDisplay(value);
93
- // Update the prompt's value by emitting a value event
94
- // This is a workaround since TextPrompt doesn't expose a direct setValue method
95
- p.value = displayValue;
96
- }
97
- function formatForDisplay(value) {
98
- const home = process.env.HOME || '';
99
- return value.startsWith(home)
100
- ? value.replace(home, '~')
101
- : value;
102
- }
103
- function getDirectoryCompletions(inputPath) {
104
- try {
105
- let dirToScan;
106
- let prefix;
107
- if (inputPath === '' || inputPath === '/') {
108
- dirToScan = inputPath || '/';
109
- prefix = '';
110
- }
111
- else if (inputPath.endsWith('/')) {
112
- // User typed a complete directory path
113
- dirToScan = inputPath;
114
- prefix = '';
115
- }
116
- else {
117
- // User is typing a partial name
118
- dirToScan = path.dirname(inputPath);
119
- prefix = path.basename(inputPath).toLowerCase();
120
- }
121
- if (!fs.existsSync(dirToScan)) {
122
- return [];
123
- }
124
- const entries = fs.readdirSync(dirToScan, { withFileTypes: true })
125
- .filter(entry => {
126
- // Only directories
127
- if (!entry.isDirectory())
128
- return false;
129
- // Skip hidden directories unless explicitly typing them
130
- if (entry.name.startsWith('.') && !prefix.startsWith('.'))
131
- return false;
132
- // Match prefix
133
- return prefix === '' || entry.name.toLowerCase().startsWith(prefix);
134
- })
135
- .map(entry => path.join(dirToScan, entry.name))
136
- .sort();
137
- return entries;
138
- }
139
- catch {
140
- return [];
141
- }
142
- }
143
- function getCommonPrefix(strings) {
144
- if (strings.length === 0)
145
- return '';
146
- if (strings.length === 1)
147
- return strings[0] || '';
148
- let prefix = strings[0] || '';
149
- for (let i = 1; i < strings.length; i++) {
150
- const str = strings[i] || '';
151
- while (prefix.length > 0 && !str.startsWith(prefix)) {
152
- prefix = prefix.slice(0, -1);
153
- }
154
- }
155
- return prefix;
156
- }
157
- const result = await prompt.prompt();
158
- if (isCancel(result)) {
159
- return result;
160
- }
161
- // Expand ~ in final result
162
- const value = String(result || '');
163
- return value.startsWith('~')
164
- ? value.replace(/^~/, process.env.HOME || '')
165
- : value;
166
- }
167
- export { isCancel };
@@ -1,44 +0,0 @@
1
- import type { StorageMode } from '../types/prompt';
2
- /**
3
- * Detected rrce-workflow project information
4
- */
5
- export interface DetectedProject {
6
- name: string;
7
- path: string;
8
- dataPath: string;
9
- source: 'global' | 'sibling' | 'parent';
10
- storageMode?: StorageMode;
11
- knowledgePath?: string;
12
- refsPath?: string;
13
- tasksPath?: string;
14
- }
15
- interface ScanOptions {
16
- excludeWorkspace?: string;
17
- workspacePath?: string;
18
- scanSiblings?: boolean;
19
- }
20
- /**
21
- * Scan for rrce-workflow projects in various locations
22
- */
23
- export declare function scanForProjects(options?: ScanOptions): DetectedProject[];
24
- /**
25
- * Parse a workspace config file
26
- */
27
- export declare function parseWorkspaceConfig(configPath: string): {
28
- name: string;
29
- storageMode: StorageMode;
30
- linkedProjects?: string[];
31
- } | null;
32
- /**
33
- * Get display label for a detected project
34
- */
35
- export declare function getProjectDisplayLabel(project: DetectedProject): string;
36
- /**
37
- * Get all linkable folders from a detected project
38
- */
39
- export declare function getProjectFolders(project: DetectedProject): Array<{
40
- path: string;
41
- type: 'knowledge' | 'refs' | 'tasks';
42
- displayName: string;
43
- }>;
44
- export {};