Skip to main content
Glama
research.ts3.91 kB
import { z } from 'zod'; import * as fs from 'fs/promises'; import * as path from 'path'; /** * Research documentation tools */ // Tool schemas export const ResearchAppendSchema = z.object({ topic: z.string().describe('Research topic or decision area'), content: z.string().describe('Research content or decision rationale'), workspacePath: z.string().optional().describe('Workspace directory path'), }); /** * Append to the research document for decisions taken */ export async function researchAppend(params: z.infer<typeof ResearchAppendSchema>) { const { topic, content, workspacePath = process.cwd() } = params; // Validate workspace path const resolvedPath = path.resolve(workspacePath); await validateWorkspacePath(resolvedPath); try { // Check if .dincoder directory exists const dincoderPath = path.join(resolvedPath, '.dincoder'); const dincoderExists = await fs.access(dincoderPath).then(() => true).catch(() => false); if (!dincoderExists) { // Create .dincoder directory if it doesn't exist (for backwards compatibility) await fs.mkdir(dincoderPath, { recursive: true }); // Create initial research.md const researchPath = path.join(dincoderPath, 'research.md'); const initialContent = `# Research Documentation ## Project Research & Technical Decisions This document captures key research findings and technical decisions made during development. --- `; await fs.writeFile(researchPath, initialContent, 'utf-8'); } // Use .dincoder/research.md const researchPath = path.join(dincoderPath, 'research.md'); // Check if file exists let existingContent = ''; try { existingContent = await fs.readFile(researchPath, 'utf-8'); } catch { // File doesn't exist, create header existingContent = `# Research Documentation ## Project Research & Technical Decisions This document captures key research findings and technical decisions made during development. --- `; } // Append new research entry const timestamp = new Date().toISOString(); const entry = ` ## ${topic} **Date**: ${timestamp} ${content} --- `; const updatedContent = existingContent + entry; await fs.writeFile(researchPath, updatedContent, 'utf-8'); // Count total entries for metrics const entryCount = (updatedContent.match(/^## /gm) || []).length - 1; // Subtract header return { success: true, message: `Appended research for topic: ${topic}`, details: { topic, timestamp, location: researchPath, entryLength: content.length, totalEntries: entryCount, nextSteps: [ 'Use artifacts_read to view all project artifacts', 'Continue documenting decisions as the project evolves', 'Review research.md periodically to maintain context' ] }, }; } catch (error) { throw new Error(`Failed to append research: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Validate workspace path for security */ async function validateWorkspacePath(workspacePath: string): Promise<void> { // Check if path exists try { const stat = await fs.stat(workspacePath); if (!stat.isDirectory()) { throw new Error('Workspace path is not a directory'); } } catch (error) { if ((error as any).code === 'ENOENT') { throw new Error('Workspace path does not exist'); } throw error; } // Prevent access to system directories const restrictedPaths = ['/etc', '/usr', '/bin', '/sbin', '/var', '/tmp']; const normalizedPath = path.normalize(workspacePath).toLowerCase(); for (const restricted of restrictedPaths) { if (normalizedPath.startsWith(restricted)) { throw new Error(`Access to system directory ${restricted} is not allowed`); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/flight505/MCP_DinCoder'

If you have feedback or need assistance with the MCP directory API, please join our Discord server