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

105 lines
3.1 KiB
JavaScript

import { registerProject, getProject, getAllProjects, unregisterProject } from '../registry.js';
import { qmd } from '../qmd.js';
import { existsSync } from 'fs';
import { join, basename } from 'path';
/**
* Register a project with GSD memory
*/
export async function register(options) {
// Validate path exists
if (!existsSync(options.path)) {
return {
success: false,
error: `Path does not exist: ${options.path}`
};
}
// Validate .planning directory exists
const planningPath = join(options.path, '.planning');
if (!existsSync(planningPath)) {
return {
success: false,
error: `No .planning directory found at: ${planningPath}`
};
}
// Determine project name
const name = options.name || basename(options.path);
// Check if QMD is available for semantic search
const qmdAvailable = await qmd.isAvailable();
let qmdCollection;
if (qmdAvailable) {
// Create QMD collection
qmdCollection = `gsd-${name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
const qmdResult = await qmd.createCollection({
name: qmdCollection,
paths: [planningPath],
context: `GSD project: ${name}`
});
if (!qmdResult.success) {
// QMD collection creation failed, but we can still register
qmdCollection = undefined;
}
}
// Register in local registry
const project = registerProject({
name,
path: options.path,
qmdCollection
});
return {
success: true,
project,
qmdCollection
};
}
/**
* Get status of all registered projects
*/
export async function status() {
const qmdAvailable = await qmd.isAvailable();
const projects = getAllProjects();
const enrichedProjects = await Promise.all(projects.map(async (project) => {
let documentCount;
if (qmdAvailable && project.qmdCollection) {
const qmdStatus = await qmd.status(project.qmdCollection);
documentCount = qmdStatus.documentCount;
}
return {
name: project.name,
path: project.path,
qmdCollection: project.qmdCollection,
registeredAt: project.registeredAt,
lastIndexed: project.lastIndexed,
documentCount
};
}));
return {
qmdAvailable,
projects: enrichedProjects
};
}
/**
* Unregister a project
*/
export async function unregister(name) {
const project = getProject(name);
if (!project) {
return {
success: false,
error: `Project not found: ${name}`
};
}
// Remove from QMD if it has a collection
if (project.qmdCollection && await qmd.isAvailable()) {
try {
// QMD doesn't have a direct remove command, so we just skip
// The collection will be orphaned but harmless
}
catch {
// Ignore QMD errors
}
}
// Remove from registry
unregisterProject(name);
return { success: true };
}
//# sourceMappingURL=register.js.map