revert: remove GSD Memory system (not ready for release)

Reverts af7a057 "feat: add GSD Memory cross-project knowledge system"

The memory system needs more work before shipping:
- Workflow integration is incomplete (writes but doesn't query)
- UX requires too much manual intervention
- Setup friction exceeds value for most users

Preserved on branch `memory-wip` for continued development.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson
2026-02-06 09:48:09 -06:00
parent 7c42763aae
commit cc3c6aca56
120 changed files with 3 additions and 11335 deletions

View File

@@ -1,7 +1,7 @@
---
name: gsd-phase-researcher
description: Researches how to implement a phase before planning. Produces RESEARCH.md consumed by gsd-planner. Spawned by /gsd:plan-phase orchestrator.
tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__gsd-memory__*
tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*
color: cyan
---
@@ -100,42 +100,6 @@ When researching "best library for X":
<tool_strategy>
## GSD Memory: First for Past Work
**If `gsd_memory_*` MCP tools are available,** query past project knowledge before external research.
**When to use:**
- Check what decisions were made in similar phases
- Find patterns established in past projects
- Learn from documented pitfalls
- See what tech stack was used for similar domains
**How to use:**
```
1. Search for relevant context:
gsd_memory_search({ query: "[phase topic]" })
2. Find relevant decisions:
gsd_memory_decisions({ query: "[topic]" })
3. Find relevant patterns:
gsd_memory_patterns({ query: "[topic]" })
4. Check for pitfalls:
gsd_memory_pitfalls({ domain: "[domain]" })
5. See tech stack choices:
gsd_memory_stack({ query: "[library or purpose]" })
```
**Best practices:**
- Query memory BEFORE web research — past decisions may answer questions
- Note which project decisions came from for context
- Past decisions are HIGH confidence for this user's preferences
- Memory augments but doesn't replace current documentation
**If tools not available:** Skip and proceed to Context7.
## Context7: First for Libraries
Context7 provides authoritative, current documentation for libraries and frameworks.

View File

@@ -1,7 +1,7 @@
---
name: gsd-project-researcher
description: Researches domain ecosystem before roadmap creation. Produces files in .planning/research/ consumed during roadmap creation. Spawned by /gsd:new-project or /gsd:new-milestone orchestrators.
tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__gsd-memory__*
tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*
color: cyan
---
@@ -139,42 +139,6 @@ When researching "best library for X":
<tool_strategy>
## GSD Memory: First for Past Work
**If `gsd_memory_*` MCP tools are available,** query past project knowledge before external research.
**When to use:**
- Check what tech stack was used in similar projects
- Find patterns established in past work
- Learn from documented pitfalls
- See what decisions were made for similar domains
**How to use:**
```
1. Search for relevant context:
gsd_memory_search({ query: "[domain or topic]" })
2. Find relevant decisions:
gsd_memory_decisions({ query: "[topic]" })
3. Find relevant patterns:
gsd_memory_patterns({ query: "[topic]" })
4. Check for pitfalls:
gsd_memory_pitfalls({ domain: "[domain]" })
5. See tech stack choices:
gsd_memory_stack({ query: "[library or purpose]" })
```
**Best practices:**
- Query memory BEFORE web research — past decisions inform recommendations
- Past stack choices are HIGH confidence for this user's preferences
- Past pitfalls are HIGH confidence — actually experienced
- Memory augments but doesn't replace current documentation
**If tools not available:** Skip and proceed to Context7.
## Context7: First for Libraries
Context7 provides authoritative, current documentation for libraries and frameworks.

View File

@@ -907,34 +907,13 @@ function uninstall(isGlobal, runtime = 'claude') {
}
}
// Remove GSD Memory MCP server config
if (settings.mcpServers && settings.mcpServers['gsd-memory']) {
delete settings.mcpServers['gsd-memory'];
settingsModified = true;
console.log(` ${green}✓${reset} Removed GSD Memory MCP config from settings`);
// Clean up empty mcpServers object
if (Object.keys(settings.mcpServers).length === 0) {
delete settings.mcpServers;
}
}
if (settingsModified) {
writeSettings(settingsPath, settings);
removedCount++;
}
}
// 6. Remove MCP server from ~/.gsd/ (Claude Code only)
if (runtime === 'claude') {
const mcpServerDir = path.join(os.homedir(), '.gsd', 'mcp-server');
if (fs.existsSync(mcpServerDir)) {
fs.rmSync(mcpServerDir, { recursive: true });
removedCount++;
console.log(` ${green}✓${reset} Removed GSD Memory MCP server`);
}
}
// 7. For OpenCode, clean up permissions from opencode.json
// 6. For OpenCode, clean up permissions from opencode.json
if (isOpencode) {
const opencodeConfigDir = getOpencodeGlobalDir();
const configPath = path.join(opencodeConfigDir, 'opencode.json');
@@ -1050,102 +1029,6 @@ function configureOpencodePermissions() {
console.log(` ${green}✓${reset} Configured read permission for GSD docs`);
}
/**
* Install the GSD Memory MCP server
* - Copies gsd-memory/ to ~/.gsd/mcp-server/
* - Runs npm install in that directory
* - Returns the path for MCP config
* @returns {{ success: boolean, serverPath?: string, error?: string }}
*/
function installMcpServer() {
const src = path.join(__dirname, '..', 'gsd-memory');
// Check if gsd-memory exists in the package
if (!fs.existsSync(src)) {
return { success: false, error: 'gsd-memory not found in package' };
}
const gsdDir = path.join(os.homedir(), '.gsd');
const destDir = path.join(gsdDir, 'mcp-server');
// Ensure ~/.gsd exists
fs.mkdirSync(gsdDir, { recursive: true });
// Clean install: remove existing mcp-server
if (fs.existsSync(destDir)) {
fs.rmSync(destDir, { recursive: true });
}
// Copy gsd-memory to ~/.gsd/mcp-server
copyDirectory(src, destDir);
// Run npm install in the mcp-server directory
try {
const { execSync } = require('child_process');
execSync('npm install --production', {
cwd: destDir,
stdio: 'pipe',
timeout: 60000
});
} catch (err) {
return {
success: false,
error: `npm install failed: ${err.message}`,
serverPath: destDir
};
}
return {
success: true,
serverPath: destDir
};
}
/**
* Configure the GSD Memory MCP server in Claude Code settings
* @param {string} settingsPath - Path to settings.json
* @param {object} settings - Settings object
* @param {string} serverPath - Path to the MCP server
*/
function configureMcpServer(settingsPath, settings, serverPath) {
if (!settings.mcpServers) {
settings.mcpServers = {};
}
// Add gsd-memory MCP server config
settings.mcpServers['gsd-memory'] = {
command: 'node',
args: [path.join(serverPath, 'dist', 'index.js')]
};
return settings;
}
/**
* Copy a directory recursively
*/
function copyDirectory(src, dest) {
fs.mkdirSync(dest, { recursive: true });
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
// Skip node_modules, .git, and test directories
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'tests') {
continue;
}
if (entry.isDirectory()) {
copyDirectory(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
/**
* Verify a directory exists and contains files
*/
@@ -1344,18 +1227,6 @@ function install(isGlobal, runtime = 'claude') {
process.exit(1);
}
// Install GSD Memory MCP server (Claude Code only, global installs only)
let mcpServerPath = null;
if (runtime === 'claude' && isGlobal) {
const mcpResult = installMcpServer();
if (mcpResult.success) {
mcpServerPath = mcpResult.serverPath;
console.log(` ${green}✓${reset} Installed GSD Memory MCP server`);
} else if (mcpResult.error) {
console.log(` ${yellow}⚠${reset} MCP server install skipped: ${mcpResult.error}`);
}
}
// Configure statusline and hooks in settings.json
// Gemini shares same hook system as Claude Code for now
const settingsPath = path.join(targetDir, 'settings.json');
@@ -1404,12 +1275,6 @@ function install(isGlobal, runtime = 'claude') {
}
}
// Configure MCP server if installed (Claude Code only)
if (mcpServerPath && runtime === 'claude') {
configureMcpServer(settingsPath, settings, mcpServerPath);
console.log(` ${green}✓${reset} Configured GSD Memory MCP server`);
}
return { settingsPath, settings, statuslineCommand, runtime };
}

View File

@@ -926,26 +926,6 @@ EOF
)"
```
## Phase 9.5: Register with Memory (Optional)
**If gsd_memory MCP tools are available:**
Check if `gsd_memory_register` tool exists. If available, register this project with GSD memory for cross-project knowledge:
```
gsd_memory_register({
path: process.cwd(),
name: "[project name from PROJECT.md]"
})
```
Display confirmation:
```
Memory: Project registered ✓
```
**If tool not available:** Skip silently. GSD memory is optional.
## Phase 10: Done
Present completion with next steps:

View File

@@ -814,32 +814,6 @@ Confirm: "Committed: chore: complete v[X.Y] milestone"
</step>
<step name="index_memory">
Index the completed milestone with GSD Memory (if available).
**Check if gsd_memory MCP tools are available:**
If `gsd_memory_index` tool exists, trigger indexing:
```
gsd_memory_index({
path: process.cwd()
})
```
This updates the project's searchable knowledge base with:
- Milestone archive documents
- Final phase summaries
- Key decisions from PROJECT.md
- Requirements validation outcomes
**If tool not available:** Skip silently.
Report (if indexed): "Indexed milestone in GSD Memory"
</step>
<step name="offer_next">
```

View File

@@ -627,22 +627,6 @@ git commit -m "docs(phase-{X}): complete phase execution"
```
</step>
<step name="index_memory">
**If gsd_memory MCP tools are available:**
Check if `gsd_memory_index` tool exists. If available, trigger indexing:
```
gsd_memory_index({
path: process.cwd()
})
```
This updates the project's searchable knowledge base with new summaries and verification results.
**If tool not available:** Skip silently.
</step>
<step name="offer_next">
Present next steps based on milestone status:

View File

@@ -1,3 +0,0 @@
node_modules/
*.log
.DS_Store

View File

@@ -1,194 +0,0 @@
# GSD Memory
Cross-project knowledge system for GSD (Get Shit Done). An MCP server that provides semantic search across all your GSD projects, extracting decisions, patterns, pitfalls, and tech stack from YAML frontmatter.
## Features
- **Cross-project search** - Find content across all registered GSD projects
- **Decision extraction** - Query decisions from SUMMARY.md and PROJECT.md
- **Pattern discovery** - Find established patterns from completed phases
- **Pitfall awareness** - Learn from documented pitfalls in RESEARCH.md
- **Tech stack tracking** - Track libraries and tools used across projects
- **QMD integration** - Uses QMD for semantic search when available, falls back to grep
## Installation
### Option 1: Add to Claude Code MCP config (recommended)
Add to `~/.claude/settings.json`:
```json
{
"mcpServers": {
"gsd-memory": {
"command": "npx",
"args": ["-y", "gsd-memory"]
}
}
}
```
### Option 2: Install globally
```bash
npm install -g gsd-memory
```
Then add to `~/.claude/settings.json`:
```json
{
"mcpServers": {
"gsd-memory": {
"command": "gsd-memory"
}
}
}
```
### Option 3: With QMD (optional, for semantic search)
Install QMD for better search results:
```bash
bun install -g github:tobi/qmd
```
GSD Memory automatically detects and uses QMD when available.
## MCP Tools
### gsd_memory_register
Register a project with GSD memory.
```json
{
"path": "/path/to/project",
"name": "my-project"
}
```
### gsd_memory_search
Search across all registered projects.
```json
{
"query": "authentication JWT",
"project": "optional-filter",
"limit": 20
}
```
### gsd_memory_decisions
Find decisions made across projects.
```json
{
"query": "database choice",
"tags": ["postgres", "prisma"]
}
```
### gsd_memory_patterns
Find established patterns.
```json
{
"query": "error handling"
}
```
### gsd_memory_pitfalls
Find documented pitfalls.
```json
{
"domain": "authentication"
}
```
### gsd_memory_stack
Find tech stack entries.
```json
{
"query": "jose"
}
```
### gsd_memory_index
Trigger re-indexing for a project.
```json
{
"project": "my-project"
}
```
### gsd_memory_status
Get status of the memory system.
```json
{}
```
## How It Works
GSD Memory extracts structured data from GSD's YAML frontmatter:
| Document | Extracted Fields |
|----------|------------------|
| SUMMARY.md | `subsystem`, `tags`, `requires`, `provides`, `tech-stack`, `key-decisions`, `patterns-established` |
| RESEARCH.md | `domain`, `confidence`, standard stack tables, pitfalls sections |
| PROJECT.md | Requirements, constraints, key decisions table |
Data is stored in `~/.gsd/projects.json` (registry) and optionally in QMD collections for semantic search.
## Development
```bash
cd gsd-memory
npm install
npm test # Run unit tests
npm run build # Build TypeScript
```
## Architecture
```
gsd-memory/
├── src/
│ ├── index.ts # MCP server entry point
│ ├── qmd.ts # QMD wrapper with grep fallback
│ ├── registry.ts # Project registry (~/.gsd/projects.json)
│ ├── extractors/ # Frontmatter and content extractors
│ │ ├── frontmatter.ts
│ │ ├── summary.ts
│ │ ├── research.ts
│ │ └── project.ts
│ └── tools/ # MCP tool implementations
│ ├── search.ts
│ ├── decisions.ts
│ ├── patterns.ts
│ ├── pitfalls.ts
│ ├── stack.ts
│ ├── register.ts
│ ├── index-tool.ts
│ └── status.ts
└── tests/
├── fixtures/ # Test fixtures
├── extractors/ # Extractor tests
└── tools/ # Tool tests
```
## License
MIT

View File

@@ -1,13 +0,0 @@
/**
* Extract YAML frontmatter from markdown content
* Returns empty object if no frontmatter or invalid YAML
*/
export declare function extractFrontmatter(markdown: string): Record<string, unknown>;
/**
* Extract frontmatter and content separately
*/
export declare function parseFrontmatter(markdown: string): {
frontmatter: Record<string, unknown>;
content: string;
};
//# sourceMappingURL=frontmatter.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/extractors/frontmatter.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAClD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB,CAaA"}

View File

@@ -1,34 +0,0 @@
import matter from 'gray-matter';
/**
* Extract YAML frontmatter from markdown content
* Returns empty object if no frontmatter or invalid YAML
*/
export function extractFrontmatter(markdown) {
try {
const { data } = matter(markdown);
return data || {};
}
catch {
// Return empty object on parse errors
return {};
}
}
/**
* Extract frontmatter and content separately
*/
export function parseFrontmatter(markdown) {
try {
const { data, content } = matter(markdown);
return {
frontmatter: data || {},
content: content.trim()
};
}
catch {
return {
frontmatter: {},
content: markdown.trim()
};
}
}
//# sourceMappingURL=frontmatter.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/extractors/frontmatter.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,IAAI,IAAI,EAAE,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;QACtC,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAI/C,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3C,OAAO;YACL,WAAW,EAAE,IAAI,IAAI,EAAE;YACvB,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE;SACxB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,WAAW,EAAE,EAAE;YACf,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE;SACzB,CAAC;IACJ,CAAC;AACH,CAAC"}

View File

@@ -1,5 +0,0 @@
export { extractFrontmatter, parseFrontmatter } from './frontmatter.js';
export { extractSummary, type SummaryData, type TechStackEntry, type Decision as SummaryDecision } from './summary.js';
export { extractResearch, type ResearchData, type Pitfall, type DontHandRoll } from './research.js';
export { extractProject, type ProjectData, type Decision as ProjectDecision, type Constraint } from './project.js';
//# sourceMappingURL=index.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extractors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AACvH,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,IAAI,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC"}

View File

@@ -1,5 +0,0 @@
export { extractFrontmatter, parseFrontmatter } from './frontmatter.js';
export { extractSummary } from './summary.js';
export { extractResearch } from './research.js';
export { extractProject } from './project.js';
//# sourceMappingURL=index.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extractors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,cAAc,EAA2E,MAAM,cAAc,CAAC;AACvH,OAAO,EAAE,eAAe,EAAsD,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,cAAc,EAAuE,MAAM,cAAc,CAAC"}

View File

@@ -1,33 +0,0 @@
export interface OutOfScopeItem {
item: string;
reason: string;
}
export interface Constraint {
type: string;
value: string;
why?: string;
}
export interface Decision {
decision: string;
rationale: string;
outcome: string;
}
export interface Requirements {
validated: string[];
active: string[];
outOfScope: OutOfScopeItem[];
}
export interface ProjectData {
name: string;
description: string;
coreValue: string;
requirements: Requirements;
context: string;
constraints: Constraint[];
decisions: Decision[];
}
/**
* Extract structured data from a PROJECT.md file
*/
export declare function extractProject(markdown: string): ProjectData;
//# sourceMappingURL=project.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/extractors/project.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,SAAS,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAU5D"}

View File

@@ -1,139 +0,0 @@
/**
* Extract structured data from a PROJECT.md file
*/
export function extractProject(markdown) {
return {
name: extractName(markdown),
description: extractSection(markdown, 'What This Is'),
coreValue: extractSection(markdown, 'Core Value'),
requirements: extractRequirements(markdown),
context: extractSection(markdown, 'Context'),
constraints: extractConstraints(markdown),
decisions: extractDecisions(markdown)
};
}
function extractName(content) {
// First line should be # ProjectName
const match = content.match(/^#\s+(.+)/m);
return match ? match[1].trim() : '';
}
function extractSection(content, sectionName) {
// Find the section and extract content until next ## heading
const regex = new RegExp(`##\\s+${sectionName}\\n\\n([\\s\\S]*?)(?=\\n##|$)`, 'i');
const match = content.match(regex);
return match ? match[1].trim() : '';
}
function extractRequirements(content) {
const requirementsSection = content.match(/##\s+Requirements\n([\s\S]*?)(?=\n##[^#]|$)/i);
if (!requirementsSection) {
return { validated: [], active: [], outOfScope: [] };
}
const section = requirementsSection[1];
return {
validated: extractValidatedRequirements(section),
active: extractActiveRequirements(section),
outOfScope: extractOutOfScope(section)
};
}
function extractValidatedRequirements(section) {
const requirements = [];
// Look for ### Validated subsection
const validatedSection = section.match(/###\s+Validated\n([\s\S]*?)(?=###|$)/i);
if (!validatedSection)
return requirements;
// Extract checkmark items: - ✓ requirement — version
const matches = validatedSection[1].match(/^-\s+✓\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+✓\s+/, '').trim();
requirements.push(content);
}
}
return requirements;
}
function extractActiveRequirements(section) {
const requirements = [];
// Look for ### Active subsection
const activeSection = section.match(/###\s+Active\n([\s\S]*?)(?=###|$)/i);
if (!activeSection)
return requirements;
// Extract checkbox items: - [ ] requirement
const matches = activeSection[1].match(/^-\s+\[\s*\]\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+\[\s*\]\s+/, '').trim();
requirements.push(content);
}
}
return requirements;
}
function extractOutOfScope(section) {
const items = [];
// Look for ### Out of Scope subsection
const outOfScopeSection = section.match(/###\s+Out of Scope\n([\s\S]*?)(?=###|$)/i);
if (!outOfScopeSection)
return items;
// Extract items: - Item — reason
const matches = outOfScopeSection[1].match(/^-\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+/, '').trim();
// Split by em-dash or double hyphen
const parts = content.split(/\s+—\s+|\s+--\s+/);
items.push({
item: parts[0].trim(),
reason: parts[1]?.trim() || ''
});
}
}
return items;
}
function extractConstraints(content) {
const constraints = [];
const constraintsSection = content.match(/##\s+Constraints\n([\s\S]*?)(?=\n##|$)/i);
if (!constraintsSection)
return constraints;
// Extract items: - **Type**: Value — Why
const matches = constraintsSection[1].match(/^-\s+\*\*([^*]+)\*\*:\s*(.+)/gm);
if (matches) {
for (const match of matches) {
const typeMatch = match.match(/\*\*([^*]+)\*\*/);
const valueMatch = match.match(/\*\*[^*]+\*\*:\s*(.+)/);
if (typeMatch && valueMatch) {
const valueParts = valueMatch[1].split(/\s+—\s+|\s+--\s+/);
constraints.push({
type: typeMatch[1].trim(),
value: valueParts[0].trim(),
why: valueParts[1]?.trim()
});
}
}
}
return constraints;
}
function extractDecisions(content) {
const decisions = [];
const decisionsSection = content.match(/##\s+Key Decisions\n([\s\S]*?)(?=\n##[^#]|\n---|$)/i);
if (!decisionsSection)
return decisions;
// Parse markdown table rows - match rows with 3+ cells
const lines = decisionsSection[1].split('\n');
for (const line of lines) {
// Must be a table row (starts and ends with |)
if (!line.trim().startsWith('|'))
continue;
// Skip header row (contains 'Decision') and separator row (contains ---)
if (line.includes('Decision') || line.includes('---'))
continue;
const cells = line.split('|').filter(c => c.trim());
if (cells.length >= 3) {
decisions.push({
decision: cells[0].trim(),
rationale: cells[1].trim(),
outcome: cells[2].trim()
});
}
}
return decisions;
}
//# sourceMappingURL=project.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/extractors/project.ts"],"names":[],"mappings":"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"}

View File

@@ -1,40 +0,0 @@
export interface StackEntry {
library: string;
version?: string;
purpose: string;
whyStandard?: string;
}
export interface Pitfall {
name: string;
description: string;
cause?: string;
prevention: string;
warningSigns?: string;
}
export interface DontHandRoll {
problem: string;
dontBuild: string;
useInstead: string;
why: string;
}
export interface UserConstraints {
locked: string[];
discretion: string[];
deferred: string[];
}
export interface ResearchData {
domain: string;
confidence: string;
researched: string;
standardStack: StackEntry[];
pitfalls: Pitfall[];
dontHandRoll: DontHandRoll[];
antiPatterns: string[];
userConstraints: UserConstraints;
primaryRecommendation: string;
}
/**
* Extract structured data from a RESEARCH.md file
*/
export declare function extractResearch(markdown: string): ResearchData;
//# sourceMappingURL=research.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../../src/extractors/research.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAE3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IAGnB,aAAa,EAAE,UAAU,EAAE,CAAC;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,eAAe,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAiB9D"}

View File

@@ -1,183 +0,0 @@
import { parseFrontmatter } from './frontmatter.js';
/**
* Extract structured data from a RESEARCH.md file
*/
export function extractResearch(markdown) {
const { frontmatter, content } = parseFrontmatter(markdown);
return {
// Metadata from frontmatter
domain: frontmatter.domain || '',
confidence: frontmatter.confidence || '',
researched: formatDate(frontmatter.researched),
// Extracted from content
standardStack: extractStandardStack(content),
pitfalls: extractPitfalls(content),
dontHandRoll: extractDontHandRoll(content),
antiPatterns: extractAntiPatterns(content),
userConstraints: extractUserConstraints(content),
primaryRecommendation: extractPrimaryRecommendation(content)
};
}
function formatDate(date) {
if (!date)
return '';
if (date instanceof Date) {
return date.toISOString().split('T')[0];
}
return String(date);
}
function extractStandardStack(content) {
const entries = [];
// Look for standard_stack section and parse markdown tables
const stackSection = extractSection(content, 'standard_stack');
if (!stackSection)
return entries;
// Parse Core and Supporting tables
const tableRows = stackSection.match(/\|[^|]+\|[^|]+\|[^|]+\|[^|]*\|/g);
if (!tableRows)
return entries;
for (const row of tableRows) {
// Skip header and separator rows
if (row.includes('Library') || row.includes('---'))
continue;
const cells = row.split('|').filter(c => c.trim());
if (cells.length >= 3) {
entries.push({
library: cells[0].trim(),
version: cells[1]?.trim(),
purpose: cells[2]?.trim(),
whyStandard: cells[3]?.trim()
});
}
}
return entries;
}
function extractPitfalls(content) {
const pitfalls = [];
const pitfallSection = extractSection(content, 'common_pitfalls');
if (!pitfallSection)
return pitfalls;
// Parse individual pitfall blocks (### Pitfall N: Name)
const pitfallBlocks = pitfallSection.split(/###\s+Pitfall\s+\d+:\s+/);
for (const block of pitfallBlocks) {
if (!block.trim())
continue;
const lines = block.split('\n');
const name = lines[0]?.trim();
if (!name)
continue;
const description = extractField(block, 'What goes wrong');
const cause = extractField(block, 'Why it happens');
const prevention = extractField(block, 'How to avoid');
const warningSigns = extractField(block, 'Warning signs');
if (description || prevention) {
pitfalls.push({
name,
description: description || '',
cause,
prevention: prevention || '',
warningSigns
});
}
}
return pitfalls;
}
function extractDontHandRoll(content) {
const items = [];
const section = extractSection(content, 'dont_hand_roll');
if (!section)
return items;
// Parse table rows
const tableRows = section.match(/\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|/g);
if (!tableRows)
return items;
for (const row of tableRows) {
if (row.includes('Problem') || row.includes('---'))
continue;
const cells = row.split('|').filter(c => c.trim());
if (cells.length >= 4) {
items.push({
problem: cells[0].trim(),
dontBuild: cells[1].trim(),
useInstead: cells[2].trim(),
why: cells[3].trim()
});
}
}
return items;
}
function extractAntiPatterns(content) {
const patterns = [];
const archSection = extractSection(content, 'architecture_patterns');
if (!archSection)
return patterns;
// Look for "Anti-Patterns to Avoid" subsection
const antiMatch = archSection.match(/###\s+Anti-Patterns to Avoid\n([\s\S]*?)(?=###|$)/);
if (!antiMatch)
return patterns;
// Extract bullet points
const bullets = antiMatch[1].match(/^-\s+\*\*([^*]+)\*\*/gm);
if (bullets) {
for (const bullet of bullets) {
const match = bullet.match(/\*\*([^*]+)\*\*/);
if (match)
patterns.push(match[1].trim());
}
}
return patterns;
}
function extractUserConstraints(content) {
const constraints = {
locked: [],
discretion: [],
deferred: []
};
const section = extractSection(content, 'user_constraints');
if (!section)
return constraints;
// Extract locked decisions
const lockedMatch = section.match(/###\s+Locked Decisions\n([\s\S]*?)(?=###|$)/);
if (lockedMatch) {
constraints.locked = extractBulletPoints(lockedMatch[1]);
}
// Extract Claude's discretion
const discretionMatch = section.match(/###\s+Claude's Discretion\n([\s\S]*?)(?=###|$)/);
if (discretionMatch) {
constraints.discretion = extractBulletPoints(discretionMatch[1]);
}
// Extract deferred items
const deferredMatch = section.match(/###\s+Deferred Ideas[^#]*\n([\s\S]*?)(?=<\/|$)/);
if (deferredMatch) {
constraints.deferred = extractBulletPoints(deferredMatch[1]);
}
return constraints;
}
function extractPrimaryRecommendation(content) {
// Look for "Primary recommendation:" in summary section
const match = content.match(/\*\*Primary recommendation:\*\*\s*([^\n]+)/);
return match ? match[1].trim() : '';
}
// Helper functions
function extractSection(content, sectionTag) {
const regex = new RegExp(`<${sectionTag}>([\\s\\S]*?)<\\/${sectionTag}>`, 'i');
const match = content.match(regex);
return match ? match[1] : null;
}
function extractField(block, fieldName) {
const regex = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*([^\\n]+)`, 'i');
const match = block.match(regex);
return match ? match[1].trim() : undefined;
}
function extractBulletPoints(text) {
const points = [];
const matches = text.match(/^-\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+/, '').trim();
if (content)
points.push(content);
}
}
return points;
}
//# sourceMappingURL=research.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,38 +0,0 @@
export interface TechStackEntry {
library: string;
version: string;
purpose?: string;
}
export interface Dependency {
phase: string;
provides: string;
}
export interface KeyFiles {
created: string[];
modified: string[];
}
export interface Decision {
decision: string;
source?: string;
}
export interface SummaryData {
phase: string;
plan: number | string;
subsystem: string;
tags: string[];
requires: Dependency[];
provides: string[];
affects: string[];
techStack: TechStackEntry[];
patterns: string[];
keyFiles: KeyFiles;
decisions: Decision[];
duration: string;
completed: string;
oneLiner: string;
}
/**
* Extract structured data from a SUMMARY.md file
*/
export declare function extractSummary(markdown: string): SummaryData;
//# sourceMappingURL=summary.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/extractors/summary.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAE1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IAGf,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAGlB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IAGnB,QAAQ,EAAE,QAAQ,CAAC;IAGnB,SAAS,EAAE,QAAQ,EAAE,CAAC;IAGtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAGlB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAgC5D"}

View File

@@ -1,88 +0,0 @@
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

View File

@@ -1 +0,0 @@
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/extractors/summary.ts"],"names":[],"mappings":"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"}

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env node
export {};
//# sourceMappingURL=index.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}

View File

@@ -1,298 +0,0 @@
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { search } from './tools/search.js';
import { findDecisions } from './tools/decisions.js';
import { findPatterns } from './tools/patterns.js';
import { findPitfalls } from './tools/pitfalls.js';
import { findStack } from './tools/stack.js';
import { register } from './tools/register.js';
import { index, indexAll } from './tools/index-tool.js';
import { getStatus } from './tools/status.js';
// Define available tools
const tools = [
{
name: 'gsd_memory_search',
description: 'Search across all registered GSD projects for content matching a query. Returns enriched results with project, phase, and document type context.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query'
},
project: {
type: 'string',
description: 'Optional: limit search to a specific project'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 20)'
}
},
required: ['query']
}
},
{
name: 'gsd_memory_decisions',
description: 'Find decisions made across GSD projects. Searches KEY-DECISIONS from SUMMARY.md frontmatter and Key Decisions tables from PROJECT.md.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter decisions by keyword'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Optional: filter by tags'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
}
}
}
},
{
name: 'gsd_memory_patterns',
description: 'Find patterns established across GSD projects. Extracts PATTERNS-ESTABLISHED from SUMMARY.md frontmatter.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter patterns by keyword'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Optional: filter by tags'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
}
}
}
},
{
name: 'gsd_memory_pitfalls',
description: 'Find documented pitfalls from RESEARCH.md files. Includes what went wrong, causes, and prevention strategies.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter pitfalls by keyword'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
domain: {
type: 'string',
description: 'Optional: filter by technology domain'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
}
}
}
},
{
name: 'gsd_memory_stack',
description: 'Find tech stack entries across GSD projects. Extracts from SUMMARY.md (tech-stack.added) and RESEARCH.md (standard stack tables).',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter by library name or purpose'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 100)'
}
}
}
},
{
name: 'gsd_memory_register',
description: 'Register a project with GSD memory. Creates a QMD collection if QMD is available.',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the project root (must contain .planning directory)'
},
name: {
type: 'string',
description: 'Optional: project name (defaults to directory name)'
}
},
required: ['path']
}
},
{
name: 'gsd_memory_index',
description: 'Trigger indexing/update for a project. Updates QMD collection if available, otherwise marks project as indexed for grep fallback.',
inputSchema: {
type: 'object',
properties: {
project: {
type: 'string',
description: 'Project name to index'
},
path: {
type: 'string',
description: 'Alternative: project path to index'
}
}
}
},
{
name: 'gsd_memory_status',
description: 'Show status of GSD memory system: QMD availability, registered projects, document counts.',
inputSchema: {
type: 'object',
properties: {}
}
}
];
// Create server instance
const server = new Server({
name: 'gsd-memory',
version: '1.0.0'
}, {
capabilities: {
tools: {}
}
});
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result;
switch (name) {
case 'gsd_memory_search':
result = await search({
query: args?.query,
project: args?.project,
limit: args?.limit
});
break;
case 'gsd_memory_decisions':
result = await findDecisions({
query: args?.query,
project: args?.project,
tags: args?.tags,
limit: args?.limit
});
break;
case 'gsd_memory_patterns':
result = await findPatterns({
query: args?.query,
project: args?.project,
tags: args?.tags,
limit: args?.limit
});
break;
case 'gsd_memory_pitfalls':
result = await findPitfalls({
query: args?.query,
project: args?.project,
domain: args?.domain,
limit: args?.limit
});
break;
case 'gsd_memory_stack':
result = await findStack({
query: args?.query,
project: args?.project,
limit: args?.limit
});
break;
case 'gsd_memory_register':
result = await register({
path: args?.path,
name: args?.name
});
break;
case 'gsd_memory_index':
if (args?.project || args?.path) {
result = await index({
project: args?.project,
path: args?.path
});
}
else {
result = await indexAll();
}
break;
case 'gsd_memory_status':
result = await getStatus();
break;
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${name}`
}
],
isError: true
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`
}
],
isError: true
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('GSD Memory MCP server started');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,67 +0,0 @@
export interface QmdSearchResult {
content: string;
source: string;
relevance: number;
context?: string;
}
export interface QmdSearchOptions {
query: string;
paths: string[];
limit?: number;
collection?: string;
}
export interface QmdCollectionOptions {
name: string;
paths: string[];
context?: string;
}
export interface QmdResult {
success: boolean;
reason?: string;
data?: unknown;
}
export interface QmdStatus {
available: boolean;
collection?: string;
documentCount?: number;
lastIndexed?: string;
}
/**
* QMD wrapper - provides semantic search when QMD is available,
* falls back to grep-based search otherwise
*/
export declare const qmd: {
/**
* Check if QMD is installed and available
*/
isAvailable(): Promise<boolean>;
/**
* Search for content using QMD or grep fallback
*/
search(options: QmdSearchOptions): Promise<QmdSearchResult[]>;
/**
* Search using QMD (when available)
*/
qmdSearch(options: QmdSearchOptions): Promise<QmdSearchResult[]>;
/**
* Grep-based fallback search
*/
grepFallback(options: QmdSearchOptions): Promise<QmdSearchResult[]>;
/**
* Find all markdown files in a directory recursively
*/
findMarkdownFiles(dir: string, files?: string[]): string[];
/**
* Create a QMD collection for a project
*/
createCollection(options: QmdCollectionOptions): Promise<QmdResult>;
/**
* Trigger reindexing of a collection
*/
index(collectionName: string): Promise<QmdResult>;
/**
* Get status of a collection
*/
status(collectionName: string): Promise<QmdStatus>;
};
//# sourceMappingURL=qmd.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"qmd.d.ts","sourceRoot":"","sources":["../src/qmd.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,GAAG;IACd;;OAEG;mBACkB,OAAO,CAAC,OAAO,CAAC;IASrC;;OAEG;oBACmB,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAUnE;;OAEG;uBACsB,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAoBtE;;OAEG;0BACyB,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA8CzE;;OAEG;2BACoB,MAAM,UAAS,MAAM,EAAE,GAAQ,MAAM,EAAE;IAyB9D;;OAEG;8BAC6B,oBAAoB,GAAG,OAAO,CAAC,SAAS,CAAC;IA4BzE;;OAEG;0BACyB,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAsBvD;;OAEG;2BAC0B,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAyBzD,CAAC"}

199
gsd-memory/dist/qmd.js vendored
View File

@@ -1,199 +0,0 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
const execAsync = promisify(exec);
/**
* QMD wrapper - provides semantic search when QMD is available,
* falls back to grep-based search otherwise
*/
export const qmd = {
/**
* Check if QMD is installed and available
*/
async isAvailable() {
try {
await execAsync('which qmd');
return true;
}
catch {
return false;
}
},
/**
* Search for content using QMD or grep fallback
*/
async search(options) {
const available = await this.isAvailable();
if (available && options.collection) {
return this.qmdSearch(options);
}
return this.grepFallback(options);
},
/**
* Search using QMD (when available)
*/
async qmdSearch(options) {
try {
const limit = options.limit || 10;
const { stdout } = await execAsync(`qmd query "${options.query}" --collection "${options.collection}" --limit ${limit} --json`);
const parsed = JSON.parse(stdout);
return (parsed.results || []).map((r) => ({
content: r.content,
source: r.source,
relevance: r.score || 0.5,
context: 'qmd'
}));
}
catch {
// Fall back to grep if QMD search fails
return this.grepFallback(options);
}
},
/**
* Grep-based fallback search
*/
async grepFallback(options) {
const results = [];
const limit = options.limit || 20;
for (const searchPath of options.paths) {
try {
const files = this.findMarkdownFiles(searchPath);
for (const file of files) {
try {
const content = readFileSync(file, 'utf-8');
const query = options.query.toLowerCase();
// Simple relevance: count matches
const matches = (content.toLowerCase().match(new RegExp(query, 'g')) || []).length;
if (matches > 0) {
// Extract context around first match
const lowerContent = content.toLowerCase();
const matchIndex = lowerContent.indexOf(query);
const start = Math.max(0, matchIndex - 100);
const end = Math.min(content.length, matchIndex + query.length + 100);
const snippet = content.slice(start, end);
results.push({
content: snippet,
source: file,
relevance: Math.min(1, matches * 0.1),
context: 'grep'
});
}
}
catch {
// Skip files that can't be read
}
}
}
catch {
// Skip paths that can't be accessed
}
}
// Sort by relevance and limit
return results
.sort((a, b) => b.relevance - a.relevance)
.slice(0, limit);
},
/**
* Find all markdown files in a directory recursively
*/
findMarkdownFiles(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() && !entry.startsWith('.') && entry !== 'node_modules') {
this.findMarkdownFiles(fullPath, files);
}
else if (stat.isFile() && 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;
},
/**
* Create a QMD collection for a project
*/
async createCollection(options) {
const available = await this.isAvailable();
if (!available) {
return {
success: false,
reason: 'qmd_not_available'
};
}
try {
const paths = options.paths.join(' ');
const contextArg = options.context ? `--context "${options.context}"` : '';
await execAsync(`qmd add "${options.name}" ${paths} ${contextArg}`);
return { success: true };
}
catch (error) {
return {
success: false,
reason: 'create_failed',
data: error instanceof Error ? error.message : String(error)
};
}
},
/**
* Trigger reindexing of a collection
*/
async index(collectionName) {
const available = await this.isAvailable();
if (!available) {
return {
success: false,
reason: 'qmd_not_available'
};
}
try {
await execAsync(`qmd update "${collectionName}"`);
return { success: true };
}
catch (error) {
return {
success: false,
reason: 'index_failed',
data: error instanceof Error ? error.message : String(error)
};
}
},
/**
* Get status of a collection
*/
async status(collectionName) {
const available = await this.isAvailable();
if (!available) {
return { available: false };
}
try {
const { stdout } = await execAsync(`qmd status "${collectionName}" --json`);
const parsed = JSON.parse(stdout);
return {
available: true,
collection: collectionName,
documentCount: parsed.documentCount || 0,
lastIndexed: parsed.lastIndexed
};
}
catch {
return {
available: true,
collection: collectionName,
documentCount: 0
};
}
}
};
//# sourceMappingURL=qmd.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,44 +0,0 @@
export interface RegisteredProject {
name: string;
path: string;
qmdCollection?: string;
registeredAt: string;
lastIndexed?: string;
}
export interface Registry {
version: string;
projects: RegisteredProject[];
}
/**
* Read the project registry
*/
export declare function readRegistry(): Registry;
/**
* Write the project registry
*/
export declare function writeRegistry(registry: Registry): void;
/**
* Register a new project
*/
export declare function registerProject(project: Omit<RegisteredProject, 'registeredAt'>): RegisteredProject;
/**
* Update a project's last indexed time
*/
export declare function updateProjectIndexTime(name: string): void;
/**
* Get a project by name
*/
export declare function getProject(name: string): RegisteredProject | undefined;
/**
* Get all registered projects
*/
export declare function getAllProjects(): RegisteredProject[];
/**
* Remove a project from the registry
*/
export declare function unregisterProject(name: string): boolean;
/**
* Get the registry path (for testing)
*/
export declare function getRegistryPath(): string;
//# sourceMappingURL=registry.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAcD;;GAEG;AACH,wBAAgB,YAAY,IAAI,QAAQ,CAavC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAGtD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,cAAc,CAAC,GAAG,iBAAiB,CA2BnG;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAQzD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAGtE;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,EAAE,CAEpD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAWvD;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC"}

View File

@@ -1,106 +0,0 @@
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

View File

@@ -1 +0,0 @@
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}

View File

@@ -1,20 +0,0 @@
export interface DecisionResult {
decision: string;
rationale?: string;
project: string;
phase?: string;
source: string;
date?: string;
tags?: string[];
}
export interface DecisionOptions {
query?: string;
project?: string;
tags?: string[];
limit?: number;
}
/**
* Search for decisions across all registered GSD projects
*/
export declare function findDecisions(options: DecisionOptions): Promise<DecisionResult[]>;
//# sourceMappingURL=decisions.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"decisions.d.ts","sourceRoot":"","sources":["../../src/tools/decisions.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAyCvF"}

View File

@@ -1,120 +0,0 @@
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

View File

@@ -1 +0,0 @@
{"version":3,"file":"decisions.js","sourceRoot":"","sources":["../../src/tools/decisions.ts"],"names":[],"mappings":"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"}

View File

@@ -1,23 +0,0 @@
export interface IndexOptions {
path?: string;
project?: string;
}
export interface IndexResult {
success: boolean;
project?: string;
documentsIndexed?: number;
error?: string;
qmdUsed: boolean;
}
/**
* Trigger indexing/update for a project
*/
export declare function index(options: IndexOptions): Promise<IndexResult>;
/**
* Index all registered projects
*/
export declare function indexAll(): Promise<{
success: boolean;
results: IndexResult[];
}>;
//# sourceMappingURL=index-tool.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index-tool.d.ts","sourceRoot":"","sources":["../../src/tools/index-tool.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2FvE;AAED;;GAEG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC,CAYD"}

View File

@@ -1,107 +0,0 @@
import { getProject, updateProjectIndexTime, getAllProjects } from '../registry.js';
import { qmd } from '../qmd.js';
import { existsSync } from 'fs';
import { join } from 'path';
/**
* Trigger indexing/update for a project
*/
export async function index(options) {
// Find the project
let projectName;
let projectPath;
let qmdCollection;
if (options.project) {
const project = getProject(options.project);
if (!project) {
return {
success: false,
error: `Project not found: ${options.project}`,
qmdUsed: false
};
}
projectName = project.name;
projectPath = project.path;
qmdCollection = project.qmdCollection;
}
else if (options.path) {
// Find project by path
const projects = getAllProjects();
const project = projects.find(p => p.path === options.path);
if (!project) {
return {
success: false,
error: `No project registered for path: ${options.path}`,
qmdUsed: false
};
}
projectName = project.name;
projectPath = project.path;
qmdCollection = project.qmdCollection;
}
else {
return {
success: false,
error: 'Either project or path must be specified',
qmdUsed: false
};
}
// Verify planning directory exists
const planningPath = join(projectPath, '.planning');
if (!existsSync(planningPath)) {
return {
success: false,
project: projectName,
error: `No .planning directory found at: ${planningPath}`,
qmdUsed: false
};
}
// Check if QMD is available
const qmdAvailable = await qmd.isAvailable();
if (qmdAvailable && qmdCollection) {
// Use QMD to update the collection
const result = await qmd.index(qmdCollection);
if (result.success) {
updateProjectIndexTime(projectName);
// Get document count
const status = await qmd.status(qmdCollection);
return {
success: true,
project: projectName,
documentsIndexed: status.documentCount,
qmdUsed: true
};
}
else {
return {
success: false,
project: projectName,
error: `QMD indexing failed: ${result.reason}`,
qmdUsed: true
};
}
}
else {
// No QMD - just update the timestamp
// Grep fallback will search files directly
updateProjectIndexTime(projectName);
return {
success: true,
project: projectName,
qmdUsed: false
};
}
}
/**
* Index all registered projects
*/
export async function indexAll() {
const projects = getAllProjects();
const results = [];
for (const project of projects) {
const result = await index({ project: project.name });
results.push(result);
}
const success = results.every(r => r.success);
return { success, results };
}
//# sourceMappingURL=index-tool.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index-tool.js","sourceRoot":"","sources":["../../src/tools/index-tool.ts"],"names":[],"mappings":"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"}

View File

@@ -1,19 +0,0 @@
export interface PatternResult {
pattern: string;
project: string;
phase: string;
source: string;
date?: string;
tags?: string[];
}
export interface PatternOptions {
query?: string;
project?: string;
tags?: string[];
limit?: number;
}
/**
* Search for patterns established across all registered GSD projects
*/
export declare function findPatterns(options: PatternOptions): Promise<PatternResult[]>;
//# sourceMappingURL=patterns.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/tools/patterns.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAuDpF"}

View File

@@ -1,81 +0,0 @@
import { getAllProjects } from '../registry.js';
import { extractSummary } from '../extractors/summary.js';
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
/**
* Search for patterns established across all registered GSD projects
*/
export async function findPatterns(options) {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
const allPatterns = [];
for (const project of projects) {
const phasesPath = join(project.path, '.planning', 'phases');
if (!existsSync(phasesPath))
continue;
// Find all SUMMARY.md files and extract patterns
const summaryFiles = findSummaryFiles(phasesPath);
for (const file of summaryFiles) {
try {
const content = readFileSync(file, 'utf-8');
const summaryData = extractSummary(content);
for (const pattern of summaryData.patterns) {
allPatterns.push({
pattern,
project: project.name,
phase: summaryData.phase,
source: file,
date: summaryData.completed,
tags: summaryData.tags
});
}
}
catch {
// Skip files that can't be parsed
}
}
}
// Filter by query if provided
let filtered = allPatterns;
if (options.query) {
const query = options.query.toLowerCase();
filtered = allPatterns.filter(p => p.pattern.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(p => p.tags?.some(t => tags.includes(t.toLowerCase())));
}
// Apply limit
const limit = options.limit || 50;
return filtered.slice(0, limit);
}
/**
* 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=patterns.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/tools/patterns.ts"],"names":[],"mappings":"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"}

View File

@@ -1,18 +0,0 @@
import { type Pitfall } from '../extractors/research.js';
export interface PitfallResult extends Pitfall {
project: string;
phase?: string;
domain: string;
source: string;
}
export interface PitfallOptions {
query?: string;
project?: string;
domain?: string;
limit?: number;
}
/**
* Search for pitfalls documented across all registered GSD projects
*/
export declare function findPitfalls(options: PitfallOptions): Promise<PitfallResult[]>;
//# sourceMappingURL=pitfalls.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"pitfalls.d.ts","sourceRoot":"","sources":["../../src/tools/pitfalls.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAI1E,MAAM,WAAW,aAAc,SAAQ,OAAO;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA4DpF"}

View File

@@ -1,85 +0,0 @@
import { getAllProjects } from '../registry.js';
import { extractResearch } from '../extractors/research.js';
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
/**
* Search for pitfalls documented across all registered GSD projects
*/
export async function findPitfalls(options) {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
const allPitfalls = [];
for (const project of projects) {
const planningPath = join(project.path, '.planning');
if (!existsSync(planningPath))
continue;
// Find all RESEARCH.md files
const researchFiles = findResearchFiles(planningPath);
for (const file of researchFiles) {
try {
const content = readFileSync(file, 'utf-8');
const researchData = extractResearch(content);
// Extract phase from path if present
const phaseMatch = file.match(/phases\/(\d+-[^/]+)/);
const phase = phaseMatch ? phaseMatch[1] : undefined;
for (const pitfall of researchData.pitfalls) {
allPitfalls.push({
...pitfall,
project: project.name,
phase,
domain: researchData.domain,
source: file
});
}
}
catch {
// Skip files that can't be parsed
}
}
}
// Filter by query if provided
let filtered = allPitfalls;
if (options.query) {
const query = options.query.toLowerCase();
filtered = allPitfalls.filter(p => p.name.toLowerCase().includes(query) ||
p.description.toLowerCase().includes(query) ||
p.prevention.toLowerCase().includes(query));
}
// Filter by domain if provided
if (options.domain) {
const domain = options.domain.toLowerCase();
filtered = filtered.filter(p => p.domain.toLowerCase().includes(domain));
}
// Apply limit
const limit = options.limit || 50;
return filtered.slice(0, limit);
}
/**
* Recursively find all RESEARCH.md files
*/
function findResearchFiles(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() && !entry.startsWith('.')) {
findResearchFiles(fullPath, files);
}
else if (entry.toUpperCase().includes('RESEARCH') && 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=pitfalls.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"pitfalls.js","sourceRoot":"","sources":["../../src/tools/pitfalls.ts"],"names":[],"mappings":"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"}

View File

@@ -1,37 +0,0 @@
import { type RegisteredProject } from '../registry.js';
export interface RegisterOptions {
path: string;
name?: string;
}
export interface RegisterResult {
success: boolean;
project?: RegisteredProject;
qmdCollection?: string;
error?: string;
}
/**
* Register a project with GSD memory
*/
export declare function register(options: RegisterOptions): Promise<RegisterResult>;
/**
* Get status of all registered projects
*/
export declare function status(): Promise<{
qmdAvailable: boolean;
projects: Array<{
name: string;
path: string;
qmdCollection?: string;
registeredAt: string;
lastIndexed?: string;
documentCount?: number;
}>;
}>;
/**
* Unregister a project
*/
export declare function unregister(name: string): Promise<{
success: boolean;
error?: string;
}>;
//# sourceMappingURL=register.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/tools/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAKxH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAqDhF;AAED;;GAEG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC;IACtC,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;CACJ,CAAC,CA4BD;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwB5F"}

View File

@@ -1,105 +0,0 @@
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

View File

@@ -1 +0,0 @@
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/tools/register.ts"],"names":[],"mappings":"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"}

View File

@@ -1,19 +0,0 @@
export interface SearchResult {
content: string;
source: string;
project: string;
phase?: string;
relevance: number;
documentType?: 'summary' | 'research' | 'project' | 'plan' | 'other';
}
export interface SearchOptions {
query: string;
project?: string;
tags?: string[];
limit?: number;
}
/**
* Search across all registered GSD projects
*/
export declare function search(options: SearchOptions): Promise<SearchResult[]>;
//# sourceMappingURL=search.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;CACtE;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAqC5E"}

View File

@@ -1,92 +0,0 @@
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

View File

@@ -1 +0,0 @@
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAW,MAAM,MAAM,CAAC;AAkB/C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAsB;IACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO;QAC9B,CAAC,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC;QAC1D,CAAC,CAAC,cAAc,EAAE,CAAC;IAErB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,UAAU,GAAmB,EAAE,CAAC;IAEtC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAErD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAS;QAExC,oCAAoC;QACpC,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;YACrC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,KAAK,EAAE,CAAC,YAAY,CAAC;YACrB,UAAU,EAAE,OAAO,CAAC,aAAa;YACjC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE;SAC3B,CAAC,CAAC;QAEH,kCAAkC;QAClC,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YACpD,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAErD,cAAc;IACd,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAClC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,MAAuB,EAAE,WAAmB;IAChE,MAAM,QAAQ,GAAiB;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,WAAW;QACpB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,YAAY,EAAE,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;KAC7C,CAAC;IAEF,gDAAgD;IAChD,QAAQ,CAAC,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAE7C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,MAAc;IACrC,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAEhD,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,IAAI,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IACrD,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAE7C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,2DAA2D;IAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACtD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;IAEnC,8BAA8B;IAC9B,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC9C,MAAM,WAAW,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;YAChD,IAAI,WAAW,CAAC,KAAK;gBAAE,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}

View File

@@ -1,19 +0,0 @@
export interface StackResult {
library: string;
version?: string;
purpose?: string;
project: string;
phase?: string;
source: string;
sourceType: 'summary' | 'research';
}
export interface StackOptions {
query?: string;
project?: string;
limit?: number;
}
/**
* Search for tech stack entries across all registered GSD projects
*/
export declare function findStack(options: StackOptions): Promise<StackResult[]>;
//# sourceMappingURL=stack.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../../src/tools/stack.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,SAAS,GAAG,UAAU,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA0C7E"}

View File

@@ -1,137 +0,0 @@
import { getAllProjects } from '../registry.js';
import { extractSummary } from '../extractors/summary.js';
import { extractResearch } from '../extractors/research.js';
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
/**
* Search for tech stack entries across all registered GSD projects
*/
export async function findStack(options) {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
const allStack = [];
for (const project of projects) {
const planningPath = join(project.path, '.planning');
if (!existsSync(planningPath))
continue;
// Extract from SUMMARY.md files
const summaryStack = extractFromSummaries(planningPath, project.name);
allStack.push(...summaryStack);
// Extract from RESEARCH.md files
const researchStack = extractFromResearch(planningPath, project.name);
allStack.push(...researchStack);
}
// Filter by query if provided
let filtered = allStack;
if (options.query) {
const query = options.query.toLowerCase();
filtered = allStack.filter(s => s.library.toLowerCase().includes(query) ||
s.purpose?.toLowerCase().includes(query));
}
// Deduplicate by library name within each project
const seen = new Set();
const deduplicated = filtered.filter(s => {
const key = `${s.project}:${s.library}`;
if (seen.has(key))
return false;
seen.add(key);
return true;
});
// Apply limit
const limit = options.limit || 100;
return deduplicated.slice(0, limit);
}
/**
* Extract tech stack from all SUMMARY.md files
*/
function extractFromSummaries(planningPath, projectName) {
const stack = [];
const phasesPath = join(planningPath, 'phases');
if (!existsSync(phasesPath))
return stack;
const summaryFiles = findFiles(phasesPath, 'SUMMARY');
for (const file of summaryFiles) {
try {
const content = readFileSync(file, 'utf-8');
const summaryData = extractSummary(content);
// Extract phase from path
const phaseMatch = file.match(/phases\/(\d+-[^/]+)/);
const phase = phaseMatch ? phaseMatch[1] : undefined;
for (const entry of summaryData.techStack) {
stack.push({
library: entry.library,
version: entry.version,
purpose: entry.purpose,
project: projectName,
phase,
source: file,
sourceType: 'summary'
});
}
}
catch {
// Skip files that can't be parsed
}
}
return stack;
}
/**
* Extract tech stack from all RESEARCH.md files
*/
function extractFromResearch(planningPath, projectName) {
const stack = [];
const researchFiles = findFiles(planningPath, 'RESEARCH');
for (const file of researchFiles) {
try {
const content = readFileSync(file, 'utf-8');
const researchData = extractResearch(content);
// Extract phase from path
const phaseMatch = file.match(/phases\/(\d+-[^/]+)/);
const phase = phaseMatch ? phaseMatch[1] : undefined;
for (const entry of researchData.standardStack) {
stack.push({
library: entry.library,
version: entry.version,
purpose: entry.purpose,
project: projectName,
phase,
source: file,
sourceType: 'research'
});
}
}
catch {
// Skip files that can't be parsed
}
}
return stack;
}
/**
* Recursively find files matching a pattern
*/
function findFiles(dir, pattern, files = []) {
try {
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
try {
const stat = statSync(fullPath);
if (stat.isDirectory() && !entry.startsWith('.')) {
findFiles(fullPath, pattern, files);
}
else if (entry.toUpperCase().includes(pattern) && 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=stack.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../../src/tools/stack.ts"],"names":[],"mappings":"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"}

View File

@@ -1,23 +0,0 @@
export interface ProjectStatus {
name: string;
path: string;
exists: boolean;
hasPlanningDir: boolean;
qmdCollection?: string;
qmdAvailable: boolean;
documentCount: number;
registeredAt: string;
lastIndexed?: string;
}
export interface MemoryStatus {
qmdAvailable: boolean;
qmdVersion?: string;
totalProjects: number;
totalDocuments: number;
projects: ProjectStatus[];
}
/**
* Get comprehensive status of GSD memory system
*/
export declare function getStatus(): Promise<MemoryStatus>;
//# sourceMappingURL=status.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CAmCvD"}

View File

@@ -1,67 +0,0 @@
import { getAllProjects } from '../registry.js';
import { qmd } from '../qmd.js';
import { existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
/**
* Get comprehensive status of GSD memory system
*/
export async function getStatus() {
const qmdAvailable = await qmd.isAvailable();
const projects = getAllProjects();
let totalDocuments = 0;
const projectStatuses = [];
for (const project of projects) {
const planningPath = join(project.path, '.planning');
const exists = existsSync(project.path);
const hasPlanningDir = existsSync(planningPath);
// Count documents in .planning directory
const documentCount = hasPlanningDir ? countMarkdownFiles(planningPath) : 0;
totalDocuments += documentCount;
projectStatuses.push({
name: project.name,
path: project.path,
exists,
hasPlanningDir,
qmdCollection: project.qmdCollection,
qmdAvailable: qmdAvailable && !!project.qmdCollection,
documentCount,
registeredAt: project.registeredAt,
lastIndexed: project.lastIndexed
});
}
return {
qmdAvailable,
totalProjects: projects.length,
totalDocuments,
projects: projectStatuses
};
}
/**
* Count markdown files in a directory recursively
*/
function countMarkdownFiles(dir) {
let count = 0;
try {
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
try {
const stat = statSync(fullPath);
if (stat.isDirectory() && !entry.startsWith('.')) {
count += countMarkdownFiles(fullPath);
}
else if (stat.isFile() && entry.endsWith('.md')) {
count++;
}
}
catch {
// Skip entries that can't be accessed
}
}
}
catch {
// Return 0 if directory can't be read
}
return count;
}
//# sourceMappingURL=status.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAsB5B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;IAElC,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,MAAM,eAAe,GAAoB,EAAE,CAAC;IAE5C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,cAAc,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;QAEhD,yCAAyC;QACzC,MAAM,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,cAAc,IAAI,aAAa,CAAC;QAEhC,eAAe,CAAC,IAAI,CAAC;YACnB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM;YACN,cAAc;YACd,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,YAAY,EAAE,YAAY,IAAI,CAAC,CAAC,OAAO,CAAC,aAAa;YACrD,aAAa;YACb,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,YAAY;QACZ,aAAa,EAAE,QAAQ,CAAC,MAAM;QAC9B,cAAc;QACd,QAAQ,EAAE,eAAe;KAC1B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,GAAW;IACrC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAEjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAEhC,IAAI,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACjD,KAAK,IAAI,kBAAkB,CAAC,QAAQ,CAAC,CAAC;gBACxC,CAAC;qBAAM,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBAClD,KAAK,EAAE,CAAC;gBACV,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,sCAAsC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +0,0 @@
{
"name": "gsd-memory",
"version": "1.0.0",
"description": "GSD Memory - Cross-project knowledge system MCP server",
"type": "module",
"main": "dist/index.js",
"bin": {
"gsd-memory": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run",
"test:watch": "vitest",
"test:integration": "vitest run --config vitest.integration.config.ts",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"gray-matter": "^4.0.3",
"yaml": "^2.3.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@@ -1,36 +0,0 @@
import matter from 'gray-matter';
/**
* Extract YAML frontmatter from markdown content
* Returns empty object if no frontmatter or invalid YAML
*/
export function extractFrontmatter(markdown: string): Record<string, unknown> {
try {
const { data } = matter(markdown);
return data || {};
} catch {
// Return empty object on parse errors
return {};
}
}
/**
* Extract frontmatter and content separately
*/
export function parseFrontmatter(markdown: string): {
frontmatter: Record<string, unknown>;
content: string;
} {
try {
const { data, content } = matter(markdown);
return {
frontmatter: data || {},
content: content.trim()
};
} catch {
return {
frontmatter: {},
content: markdown.trim()
};
}
}

View File

@@ -1,4 +0,0 @@
export { extractFrontmatter, parseFrontmatter } from './frontmatter.js';
export { extractSummary, type SummaryData, type TechStackEntry, type Decision as SummaryDecision } from './summary.js';
export { extractResearch, type ResearchData, type Pitfall, type DontHandRoll } from './research.js';
export { extractProject, type ProjectData, type Decision as ProjectDecision, type Constraint } from './project.js';

View File

@@ -1,193 +0,0 @@
export interface OutOfScopeItem {
item: string;
reason: string;
}
export interface Constraint {
type: string;
value: string;
why?: string;
}
export interface Decision {
decision: string;
rationale: string;
outcome: string;
}
export interface Requirements {
validated: string[];
active: string[];
outOfScope: OutOfScopeItem[];
}
export interface ProjectData {
name: string;
description: string;
coreValue: string;
requirements: Requirements;
context: string;
constraints: Constraint[];
decisions: Decision[];
}
/**
* Extract structured data from a PROJECT.md file
*/
export function extractProject(markdown: string): ProjectData {
return {
name: extractName(markdown),
description: extractSection(markdown, 'What This Is'),
coreValue: extractSection(markdown, 'Core Value'),
requirements: extractRequirements(markdown),
context: extractSection(markdown, 'Context'),
constraints: extractConstraints(markdown),
decisions: extractDecisions(markdown)
};
}
function extractName(content: string): string {
// First line should be # ProjectName
const match = content.match(/^#\s+(.+)/m);
return match ? match[1].trim() : '';
}
function extractSection(content: string, sectionName: string): string {
// Find the section and extract content until next ## heading
const regex = new RegExp(`##\\s+${sectionName}\\n\\n([\\s\\S]*?)(?=\\n##|$)`, 'i');
const match = content.match(regex);
return match ? match[1].trim() : '';
}
function extractRequirements(content: string): Requirements {
const requirementsSection = content.match(/##\s+Requirements\n([\s\S]*?)(?=\n##[^#]|$)/i);
if (!requirementsSection) {
return { validated: [], active: [], outOfScope: [] };
}
const section = requirementsSection[1];
return {
validated: extractValidatedRequirements(section),
active: extractActiveRequirements(section),
outOfScope: extractOutOfScope(section)
};
}
function extractValidatedRequirements(section: string): string[] {
const requirements: string[] = [];
// Look for ### Validated subsection
const validatedSection = section.match(/###\s+Validated\n([\s\S]*?)(?=###|$)/i);
if (!validatedSection) return requirements;
// Extract checkmark items: - ✓ requirement — version
const matches = validatedSection[1].match(/^-\s+✓\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+✓\s+/, '').trim();
requirements.push(content);
}
}
return requirements;
}
function extractActiveRequirements(section: string): string[] {
const requirements: string[] = [];
// Look for ### Active subsection
const activeSection = section.match(/###\s+Active\n([\s\S]*?)(?=###|$)/i);
if (!activeSection) return requirements;
// Extract checkbox items: - [ ] requirement
const matches = activeSection[1].match(/^-\s+\[\s*\]\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+\[\s*\]\s+/, '').trim();
requirements.push(content);
}
}
return requirements;
}
function extractOutOfScope(section: string): OutOfScopeItem[] {
const items: OutOfScopeItem[] = [];
// Look for ### Out of Scope subsection
const outOfScopeSection = section.match(/###\s+Out of Scope\n([\s\S]*?)(?=###|$)/i);
if (!outOfScopeSection) return items;
// Extract items: - Item — reason
const matches = outOfScopeSection[1].match(/^-\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+/, '').trim();
// Split by em-dash or double hyphen
const parts = content.split(/\s+—\s+|\s+--\s+/);
items.push({
item: parts[0].trim(),
reason: parts[1]?.trim() || ''
});
}
}
return items;
}
function extractConstraints(content: string): Constraint[] {
const constraints: Constraint[] = [];
const constraintsSection = content.match(/##\s+Constraints\n([\s\S]*?)(?=\n##|$)/i);
if (!constraintsSection) return constraints;
// Extract items: - **Type**: Value — Why
const matches = constraintsSection[1].match(/^-\s+\*\*([^*]+)\*\*:\s*(.+)/gm);
if (matches) {
for (const match of matches) {
const typeMatch = match.match(/\*\*([^*]+)\*\*/);
const valueMatch = match.match(/\*\*[^*]+\*\*:\s*(.+)/);
if (typeMatch && valueMatch) {
const valueParts = valueMatch[1].split(/\s+—\s+|\s+--\s+/);
constraints.push({
type: typeMatch[1].trim(),
value: valueParts[0].trim(),
why: valueParts[1]?.trim()
});
}
}
}
return constraints;
}
function extractDecisions(content: string): Decision[] {
const decisions: Decision[] = [];
const decisionsSection = content.match(/##\s+Key Decisions\n([\s\S]*?)(?=\n##[^#]|\n---|$)/i);
if (!decisionsSection) return decisions;
// Parse markdown table rows - match rows with 3+ cells
const lines = decisionsSection[1].split('\n');
for (const line of lines) {
// Must be a table row (starts and ends with |)
if (!line.trim().startsWith('|')) continue;
// Skip header row (contains 'Decision') and separator row (contains ---)
if (line.includes('Decision') || line.includes('---')) continue;
const cells = line.split('|').filter(c => c.trim());
if (cells.length >= 3) {
decisions.push({
decision: cells[0].trim(),
rationale: cells[1].trim(),
outcome: cells[2].trim()
});
}
}
return decisions;
}

View File

@@ -1,250 +0,0 @@
import { parseFrontmatter } from './frontmatter.js';
export interface StackEntry {
library: string;
version?: string;
purpose: string;
whyStandard?: string;
}
export interface Pitfall {
name: string;
description: string;
cause?: string;
prevention: string;
warningSigns?: string;
}
export interface DontHandRoll {
problem: string;
dontBuild: string;
useInstead: string;
why: string;
}
export interface UserConstraints {
locked: string[];
discretion: string[];
deferred: string[];
}
export interface ResearchData {
// Metadata
domain: string;
confidence: string;
researched: string;
// Extracted sections
standardStack: StackEntry[];
pitfalls: Pitfall[];
dontHandRoll: DontHandRoll[];
antiPatterns: string[];
userConstraints: UserConstraints;
primaryRecommendation: string;
}
/**
* Extract structured data from a RESEARCH.md file
*/
export function extractResearch(markdown: string): ResearchData {
const { frontmatter, content } = parseFrontmatter(markdown);
return {
// Metadata from frontmatter
domain: (frontmatter.domain as string) || '',
confidence: (frontmatter.confidence as string) || '',
researched: formatDate(frontmatter.researched),
// Extracted from content
standardStack: extractStandardStack(content),
pitfalls: extractPitfalls(content),
dontHandRoll: extractDontHandRoll(content),
antiPatterns: extractAntiPatterns(content),
userConstraints: extractUserConstraints(content),
primaryRecommendation: extractPrimaryRecommendation(content)
};
}
function formatDate(date: unknown): string {
if (!date) return '';
if (date instanceof Date) {
return date.toISOString().split('T')[0];
}
return String(date);
}
function extractStandardStack(content: string): StackEntry[] {
const entries: StackEntry[] = [];
// Look for standard_stack section and parse markdown tables
const stackSection = extractSection(content, 'standard_stack');
if (!stackSection) return entries;
// Parse Core and Supporting tables
const tableRows = stackSection.match(/\|[^|]+\|[^|]+\|[^|]+\|[^|]*\|/g);
if (!tableRows) return entries;
for (const row of tableRows) {
// Skip header and separator rows
if (row.includes('Library') || row.includes('---')) continue;
const cells = row.split('|').filter(c => c.trim());
if (cells.length >= 3) {
entries.push({
library: cells[0].trim(),
version: cells[1]?.trim(),
purpose: cells[2]?.trim(),
whyStandard: cells[3]?.trim()
});
}
}
return entries;
}
function extractPitfalls(content: string): Pitfall[] {
const pitfalls: Pitfall[] = [];
const pitfallSection = extractSection(content, 'common_pitfalls');
if (!pitfallSection) return pitfalls;
// Parse individual pitfall blocks (### Pitfall N: Name)
const pitfallBlocks = pitfallSection.split(/###\s+Pitfall\s+\d+:\s+/);
for (const block of pitfallBlocks) {
if (!block.trim()) continue;
const lines = block.split('\n');
const name = lines[0]?.trim();
if (!name) continue;
const description = extractField(block, 'What goes wrong');
const cause = extractField(block, 'Why it happens');
const prevention = extractField(block, 'How to avoid');
const warningSigns = extractField(block, 'Warning signs');
if (description || prevention) {
pitfalls.push({
name,
description: description || '',
cause,
prevention: prevention || '',
warningSigns
});
}
}
return pitfalls;
}
function extractDontHandRoll(content: string): DontHandRoll[] {
const items: DontHandRoll[] = [];
const section = extractSection(content, 'dont_hand_roll');
if (!section) return items;
// Parse table rows
const tableRows = section.match(/\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|/g);
if (!tableRows) return items;
for (const row of tableRows) {
if (row.includes('Problem') || row.includes('---')) continue;
const cells = row.split('|').filter(c => c.trim());
if (cells.length >= 4) {
items.push({
problem: cells[0].trim(),
dontBuild: cells[1].trim(),
useInstead: cells[2].trim(),
why: cells[3].trim()
});
}
}
return items;
}
function extractAntiPatterns(content: string): string[] {
const patterns: string[] = [];
const archSection = extractSection(content, 'architecture_patterns');
if (!archSection) return patterns;
// Look for "Anti-Patterns to Avoid" subsection
const antiMatch = archSection.match(/###\s+Anti-Patterns to Avoid\n([\s\S]*?)(?=###|$)/);
if (!antiMatch) return patterns;
// Extract bullet points
const bullets = antiMatch[1].match(/^-\s+\*\*([^*]+)\*\*/gm);
if (bullets) {
for (const bullet of bullets) {
const match = bullet.match(/\*\*([^*]+)\*\*/);
if (match) patterns.push(match[1].trim());
}
}
return patterns;
}
function extractUserConstraints(content: string): UserConstraints {
const constraints: UserConstraints = {
locked: [],
discretion: [],
deferred: []
};
const section = extractSection(content, 'user_constraints');
if (!section) return constraints;
// Extract locked decisions
const lockedMatch = section.match(/###\s+Locked Decisions\n([\s\S]*?)(?=###|$)/);
if (lockedMatch) {
constraints.locked = extractBulletPoints(lockedMatch[1]);
}
// Extract Claude's discretion
const discretionMatch = section.match(/###\s+Claude's Discretion\n([\s\S]*?)(?=###|$)/);
if (discretionMatch) {
constraints.discretion = extractBulletPoints(discretionMatch[1]);
}
// Extract deferred items
const deferredMatch = section.match(/###\s+Deferred Ideas[^#]*\n([\s\S]*?)(?=<\/|$)/);
if (deferredMatch) {
constraints.deferred = extractBulletPoints(deferredMatch[1]);
}
return constraints;
}
function extractPrimaryRecommendation(content: string): string {
// Look for "Primary recommendation:" in summary section
const match = content.match(/\*\*Primary recommendation:\*\*\s*([^\n]+)/);
return match ? match[1].trim() : '';
}
// Helper functions
function extractSection(content: string, sectionTag: string): string | null {
const regex = new RegExp(`<${sectionTag}>([\\s\\S]*?)<\\/${sectionTag}>`, 'i');
const match = content.match(regex);
return match ? match[1] : null;
}
function extractField(block: string, fieldName: string): string | undefined {
const regex = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*([^\\n]+)`, 'i');
const match = block.match(regex);
return match ? match[1].trim() : undefined;
}
function extractBulletPoints(text: string): string[] {
const points: string[] = [];
const matches = text.match(/^-\s+(.+)/gm);
if (matches) {
for (const match of matches) {
const content = match.replace(/^-\s+/, '').trim();
if (content) points.push(content);
}
}
return points;
}

View File

@@ -1,155 +0,0 @@
import { parseFrontmatter } from './frontmatter.js';
export interface TechStackEntry {
library: string;
version: string;
purpose?: string;
}
export interface Dependency {
phase: string;
provides: string;
}
export interface KeyFiles {
created: string[];
modified: string[];
}
export interface Decision {
decision: string;
source?: string;
}
export interface SummaryData {
// Identifiers
phase: string;
plan: number | string;
subsystem: string;
tags: string[];
// Dependency graph
requires: Dependency[];
provides: string[];
affects: string[];
// Tech tracking
techStack: TechStackEntry[];
patterns: string[];
// Files
keyFiles: KeyFiles;
// Decisions
decisions: Decision[];
// Metadata
duration: string;
completed: string;
// Content
oneLiner: string;
}
/**
* Extract structured data from a SUMMARY.md file
*/
export function extractSummary(markdown: string): SummaryData {
const { frontmatter, content } = parseFrontmatter(markdown);
return {
// Identifiers
phase: (frontmatter.phase as string) || '',
plan: (frontmatter.plan as number | string) || '',
subsystem: (frontmatter.subsystem as string) || '',
tags: (frontmatter.tags as string[]) || [],
// Dependency graph
requires: extractRequires(frontmatter.requires),
provides: (frontmatter.provides as string[]) || [],
affects: (frontmatter.affects as string[]) || [],
// Tech tracking
techStack: extractTechStack(frontmatter['tech-stack']),
patterns: (frontmatter['patterns-established'] as string[]) || [],
// Files
keyFiles: extractKeyFiles(frontmatter['key-files']),
// Decisions
decisions: extractDecisions(frontmatter['key-decisions']),
// Metadata
duration: (frontmatter.duration as string) || '',
completed: formatCompleted(frontmatter.completed),
// Content
oneLiner: extractOneLiner(content)
};
}
function extractRequires(requires: unknown): Dependency[] {
if (!Array.isArray(requires)) return [];
return requires.map((req) => ({
phase: (req as Record<string, unknown>).phase as string || '',
provides: (req as Record<string, unknown>).provides as string || ''
}));
}
function extractTechStack(techStack: unknown): TechStackEntry[] {
if (!techStack || typeof techStack !== 'object') return [];
const ts = techStack as Record<string, unknown>;
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: unknown): KeyFiles {
if (!keyFiles || typeof keyFiles !== 'object') {
return { created: [], modified: [] };
}
const kf = keyFiles as Record<string, unknown>;
return {
created: (kf.created as string[]) || [],
modified: (kf.modified as string[]) || []
};
}
function extractDecisions(decisions: unknown): Decision[] {
if (!Array.isArray(decisions)) return [];
return decisions.map((d) => ({
decision: String(d),
source: 'frontmatter'
}));
}
function formatCompleted(completed: unknown): string {
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: string): string {
// 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() : '';
}

View File

@@ -1,321 +0,0 @@
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type Tool
} from '@modelcontextprotocol/sdk/types.js';
import { search } from './tools/search.js';
import { findDecisions } from './tools/decisions.js';
import { findPatterns } from './tools/patterns.js';
import { findPitfalls } from './tools/pitfalls.js';
import { findStack } from './tools/stack.js';
import { register, status as registerStatus, unregister } from './tools/register.js';
import { index, indexAll } from './tools/index-tool.js';
import { getStatus } from './tools/status.js';
// Define available tools
const tools: Tool[] = [
{
name: 'gsd_memory_search',
description: 'Search across all registered GSD projects for content matching a query. Returns enriched results with project, phase, and document type context.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query'
},
project: {
type: 'string',
description: 'Optional: limit search to a specific project'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 20)'
}
},
required: ['query']
}
},
{
name: 'gsd_memory_decisions',
description: 'Find decisions made across GSD projects. Searches KEY-DECISIONS from SUMMARY.md frontmatter and Key Decisions tables from PROJECT.md.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter decisions by keyword'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Optional: filter by tags'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
}
}
}
},
{
name: 'gsd_memory_patterns',
description: 'Find patterns established across GSD projects. Extracts PATTERNS-ESTABLISHED from SUMMARY.md frontmatter.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter patterns by keyword'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Optional: filter by tags'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
}
}
}
},
{
name: 'gsd_memory_pitfalls',
description: 'Find documented pitfalls from RESEARCH.md files. Includes what went wrong, causes, and prevention strategies.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter pitfalls by keyword'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
domain: {
type: 'string',
description: 'Optional: filter by technology domain'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
}
}
}
},
{
name: 'gsd_memory_stack',
description: 'Find tech stack entries across GSD projects. Extracts from SUMMARY.md (tech-stack.added) and RESEARCH.md (standard stack tables).',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional: filter by library name or purpose'
},
project: {
type: 'string',
description: 'Optional: limit to a specific project'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 100)'
}
}
}
},
{
name: 'gsd_memory_register',
description: 'Register a project with GSD memory. Creates a QMD collection if QMD is available.',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the project root (must contain .planning directory)'
},
name: {
type: 'string',
description: 'Optional: project name (defaults to directory name)'
}
},
required: ['path']
}
},
{
name: 'gsd_memory_index',
description: 'Trigger indexing/update for a project. Updates QMD collection if available, otherwise marks project as indexed for grep fallback.',
inputSchema: {
type: 'object',
properties: {
project: {
type: 'string',
description: 'Project name to index'
},
path: {
type: 'string',
description: 'Alternative: project path to index'
}
}
}
},
{
name: 'gsd_memory_status',
description: 'Show status of GSD memory system: QMD availability, registered projects, document counts.',
inputSchema: {
type: 'object',
properties: {}
}
}
];
// Create server instance
const server = new Server(
{
name: 'gsd-memory',
version: '1.0.0'
},
{
capabilities: {
tools: {}
}
}
);
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result: unknown;
switch (name) {
case 'gsd_memory_search':
result = await search({
query: args?.query as string,
project: args?.project as string | undefined,
limit: args?.limit as number | undefined
});
break;
case 'gsd_memory_decisions':
result = await findDecisions({
query: args?.query as string | undefined,
project: args?.project as string | undefined,
tags: args?.tags as string[] | undefined,
limit: args?.limit as number | undefined
});
break;
case 'gsd_memory_patterns':
result = await findPatterns({
query: args?.query as string | undefined,
project: args?.project as string | undefined,
tags: args?.tags as string[] | undefined,
limit: args?.limit as number | undefined
});
break;
case 'gsd_memory_pitfalls':
result = await findPitfalls({
query: args?.query as string | undefined,
project: args?.project as string | undefined,
domain: args?.domain as string | undefined,
limit: args?.limit as number | undefined
});
break;
case 'gsd_memory_stack':
result = await findStack({
query: args?.query as string | undefined,
project: args?.project as string | undefined,
limit: args?.limit as number | undefined
});
break;
case 'gsd_memory_register':
result = await register({
path: args?.path as string,
name: args?.name as string | undefined
});
break;
case 'gsd_memory_index':
if (args?.project || args?.path) {
result = await index({
project: args?.project as string | undefined,
path: args?.path as string | undefined
});
} else {
result = await indexAll();
}
break;
case 'gsd_memory_status':
result = await getStatus();
break;
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${name}`
}
],
isError: true
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`
}
],
isError: true
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('GSD Memory MCP server started');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

View File

@@ -1,255 +0,0 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { readFileSync, readdirSync, statSync } from 'fs';
import { join, relative } from 'path';
const execAsync = promisify(exec);
export interface QmdSearchResult {
content: string;
source: string;
relevance: number;
context?: string;
}
export interface QmdSearchOptions {
query: string;
paths: string[];
limit?: number;
collection?: string;
}
export interface QmdCollectionOptions {
name: string;
paths: string[];
context?: string;
}
export interface QmdResult {
success: boolean;
reason?: string;
data?: unknown;
}
export interface QmdStatus {
available: boolean;
collection?: string;
documentCount?: number;
lastIndexed?: string;
}
/**
* QMD wrapper - provides semantic search when QMD is available,
* falls back to grep-based search otherwise
*/
export const qmd = {
/**
* Check if QMD is installed and available
*/
async isAvailable(): Promise<boolean> {
try {
await execAsync('which qmd');
return true;
} catch {
return false;
}
},
/**
* Search for content using QMD or grep fallback
*/
async search(options: QmdSearchOptions): Promise<QmdSearchResult[]> {
const available = await this.isAvailable();
if (available && options.collection) {
return this.qmdSearch(options);
}
return this.grepFallback(options);
},
/**
* Search using QMD (when available)
*/
async qmdSearch(options: QmdSearchOptions): Promise<QmdSearchResult[]> {
try {
const limit = options.limit || 10;
const { stdout } = await execAsync(
`qmd query "${options.query}" --collection "${options.collection}" --limit ${limit} --json`
);
const parsed = JSON.parse(stdout);
return (parsed.results || []).map((r: { content: string; source: string; score: number }) => ({
content: r.content,
source: r.source,
relevance: r.score || 0.5,
context: 'qmd'
}));
} catch {
// Fall back to grep if QMD search fails
return this.grepFallback(options);
}
},
/**
* Grep-based fallback search
*/
async grepFallback(options: QmdSearchOptions): Promise<QmdSearchResult[]> {
const results: QmdSearchResult[] = [];
const limit = options.limit || 20;
for (const searchPath of options.paths) {
try {
const files = this.findMarkdownFiles(searchPath);
for (const file of files) {
try {
const content = readFileSync(file, 'utf-8');
const query = options.query.toLowerCase();
// Simple relevance: count matches
const matches = (content.toLowerCase().match(new RegExp(query, 'g')) || []).length;
if (matches > 0) {
// Extract context around first match
const lowerContent = content.toLowerCase();
const matchIndex = lowerContent.indexOf(query);
const start = Math.max(0, matchIndex - 100);
const end = Math.min(content.length, matchIndex + query.length + 100);
const snippet = content.slice(start, end);
results.push({
content: snippet,
source: file,
relevance: Math.min(1, matches * 0.1),
context: 'grep'
});
}
} catch {
// Skip files that can't be read
}
}
} catch {
// Skip paths that can't be accessed
}
}
// Sort by relevance and limit
return results
.sort((a, b) => b.relevance - a.relevance)
.slice(0, limit);
},
/**
* Find all markdown files in a directory recursively
*/
findMarkdownFiles(dir: string, files: string[] = []): string[] {
try {
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
try {
const stat = statSync(fullPath);
if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
this.findMarkdownFiles(fullPath, files);
} else if (stat.isFile() && 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;
},
/**
* Create a QMD collection for a project
*/
async createCollection(options: QmdCollectionOptions): Promise<QmdResult> {
const available = await this.isAvailable();
if (!available) {
return {
success: false,
reason: 'qmd_not_available'
};
}
try {
const paths = options.paths.join(' ');
const contextArg = options.context ? `--context "${options.context}"` : '';
await execAsync(
`qmd add "${options.name}" ${paths} ${contextArg}`
);
return { success: true };
} catch (error) {
return {
success: false,
reason: 'create_failed',
data: error instanceof Error ? error.message : String(error)
};
}
},
/**
* Trigger reindexing of a collection
*/
async index(collectionName: string): Promise<QmdResult> {
const available = await this.isAvailable();
if (!available) {
return {
success: false,
reason: 'qmd_not_available'
};
}
try {
await execAsync(`qmd update "${collectionName}"`);
return { success: true };
} catch (error) {
return {
success: false,
reason: 'index_failed',
data: error instanceof Error ? error.message : String(error)
};
}
},
/**
* Get status of a collection
*/
async status(collectionName: string): Promise<QmdStatus> {
const available = await this.isAvailable();
if (!available) {
return { available: false };
}
try {
const { stdout } = await execAsync(`qmd status "${collectionName}" --json`);
const parsed = JSON.parse(stdout);
return {
available: true,
collection: collectionName,
documentCount: parsed.documentCount || 0,
lastIndexed: parsed.lastIndexed
};
} catch {
return {
available: true,
collection: collectionName,
documentCount: 0
};
}
}
};

View File

@@ -1,137 +0,0 @@
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
export interface RegisteredProject {
name: string;
path: string;
qmdCollection?: string;
registeredAt: string;
lastIndexed?: string;
}
export interface Registry {
version: string;
projects: RegisteredProject[];
}
const REGISTRY_DIR = join(homedir(), '.gsd');
const REGISTRY_PATH = join(REGISTRY_DIR, 'projects.json');
/**
* Ensure the registry directory exists
*/
function ensureRegistryDir(): void {
if (!existsSync(REGISTRY_DIR)) {
mkdirSync(REGISTRY_DIR, { recursive: true });
}
}
/**
* Read the project registry
*/
export function readRegistry(): Registry {
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: Registry): void {
ensureRegistryDir();
writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
}
/**
* Register a new project
*/
export function registerProject(project: Omit<RegisteredProject, 'registeredAt'>): RegisteredProject {
const registry = readRegistry();
// Check if project already exists
const existingIndex = registry.projects.findIndex(
p => p.path === project.path || p.name === project.name
);
const newProject: RegisteredProject = {
...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: string): void {
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: string): RegisteredProject | undefined {
const registry = readRegistry();
return registry.projects.find(p => p.name === name);
}
/**
* Get all registered projects
*/
export function getAllProjects(): RegisteredProject[] {
return readRegistry().projects;
}
/**
* Remove a project from the registry
*/
export function unregisterProject(name: string): boolean {
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(): string {
return REGISTRY_PATH;
}

View File

@@ -1,153 +0,0 @@
import { getAllProjects } from '../registry.js';
import { extractSummary, type SummaryData } from '../extractors/summary.js';
import { extractProject, type ProjectData } from '../extractors/project.js';
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, basename } from 'path';
export interface DecisionResult {
decision: string;
rationale?: string;
project: string;
phase?: string;
source: string;
date?: string;
tags?: string[];
}
export interface DecisionOptions {
query?: string;
project?: string;
tags?: string[];
limit?: number;
}
/**
* Search for decisions across all registered GSD projects
*/
export async function findDecisions(options: DecisionOptions): Promise<DecisionResult[]> {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
const allDecisions: DecisionResult[] = [];
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: string, projectName: string): DecisionResult[] {
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: string, projectName: string): DecisionResult[] {
const decisions: DecisionResult[] = [];
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: string, files: string[] = []): string[] {
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;
}

View File

@@ -1,133 +0,0 @@
import { getProject, updateProjectIndexTime, getAllProjects } from '../registry.js';
import { qmd } from '../qmd.js';
import { existsSync } from 'fs';
import { join } from 'path';
export interface IndexOptions {
path?: string;
project?: string;
}
export interface IndexResult {
success: boolean;
project?: string;
documentsIndexed?: number;
error?: string;
qmdUsed: boolean;
}
/**
* Trigger indexing/update for a project
*/
export async function index(options: IndexOptions): Promise<IndexResult> {
// Find the project
let projectName: string | undefined;
let projectPath: string | undefined;
let qmdCollection: string | undefined;
if (options.project) {
const project = getProject(options.project);
if (!project) {
return {
success: false,
error: `Project not found: ${options.project}`,
qmdUsed: false
};
}
projectName = project.name;
projectPath = project.path;
qmdCollection = project.qmdCollection;
} else if (options.path) {
// Find project by path
const projects = getAllProjects();
const project = projects.find(p => p.path === options.path);
if (!project) {
return {
success: false,
error: `No project registered for path: ${options.path}`,
qmdUsed: false
};
}
projectName = project.name;
projectPath = project.path;
qmdCollection = project.qmdCollection;
} else {
return {
success: false,
error: 'Either project or path must be specified',
qmdUsed: false
};
}
// Verify planning directory exists
const planningPath = join(projectPath, '.planning');
if (!existsSync(planningPath)) {
return {
success: false,
project: projectName,
error: `No .planning directory found at: ${planningPath}`,
qmdUsed: false
};
}
// Check if QMD is available
const qmdAvailable = await qmd.isAvailable();
if (qmdAvailable && qmdCollection) {
// Use QMD to update the collection
const result = await qmd.index(qmdCollection);
if (result.success) {
updateProjectIndexTime(projectName);
// Get document count
const status = await qmd.status(qmdCollection);
return {
success: true,
project: projectName,
documentsIndexed: status.documentCount,
qmdUsed: true
};
} else {
return {
success: false,
project: projectName,
error: `QMD indexing failed: ${result.reason}`,
qmdUsed: true
};
}
} else {
// No QMD - just update the timestamp
// Grep fallback will search files directly
updateProjectIndexTime(projectName);
return {
success: true,
project: projectName,
qmdUsed: false
};
}
}
/**
* Index all registered projects
*/
export async function indexAll(): Promise<{
success: boolean;
results: IndexResult[];
}> {
const projects = getAllProjects();
const results: IndexResult[] = [];
for (const project of projects) {
const result = await index({ project: project.name });
results.push(result);
}
const success = results.every(r => r.success);
return { success, results };
}

View File

@@ -1,108 +0,0 @@
import { getAllProjects } from '../registry.js';
import { extractSummary } from '../extractors/summary.js';
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
export interface PatternResult {
pattern: string;
project: string;
phase: string;
source: string;
date?: string;
tags?: string[];
}
export interface PatternOptions {
query?: string;
project?: string;
tags?: string[];
limit?: number;
}
/**
* Search for patterns established across all registered GSD projects
*/
export async function findPatterns(options: PatternOptions): Promise<PatternResult[]> {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
const allPatterns: PatternResult[] = [];
for (const project of projects) {
const phasesPath = join(project.path, '.planning', 'phases');
if (!existsSync(phasesPath)) continue;
// Find all SUMMARY.md files and extract patterns
const summaryFiles = findSummaryFiles(phasesPath);
for (const file of summaryFiles) {
try {
const content = readFileSync(file, 'utf-8');
const summaryData = extractSummary(content);
for (const pattern of summaryData.patterns) {
allPatterns.push({
pattern,
project: project.name,
phase: summaryData.phase,
source: file,
date: summaryData.completed,
tags: summaryData.tags
});
}
} catch {
// Skip files that can't be parsed
}
}
}
// Filter by query if provided
let filtered = allPatterns;
if (options.query) {
const query = options.query.toLowerCase();
filtered = allPatterns.filter(p =>
p.pattern.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(p =>
p.tags?.some(t => tags.includes(t.toLowerCase()))
);
}
// Apply limit
const limit = options.limit || 50;
return filtered.slice(0, limit);
}
/**
* Recursively find all SUMMARY.md files
*/
function findSummaryFiles(dir: string, files: string[] = []): string[] {
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;
}

View File

@@ -1,111 +0,0 @@
import { getAllProjects } from '../registry.js';
import { extractResearch, type Pitfall } from '../extractors/research.js';
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
export interface PitfallResult extends Pitfall {
project: string;
phase?: string;
domain: string;
source: string;
}
export interface PitfallOptions {
query?: string;
project?: string;
domain?: string;
limit?: number;
}
/**
* Search for pitfalls documented across all registered GSD projects
*/
export async function findPitfalls(options: PitfallOptions): Promise<PitfallResult[]> {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
const allPitfalls: PitfallResult[] = [];
for (const project of projects) {
const planningPath = join(project.path, '.planning');
if (!existsSync(planningPath)) continue;
// Find all RESEARCH.md files
const researchFiles = findResearchFiles(planningPath);
for (const file of researchFiles) {
try {
const content = readFileSync(file, 'utf-8');
const researchData = extractResearch(content);
// Extract phase from path if present
const phaseMatch = file.match(/phases\/(\d+-[^/]+)/);
const phase = phaseMatch ? phaseMatch[1] : undefined;
for (const pitfall of researchData.pitfalls) {
allPitfalls.push({
...pitfall,
project: project.name,
phase,
domain: researchData.domain,
source: file
});
}
} catch {
// Skip files that can't be parsed
}
}
}
// Filter by query if provided
let filtered = allPitfalls;
if (options.query) {
const query = options.query.toLowerCase();
filtered = allPitfalls.filter(p =>
p.name.toLowerCase().includes(query) ||
p.description.toLowerCase().includes(query) ||
p.prevention.toLowerCase().includes(query)
);
}
// Filter by domain if provided
if (options.domain) {
const domain = options.domain.toLowerCase();
filtered = filtered.filter(p =>
p.domain.toLowerCase().includes(domain)
);
}
// Apply limit
const limit = options.limit || 50;
return filtered.slice(0, limit);
}
/**
* Recursively find all RESEARCH.md files
*/
function findResearchFiles(dir: string, files: string[] = []): string[] {
try {
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
try {
const stat = statSync(fullPath);
if (stat.isDirectory() && !entry.startsWith('.')) {
findResearchFiles(fullPath, files);
} else if (entry.toUpperCase().includes('RESEARCH') && 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;
}

View File

@@ -1,146 +0,0 @@
import { registerProject, getProject, getAllProjects, unregisterProject, type RegisteredProject } from '../registry.js';
import { qmd } from '../qmd.js';
import { existsSync } from 'fs';
import { join, basename } from 'path';
export interface RegisterOptions {
path: string;
name?: string;
}
export interface RegisterResult {
success: boolean;
project?: RegisteredProject;
qmdCollection?: string;
error?: string;
}
/**
* Register a project with GSD memory
*/
export async function register(options: RegisterOptions): Promise<RegisterResult> {
// 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: string | undefined;
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(): Promise<{
qmdAvailable: boolean;
projects: Array<{
name: string;
path: string;
qmdCollection?: string;
registeredAt: string;
lastIndexed?: string;
documentCount?: number;
}>;
}> {
const qmdAvailable = await qmd.isAvailable();
const projects = getAllProjects();
const enrichedProjects = await Promise.all(
projects.map(async (project) => {
let documentCount: number | undefined;
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: string): Promise<{ success: boolean; error?: string }> {
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 };
}

View File

@@ -1,117 +0,0 @@
import { qmd, QmdSearchResult } from '../qmd.js';
import { getAllProjects } from '../registry.js';
import { extractFrontmatter } from '../extractors/frontmatter.js';
import { readFileSync, existsSync } from 'fs';
import { join, basename, dirname } from 'path';
export interface SearchResult {
content: string;
source: string;
project: string;
phase?: string;
relevance: number;
documentType?: 'summary' | 'research' | 'project' | 'plan' | 'other';
}
export interface SearchOptions {
query: string;
project?: string;
tags?: string[];
limit?: number;
}
/**
* Search across all registered GSD projects
*/
export async function search(options: SearchOptions): Promise<SearchResult[]> {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
if (projects.length === 0) {
return [];
}
const allResults: SearchResult[] = [];
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: QmdSearchResult, projectName: string): SearchResult {
const enriched: SearchResult = {
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: string): SearchResult['documentType'] {
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: string): string | undefined {
// 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;
}

View File

@@ -1,171 +0,0 @@
import { getAllProjects } from '../registry.js';
import { extractSummary, type TechStackEntry } from '../extractors/summary.js';
import { extractResearch } from '../extractors/research.js';
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
export interface StackResult {
library: string;
version?: string;
purpose?: string;
project: string;
phase?: string;
source: string;
sourceType: 'summary' | 'research';
}
export interface StackOptions {
query?: string;
project?: string;
limit?: number;
}
/**
* Search for tech stack entries across all registered GSD projects
*/
export async function findStack(options: StackOptions): Promise<StackResult[]> {
const projects = options.project
? getAllProjects().filter(p => p.name === options.project)
: getAllProjects();
const allStack: StackResult[] = [];
for (const project of projects) {
const planningPath = join(project.path, '.planning');
if (!existsSync(planningPath)) continue;
// Extract from SUMMARY.md files
const summaryStack = extractFromSummaries(planningPath, project.name);
allStack.push(...summaryStack);
// Extract from RESEARCH.md files
const researchStack = extractFromResearch(planningPath, project.name);
allStack.push(...researchStack);
}
// Filter by query if provided
let filtered = allStack;
if (options.query) {
const query = options.query.toLowerCase();
filtered = allStack.filter(s =>
s.library.toLowerCase().includes(query) ||
s.purpose?.toLowerCase().includes(query)
);
}
// Deduplicate by library name within each project
const seen = new Set<string>();
const deduplicated = filtered.filter(s => {
const key = `${s.project}:${s.library}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
// Apply limit
const limit = options.limit || 100;
return deduplicated.slice(0, limit);
}
/**
* Extract tech stack from all SUMMARY.md files
*/
function extractFromSummaries(planningPath: string, projectName: string): StackResult[] {
const stack: StackResult[] = [];
const phasesPath = join(planningPath, 'phases');
if (!existsSync(phasesPath)) return stack;
const summaryFiles = findFiles(phasesPath, 'SUMMARY');
for (const file of summaryFiles) {
try {
const content = readFileSync(file, 'utf-8');
const summaryData = extractSummary(content);
// Extract phase from path
const phaseMatch = file.match(/phases\/(\d+-[^/]+)/);
const phase = phaseMatch ? phaseMatch[1] : undefined;
for (const entry of summaryData.techStack) {
stack.push({
library: entry.library,
version: entry.version,
purpose: entry.purpose,
project: projectName,
phase,
source: file,
sourceType: 'summary'
});
}
} catch {
// Skip files that can't be parsed
}
}
return stack;
}
/**
* Extract tech stack from all RESEARCH.md files
*/
function extractFromResearch(planningPath: string, projectName: string): StackResult[] {
const stack: StackResult[] = [];
const researchFiles = findFiles(planningPath, 'RESEARCH');
for (const file of researchFiles) {
try {
const content = readFileSync(file, 'utf-8');
const researchData = extractResearch(content);
// Extract phase from path
const phaseMatch = file.match(/phases\/(\d+-[^/]+)/);
const phase = phaseMatch ? phaseMatch[1] : undefined;
for (const entry of researchData.standardStack) {
stack.push({
library: entry.library,
version: entry.version,
purpose: entry.purpose,
project: projectName,
phase,
source: file,
sourceType: 'research'
});
}
} catch {
// Skip files that can't be parsed
}
}
return stack;
}
/**
* Recursively find files matching a pattern
*/
function findFiles(dir: string, pattern: string, files: string[] = []): string[] {
try {
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
try {
const stat = statSync(fullPath);
if (stat.isDirectory() && !entry.startsWith('.')) {
findFiles(fullPath, pattern, files);
} else if (entry.toUpperCase().includes(pattern) && 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;
}

View File

@@ -1,94 +0,0 @@
import { getAllProjects } from '../registry.js';
import { qmd } from '../qmd.js';
import { existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
export interface ProjectStatus {
name: string;
path: string;
exists: boolean;
hasPlanningDir: boolean;
qmdCollection?: string;
qmdAvailable: boolean;
documentCount: number;
registeredAt: string;
lastIndexed?: string;
}
export interface MemoryStatus {
qmdAvailable: boolean;
qmdVersion?: string;
totalProjects: number;
totalDocuments: number;
projects: ProjectStatus[];
}
/**
* Get comprehensive status of GSD memory system
*/
export async function getStatus(): Promise<MemoryStatus> {
const qmdAvailable = await qmd.isAvailable();
const projects = getAllProjects();
let totalDocuments = 0;
const projectStatuses: ProjectStatus[] = [];
for (const project of projects) {
const planningPath = join(project.path, '.planning');
const exists = existsSync(project.path);
const hasPlanningDir = existsSync(planningPath);
// Count documents in .planning directory
const documentCount = hasPlanningDir ? countMarkdownFiles(planningPath) : 0;
totalDocuments += documentCount;
projectStatuses.push({
name: project.name,
path: project.path,
exists,
hasPlanningDir,
qmdCollection: project.qmdCollection,
qmdAvailable: qmdAvailable && !!project.qmdCollection,
documentCount,
registeredAt: project.registeredAt,
lastIndexed: project.lastIndexed
});
}
return {
qmdAvailable,
totalProjects: projects.length,
totalDocuments,
projects: projectStatuses
};
}
/**
* Count markdown files in a directory recursively
*/
function countMarkdownFiles(dir: string): number {
let count = 0;
try {
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
try {
const stat = statSync(fullPath);
if (stat.isDirectory() && !entry.startsWith('.')) {
count += countMarkdownFiles(fullPath);
} else if (stat.isFile() && entry.endsWith('.md')) {
count++;
}
} catch {
// Skip entries that can't be accessed
}
}
} catch {
// Return 0 if directory can't be read
}
return count;
}

View File

@@ -1,141 +0,0 @@
import { describe, it, expect } from 'vitest';
import { extractFrontmatter } from '../../src/extractors/frontmatter.js';
import { readFixture } from '../setup.js';
describe('frontmatter extractor', () => {
it('extracts YAML frontmatter from markdown', () => {
const md = `---
phase: 01-foundation
plan: "01"
tags: [auth, jwt]
---
# Content`;
const result = extractFrontmatter(md);
expect(result.phase).toBe('01-foundation');
expect(result.plan).toBe('01');
expect(result.tags).toEqual(['auth', 'jwt']);
});
it('parses numeric plan values as numbers', () => {
const md = `---
phase: 01-foundation
plan: 01
---
# Content`;
const result = extractFrontmatter(md);
expect(result.plan).toBe(1); // YAML parses 01 as number
});
it('returns empty object for no frontmatter', () => {
const result = extractFrontmatter('# Just content');
expect(result).toEqual({});
});
it('returns empty object for markdown starting with heading', () => {
const md = `# Title
Some content here.`;
const result = extractFrontmatter(md);
expect(result).toEqual({});
});
it('handles malformed YAML gracefully', () => {
const md = `---
invalid: [unclosed
broken: yaml: here
---
# Content`;
expect(() => extractFrontmatter(md)).not.toThrow();
const result = extractFrontmatter(md);
expect(result).toEqual({});
});
it('handles empty frontmatter', () => {
const md = `---
---
# Content`;
const result = extractFrontmatter(md);
expect(result).toEqual({});
});
it('extracts nested objects', () => {
const md = `---
tech-stack:
added:
- jose: 5.2.0
- bcrypt: 5.1.1
patterns:
- httpOnly cookies
---
# Content`;
const result = extractFrontmatter(md);
expect(result['tech-stack']).toBeDefined();
// YAML parses these as objects with key-value pairs
expect(result['tech-stack'].added).toHaveLength(2);
expect(result['tech-stack'].added[0]).toHaveProperty('jose', '5.2.0');
expect(result['tech-stack'].patterns).toContain('httpOnly cookies');
});
it('extracts requires/provides structure', () => {
const md = `---
requires:
- phase: 01-auth
provides: User model
provides:
- Dashboard component
- Real-time updates
---
# Content`;
const result = extractFrontmatter(md);
expect(result.requires).toHaveLength(1);
expect(result.requires[0].phase).toBe('01-auth');
expect(result.provides).toContain('Dashboard component');
});
it('extracts from real SUMMARY.md fixture', () => {
const summary = readFixture('sample-summary.md');
const result = extractFrontmatter(summary);
expect(result.phase).toBe('01-foundation');
expect(result.plan).toBe(1); // YAML parses 01 as number
expect(result.subsystem).toBe('auth');
expect(result.tags).toContain('jwt');
expect(result['key-decisions']).toBeDefined();
expect(result['key-decisions'].length).toBeGreaterThan(0);
});
it('extracts from real RESEARCH.md fixture', () => {
const research = readFixture('sample-research.md');
const result = extractFrontmatter(research);
// Research frontmatter structure (lowercase keys)
expect(result).toHaveProperty('researched');
expect(result).toHaveProperty('domain');
expect(result).toHaveProperty('confidence');
expect(result.confidence).toBe('HIGH');
});
it('handles multiline strings in frontmatter', () => {
const md = `---
description: |
This is a multiline
description that spans
multiple lines
---
# Content`;
const result = extractFrontmatter(md);
expect(result.description).toContain('multiline');
expect(result.description).toContain('multiple lines');
});
it('handles special characters in values', () => {
const md = `---
decision: "Used jose instead of jsonwebtoken - ESM native"
path: src/lib/auth.ts
---
# Content`;
const result = extractFrontmatter(md);
expect(result.decision).toBe('Used jose instead of jsonwebtoken - ESM native');
expect(result.path).toBe('src/lib/auth.ts');
});
});

View File

@@ -1,109 +0,0 @@
import { describe, it, expect } from 'vitest';
import { extractProject, type ProjectData } from '../../src/extractors/project.js';
import { readFixture } from '../setup.js';
describe('project extractor', () => {
it('extracts project name from title', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.name).toBe('TaskFlow');
});
it('extracts what this is description', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.description).toContain('task management');
expect(result.description).toContain('terminal');
});
it('extracts core value', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.coreValue).toContain('Fast task capture');
});
it('extracts validated requirements', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.requirements.validated.length).toBeGreaterThan(0);
expect(result.requirements.validated[0]).toContain('CLI task creation');
});
it('extracts active requirements', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.requirements.active.length).toBeGreaterThan(0);
expect(result.requirements.active).toContainEqual(
expect.stringContaining('dashboard')
);
});
it('extracts out of scope items', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.requirements.outOfScope.length).toBeGreaterThan(0);
expect(result.requirements.outOfScope[0]).toHaveProperty('item');
expect(result.requirements.outOfScope[0]).toHaveProperty('reason');
});
it('extracts constraints', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.constraints.length).toBeGreaterThan(0);
expect(result.constraints[0]).toHaveProperty('type');
expect(result.constraints[0]).toHaveProperty('value');
});
it('extracts key decisions table', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.decisions.length).toBeGreaterThan(0);
expect(result.decisions[0]).toHaveProperty('decision');
expect(result.decisions[0]).toHaveProperty('rationale');
expect(result.decisions[0]).toHaveProperty('outcome');
});
it('extracts context section', () => {
const project = readFixture('sample-project.md');
const result = extractProject(project);
expect(result.context).toBeDefined();
expect(result.context).toContain('developers');
});
it('handles project without optional sections', () => {
const minimalProject = `# MinimalProject
## What This Is
A minimal test project.
## Core Value
Test core value.
## Requirements
### Active
- [ ] One feature
`;
const result = extractProject(minimalProject);
expect(result.name).toBe('MinimalProject');
expect(result.description).toContain('minimal');
expect(result.coreValue).toContain('Test core value');
expect(result.requirements.active).toHaveLength(1);
expect(result.requirements.validated).toEqual([]);
expect(result.decisions).toEqual([]);
});
});

View File

@@ -1,89 +0,0 @@
import { describe, it, expect } from 'vitest';
import { extractResearch, type ResearchData } from '../../src/extractors/research.js';
import { readFixture } from '../setup.js';
describe('research extractor', () => {
it('extracts metadata from frontmatter', () => {
const research = readFixture('sample-research.md');
const result = extractResearch(research);
expect(result.domain).toBe('WebSocket and Server-Sent Events for real-time updates');
expect(result.confidence).toBe('HIGH');
expect(result.researched).toBe('2025-01-20');
});
it('extracts pitfalls section', () => {
const research = readFixture('sample-research.md');
const result = extractResearch(research);
expect(result.pitfalls.length).toBeGreaterThan(0);
expect(result.pitfalls[0]).toHaveProperty('name');
expect(result.pitfalls[0]).toHaveProperty('description');
expect(result.pitfalls[0]).toHaveProperty('prevention');
});
it('extracts standard stack table', () => {
const research = readFixture('sample-research.md');
const result = extractResearch(research);
expect(result.standardStack.length).toBeGreaterThan(0);
expect(result.standardStack[0]).toHaveProperty('library');
expect(result.standardStack[0]).toHaveProperty('purpose');
});
it('extracts dont-hand-roll items', () => {
const research = readFixture('sample-research.md');
const result = extractResearch(research);
expect(result.dontHandRoll.length).toBeGreaterThan(0);
expect(result.dontHandRoll[0]).toHaveProperty('problem');
expect(result.dontHandRoll[0]).toHaveProperty('useInstead');
});
it('extracts anti-patterns', () => {
const research = readFixture('sample-research.md');
const result = extractResearch(research);
expect(result.antiPatterns.length).toBeGreaterThan(0);
});
it('extracts user constraints if present', () => {
const research = readFixture('sample-research.md');
const result = extractResearch(research);
expect(result.userConstraints).toBeDefined();
expect(result.userConstraints.locked.length).toBeGreaterThan(0);
});
it('extracts primary recommendation', () => {
const research = readFixture('sample-research.md');
const result = extractResearch(research);
expect(result.primaryRecommendation).toBeDefined();
expect(result.primaryRecommendation).toContain('SSE');
});
it('handles research without optional sections', () => {
const minimalResearch = `---
domain: testing
confidence: LOW
researched: 2025-01-20
---
# Research
## Summary
Basic research document.
**Primary recommendation:** Use standard tools.
`;
const result = extractResearch(minimalResearch);
expect(result.domain).toBe('testing');
expect(result.pitfalls).toEqual([]);
expect(result.standardStack).toEqual([]);
expect(result.dontHandRoll).toEqual([]);
});
});

View File

@@ -1,112 +0,0 @@
import { describe, it, expect } from 'vitest';
import { extractSummary, type SummaryData } from '../../src/extractors/summary.js';
import { readFixture } from '../setup.js';
describe('summary extractor', () => {
it('extracts decisions from frontmatter', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.decisions.length).toBeGreaterThan(0);
expect(result.decisions[0]).toHaveProperty('decision');
expect(result.decisions[0].decision).toContain('jose');
});
it('extracts tech stack additions', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.techStack.length).toBeGreaterThan(0);
expect(result.techStack).toContainEqual(
expect.objectContaining({
library: 'jose',
version: '5.2.0'
})
);
});
it('extracts patterns established', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.patterns.length).toBeGreaterThan(0);
expect(result.patterns[0]).toContain('Token rotation');
});
it('extracts requires/provides for dependency graph', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.requires).toBeDefined();
expect(result.provides).toBeDefined();
expect(result.provides.length).toBeGreaterThan(0);
});
it('extracts phase and plan identifiers', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.phase).toBe('01-foundation');
expect(result.plan).toBe(1);
});
it('extracts subsystem and tags', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.subsystem).toBe('auth');
expect(result.tags).toContain('jwt');
expect(result.tags).toContain('prisma');
});
it('extracts affects list', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.affects).toBeDefined();
expect(result.affects).toContain('02-features');
});
it('extracts key files', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.keyFiles).toBeDefined();
expect(result.keyFiles.created).toContain('src/lib/auth.ts');
});
it('extracts duration and completion date', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.duration).toBe('28min');
expect(result.completed).toBe('2025-01-15');
});
it('handles summary without optional fields', () => {
const minimalSummary = `---
phase: 01-test
plan: 01
subsystem: testing
tags: [test]
---
# Phase 1 Summary
**Test phase**`;
const result = extractSummary(minimalSummary);
expect(result.phase).toBe('01-test');
expect(result.decisions).toEqual([]);
expect(result.techStack).toEqual([]);
expect(result.patterns).toEqual([]);
});
it('extracts one-liner from content', () => {
const summary = readFixture('sample-summary.md');
const result = extractSummary(summary);
expect(result.oneLiner).toBeDefined();
expect(result.oneLiner).toContain('JWT auth');
});
});

View File

@@ -1,43 +0,0 @@
# MockProject
## What This Is
A test project for validating GSD memory extraction. Contains all expected document types and frontmatter structures.
## Core Value
Test coverage for GSD memory extractors.
## Requirements
### Validated
- ✓ Extractor parses SUMMARY.md frontmatter — Phase 1
### Active
- [ ] Cross-project search works
- [ ] Decision extraction includes rationale
### Out of Scope
- Production deployment — test fixture only
## Context
Mock project used in GSD memory test suite.
## Constraints
- **Tech stack**: TypeScript, Vitest
- **Scope**: Test fixtures only
## Key Decisions
| Decision | Rationale | Outcome |
|----------|-----------|---------|
| YAML frontmatter | Machine-readable metadata | ✓ Good |
| Gray-matter parser | Battle-tested, handles edge cases | — Pending |
---
*Last updated: 2025-01-20 after creation*

View File

@@ -1,60 +0,0 @@
# Project State
## Project Reference
See: .planning/PROJECT.md (updated 2025-01-20)
**Core value:** Test coverage for GSD memory extractors
**Current focus:** Phase 1 Foundation
## Current Position
Phase: 1 of 2 (Foundation)
Plan: 1 of 1 in current phase
Status: Phase complete
Last activity: 2025-01-20 — Completed foundation phase
Progress: [█████░░░░░] 50%
## Performance Metrics
**Velocity:**
- Total plans completed: 1
- Average duration: 15 min
- Total execution time: 0.25 hours
**By Phase:**
| Phase | Plans | Total | Avg/Plan |
|-------|-------|-------|----------|
| 01-foundation | 1 | 15min | 15min |
**Recent Trend:**
- Last 5 plans: [15min]
- Trend: Stable
*Updated after each plan completion*
## Accumulated Context
### Decisions
Decisions are logged in PROJECT.md Key Decisions table.
Recent decisions affecting current work:
- [Phase 1]: Used YAML frontmatter for metadata
- [Phase 1]: Selected gray-matter parser
### Pending Todos
None yet.
### Blockers/Concerns
None yet.
## Session Continuity
Last session: 2025-01-20 14:30
Stopped at: Phase 1 complete, ready for Phase 2
Resume file: None

View File

@@ -1,9 +0,0 @@
{
"project": "MockProject",
"version": "1.0.0",
"created": "2025-01-20",
"settings": {
"parallelization": true,
"atomicCommits": true
}
}

View File

@@ -1,95 +0,0 @@
---
phase: 01-foundation
plan: 01
subsystem: testing
tags: [vitest, typescript, testing]
requires:
- phase: none
provides: greenfield project
provides:
- Test fixture structure
- Extractor test coverage
- Mock planning directory
affects: [02-integration]
tech-stack:
added:
- vitest: 1.0.0
- gray-matter: 4.0.3
patterns:
- TDD for extractors
- Fixture-based testing
key-files:
created:
- tests/fixtures/sample-summary.md
- tests/fixtures/sample-research.md
- tests/fixtures/sample-project.md
modified: []
key-decisions:
- "Used Vitest for test runner - fast, ESM native"
- "Created realistic fixtures from actual GSD templates"
patterns-established:
- "Fixture files mirror real GSD output"
- "Tests verify extraction accuracy"
duration: 15min
completed: 2025-01-20
---
# Phase 1: Foundation Summary
**Test fixtures and extractor test coverage for GSD memory server**
## Performance
- **Duration:** 15 min
- **Started:** 2025-01-20T14:00:00Z
- **Completed:** 2025-01-20T14:15:00Z
- **Tasks:** 3
- **Files modified:** 5
## Accomplishments
- Created realistic test fixtures
- Set up Vitest configuration
- Established TDD pattern for extractors
## Task Commits
1. **Task 1: Create fixtures** - `fix001` (feat)
2. **Task 2: Configure Vitest** - `fix002` (chore)
3. **Task 3: Write extractor tests** - `fix003` (test)
## Files Created/Modified
- `tests/fixtures/sample-summary.md` - SUMMARY.md fixture
- `tests/fixtures/sample-research.md` - RESEARCH.md fixture
- `tests/fixtures/sample-project.md` - PROJECT.md fixture
- `vitest.config.ts` - Test configuration
## Decisions Made
- Used Vitest over Jest for ESM support
- Created comprehensive fixtures from real templates
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Fixtures complete, ready for extractor implementation
- Test patterns established
---
*Phase: 01-foundation*
*Completed: 2025-01-20*

View File

@@ -1,100 +0,0 @@
# Phase 1: Foundation - Research
**Researched:** 2025-01-18
**Domain:** TypeScript testing and YAML parsing
**Confidence:** HIGH
<research_summary>
## Summary
Researched testing frameworks and YAML parsing for TypeScript MCP server development. Vitest is the clear standard for ESM TypeScript projects. Gray-matter is battle-tested for markdown frontmatter parsing.
**Primary recommendation:** Use Vitest + gray-matter. Follow TDD for all extractors.
</research_summary>
<standard_stack>
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| vitest | 1.0.0 | Test runner | Fast, ESM native, TS support |
| gray-matter | 4.0.3 | Frontmatter parsing | Handles edge cases, widely used |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| yaml | 2.3.0 | YAML parsing | Complex YAML beyond frontmatter |
### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Vitest | Jest | Jest slower for ESM, more config |
| gray-matter | manual regex | Gray-matter handles edge cases |
</standard_stack>
<dont_hand_roll>
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| YAML frontmatter | Regex parsing | gray-matter | Multiline, escaping, edge cases |
| Test runner | Custom harness | Vitest | Watch mode, coverage, parallel |
**Key insight:** YAML parsing has many edge cases. Gray-matter handles them all.
</dont_hand_roll>
<common_pitfalls>
## Common Pitfalls
### Pitfall 1: Malformed YAML
**What goes wrong:** Parser throws on invalid YAML
**Why it happens:** User-edited files may have syntax errors
**How to avoid:** Wrap parsing in try-catch, return empty object on failure
**Warning signs:** Tests fail with YAML parse errors
### Pitfall 2: Missing Frontmatter
**What goes wrong:** Code assumes frontmatter exists
**Why it happens:** Not all markdown files have frontmatter
**How to avoid:** Always check for empty result
**Warning signs:** Undefined errors on .data access
</common_pitfalls>
<sources>
## Sources
### Primary (HIGH confidence)
- Vitest documentation - test configuration
- gray-matter npm - API reference
### Secondary (MEDIUM confidence)
- None
### Tertiary (LOW confidence - needs validation)
- None
</sources>
<metadata>
## Metadata
**Research scope:**
- Core technology: TypeScript testing
- Ecosystem: Vitest, gray-matter
- Patterns: TDD, fixture-based testing
- Pitfalls: YAML parsing edge cases
**Confidence breakdown:**
- Standard stack: HIGH - widely used
- Architecture: HIGH - standard patterns
- Pitfalls: HIGH - documented extensively
- Code examples: HIGH - from official docs
**Research date:** 2025-01-18
**Valid until:** 2025-02-18 (30 days - stable tech)
</metadata>
---
*Phase: 01-foundation*
*Research completed: 2025-01-18*
*Ready for planning: yes*

View File

@@ -1,21 +0,0 @@
---
domain: typescript-testing
confidence: HIGH
researched: 2025-01-18
---
# Research Summary
Research completed on TypeScript testing ecosystem for GSD memory server.
## Key Findings
- Vitest is the standard for TypeScript projects
- Gray-matter handles YAML frontmatter parsing
- MCP SDK provides standard server implementation
## Recommendations
1. Use Vitest for all tests
2. Use gray-matter for frontmatter extraction
3. Follow MCP SDK patterns for tool implementation

Some files were not shown because too many files have changed in this diff Show More