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

106 lines
2.8 KiB
JavaScript

import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
const REGISTRY_DIR = join(homedir(), '.gsd');
const REGISTRY_PATH = join(REGISTRY_DIR, 'projects.json');
/**
* Ensure the registry directory exists
*/
function ensureRegistryDir() {
if (!existsSync(REGISTRY_DIR)) {
mkdirSync(REGISTRY_DIR, { recursive: true });
}
}
/**
* Read the project registry
*/
export function readRegistry() {
ensureRegistryDir();
if (!existsSync(REGISTRY_PATH)) {
return { version: '1.0.0', projects: [] };
}
try {
const content = readFileSync(REGISTRY_PATH, 'utf-8');
return JSON.parse(content);
}
catch {
return { version: '1.0.0', projects: [] };
}
}
/**
* Write the project registry
*/
export function writeRegistry(registry) {
ensureRegistryDir();
writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
}
/**
* Register a new project
*/
export function registerProject(project) {
const registry = readRegistry();
// Check if project already exists
const existingIndex = registry.projects.findIndex(p => p.path === project.path || p.name === project.name);
const newProject = {
...project,
registeredAt: new Date().toISOString()
};
if (existingIndex >= 0) {
// Update existing project
registry.projects[existingIndex] = {
...registry.projects[existingIndex],
...newProject,
registeredAt: registry.projects[existingIndex].registeredAt
};
}
else {
// Add new project
registry.projects.push(newProject);
}
writeRegistry(registry);
return newProject;
}
/**
* Update a project's last indexed time
*/
export function updateProjectIndexTime(name) {
const registry = readRegistry();
const project = registry.projects.find(p => p.name === name);
if (project) {
project.lastIndexed = new Date().toISOString();
writeRegistry(registry);
}
}
/**
* Get a project by name
*/
export function getProject(name) {
const registry = readRegistry();
return registry.projects.find(p => p.name === name);
}
/**
* Get all registered projects
*/
export function getAllProjects() {
return readRegistry().projects;
}
/**
* Remove a project from the registry
*/
export function unregisterProject(name) {
const registry = readRegistry();
const index = registry.projects.findIndex(p => p.name === name);
if (index >= 0) {
registry.projects.splice(index, 1);
writeRegistry(registry);
return true;
}
return false;
}
/**
* Get the registry path (for testing)
*/
export function getRegistryPath() {
return REGISTRY_PATH;
}
//# sourceMappingURL=registry.js.map