lamplighter-mcp
Version:
An intelligent context engine for AI-assisted software development
186 lines (185 loc) • 7.79 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskManager = void 0;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const dotenv_1 = __importDefault(require("dotenv"));
// Load environment variables
dotenv_1.default.config();
const DEFAULT_CONTEXT_DIR = './lamplighter_context';
const FEATURE_TASKS_DIR = 'feature_tasks';
class TaskManager {
contextDir;
featureTasksDir;
constructor() {
this.contextDir = process.env.LAMPLIGHTER_CONTEXT_DIR || DEFAULT_CONTEXT_DIR;
this.featureTasksDir = path.join(this.contextDir, FEATURE_TASKS_DIR);
console.log(`[TaskManager] Initialized. Task files located in: ${this.featureTasksDir}`);
}
/**
* Update the status of a task
*/
async updateTaskStatus(featureIdentifier, taskIdentifier, newStatus) {
try {
// Build the path to the feature task file
const filePath = path.join(this.featureTasksDir, `feature_${featureIdentifier}_tasks.md`);
// Check if file exists
try {
await fs.access(filePath);
}
catch (error) {
throw new Error(`Task file for feature "${featureIdentifier}" not found.`);
}
// Read the file content
const content = await fs.readFile(filePath, 'utf-8');
// Parse tasks from the content
const tasks = this.parseTasks(content);
// Find the task by identifier (text match)
const taskIndex = tasks.findIndex(task =>
// Check for exact match or if the task text contains the identifier
task.text === taskIdentifier ||
task.text.includes(taskIdentifier));
if (taskIndex === -1) {
throw new Error(`Task "${taskIdentifier}" not found in feature "${featureIdentifier}".`);
}
// Update the task's status
const updatedContent = this.updateTaskInContent(content, tasks[taskIndex], newStatus);
// Write the updated content back to the file
await fs.writeFile(filePath, updatedContent, 'utf-8');
console.log(`[TaskManager] Updated task "${taskIdentifier}" in feature "${featureIdentifier}" to status: ${newStatus}`);
}
catch (error) {
console.error('[TaskManager] Error updating task status:', error);
throw new Error(`Failed to update task status: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Suggest the next task to work on from a feature
*/
async suggestNextTask(featureIdentifier) {
try {
// Build the path to the feature task file
const filePath = path.join(this.featureTasksDir, `feature_${featureIdentifier}_tasks.md`);
// Check if file exists
try {
await fs.access(filePath);
}
catch (error) {
throw new Error(`Task file for feature "${featureIdentifier}" not found.`);
}
// Read the file content
const content = await fs.readFile(filePath, 'utf-8');
// Parse tasks from the content
const tasks = this.parseTasks(content);
// Find the first task with status 'ToDo'
const nextTask = tasks.find(task => task.status === 'ToDo');
if (!nextTask) {
console.log(`[TaskManager] No pending tasks found for feature "${featureIdentifier}".`);
return null;
}
console.log(`[TaskManager] Suggested next task: "${nextTask.text}"`);
return nextTask.text;
}
catch (error) {
console.error('[TaskManager] Error suggesting next task:', error);
throw new Error(`Failed to suggest next task: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Parse tasks from a markdown file content
* This regex looks for GitHub-style task list items:
* - [ ] Task description (ToDo)
* - [x] Task description (Done)
* - [X] Task description (Done, alternative)
*/
parseTasks(content) {
const tasks = [];
const lines = content.split('\n');
// Regular expression to match task checklist items
const taskRegex = /^(\s*)-\s*\[([ xX])\]\s*(.+)$/;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const match = line.match(taskRegex);
if (match) {
const statusChar = match[2];
const status = statusChar === ' ' ? 'ToDo' :
statusChar.toLowerCase() === 'x' ? 'Done' :
'InProgress'; // Fallback, though we expect just [ ] and [x]
tasks.push({
text: match[3].trim(),
status,
lineNumber: i,
originalLine: line
});
}
}
return tasks;
}
/**
* Update a task's status in the content string
*/
updateTaskInContent(content, task, newStatus) {
// Split content into lines
const lines = content.split('\n');
// Get the original line
const originalLine = lines[task.lineNumber];
// Replace the status marker
let updatedLine;
switch (newStatus) {
case 'ToDo':
updatedLine = originalLine.replace(/\[([ xX])\]/, '[ ]');
break;
case 'InProgress':
// Optional: you could use a different marker for in-progress, e.g., [~]
// For now, we'll treat it the same as ToDo
updatedLine = originalLine.replace(/\[([ xX])\]/, '[ ]');
break;
case 'Done':
updatedLine = originalLine.replace(/\[([ xX])\]/, '[x]');
break;
default:
updatedLine = originalLine;
}
// Replace the line in the content
lines[task.lineNumber] = updatedLine;
// Join the lines back into a string
return lines.join('\n');
}
}
exports.TaskManager = TaskManager;