squabble-mcp
Version:
Engineer-driven development with critical-thinking PM collaboration - MCP server for Claude
155 lines (138 loc) • 4.16 kB
JavaScript
import * as fs from 'fs';
import * as path from 'path';
// Default templates
const DEFAULT_TEMPLATES = {
'implementation-plan': `# Implementation Plan for {{taskTitle}}
**Task ID**: {{taskId}}
## Approach
{{approach}}
## Implementation Steps
{{steps}}
## Potential Risks
{{risks}}
---
*Please review this plan before I proceed with implementation.*`,
'implementation-report': `# Implementation Report for {{taskTitle}}
**Task ID**: {{taskId}}
## Summary
{{summary}}
## Files Changed
{{filesChanged}}
## Tests Added
{{testsAdded}}
## Concerns
{{concerns}}
## Questions for Review
{{questions}}
---
*Please review the implementation and provide feedback.*`,
'review': `# PM Review for {{taskTitle}}
**Task ID**: {{taskId}}
## Review Notes
{{reviewNotes}}
## Decision
{{decision}}
## Requested Changes
{{requestedChanges}}
---
*Review completed at {{timestamp}}*`
};
export class TemplateService {
templateDir;
constructor(workspaceDir) {
this.templateDir = path.join(workspaceDir, 'templates');
}
/**
* Get template content by type
*/
getTemplate(type) {
const templatePath = this.getTemplatePath(type);
// If custom template exists, use it; otherwise use default
if (fs.existsSync(templatePath)) {
return fs.readFileSync(templatePath, 'utf-8');
}
return DEFAULT_TEMPLATES[type];
}
/**
* Fill template with context values
*/
fillTemplate(template, context) {
let filled = template;
// Replace all placeholders
Object.entries(context).forEach(([key, value]) => {
const placeholder = `{{${key}}}`;
let replacement = '';
if (value === undefined || value === null) {
replacement = '';
}
else if (Array.isArray(value)) {
// Format arrays as bullet points
replacement = value.length > 0
? value.map(item => `- ${item}`).join('\n')
: '- None';
}
else if (typeof value === 'boolean') {
replacement = value ? 'Yes' : 'No';
}
else {
replacement = String(value);
}
// Replace all occurrences
filled = filled.replace(new RegExp(placeholder, 'g'), replacement);
});
// Add timestamp if placeholder exists
filled = filled.replace(/{{timestamp}}/g, new Date().toISOString());
// Clean up any remaining placeholders
filled = filled.replace(/{{[^}]+}}/g, '');
return filled;
}
/**
* Ensure templates directory exists and has default templates
*/
async ensureTemplatesExist() {
// Create templates directory if it doesn't exist
if (!fs.existsSync(this.templateDir)) {
fs.mkdirSync(this.templateDir, { recursive: true });
}
// Copy default templates if they don't exist
for (const [type, content] of Object.entries(DEFAULT_TEMPLATES)) {
const templatePath = this.getTemplatePath(type);
if (!fs.existsSync(templatePath)) {
fs.writeFileSync(templatePath, content, 'utf-8');
}
}
}
/**
* Get path to template file
*/
getTemplatePath(type) {
return path.join(this.templateDir, `${type}.md`);
}
/**
* Check if custom template exists
*/
hasCustomTemplate(type) {
return fs.existsSync(this.getTemplatePath(type));
}
/**
* Update custom template
*/
updateTemplate(type, content) {
const templatePath = this.getTemplatePath(type);
fs.writeFileSync(templatePath, content, 'utf-8');
}
/**
* Reset template to default
*/
resetTemplate(type) {
const templatePath = this.getTemplatePath(type);
fs.writeFileSync(templatePath, DEFAULT_TEMPLATES[type], 'utf-8');
}
/**
* Get all available templates
*/
getAvailableTemplates() {
return Object.keys(DEFAULT_TEMPLATES);
}
}
//# sourceMappingURL=template-service.js.map