Files
msd-core/gsd-memory/dist/extractors/summary.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

88 lines
2.8 KiB
JavaScript

import { parseFrontmatter } from './frontmatter.js';
/**
* Extract structured data from a SUMMARY.md file
*/
export function extractSummary(markdown) {
const { frontmatter, content } = parseFrontmatter(markdown);
return {
// Identifiers
phase: frontmatter.phase || '',
plan: frontmatter.plan || '',
subsystem: frontmatter.subsystem || '',
tags: frontmatter.tags || [],
// Dependency graph
requires: extractRequires(frontmatter.requires),
provides: frontmatter.provides || [],
affects: frontmatter.affects || [],
// Tech tracking
techStack: extractTechStack(frontmatter['tech-stack']),
patterns: frontmatter['patterns-established'] || [],
// Files
keyFiles: extractKeyFiles(frontmatter['key-files']),
// Decisions
decisions: extractDecisions(frontmatter['key-decisions']),
// Metadata
duration: frontmatter.duration || '',
completed: formatCompleted(frontmatter.completed),
// Content
oneLiner: extractOneLiner(content)
};
}
function extractRequires(requires) {
if (!Array.isArray(requires))
return [];
return requires.map((req) => ({
phase: req.phase || '',
provides: req.provides || ''
}));
}
function extractTechStack(techStack) {
if (!techStack || typeof techStack !== 'object')
return [];
const ts = techStack;
const added = ts.added;
if (!Array.isArray(added))
return [];
return added.map((entry) => {
// Handle both object format { library: version } and string format
if (typeof entry === 'object' && entry !== null) {
const [library, version] = Object.entries(entry)[0] || ['', ''];
return { library, version: String(version) };
}
return { library: String(entry), version: '' };
});
}
function extractKeyFiles(keyFiles) {
if (!keyFiles || typeof keyFiles !== 'object') {
return { created: [], modified: [] };
}
const kf = keyFiles;
return {
created: kf.created || [],
modified: kf.modified || []
};
}
function extractDecisions(decisions) {
if (!Array.isArray(decisions))
return [];
return decisions.map((d) => ({
decision: String(d),
source: 'frontmatter'
}));
}
function formatCompleted(completed) {
if (!completed)
return '';
// If it's a Date object (YAML parsed it), format as ISO date
if (completed instanceof Date) {
return completed.toISOString().split('T')[0];
}
return String(completed);
}
function extractOneLiner(content) {
// The one-liner is the bold text after the title
// Pattern: # Title\n\n**one-liner**
const match = content.match(/^#[^\n]+\n\n\*\*([^*]+)\*\*/m);
return match ? match[1].trim() : '';
}
//# sourceMappingURL=summary.js.map