@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
522 lines (521 loc) • 16.9 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
ErrorCode2["DB_CONNECTION_FAILED"] = "DB_001";
ErrorCode2["DB_QUERY_FAILED"] = "DB_002";
ErrorCode2["DB_TRANSACTION_FAILED"] = "DB_003";
ErrorCode2["DB_MIGRATION_FAILED"] = "DB_004";
ErrorCode2["DB_CONSTRAINT_VIOLATION"] = "DB_005";
ErrorCode2["DB_SCHEMA_ERROR"] = "DB_006";
ErrorCode2["DB_INSERT_FAILED"] = "DB_007";
ErrorCode2["DB_UPDATE_FAILED"] = "DB_008";
ErrorCode2["DB_DELETE_FAILED"] = "DB_009";
ErrorCode2["DB_CORRUPTION"] = "DB_010";
ErrorCode2["FRAME_NOT_FOUND"] = "FRAME_001";
ErrorCode2["FRAME_INVALID_STATE"] = "FRAME_002";
ErrorCode2["FRAME_PARENT_NOT_FOUND"] = "FRAME_003";
ErrorCode2["FRAME_CYCLE_DETECTED"] = "FRAME_004";
ErrorCode2["FRAME_ALREADY_CLOSED"] = "FRAME_005";
ErrorCode2["FRAME_INIT_FAILED"] = "FRAME_006";
ErrorCode2["FRAME_INVALID_INPUT"] = "FRAME_007";
ErrorCode2["FRAME_STACK_OVERFLOW"] = "FRAME_008";
ErrorCode2["TASK_NOT_FOUND"] = "TASK_001";
ErrorCode2["TASK_INVALID_STATE"] = "TASK_002";
ErrorCode2["TASK_DEPENDENCY_CONFLICT"] = "TASK_003";
ErrorCode2["TASK_CIRCULAR_DEPENDENCY"] = "TASK_004";
ErrorCode2["LINEAR_AUTH_FAILED"] = "LINEAR_001";
ErrorCode2["LINEAR_API_ERROR"] = "LINEAR_002";
ErrorCode2["LINEAR_SYNC_FAILED"] = "LINEAR_003";
ErrorCode2["LINEAR_WEBHOOK_FAILED"] = "LINEAR_004";
ErrorCode2["MCP_TOOL_NOT_FOUND"] = "MCP_001";
ErrorCode2["MCP_INVALID_PARAMS"] = "MCP_002";
ErrorCode2["MCP_EXECUTION_FAILED"] = "MCP_003";
ErrorCode2["MCP_RATE_LIMITED"] = "MCP_004";
ErrorCode2["PROJECT_NOT_FOUND"] = "PROJECT_001";
ErrorCode2["PROJECT_INVALID_PATH"] = "PROJECT_002";
ErrorCode2["PROJECT_GIT_ERROR"] = "PROJECT_003";
ErrorCode2["VALIDATION_FAILED"] = "VAL_001";
ErrorCode2["INVALID_INPUT"] = "VAL_002";
ErrorCode2["MISSING_REQUIRED_FIELD"] = "VAL_003";
ErrorCode2["TYPE_MISMATCH"] = "VAL_004";
ErrorCode2["INITIALIZATION_ERROR"] = "SYS_001";
ErrorCode2["NOT_FOUND"] = "SYS_002";
ErrorCode2["INTERNAL_ERROR"] = "SYS_003";
ErrorCode2["CONFIGURATION_ERROR"] = "SYS_004";
ErrorCode2["PERMISSION_DENIED"] = "SYS_005";
ErrorCode2["RESOURCE_EXHAUSTED"] = "SYS_006";
ErrorCode2["SERVICE_UNAVAILABLE"] = "SYS_007";
ErrorCode2["SYSTEM_INIT_FAILED"] = "SYS_008";
ErrorCode2["UNKNOWN_ERROR"] = "SYS_009";
ErrorCode2["OPERATION_TIMEOUT"] = "SYS_010";
ErrorCode2["AUTH_FAILED"] = "AUTH_001";
ErrorCode2["TOKEN_EXPIRED"] = "AUTH_002";
ErrorCode2["INVALID_CREDENTIALS"] = "AUTH_003";
ErrorCode2["FILE_NOT_FOUND"] = "FS_001";
ErrorCode2["DISK_FULL"] = "FS_002";
ErrorCode2["NOT_GIT_REPO"] = "GIT_001";
ErrorCode2["GIT_COMMAND_FAILED"] = "GIT_002";
ErrorCode2["INVALID_BRANCH"] = "GIT_003";
ErrorCode2["NETWORK_ERROR"] = "NET_001";
ErrorCode2["API_ERROR"] = "NET_002";
ErrorCode2["STACK_CONTEXT_NOT_FOUND"] = "COLLAB_001";
ErrorCode2["HANDOFF_REQUEST_EXPIRED"] = "COLLAB_002";
ErrorCode2["MERGE_CONFLICT_UNRESOLVABLE"] = "COLLAB_003";
ErrorCode2["PERMISSION_VIOLATION"] = "COLLAB_004";
ErrorCode2["OPERATION_FAILED"] = "COLLAB_005";
ErrorCode2["OPERATION_EXPIRED"] = "COLLAB_006";
ErrorCode2["INVALID_STATE"] = "COLLAB_007";
ErrorCode2["RESOURCE_NOT_FOUND"] = "COLLAB_008";
ErrorCode2["HANDOFF_ALREADY_EXISTS"] = "COLLAB_009";
ErrorCode2["MERGE_SESSION_INVALID"] = "COLLAB_010";
ErrorCode2["STACK_SWITCH_FAILED"] = "COLLAB_011";
ErrorCode2["APPROVAL_TIMEOUT"] = "COLLAB_012";
ErrorCode2["CONFLICT_RESOLUTION_FAILED"] = "COLLAB_013";
ErrorCode2["TEAM_ACCESS_DENIED"] = "COLLAB_014";
ErrorCode2["STACK_LIMIT_EXCEEDED"] = "COLLAB_015";
return ErrorCode2;
})(ErrorCode || {});
class StackMemoryError extends Error {
code;
context;
cause;
isRetryable;
httpStatus;
timestamp;
constructor(options) {
super(options.message);
this.name = this.constructor.name;
this.code = options.code;
this.context = options.context;
this.cause = options.cause;
this.isRetryable = options.isRetryable ?? false;
this.httpStatus = options.httpStatus ?? 500;
this.timestamp = /* @__PURE__ */ new Date();
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
context: this.context,
isRetryable: this.isRetryable,
httpStatus: this.httpStatus,
timestamp: this.timestamp.toISOString(),
stack: this.stack,
cause: this.cause?.message
};
}
}
class DatabaseError extends StackMemoryError {
constructor(message, code = "DB_002" /* DB_QUERY_FAILED */, context, cause) {
super({
code,
message,
context,
cause,
isRetryable: code === "DB_001" /* DB_CONNECTION_FAILED */,
httpStatus: 503
});
}
}
class FrameError extends StackMemoryError {
constructor(message, code = "FRAME_002" /* FRAME_INVALID_STATE */, context) {
super({
code,
message,
context,
isRetryable: false,
httpStatus: 400
});
}
}
class TaskError extends StackMemoryError {
constructor(message, code = "TASK_002" /* TASK_INVALID_STATE */, context) {
super({
code,
message,
context,
isRetryable: false,
httpStatus: 400
});
}
}
class IntegrationError extends StackMemoryError {
constructor(message, code = "LINEAR_002" /* LINEAR_API_ERROR */, context, cause) {
super({
code,
message,
context,
cause,
isRetryable: true,
httpStatus: 502
});
}
}
class MCPError extends StackMemoryError {
constructor(message, code = "MCP_003" /* MCP_EXECUTION_FAILED */, context) {
super({
code,
message,
context,
isRetryable: code === "MCP_004" /* MCP_RATE_LIMITED */,
httpStatus: code === "MCP_004" /* MCP_RATE_LIMITED */ ? 429 : 400
});
}
}
class ValidationError extends StackMemoryError {
constructor(message, code = "VAL_001" /* VALIDATION_FAILED */, context) {
super({
code,
message,
context,
isRetryable: false,
httpStatus: 400
});
}
}
class ProjectError extends StackMemoryError {
constructor(message, code = "PROJECT_001" /* PROJECT_NOT_FOUND */, context) {
super({
code,
message,
context,
isRetryable: false,
httpStatus: 404
});
}
}
class SystemError extends StackMemoryError {
constructor(message, code = "SYS_003" /* INTERNAL_ERROR */, context, cause) {
super({
code,
message,
context,
cause,
isRetryable: code === "SYS_007" /* SERVICE_UNAVAILABLE */,
httpStatus: 500
});
}
}
function isRetryableError(error) {
if (error instanceof StackMemoryError) {
return error.isRetryable;
}
if (error instanceof Error) {
const message = error.message.toLowerCase();
return message.includes("econnrefused") || message.includes("timeout") || message.includes("enotfound") || message.includes("socket hang up");
}
return false;
}
function getErrorMessage(error) {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
if (error && typeof error === "object" && "message" in error) {
return String(error.message);
}
return "An unknown error occurred";
}
function wrapError(error, defaultMessage, code = "SYS_003" /* INTERNAL_ERROR */, context) {
if (error instanceof StackMemoryError) {
return error;
}
const cause = error instanceof Error ? error : void 0;
const message = error instanceof Error ? error.message : defaultMessage;
return new SystemError(message, code, context, cause);
}
function isStackMemoryError(error) {
return error instanceof StackMemoryError;
}
function createErrorHandler(defaultContext) {
return (error, additionalContext) => {
const context = { ...defaultContext, ...additionalContext };
if (error instanceof StackMemoryError) {
return new StackMemoryError({
code: error.code,
message: error.message,
context: { ...error.context, ...context },
cause: error.cause,
isRetryable: error.isRetryable,
httpStatus: error.httpStatus
});
}
return wrapError(
error,
getErrorMessage(error),
"SYS_003" /* INTERNAL_ERROR */,
context
);
};
}
function getUserFriendlyMessage(code) {
switch (code) {
// Auth errors
case "AUTH_001" /* AUTH_FAILED */:
return "Authentication failed. Please check your credentials and try again.";
case "AUTH_002" /* TOKEN_EXPIRED */:
return "Your session has expired. Please log in again.";
case "AUTH_003" /* INVALID_CREDENTIALS */:
return "Invalid credentials provided. Please check and try again.";
// File system errors
case "FS_001" /* FILE_NOT_FOUND */:
return "The requested file or directory was not found.";
case "SYS_005" /* PERMISSION_DENIED */:
return "Permission denied. Please check file permissions or run with appropriate privileges.";
case "FS_002" /* DISK_FULL */:
return "Insufficient disk space. Please free up space and try again.";
// Git errors
case "GIT_001" /* NOT_GIT_REPO */:
return "This command requires a git repository. Please run it from within a git repository.";
case "GIT_002" /* GIT_COMMAND_FAILED */:
return "Git operation failed. Please ensure your repository is in a valid state.";
case "GIT_003" /* INVALID_BRANCH */:
return "Invalid branch specified. Please check the branch name and try again.";
// Database errors
case "DB_001" /* DB_CONNECTION_FAILED */:
return "Database connection failed. Please try again or contact support if the issue persists.";
case "DB_002" /* DB_QUERY_FAILED */:
return "Database query failed. Please try again.";
case "DB_010" /* DB_CORRUPTION */:
return "Database appears to be corrupted. Please contact support.";
// Network errors
case "NET_001" /* NETWORK_ERROR */:
return "Network error. Please check your internet connection and try again.";
case "NET_002" /* API_ERROR */:
return "API request failed. Please try again later.";
case "SYS_010" /* OPERATION_TIMEOUT */:
return "The operation timed out. Please try again.";
// Validation errors
case "VAL_002" /* INVALID_INPUT */:
return "Invalid input provided. Please check your command and try again.";
case "VAL_001" /* VALIDATION_FAILED */:
return "Validation failed. Please check your input and try again.";
case "VAL_003" /* MISSING_REQUIRED_FIELD */:
return "A required field is missing. Please provide all required information.";
// System errors
case "SYS_004" /* CONFIGURATION_ERROR */:
return "Configuration error. Please check your settings.";
case "SYS_007" /* SERVICE_UNAVAILABLE */:
return "Service is temporarily unavailable. Please try again later.";
// Default
default:
return "An unexpected error occurred. Please try again or contact support.";
}
}
class ErrorHandler {
static retryMap = /* @__PURE__ */ new Map();
static MAX_RETRIES = 3;
/**
* Handle an error and exit the process
*/
static handle(error, operation) {
if (error instanceof StackMemoryError) {
const userMessage = getUserFriendlyMessage(error.code);
console.error(`\u274C ${userMessage}`);
if (error.isRetryable) {
console.error("\u{1F4A1} This error may be recoverable. Please try again.");
}
process.exit(1);
}
if (error instanceof Error) {
let stackMemoryError;
if ("code" in error && typeof error.code === "string") {
stackMemoryError = ErrorHandler.fromNodeError(
error,
{ operation }
);
} else {
stackMemoryError = wrapError(
error,
error.message,
"COLLAB_005" /* OPERATION_FAILED */,
{
operation
}
);
}
const userMessage = getUserFriendlyMessage(stackMemoryError.code);
console.error(`\u274C ${userMessage}`);
if (stackMemoryError.isRetryable) {
console.error("\u{1F4A1} This error may be recoverable. Please try again.");
}
process.exit(1);
}
console.error("\u274C An unexpected error occurred.");
process.exit(1);
}
/**
* Convert Node.js error to StackMemoryError
*/
static fromNodeError(nodeError, context = {}) {
const code = nodeError.code;
switch (code) {
case "ENOENT":
return new SystemError(
`File or directory not found: ${nodeError.path}`,
"FS_001" /* FILE_NOT_FOUND */,
{ ...context, path: nodeError.path },
nodeError
);
case "EACCES":
case "EPERM":
return new SystemError(
`Permission denied: ${nodeError.path}`,
"SYS_005" /* PERMISSION_DENIED */,
{ ...context, path: nodeError.path },
nodeError
);
case "ENOSPC":
return new SystemError(
"No space left on device",
"FS_002" /* DISK_FULL */,
context,
nodeError
);
case "ETIMEDOUT":
return new SystemError(
"Operation timed out",
"SYS_010" /* OPERATION_TIMEOUT */,
context,
nodeError
);
default:
return new SystemError(
nodeError.message,
"SYS_009" /* UNKNOWN_ERROR */,
{ ...context, nodeErrorCode: code },
nodeError
);
}
}
/**
* Safely execute an operation with optional fallback
*/
static async safeExecute(operation, operationName, fallback) {
try {
return await operation();
} catch (error) {
if (fallback !== void 0) {
return fallback;
}
ErrorHandler.handle(error, operationName);
}
}
/**
* Execute with automatic retry and exponential backoff
*/
static async withRetry(operation, operationName, maxRetries = ErrorHandler.MAX_RETRIES) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await operation();
ErrorHandler.retryMap.delete(operationName);
return result;
} catch (error) {
lastError = error;
if (error instanceof StackMemoryError && !error.isRetryable) {
ErrorHandler.handle(error, operationName);
}
if (attempt === maxRetries) {
break;
}
const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 5e3);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
ErrorHandler.handle(
lastError,
`${operationName} (after ${maxRetries} attempts)`
);
}
/**
* Create a circuit breaker for an operation
*/
static createCircuitBreaker(operation, operationName, threshold = 5) {
let failures = 0;
let lastFailure = 0;
const resetTimeout = 3e4;
return async () => {
const now = Date.now();
if (now - lastFailure > resetTimeout) {
failures = 0;
}
if (failures >= threshold) {
throw new SystemError(
`Circuit breaker open for '${operationName}'`,
"SYS_007" /* SERVICE_UNAVAILABLE */,
{ operationName, failures, threshold }
);
}
try {
const result = await operation();
failures = 0;
return result;
} catch (error) {
failures++;
lastFailure = now;
throw error;
}
};
}
}
const validateInput = (value, name, validator) => {
if (!validator(value)) {
throw new ValidationError(
`Invalid ${name}: ${String(value)}`,
"VAL_002" /* INVALID_INPUT */,
{ name, value }
);
}
};
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email) || email.length > 254) {
throw new ValidationError(
`Invalid email format: ${email}`,
"VAL_002" /* INVALID_INPUT */,
{ email }
);
}
};
const validatePath = (filePath) => {
if (!filePath || filePath.includes("..") || filePath.includes("\0")) {
throw new ValidationError(
`Invalid path: ${filePath}`,
"VAL_002" /* INVALID_INPUT */,
{ path: filePath }
);
}
};
export * from "./error-utils.js";
export {
DatabaseError,
ErrorCode,
ErrorHandler,
FrameError,
IntegrationError,
MCPError,
ProjectError,
StackMemoryError,
SystemError,
TaskError,
ValidationError,
createErrorHandler,
getErrorMessage,
getUserFriendlyMessage,
isRetryableError,
isStackMemoryError,
validateEmail,
validateInput,
validatePath,
wrapError
};
//# sourceMappingURL=index.js.map