Implements MCP server for semantic search across GSD projects: MCP Tools: - gsd_memory_search: Search across all registered projects - gsd_memory_decisions: Find decisions from SUMMARY/PROJECT.md - gsd_memory_patterns: Find patterns from SUMMARY.md - gsd_memory_pitfalls: Find pitfalls from RESEARCH.md - gsd_memory_stack: Find tech stack entries - gsd_memory_register: Register project with memory - gsd_memory_index: Trigger indexing/update - gsd_memory_status: Show system status Architecture: - QMD wrapper with grep fallback when QMD unavailable - YAML frontmatter extractors for GSD document types - Project registry at ~/.gsd/projects.json - Auto-install via bin/install.js for Claude Code Workflow integrations: - new-project: Registers project with memory - plan-phase: Indexes after planning - execute-phase: Indexes after execution - complete-milestone: Indexes milestone completion - Researcher agents query memory before Context7 Tests: 117 passing (66 unit + 51 integration) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
92 lines
2.8 KiB
JavaScript
92 lines
2.8 KiB
JavaScript
import { qmd } from '../qmd.js';
|
|
import { getAllProjects } from '../registry.js';
|
|
import { extractFrontmatter } from '../extractors/frontmatter.js';
|
|
import { readFileSync, existsSync } from 'fs';
|
|
import { join, basename } from 'path';
|
|
/**
|
|
* Search across all registered GSD projects
|
|
*/
|
|
export async function search(options) {
|
|
const projects = options.project
|
|
? getAllProjects().filter(p => p.name === options.project)
|
|
: getAllProjects();
|
|
if (projects.length === 0) {
|
|
return [];
|
|
}
|
|
const allResults = [];
|
|
for (const project of projects) {
|
|
const planningPath = join(project.path, '.planning');
|
|
if (!existsSync(planningPath))
|
|
continue;
|
|
// Search using QMD or grep fallback
|
|
const searchResults = await qmd.search({
|
|
query: options.query,
|
|
paths: [planningPath],
|
|
collection: project.qmdCollection,
|
|
limit: options.limit || 20
|
|
});
|
|
// Enrich results with GSD context
|
|
for (const result of searchResults) {
|
|
const enriched = enrichResult(result, project.name);
|
|
allResults.push(enriched);
|
|
}
|
|
}
|
|
// Sort by relevance
|
|
allResults.sort((a, b) => b.relevance - a.relevance);
|
|
// Apply limit
|
|
const limit = options.limit || 20;
|
|
return allResults.slice(0, limit);
|
|
}
|
|
/**
|
|
* Enrich a search result with GSD-specific context
|
|
*/
|
|
function enrichResult(result, projectName) {
|
|
const enriched = {
|
|
content: result.content,
|
|
source: result.source,
|
|
project: projectName,
|
|
relevance: result.relevance,
|
|
documentType: getDocumentType(result.source)
|
|
};
|
|
// Try to extract phase from frontmatter or path
|
|
enriched.phase = extractPhase(result.source);
|
|
return enriched;
|
|
}
|
|
/**
|
|
* Determine document type from file path/name
|
|
*/
|
|
function getDocumentType(source) {
|
|
const filename = basename(source).toUpperCase();
|
|
if (filename.includes('SUMMARY'))
|
|
return 'summary';
|
|
if (filename.includes('RESEARCH'))
|
|
return 'research';
|
|
if (filename.includes('PROJECT'))
|
|
return 'project';
|
|
if (filename.includes('PLAN'))
|
|
return 'plan';
|
|
return 'other';
|
|
}
|
|
/**
|
|
* Extract phase identifier from file path or content
|
|
*/
|
|
function extractPhase(source) {
|
|
// Try to get from path: .planning/phases/01-foundation/...
|
|
const pathMatch = source.match(/phases\/(\d+-[^/]+)/);
|
|
if (pathMatch)
|
|
return pathMatch[1];
|
|
// Try to get from frontmatter
|
|
try {
|
|
if (existsSync(source)) {
|
|
const content = readFileSync(source, 'utf-8');
|
|
const frontmatter = extractFrontmatter(content);
|
|
if (frontmatter.phase)
|
|
return String(frontmatter.phase);
|
|
}
|
|
}
|
|
catch {
|
|
// Ignore errors reading file
|
|
}
|
|
return undefined;
|
|
}
|
|
//# sourceMappingURL=search.js.map
|