Files
msd-core/gsd-memory/dist/tools/search.js
Lex Christopherson af7a057294 feat: add GSD Memory cross-project knowledge system
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>
2026-02-05 13:13:22 -06:00

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