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>
120 lines
4.0 KiB
JavaScript
120 lines
4.0 KiB
JavaScript
import { getAllProjects } from '../registry.js';
|
|
import { extractSummary } from '../extractors/summary.js';
|
|
import { extractProject } from '../extractors/project.js';
|
|
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
import { join } from 'path';
|
|
/**
|
|
* Search for decisions across all registered GSD projects
|
|
*/
|
|
export async function findDecisions(options) {
|
|
const projects = options.project
|
|
? getAllProjects().filter(p => p.name === options.project)
|
|
: getAllProjects();
|
|
const allDecisions = [];
|
|
for (const project of projects) {
|
|
const planningPath = join(project.path, '.planning');
|
|
if (!existsSync(planningPath))
|
|
continue;
|
|
// Extract decisions from PROJECT.md
|
|
const projectDecisions = extractProjectDecisions(planningPath, project.name);
|
|
allDecisions.push(...projectDecisions);
|
|
// Extract decisions from all SUMMARY.md files
|
|
const summaryDecisions = extractSummaryDecisions(planningPath, project.name);
|
|
allDecisions.push(...summaryDecisions);
|
|
}
|
|
// Filter by query if provided
|
|
let filtered = allDecisions;
|
|
if (options.query) {
|
|
const query = options.query.toLowerCase();
|
|
filtered = allDecisions.filter(d => d.decision.toLowerCase().includes(query) ||
|
|
d.rationale?.toLowerCase().includes(query));
|
|
}
|
|
// Filter by tags if provided
|
|
if (options.tags && options.tags.length > 0) {
|
|
const tags = options.tags.map(t => t.toLowerCase());
|
|
filtered = filtered.filter(d => d.tags?.some(t => tags.includes(t.toLowerCase())));
|
|
}
|
|
// Apply limit
|
|
const limit = options.limit || 50;
|
|
return filtered.slice(0, limit);
|
|
}
|
|
/**
|
|
* Extract decisions from PROJECT.md
|
|
*/
|
|
function extractProjectDecisions(planningPath, projectName) {
|
|
const projectPath = join(planningPath, 'PROJECT.md');
|
|
if (!existsSync(projectPath))
|
|
return [];
|
|
try {
|
|
const content = readFileSync(projectPath, 'utf-8');
|
|
const projectData = extractProject(content);
|
|
return projectData.decisions.map(d => ({
|
|
decision: d.decision,
|
|
rationale: d.rationale,
|
|
project: projectName,
|
|
source: projectPath
|
|
}));
|
|
}
|
|
catch {
|
|
return [];
|
|
}
|
|
}
|
|
/**
|
|
* Extract decisions from all SUMMARY.md files in a project
|
|
*/
|
|
function extractSummaryDecisions(planningPath, projectName) {
|
|
const decisions = [];
|
|
const phasesPath = join(planningPath, 'phases');
|
|
if (!existsSync(phasesPath))
|
|
return decisions;
|
|
// Find all SUMMARY.md files
|
|
const summaryFiles = findSummaryFiles(phasesPath);
|
|
for (const file of summaryFiles) {
|
|
try {
|
|
const content = readFileSync(file, 'utf-8');
|
|
const summaryData = extractSummary(content);
|
|
for (const decision of summaryData.decisions) {
|
|
decisions.push({
|
|
decision: decision.decision,
|
|
project: projectName,
|
|
phase: summaryData.phase,
|
|
source: file,
|
|
date: summaryData.completed,
|
|
tags: summaryData.tags
|
|
});
|
|
}
|
|
}
|
|
catch {
|
|
// Skip files that can't be parsed
|
|
}
|
|
}
|
|
return decisions;
|
|
}
|
|
/**
|
|
* Recursively find all SUMMARY.md files
|
|
*/
|
|
function findSummaryFiles(dir, files = []) {
|
|
try {
|
|
const entries = readdirSync(dir);
|
|
for (const entry of entries) {
|
|
const fullPath = join(dir, entry);
|
|
try {
|
|
const stat = statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
findSummaryFiles(fullPath, files);
|
|
}
|
|
else if (entry.toUpperCase().includes('SUMMARY') && entry.endsWith('.md')) {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
catch {
|
|
// Skip entries that can't be accessed
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
// Return empty if directory can't be read
|
|
}
|
|
return files;
|
|
}
|
|
//# sourceMappingURL=decisions.js.map
|