feat: Implement recursive core-memory database discovery and project listing

- Added `findAllCoreMemoryDatabases` function to recursively locate core-memory databases in nested project structures.
- Updated `listAllProjects` to utilize the new recursive function for improved project listing.
- Enhanced `getMemoriesFromProject` and `findMemoryAcrossProjects` to support nested project structures.

feat: Introduce spec context injection in hooks configuration

- Added a new hook configuration for "Spec Context Injection" to load project specs based on prompt keywords.

chore: Add gray-matter dependency for YAML frontmatter parsing

- Included `gray-matter` package in `package.json` for parsing YAML frontmatter in markdown files.

feat: Create Spec Index Builder tool for managing project specs

- Implemented `spec-index-builder.ts` to scan markdown files, extract YAML frontmatter, and generate index cache files for different spec dimensions.

feat: Develop Spec Init tool for initializing spec directories and seed documents

- Created `spec-init.ts` to set up the directory structure and seed documents for the spec system.

feat: Build Spec Keyword Extractor for keyword extraction from prompts

- Added `spec-keyword-extractor.ts` to extract keywords from user prompts, supporting both English and Chinese text.

feat: Implement Spec Loader for loading and filtering specs based on keywords

- Developed `spec-loader.ts` to handle loading of specs, filtering by read mode and keyword matches, and formatting output for CLI or hooks.
This commit is contained in:
catlog22
2026-02-26 12:51:29 +08:00
parent a35fb0fe8f
commit 2b5c334bc4
27 changed files with 2595 additions and 955 deletions

View File

@@ -1643,7 +1643,45 @@ function getCCWHome(): string {
}
/**
* List all projects with their memory counts
* Recursively find all core-memory databases in nested project structure
* Handles both flat structure (projects/my-project/) and nested structure (projects/d-/my-project/)
*/
function findAllCoreMemoryDatabases(
projectsDir: string,
baseRelPath: string = ''
): Array<{ projectId: string; dbPath: string }> {
const results: Array<{ projectId: string; dbPath: string }> = [];
const entries = readdirSync(projectsDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
// Skip hidden directories
if (entry.name.startsWith('.')) continue;
const currentPath = join(projectsDir, entry.name);
const currentRelPath = baseRelPath ? join(baseRelPath, entry.name) : entry.name;
// Check if this directory has a core-memory database
const coreMemoryDb = join(currentPath, 'core-memory', 'core_memory.db');
if (existsSync(coreMemoryDb)) {
// Found a project - use relative path as project ID
results.push({
projectId: currentRelPath,
dbPath: coreMemoryDb
});
}
// Recurse into subdirectories to find nested projects
const nestedResults = findAllCoreMemoryDatabases(currentPath, currentRelPath);
results.push(...nestedResults);
}
return results;
}
/**
* List all projects with their memory counts (supports nested project structure)
*/
export function listAllProjects(): ProjectInfo[] {
const projectsDir = join(getCCWHome(), 'projects');
@@ -1652,43 +1690,38 @@ export function listAllProjects(): ProjectInfo[] {
return [];
}
// Find all core-memory databases recursively
const allProjects = findAllCoreMemoryDatabases(projectsDir);
const projects: ProjectInfo[] = [];
const entries = readdirSync(projectsDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const projectId = entry.name;
const coreMemoryDb = join(projectsDir, projectId, 'core-memory', 'core_memory.db');
for (const { projectId, dbPath } of allProjects) {
let memoriesCount = 0;
let clustersCount = 0;
let lastUpdated: string | undefined;
if (existsSync(coreMemoryDb)) {
try {
const db = new Database(dbPath, { readonly: true });
// Count memories
const memResult = db.prepare('SELECT COUNT(*) as count FROM memories').get() as { count: number };
memoriesCount = memResult?.count || 0;
// Count clusters
try {
const db = new Database(coreMemoryDb, { readonly: true });
// Count memories
const memResult = db.prepare('SELECT COUNT(*) as count FROM memories').get() as { count: number };
memoriesCount = memResult?.count || 0;
// Count clusters
try {
const clusterResult = db.prepare('SELECT COUNT(*) as count FROM session_clusters').get() as { count: number };
clustersCount = clusterResult?.count || 0;
} catch {
// Table might not exist
}
// Get last update time
const lastMemory = db.prepare('SELECT MAX(updated_at) as last FROM memories').get() as { last: string };
lastUpdated = lastMemory?.last;
db.close();
const clusterResult = db.prepare('SELECT COUNT(*) as count FROM session_clusters').get() as { count: number };
clustersCount = clusterResult?.count || 0;
} catch {
// Database might be locked or corrupted
// Table might not exist
}
// Get last update time
const lastMemory = db.prepare('SELECT MAX(updated_at) as last FROM memories').get() as { last: string };
lastUpdated = lastMemory?.last;
db.close();
} catch {
// Database might be locked or corrupted
}
// Convert project ID back to approximate path
@@ -1715,7 +1748,8 @@ export function listAllProjects(): ProjectInfo[] {
}
/**
* Get memories from another project by ID
* Get memories from another project by ID (supports nested project structure)
* @param projectId - Project ID which can be a nested path like "d-/ccws"
*/
export function getMemoriesFromProject(projectId: string): CoreMemory[] {
const projectsDir = join(getCCWHome(), 'projects');
@@ -1746,8 +1780,8 @@ export function getMemoriesFromProject(projectId: string): CoreMemory[] {
}
/**
* Find a memory by ID across all projects
* Searches through all project databases to locate a specific memory
* Find a memory by ID across all projects (supports nested project structure)
* Searches through all project databases recursively to locate a specific memory
*/
export function findMemoryAcrossProjects(memoryId: string): { memory: CoreMemory; projectId: string } | null {
const projectsDir = join(getCCWHome(), 'projects');
@@ -1756,18 +1790,12 @@ export function findMemoryAcrossProjects(memoryId: string): { memory: CoreMemory
return null;
}
const entries = readdirSync(projectsDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const projectId = entry.name;
const coreMemoryDb = join(projectsDir, projectId, 'core-memory', 'core_memory.db');
if (!existsSync(coreMemoryDb)) continue;
// Find all core-memory databases recursively
const allProjects = findAllCoreMemoryDatabases(projectsDir);
for (const { projectId, dbPath } of allProjects) {
try {
const db = new Database(coreMemoryDb, { readonly: true });
const db = new Database(dbPath, { readonly: true });
const row = db.prepare('SELECT * FROM memories WHERE id = ?').get(memoryId) as any;
db.close();