onerios-mcp-server
Version:
OneriosMCP server providing memory, backlog management, file operations, and utility functions for enhanced AI assistant capabilities
314 lines (313 loc) • 10.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BacklogDataSchema = exports.IssueSchema = exports.TaskSchema = exports.IssueStatus = exports.TaskStatus = void 0;
exports.createIssue = createIssue;
exports.listIssues = listIssues;
exports.selectIssue = selectIssue;
exports.initializeIssue = initializeIssue;
exports.updateIssueStatus = updateIssueStatus;
exports.addTask = addTask;
exports.listTasks = listTasks;
exports.updateTaskStatus = updateTaskStatus;
exports.getBacklogStats = getBacklogStats;
const zod_1 = require("zod");
const fs_1 = require("fs");
const uuid_1 = require("uuid");
// Status enums
var TaskStatus;
(function (TaskStatus) {
TaskStatus["NEW"] = "New";
TaskStatus["IN_WORK"] = "InWork";
TaskStatus["DONE"] = "Done";
})(TaskStatus || (exports.TaskStatus = TaskStatus = {}));
var IssueStatus;
(function (IssueStatus) {
IssueStatus["NEW"] = "New";
IssueStatus["IN_WORK"] = "InWork";
IssueStatus["DONE"] = "Done";
})(IssueStatus || (exports.IssueStatus = IssueStatus = {}));
// Schemas for validation
exports.TaskSchema = zod_1.z.object({
id: zod_1.z.string(),
title: zod_1.z.string(),
description: zod_1.z.string().optional(),
status: zod_1.z.nativeEnum(TaskStatus),
created: zod_1.z.string().datetime(),
updated: zod_1.z.string().datetime()
});
exports.IssueSchema = zod_1.z.object({
name: zod_1.z.string(),
description: zod_1.z.string().optional(),
status: zod_1.z.nativeEnum(IssueStatus),
tasks: zod_1.z.record(zod_1.z.string(), exports.TaskSchema),
created: zod_1.z.string().datetime(),
updated: zod_1.z.string().datetime()
});
exports.BacklogDataSchema = zod_1.z.object({
issues: zod_1.z.record(zod_1.z.string(), exports.IssueSchema),
activeIssue: zod_1.z.string().optional()
});
// Default file path
const DEFAULT_BACKLOG_FILE = 'backlog.json';
/**
* Get the backlog file path from environment or use default
*/
function getBacklogFilePath() {
return process.env.BACKLOG_FILE || DEFAULT_BACKLOG_FILE;
}
/**
* Load backlog data from file
*/
async function loadBacklogData() {
const filePath = getBacklogFilePath();
try {
const fileExists = await fs_1.promises.access(filePath).then(() => true).catch(() => false);
if (!fileExists) {
return { issues: {} };
}
const content = await fs_1.promises.readFile(filePath, 'utf-8');
const data = JSON.parse(content);
return exports.BacklogDataSchema.parse(data);
}
catch (error) {
console.error(`Error loading backlog data: ${error}`);
return { issues: {} };
}
}
/**
* Save backlog data to file
*/
async function saveBacklogData(data) {
const filePath = getBacklogFilePath();
try {
await fs_1.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
}
catch (error) {
console.error(`Error saving backlog data: ${error}`);
throw new Error(`Failed to save backlog data: ${error}`);
}
}
/**
* Generate a short unique ID for tasks
*/
function generateTaskId() {
return (0, uuid_1.v4)().substring(0, 8);
}
/**
* Get current ISO datetime string
*/
function getCurrentDateTime() {
return new Date().toISOString();
}
/**
* Create a new issue
*/
async function createIssue(name, description = '', status = IssueStatus.NEW) {
const data = await loadBacklogData();
if (data.issues[name]) {
throw new Error(`Issue '${name}' already exists`);
}
const now = getCurrentDateTime();
data.issues[name] = {
name,
description,
status,
tasks: {},
created: now,
updated: now
};
// Set as active issue
data.activeIssue = name;
await saveBacklogData(data);
return `Successfully created issue: ${name} with status: ${status}`;
}
/**
* List all issues
*/
async function listIssues() {
const data = await loadBacklogData();
if (Object.keys(data.issues).length === 0) {
return "No issues found. Use 'createIssue' to create a new issue.";
}
const result = ["Available issues:"];
for (const [issueName, issue] of Object.entries(data.issues)) {
const taskCount = Object.keys(issue.tasks).length;
const activeMarker = issueName === data.activeIssue ? " (active)" : "";
const descriptionPreview = issue.description.length > 30
? issue.description.substring(0, 30) + "..."
: issue.description;
result.push(`- ${issueName}${activeMarker}: Status: ${issue.status}, Tasks: ${taskCount}`);
if (descriptionPreview) {
result.push(` Description: ${descriptionPreview}`);
}
}
return result.join('\n');
}
/**
* Select an issue as active
*/
async function selectIssue(name) {
const data = await loadBacklogData();
if (!data.issues[name]) {
throw new Error(`Issue '${name}' not found`);
}
data.activeIssue = name;
await saveBacklogData(data);
return `Selected issue: ${name}`;
}
/**
* Initialize or reset an issue
*/
async function initializeIssue(name, description = '', status = IssueStatus.NEW) {
const data = await loadBacklogData();
const now = getCurrentDateTime();
const existingIssue = data.issues[name];
data.issues[name] = {
name,
description,
status,
tasks: {},
created: existingIssue?.created || now,
updated: now
};
// Set as active issue
data.activeIssue = name;
await saveBacklogData(data);
return `Successfully initialized issue: ${name} with status: ${status}`;
}
/**
* Update issue status
*/
async function updateIssueStatus(name, status) {
const data = await loadBacklogData();
if (!data.issues[name]) {
throw new Error(`Issue '${name}' not found`);
}
const oldStatus = data.issues[name].status;
data.issues[name].status = status;
data.issues[name].updated = getCurrentDateTime();
await saveBacklogData(data);
return `Successfully updated issue '${name}' status from '${oldStatus}' to '${status}'`;
}
/**
* Add a task to the active issue
*/
async function addTask(title, description = '') {
const data = await loadBacklogData();
if (!data.activeIssue) {
throw new Error("No active issue. Please select an issue using 'selectIssue' first");
}
if (!data.issues[data.activeIssue]) {
throw new Error(`Active issue '${data.activeIssue}' not found`);
}
const taskId = generateTaskId();
const now = getCurrentDateTime();
data.issues[data.activeIssue].tasks[taskId] = {
id: taskId,
title,
description,
status: TaskStatus.NEW,
created: now,
updated: now
};
data.issues[data.activeIssue].updated = now;
await saveBacklogData(data);
return `Successfully added task: ${title} (ID: ${taskId}) to issue '${data.activeIssue}'`;
}
/**
* List tasks in the active issue, optionally filtered by status
*/
async function listTasks(statusFilter) {
const data = await loadBacklogData();
if (!data.activeIssue) {
throw new Error("No active issue. Please select an issue using 'selectIssue' first");
}
if (!data.issues[data.activeIssue]) {
throw new Error(`Active issue '${data.activeIssue}' not found`);
}
const issue = data.issues[data.activeIssue];
const tasks = Object.values(issue.tasks);
if (tasks.length === 0) {
return `No tasks found in issue '${data.activeIssue}'`;
}
const filteredTasks = statusFilter
? tasks.filter(task => task.status === statusFilter)
: tasks;
if (filteredTasks.length === 0) {
return statusFilter
? `No tasks found with status '${statusFilter}' in issue '${data.activeIssue}'`
: "No tasks found";
}
const result = [`Tasks for issue: ${data.activeIssue}`];
if (issue.description) {
result.push(`Issue description: ${issue.description}`);
}
for (const task of filteredTasks) {
result.push(`\nID: ${task.id}`);
result.push(`Title: ${task.title}`);
result.push(`Status: ${task.status}`);
if (task.description) {
result.push(`Description: ${task.description}`);
}
}
return result.join('\n');
}
/**
* Update task status
*/
async function updateTaskStatus(taskId, status) {
const data = await loadBacklogData();
if (!data.activeIssue) {
throw new Error("No active issue. Please select an issue using 'selectIssue' first");
}
if (!data.issues[data.activeIssue]) {
throw new Error(`Active issue '${data.activeIssue}' not found`);
}
const task = data.issues[data.activeIssue].tasks[taskId];
if (!task) {
throw new Error(`Task with ID '${taskId}' not found in issue '${data.activeIssue}'`);
}
const oldStatus = task.status;
task.status = status;
task.updated = getCurrentDateTime();
data.issues[data.activeIssue].updated = getCurrentDateTime();
await saveBacklogData(data);
return `Successfully updated task '${task.title}' (ID: ${taskId}) status from '${oldStatus}' to '${status}'`;
}
/**
* Get backlog statistics
*/
async function getBacklogStats() {
const data = await loadBacklogData();
const issueCount = Object.keys(data.issues).length;
let totalTasks = 0;
let tasksByStatus = { [TaskStatus.NEW]: 0, [TaskStatus.IN_WORK]: 0, [TaskStatus.DONE]: 0 };
let issuesByStatus = { [IssueStatus.NEW]: 0, [IssueStatus.IN_WORK]: 0, [IssueStatus.DONE]: 0 };
for (const issue of Object.values(data.issues)) {
issuesByStatus[issue.status]++;
const tasks = Object.values(issue.tasks);
totalTasks += tasks.length;
for (const task of tasks) {
tasksByStatus[task.status]++;
}
}
const result = [
"📊 Backlog Statistics",
`Total Issues: ${issueCount}`,
`Total Tasks: ${totalTasks}`,
"",
"Issues by Status:",
` • New: ${issuesByStatus[IssueStatus.NEW]}`,
` • In Work: ${issuesByStatus[IssueStatus.IN_WORK]}`,
` • Done: ${issuesByStatus[IssueStatus.DONE]}`,
"",
"Tasks by Status:",
` • New: ${tasksByStatus[TaskStatus.NEW]}`,
` • In Work: ${tasksByStatus[TaskStatus.IN_WORK]}`,
` • Done: ${tasksByStatus[TaskStatus.DONE]}`
];
if (data.activeIssue) {
result.push(`\nActive Issue: ${data.activeIssue}`);
}
return result.join('\n');
}