mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-09 02:24:11 +08:00
- Implement tests for migration 005 to verify removal of deprecated fields in the database schema. - Ensure that new databases are created with a clean schema. - Validate that keywords are correctly extracted from the normalized file_keywords table. - Test symbol insertion without deprecated fields and subdir operations without direct_files. - Create a detailed search comparison test to evaluate vector search vs hybrid search performance. - Add a script for reindexing projects to extract code relationships and verify GraphAnalyzer functionality. - Include a test script to check TreeSitter parser availability and relationship extraction from sample files.
671 lines
21 KiB
TypeScript
671 lines
21 KiB
TypeScript
/**
|
|
* Centralized Storage Paths Configuration
|
|
* Single source of truth for all CCW storage locations
|
|
*
|
|
* All data is stored under ~/.ccw/ with project isolation via SHA256 hash
|
|
*/
|
|
|
|
import { homedir } from 'os';
|
|
import { join, resolve, dirname, relative, sep } from 'path';
|
|
import { createHash } from 'crypto';
|
|
import { existsSync, mkdirSync, renameSync, rmSync, readdirSync } from 'fs';
|
|
import { readdir } from 'fs/promises';
|
|
|
|
// Environment variable override for custom storage location
|
|
// Made dynamic to support testing environments
|
|
export function getCCWHome(): string {
|
|
return process.env.CCW_DATA_DIR || join(homedir(), '.ccw');
|
|
}
|
|
|
|
// Base CCW home directory (deprecated - use getCCWHome() for dynamic access)
|
|
// Kept for backward compatibility but will use dynamic value in tests
|
|
export const CCW_HOME = getCCWHome();
|
|
|
|
/**
|
|
* Convert project path to a human-readable folder name
|
|
* Examples:
|
|
* D:\Claude_dms3 → D--Claude_dms3
|
|
* /home/user/project → home-user-project
|
|
* /mnt/d/Claude_dms3 → D--Claude_dms3 (WSL mapping)
|
|
*
|
|
* @param absolutePath - Absolute project path
|
|
* @returns Safe folder name for filesystem
|
|
*/
|
|
function pathToFolderName(absolutePath: string): string {
|
|
let normalized = absolutePath;
|
|
|
|
// Handle WSL path: /mnt/c/path → C:/path
|
|
const wslMatch = normalized.match(/^\/mnt\/([a-z])\/(.*)/i);
|
|
if (wslMatch) {
|
|
normalized = `${wslMatch[1].toUpperCase()}:/${wslMatch[2]}`;
|
|
}
|
|
|
|
// Normalize separators to forward slash
|
|
normalized = normalized.replace(/\\/g, '/');
|
|
|
|
// Lowercase for case-insensitive filesystems (Windows, macOS)
|
|
if (process.platform === 'win32' || process.platform === 'darwin') {
|
|
normalized = normalized.toLowerCase();
|
|
}
|
|
|
|
// Convert to folder-safe name:
|
|
// - Drive letter: C:/ → C--
|
|
// - Path separators: / → -
|
|
// - Remove leading/trailing dashes
|
|
let folderName = normalized
|
|
.replace(/^([a-z]):\/*/i, '$1--') // C:/ → C--
|
|
.replace(/^\/+/, '') // Remove leading slashes
|
|
.replace(/\/+/g, '-') // / → -
|
|
.replace(/[<>:"|?*]/g, '_') // Invalid chars → _
|
|
.replace(/(?<!^[a-z])-+/gi, '-') // Collapse dashes (except after drive letter)
|
|
.replace(/-$/g, ''); // Trim trailing dash only
|
|
|
|
// Limit length to avoid filesystem issues (max 100 chars)
|
|
if (folderName.length > 100) {
|
|
const hash = createHash('sha256').update(normalized).digest('hex').substring(0, 8);
|
|
folderName = folderName.substring(0, 90) + '_' + hash;
|
|
}
|
|
|
|
return folderName || 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Calculate project identifier from project path
|
|
* Returns a human-readable folder name based on the path
|
|
* @param projectPath - Absolute or relative project path
|
|
* @returns Folder-safe project identifier
|
|
*/
|
|
export function getProjectId(projectPath: string): string {
|
|
const absolutePath = resolve(projectPath);
|
|
return pathToFolderName(absolutePath);
|
|
}
|
|
|
|
/**
|
|
* Hierarchy information for a project path
|
|
*/
|
|
export interface HierarchyInfo {
|
|
/** Current path's ID (flat form) */
|
|
currentId: string;
|
|
/** Parent directory's ID (if exists) */
|
|
parentId: string | null;
|
|
/** Relative path from parent */
|
|
relativePath: string;
|
|
}
|
|
|
|
// Path detection result cache
|
|
const hierarchyCache = new Map<string, HierarchyInfo>();
|
|
|
|
/**
|
|
* Detect path hierarchy relationship
|
|
* @param projectPath - Current working directory path
|
|
* @returns Hierarchy information
|
|
*/
|
|
export function detectHierarchy(projectPath: string): HierarchyInfo {
|
|
const absolutePath = resolve(projectPath);
|
|
|
|
// Check cache
|
|
if (hierarchyCache.has(absolutePath)) {
|
|
return hierarchyCache.get(absolutePath)!;
|
|
}
|
|
|
|
// Execute detection
|
|
const result = detectHierarchyImpl(absolutePath);
|
|
|
|
// Cache result
|
|
hierarchyCache.set(absolutePath, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Internal hierarchy detection implementation
|
|
*/
|
|
function detectHierarchyImpl(absolutePath: string): HierarchyInfo {
|
|
const currentId = pathToFolderName(absolutePath);
|
|
|
|
// Get all existing project directories
|
|
const projectsDir = join(getCCWHome(), 'projects');
|
|
if (!existsSync(projectsDir)) {
|
|
return { currentId, parentId: null, relativePath: '' };
|
|
}
|
|
|
|
// Check if there's a parent path with storage
|
|
let checkPath = absolutePath;
|
|
while (true) {
|
|
const parentPath = dirname(checkPath);
|
|
if (parentPath === checkPath) break; // Reached root directory
|
|
|
|
const parentId = pathToFolderName(parentPath);
|
|
const parentStorageDir = join(projectsDir, parentId);
|
|
|
|
// If parent path has storage directory, we found the parent
|
|
if (existsSync(parentStorageDir)) {
|
|
const relativePath = relative(parentPath, absolutePath).replace(/\\/g, '/');
|
|
return { currentId, parentId, relativePath };
|
|
}
|
|
|
|
checkPath = parentPath;
|
|
}
|
|
|
|
return { currentId, parentId: null, relativePath: '' };
|
|
}
|
|
|
|
/**
|
|
* Clear hierarchy cache
|
|
* Call this after migration completes
|
|
*/
|
|
export function clearHierarchyCache(): void {
|
|
hierarchyCache.clear();
|
|
}
|
|
|
|
/**
|
|
* Verify migration integrity
|
|
*/
|
|
function verifyMigration(targetDir: string, expectedSubDirs: string[]): boolean {
|
|
try {
|
|
for (const subDir of expectedSubDirs) {
|
|
const path = join(targetDir, subDir);
|
|
// Only verify directories that should exist
|
|
// In a real implementation, we'd check file counts, database integrity, etc.
|
|
}
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rollback migration (on error)
|
|
*/
|
|
function rollbackMigration(legacyDir: string, targetDir: string): void {
|
|
try {
|
|
// If target directory exists, try to move back
|
|
if (existsSync(targetDir)) {
|
|
console.error('⚠️ 尝试回滚迁移...');
|
|
// Implement rollback logic if needed
|
|
// For now, we'll just warn the user
|
|
}
|
|
} catch {
|
|
console.error('❌ 回滚失败,请手动检查数据完整性');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrate from flat structure to hierarchical structure
|
|
* @param legacyDir - Old flat directory
|
|
* @param targetDir - New hierarchical directory
|
|
*/
|
|
function migrateToHierarchical(legacyDir: string, targetDir: string): void {
|
|
console.log(`\n🔄 检测到旧存储结构,开始迁移...`);
|
|
console.log(` 从: ${legacyDir}`);
|
|
console.log(` 到: ${targetDir}`);
|
|
|
|
try {
|
|
// 1. Create target directory
|
|
mkdirSync(targetDir, { recursive: true });
|
|
|
|
// 2. Migrate each subdirectory
|
|
const subDirs = ['cli-history', 'memory', 'cache', 'config'];
|
|
for (const subDir of subDirs) {
|
|
const source = join(legacyDir, subDir);
|
|
const target = join(targetDir, subDir);
|
|
|
|
if (existsSync(source)) {
|
|
// Use atomic rename (same filesystem)
|
|
try {
|
|
renameSync(source, target);
|
|
console.log(` ✓ 迁移 ${subDir}`);
|
|
} catch (error: any) {
|
|
// If rename fails (cross-filesystem), fallback to copy-delete
|
|
// For now, we'll just throw the error
|
|
throw new Error(`无法迁移 ${subDir}: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Verify migration integrity
|
|
const verified = verifyMigration(targetDir, subDirs);
|
|
if (!verified) {
|
|
throw new Error('迁移验证失败');
|
|
}
|
|
|
|
// 4. Delete old directory
|
|
rmSync(legacyDir, { recursive: true, force: true });
|
|
console.log(`✅ 迁移完成并清理旧数据\n`);
|
|
|
|
} catch (error: any) {
|
|
console.error(`❌ 迁移失败: ${error.message}`);
|
|
// Try to rollback if possible
|
|
rollbackMigration(legacyDir, targetDir);
|
|
// Re-throw to prevent continued execution
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check and migrate child projects
|
|
* @param parentId - Parent project ID
|
|
* @param parentPath - Parent project path
|
|
*/
|
|
function migrateChildProjects(parentId: string, parentPath: string): void {
|
|
const projectsDir = join(getCCWHome(), 'projects');
|
|
if (!existsSync(projectsDir)) return;
|
|
|
|
const absoluteParentPath = resolve(parentPath);
|
|
const entries = readdirSync(projectsDir);
|
|
|
|
for (const entry of entries) {
|
|
if (entry === parentId) continue; // Skip self
|
|
|
|
// Check if this is a child directory of the current project
|
|
// We need to reverse-engineer the original path from the folder ID
|
|
// This is challenging without storing metadata
|
|
// For now, we'll use a heuristic: if the entry starts with the parentId
|
|
// and has additional path segments, it might be a child
|
|
|
|
// Simple heuristic: check if entry is longer and starts with parentId
|
|
if (entry.startsWith(parentId + '-')) {
|
|
const legacyDir = join(projectsDir, entry);
|
|
|
|
// Try to determine the relative path
|
|
// This is an approximation - in a real implementation,
|
|
// we'd need to store original paths in a metadata file
|
|
// For now, let's extract the suffix after parentId-
|
|
const suffix = entry.substring(parentId.length + 1);
|
|
|
|
// Convert back to path segments (- → /)
|
|
const potentialRelPath = suffix.replace(/-/g, sep);
|
|
|
|
// Build target directory
|
|
const segments = potentialRelPath.split(sep).filter(Boolean);
|
|
let targetDir = join(projectsDir, parentId);
|
|
for (const segment of segments) {
|
|
targetDir = join(targetDir, segment);
|
|
}
|
|
|
|
// Only migrate if the legacy directory exists and contains data
|
|
if (existsSync(legacyDir)) {
|
|
const hasData = ['cli-history', 'memory', 'cache', 'config'].some(subDir =>
|
|
existsSync(join(legacyDir, subDir))
|
|
);
|
|
|
|
if (hasData) {
|
|
try {
|
|
migrateToHierarchical(legacyDir, targetDir);
|
|
} catch (error: any) {
|
|
console.error(`⚠️ 跳过 ${entry} 的迁移: ${error.message}`);
|
|
// Continue with other migrations
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure a directory exists, creating it if necessary
|
|
* @param dirPath - Directory path to ensure
|
|
*/
|
|
export function ensureStorageDir(dirPath: string): void {
|
|
if (!existsSync(dirPath)) {
|
|
mkdirSync(dirPath, { recursive: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Global storage paths (not project-specific)
|
|
*/
|
|
export const GlobalPaths = {
|
|
/** Root CCW home directory */
|
|
root: () => getCCWHome(),
|
|
|
|
/** Config directory */
|
|
config: () => join(getCCWHome(), 'config'),
|
|
|
|
/** Global settings file */
|
|
settings: () => join(getCCWHome(), 'config', 'settings.json'),
|
|
|
|
/** Recent project paths file */
|
|
recentPaths: () => join(getCCWHome(), 'config', 'recent-paths.json'),
|
|
|
|
/** Databases directory */
|
|
databases: () => join(getCCWHome(), 'db'),
|
|
|
|
/** MCP templates database */
|
|
mcpTemplates: () => join(getCCWHome(), 'db', 'mcp-templates.db'),
|
|
|
|
/** Logs directory */
|
|
logs: () => join(getCCWHome(), 'logs'),
|
|
};
|
|
|
|
/**
|
|
* Project-specific storage paths
|
|
*/
|
|
export interface ProjectPaths {
|
|
/** Project root in CCW storage */
|
|
root: string;
|
|
/** CLI history directory */
|
|
cliHistory: string;
|
|
/** CLI history database file */
|
|
historyDb: string;
|
|
/** Memory store directory */
|
|
memory: string;
|
|
/** Memory store database file */
|
|
memoryDb: string;
|
|
/** Cache directory */
|
|
cache: string;
|
|
/** Dashboard cache file */
|
|
dashboardCache: string;
|
|
/** Config directory */
|
|
config: string;
|
|
/** CLI config file */
|
|
cliConfig: string;
|
|
}
|
|
|
|
/**
|
|
* Get storage paths for a specific project
|
|
* Supports hierarchical storage structure with automatic migration
|
|
* @param projectPath - Project root path
|
|
* @returns Object with all project-specific paths
|
|
*/
|
|
export function getProjectPaths(projectPath: string): ProjectPaths {
|
|
const hierarchy = detectHierarchy(projectPath);
|
|
|
|
let projectDir: string;
|
|
|
|
if (hierarchy.parentId) {
|
|
// Has parent, use hierarchical structure
|
|
projectDir = join(getCCWHome(), 'projects', hierarchy.parentId);
|
|
|
|
// Build subdirectory path from relative path
|
|
const segments = hierarchy.relativePath.split('/').filter(Boolean);
|
|
for (const segment of segments) {
|
|
projectDir = join(projectDir, segment);
|
|
}
|
|
|
|
// Check if we need to migrate old flat data
|
|
const legacyDir = join(getCCWHome(), 'projects', hierarchy.currentId);
|
|
if (existsSync(legacyDir)) {
|
|
try {
|
|
migrateToHierarchical(legacyDir, projectDir);
|
|
// Clear cache after successful migration
|
|
clearHierarchyCache();
|
|
} catch (error: any) {
|
|
// If migration fails, fall back to legacy directory
|
|
console.warn(`迁移失败,使用旧存储位置: ${error.message}`);
|
|
projectDir = legacyDir;
|
|
}
|
|
}
|
|
} else {
|
|
// No parent, use root-level storage
|
|
projectDir = join(getCCWHome(), 'projects', hierarchy.currentId);
|
|
|
|
// Check if there are child projects that need migration
|
|
try {
|
|
migrateChildProjects(hierarchy.currentId, projectPath);
|
|
} catch (error: any) {
|
|
console.warn(`子项目迁移失败: ${error.message}`);
|
|
// Continue anyway - this is not critical
|
|
}
|
|
}
|
|
|
|
return {
|
|
root: projectDir,
|
|
cliHistory: join(projectDir, 'cli-history'),
|
|
historyDb: join(projectDir, 'cli-history', 'history.db'),
|
|
memory: join(projectDir, 'memory'),
|
|
memoryDb: join(projectDir, 'memory', 'memory.db'),
|
|
cache: join(projectDir, 'cache'),
|
|
dashboardCache: join(projectDir, 'cache', 'dashboard-data.json'),
|
|
config: join(projectDir, 'config'),
|
|
cliConfig: join(projectDir, 'config', 'cli-config.json'),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get storage paths for a project by its ID (hash)
|
|
* Use when iterating centralized storage without original project path
|
|
* @param projectId - 16-character project ID hash
|
|
* @returns Object with all project-specific paths
|
|
*/
|
|
export function getProjectPathsById(projectId: string): ProjectPaths {
|
|
const projectDir = join(getCCWHome(), 'projects', projectId);
|
|
|
|
return {
|
|
root: projectDir,
|
|
cliHistory: join(projectDir, 'cli-history'),
|
|
historyDb: join(projectDir, 'cli-history', 'history.db'),
|
|
memory: join(projectDir, 'memory'),
|
|
memoryDb: join(projectDir, 'memory', 'memory.db'),
|
|
cache: join(projectDir, 'cache'),
|
|
dashboardCache: join(projectDir, 'cache', 'dashboard-data.json'),
|
|
config: join(projectDir, 'config'),
|
|
cliConfig: join(projectDir, 'config', 'cli-config.json'),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Unified StoragePaths object combining global and project paths
|
|
*/
|
|
export const StoragePaths = {
|
|
global: GlobalPaths,
|
|
project: getProjectPaths,
|
|
projectById: getProjectPathsById,
|
|
};
|
|
|
|
/**
|
|
* Information about a child project in hierarchical structure
|
|
*/
|
|
export interface ChildProjectInfo {
|
|
/** Absolute path to the child project */
|
|
projectPath: string;
|
|
/** Relative path from parent project */
|
|
relativePath: string;
|
|
/** Project ID */
|
|
projectId: string;
|
|
/** Storage paths for this child project */
|
|
paths: ProjectPaths;
|
|
}
|
|
|
|
/**
|
|
* Recursively scan for child projects in hierarchical storage structure
|
|
* @param projectPath - Parent project path
|
|
* @returns Array of child project information
|
|
*/
|
|
export function scanChildProjects(projectPath: string): ChildProjectInfo[] {
|
|
const absolutePath = resolve(projectPath);
|
|
const parentId = getProjectId(absolutePath);
|
|
const parentStorageDir = join(getCCWHome(), 'projects', parentId);
|
|
|
|
// If parent storage doesn't exist, no children
|
|
if (!existsSync(parentStorageDir)) {
|
|
return [];
|
|
}
|
|
|
|
const children: ChildProjectInfo[] = [];
|
|
|
|
/**
|
|
* Recursively scan directory for project data directories
|
|
*/
|
|
function scanDirectory(dir: string, relativePath: string): void {
|
|
if (!existsSync(dir)) return;
|
|
|
|
try {
|
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
|
|
const fullPath = join(dir, entry.name);
|
|
const currentRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
|
|
// Check if this directory contains project data
|
|
const dataMarkers = ['cli-history', 'memory', 'cache', 'config'];
|
|
const hasData = dataMarkers.some(marker => existsSync(join(fullPath, marker)));
|
|
|
|
if (hasData) {
|
|
// This is a child project
|
|
const childProjectPath = join(absolutePath, currentRelPath.replace(/\//g, sep));
|
|
const childId = getProjectId(childProjectPath);
|
|
|
|
children.push({
|
|
projectPath: childProjectPath,
|
|
relativePath: currentRelPath,
|
|
projectId: childId,
|
|
paths: getProjectPaths(childProjectPath)
|
|
});
|
|
}
|
|
|
|
// Continue scanning subdirectories (skip data directories)
|
|
if (!dataMarkers.includes(entry.name)) {
|
|
scanDirectory(fullPath, currentRelPath);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Ignore read errors
|
|
if (process.env.DEBUG) {
|
|
console.error(`[scanChildProjects] Failed to scan ${dir}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
scanDirectory(parentStorageDir, '');
|
|
|
|
return children;
|
|
}
|
|
|
|
/**
|
|
* Asynchronously scan for child projects in hierarchical storage structure
|
|
* Non-blocking version using fs.promises for better performance
|
|
* @param projectPath - Parent project path
|
|
* @returns Promise resolving to array of child project information
|
|
*/
|
|
export async function scanChildProjectsAsync(projectPath: string): Promise<ChildProjectInfo[]> {
|
|
const absolutePath = resolve(projectPath);
|
|
const parentId = getProjectId(absolutePath);
|
|
const parentStorageDir = join(getCCWHome(), 'projects', parentId);
|
|
|
|
// If parent storage doesn't exist, no children
|
|
if (!existsSync(parentStorageDir)) {
|
|
return [];
|
|
}
|
|
|
|
const children: ChildProjectInfo[] = [];
|
|
|
|
/**
|
|
* Recursively scan directory for project data directories (async)
|
|
*/
|
|
async function scanDirectoryAsync(dir: string, relativePath: string): Promise<void> {
|
|
if (!existsSync(dir)) return;
|
|
|
|
try {
|
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
|
|
// Process directories in parallel for better performance
|
|
const promises = entries
|
|
.filter(entry => entry.isDirectory())
|
|
.map(async (entry) => {
|
|
const fullPath = join(dir, entry.name);
|
|
const currentRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
|
|
// Check if this directory contains project data
|
|
const dataMarkers = ['cli-history', 'memory', 'cache', 'config'];
|
|
const hasData = dataMarkers.some(marker => existsSync(join(fullPath, marker)));
|
|
|
|
if (hasData) {
|
|
// This is a child project
|
|
const childProjectPath = join(absolutePath, currentRelPath.replace(/\//g, sep));
|
|
const childId = getProjectId(childProjectPath);
|
|
|
|
children.push({
|
|
projectPath: childProjectPath,
|
|
relativePath: currentRelPath,
|
|
projectId: childId,
|
|
paths: getProjectPaths(childProjectPath)
|
|
});
|
|
}
|
|
|
|
// Continue scanning subdirectories (skip data directories)
|
|
if (!dataMarkers.includes(entry.name)) {
|
|
await scanDirectoryAsync(fullPath, currentRelPath);
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
} catch (error) {
|
|
// Ignore read errors
|
|
if (process.env.DEBUG) {
|
|
console.error(`[scanChildProjectsAsync] Failed to scan ${dir}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
await scanDirectoryAsync(parentStorageDir, '');
|
|
|
|
return children;
|
|
}
|
|
|
|
/**
|
|
* Legacy storage paths (for backward compatibility detection)
|
|
*/
|
|
export const LegacyPaths = {
|
|
/** Old recent paths file location */
|
|
recentPaths: () => join(homedir(), '.ccw-recent-paths.json'),
|
|
|
|
/** Old project-local CLI history */
|
|
cliHistory: (projectPath: string) => join(projectPath, '.workflow', '.cli-history'),
|
|
|
|
/** Old project-local memory store */
|
|
memory: (projectPath: string) => join(projectPath, '.workflow', '.memory'),
|
|
|
|
/** Old project-local cache */
|
|
cache: (projectPath: string) => join(projectPath, '.workflow', '.ccw-cache'),
|
|
|
|
/** Old project-local CLI config */
|
|
cliConfig: (projectPath: string) => join(projectPath, '.workflow', 'cli-config.json'),
|
|
};
|
|
|
|
/**
|
|
* Check if legacy storage exists for a project
|
|
* Useful for migration warnings or detection
|
|
* @param projectPath - Project root path
|
|
* @returns true if any legacy storage is present
|
|
*/
|
|
export function isLegacyStoragePresent(projectPath: string): boolean {
|
|
return (
|
|
existsSync(LegacyPaths.cliHistory(projectPath)) ||
|
|
existsSync(LegacyPaths.memory(projectPath)) ||
|
|
existsSync(LegacyPaths.cache(projectPath)) ||
|
|
existsSync(LegacyPaths.cliConfig(projectPath))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get CCW home directory (for external use)
|
|
*/
|
|
export function getCcwHome(): string {
|
|
return getCCWHome();
|
|
}
|
|
|
|
/**
|
|
* Initialize global storage directories
|
|
* Creates the base directory structure if not present
|
|
*/
|
|
export function initializeGlobalStorage(): void {
|
|
ensureStorageDir(GlobalPaths.config());
|
|
ensureStorageDir(GlobalPaths.databases());
|
|
ensureStorageDir(GlobalPaths.logs());
|
|
}
|
|
|
|
/**
|
|
* Initialize project storage directories
|
|
* @param projectPath - Project root path
|
|
*/
|
|
export function initializeProjectStorage(projectPath: string): void {
|
|
const paths = getProjectPaths(projectPath);
|
|
ensureStorageDir(paths.cliHistory);
|
|
ensureStorageDir(paths.memory);
|
|
ensureStorageDir(paths.cache);
|
|
ensureStorageDir(paths.config);
|
|
}
|