chore: add tm-core package with tag and tasks.json

This commit is contained in:
Ralph Khreish
2025-08-06 20:09:29 +02:00
parent df26c65632
commit aee1996dc2
37 changed files with 11766 additions and 12 deletions

View File

@@ -0,0 +1,45 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
env: {
node: true,
es2022: true
},
extends: ['eslint:recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
rules: {
// General code quality
'no-console': 'warn',
'prefer-const': 'error',
'no-var': 'error',
'object-shorthand': 'error',
'prefer-template': 'error',
'no-duplicate-imports': 'error',
// TypeScript specific rules (basic)
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }]
},
ignorePatterns: [
'dist/',
'node_modules/',
'coverage/',
'*.js',
'!.eslintrc.cjs'
],
overrides: [
{
files: ['**/*.test.ts', '**/*.spec.ts'],
env: {
jest: true
},
rules: {
'no-console': 'off'
}
}
]
};

83
packages/tm-core/.gitignore vendored Normal file
View File

@@ -0,0 +1,83 @@
# Dependencies
node_modules/
*.pnp
.pnp.js
# Build output
dist/
build/
*.tsbuildinfo
# Coverage reports
coverage/
*.lcov
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

View File

@@ -0,0 +1,14 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "lf",
"quoteProps": "as-needed",
"bracketSameLine": false,
"proseWrap": "preserve"
}

View File

@@ -0,0 +1,70 @@
# Changelog
All notable changes to the @task-master/tm-core package will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Initial package structure and configuration
- TypeScript support with strict mode
- Dual ESM/CJS build system with tsup
- Jest testing framework with TypeScript support
- ESLint and Prettier for code quality
- Modular architecture with barrel exports
- Placeholder implementations for all modules
- Comprehensive documentation and README
### Development Infrastructure
- tsup configuration for dual format builds
- Jest configuration with ESM support
- ESLint configuration with TypeScript rules
- Prettier configuration for consistent formatting
- Complete package.json with all required fields
- TypeScript configuration with strict settings
- .gitignore for development files
### Package Structure
- `src/types/` - TypeScript type definitions (placeholder)
- `src/providers/` - AI provider implementations (placeholder)
- `src/storage/` - Storage layer abstractions (placeholder)
- `src/parser/` - Task parsing utilities (placeholder)
- `src/utils/` - Common utility functions (placeholder)
- `src/errors/` - Custom error classes (placeholder)
- `tests/` - Test directories and setup
## [1.0.0] - TBD
### Planned Features
- Complete TypeScript type system
- AI provider implementations
- Storage adapters
- Task parsing capabilities
- Comprehensive utility functions
- Custom error handling
- Full test coverage
- Complete documentation
---
## Release Notes
### Version 1.0.0 (Coming Soon)
This will be the first stable release of tm-core with complete implementations of all modules. Currently, all modules contain placeholder implementations to establish the package structure and enable development of dependent packages.
### Development Status
- ✅ Package structure and configuration
- ✅ Build and test infrastructure
- ✅ Development tooling setup
- 🚧 TypeScript types implementation (Task 116)
- 🚧 AI provider system (Task 117)
- 🚧 Storage layer (Task 118)
- 🚧 Task parser (Task 119)
- 🚧 Utility functions (Task 120)
- 🚧 Error handling (Task 121)
- 🚧 Configuration system (Task 122)
- 🚧 Testing infrastructure (Task 123)
- 🚧 Documentation (Task 124)
- 🚧 Package finalization (Task 125)

226
packages/tm-core/README.md Normal file
View File

@@ -0,0 +1,226 @@
# @task-master/tm-core
Core library for Task Master AI - providing task management and orchestration capabilities with TypeScript support.
## Overview
`tm-core` is the foundational library that powers Task Master AI's task management system. It provides a comprehensive set of tools for creating, managing, and orchestrating tasks with AI integration.
## Features
- **TypeScript-first**: Built with full TypeScript support and strict type checking
- **Dual Format**: Supports both ESM and CommonJS with automatic format detection
- **Modular Architecture**: Clean separation of concerns with dedicated modules for different functionality
- **AI Provider Integration**: Pluggable AI provider system for task generation and management
- **Flexible Storage**: Abstracted storage layer supporting different persistence strategies
- **Task Parsing**: Advanced parsing capabilities for various task definition formats
- **Error Handling**: Comprehensive error system with specific error types
- **Testing**: Complete test coverage with Jest and TypeScript support
## Installation
```bash
npm install @task-master/tm-core
```
## Usage
### Basic Usage
```typescript
import { generateTaskId, PlaceholderTask } from '@task-master/tm-core';
// Generate a unique task ID
const taskId = generateTaskId();
// Create a task (coming soon - full implementation)
const task: PlaceholderTask = {
id: taskId,
title: 'My Task',
status: 'pending',
priority: 'medium'
};
```
### Modular Imports
You can import specific modules to reduce bundle size:
```typescript
// Import types only
import type { TaskId, TaskStatus } from '@task-master/tm-core/types';
// Import utilities
import { generateTaskId, formatDate } from '@task-master/tm-core/utils';
// Import providers
import { PlaceholderProvider } from '@task-master/tm-core/providers';
// Import storage
import { PlaceholderStorage } from '@task-master/tm-core/storage';
// Import parsers
import { PlaceholderParser } from '@task-master/tm-core/parser';
// Import errors
import { TmCoreError, TaskNotFoundError } from '@task-master/tm-core/errors';
```
## Architecture
The library is organized into several key modules:
- **types/**: TypeScript type definitions and interfaces
- **providers/**: AI provider implementations for task generation
- **storage/**: Storage adapters for different persistence strategies
- **parser/**: Task parsing utilities for various formats
- **utils/**: Common utility functions and helpers
- **errors/**: Custom error classes and error handling
## Development
### Prerequisites
- Node.js >= 18.0.0
- npm or yarn
### Setup
```bash
# Install dependencies
npm install
# Build the library
npm run build
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Lint code
npm run lint
# Format code
npm run format
```
### Scripts
- `build`: Build the library for both ESM and CJS formats
- `build:watch`: Build in watch mode for development
- `test`: Run the test suite
- `test:watch`: Run tests in watch mode
- `test:coverage`: Run tests with coverage reporting
- `lint`: Lint TypeScript files
- `lint:fix`: Lint and auto-fix issues
- `format`: Format code with Prettier
- `format:check`: Check code formatting
- `typecheck`: Type-check without emitting files
- `clean`: Clean build artifacts
- `dev`: Development mode with watch
## ESM and CommonJS Support
This package supports both ESM and CommonJS formats automatically:
```javascript
// ESM
import { generateTaskId } from '@task-master/tm-core';
// CommonJS
const { generateTaskId } = require('@task-master/tm-core');
```
## Roadmap
This is the initial package structure. The following features are planned for implementation:
### Task 116: TypeScript Types
- [ ] Complete type definitions for tasks, projects, and configurations
- [ ] Zod schema validation
- [ ] Generic type utilities
### Task 117: AI Provider System
- [ ] Base provider interface
- [ ] Anthropic Claude integration
- [ ] OpenAI integration
- [ ] Perplexity integration
- [ ] Provider factory and registry
### Task 118: Storage Layer
- [ ] File system storage adapter
- [ ] Memory storage adapter
- [ ] Storage interface and factory
### Task 119: Task Parser
- [ ] PRD parser implementation
- [ ] Markdown parser
- [ ] JSON task format parser
- [ ] Validation utilities
### Task 120: Utility Functions
- [ ] Task ID generation
- [ ] Date formatting
- [ ] Validation helpers
- [ ] File system utilities
### Task 121: Error Handling
- [ ] Task-specific errors
- [ ] Storage errors
- [ ] Provider errors
- [ ] Validation errors
### Task 122: Configuration System
- [ ] Configuration schema
- [ ] Default configurations
- [ ] Environment variable support
### Task 123: Testing Infrastructure
- [ ] Unit test coverage
- [ ] Integration tests
- [ ] Mock utilities
### Task 124: Documentation
- [ ] API documentation
- [ ] Usage examples
- [ ] Migration guides
### Task 125: Package Finalization
- [ ] Final testing and validation
- [ ] Release preparation
- [ ] CI/CD integration
## Implementation Checklist
### ✅ Task 115: Initialize tm-core Package Structure (COMPLETED)
- [x] Create tm-core directory structure and base configuration files
- [x] Configure build and test infrastructure
- [x] Create barrel export files for all directories
- [x] Add development tooling and documentation
- [x] Validate package structure and prepare for development
### 🚧 Remaining Implementation Tasks
- [ ] **Task 116**: TypeScript Types - Complete type definitions for tasks, projects, and configurations
- [ ] **Task 117**: AI Provider System - Base provider interface and integrations
- [ ] **Task 118**: Storage Layer - File system and memory storage adapters
- [ ] **Task 119**: Task Parser - PRD, Markdown, and JSON parsers
- [ ] **Task 120**: Utility Functions - Task ID generation, validation helpers
- [ ] **Task 121**: Error Handling - Task-specific and validation errors
- [ ] **Task 122**: Configuration System - Schema and environment support
- [ ] **Task 123**: Testing Infrastructure - Complete unit and integration tests
- [ ] **Task 124**: Documentation - API docs and usage examples
- [ ] **Task 125**: Package Finalization - Release preparation and CI/CD
## Contributing
This package is part of the Task Master AI project. Please refer to the main project's contributing guidelines.
## License
MIT - See the main project's LICENSE file for details.
## Support
For questions and support, please refer to the main Task Master AI documentation.

View File

@@ -0,0 +1,50 @@
/** @type {import('jest').Config} */
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: [
'**/tests/**/*.test.ts',
'**/tests/**/*.spec.ts',
'**/__tests__/**/*.ts',
'**/?(*.)+(spec|test).ts'
],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
tsconfig: {
module: 'ESNext',
target: 'ES2022'
}
}
]
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^@/types/(.*)$': '<rootDir>/src/types/$1',
'^@/providers/(.*)$': '<rootDir>/src/providers/$1',
'^@/storage/(.*)$': '<rootDir>/src/storage/$1',
'^@/parser/(.*)$': '<rootDir>/src/parser/$1',
'^@/utils/(.*)$': '<rootDir>/src/utils/$1',
'^@/errors/(.*)$': '<rootDir>/src/errors/$1'
},
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/**/index.ts'],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
testTimeout: 10000,
verbose: true,
clearMocks: true,
restoreMocks: true
};

7021
packages/tm-core/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
{
"name": "@task-master/tm-core",
"version": "1.0.0",
"description": "Core library for Task Master - TypeScript task management system",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./types": {
"types": "./dist/types/index.d.ts",
"import": "./dist/types/index.js",
"require": "./dist/types/index.cjs"
},
"./providers": {
"types": "./dist/providers/index.d.ts",
"import": "./dist/providers/index.js",
"require": "./dist/providers/index.cjs"
},
"./storage": {
"types": "./dist/storage/index.d.ts",
"import": "./dist/storage/index.js",
"require": "./dist/storage/index.cjs"
},
"./parser": {
"types": "./dist/parser/index.d.ts",
"import": "./dist/parser/index.js",
"require": "./dist/parser/index.cjs"
},
"./utils": {
"types": "./dist/utils/index.d.ts",
"import": "./dist/utils/index.js",
"require": "./dist/utils/index.cjs"
},
"./errors": {
"types": "./dist/errors/index.d.ts",
"import": "./dist/errors/index.js",
"require": "./dist/errors/index.cjs"
}
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src --ext .ts",
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"zod": "^3.22.4"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.11.30",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"eslint": "^8.57.0",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tsup": "^8.0.2",
"typescript": "^5.4.3"
},
"engines": {
"node": ">=18.0.0"
},
"files": ["dist", "README.md", "CHANGELOG.md"],
"keywords": ["task-management", "typescript", "ai", "prd", "parser"],
"author": "Task Master AI",
"license": "MIT"
}

View File

@@ -0,0 +1,30 @@
/**
* @fileoverview Zod validation schema for IConfiguration interface
* This file provides the main schema export for configuration validation
*/
export {
configurationSchema,
partialConfigurationSchema,
modelConfigSchema,
providerConfigSchema,
taskSettingsSchema,
tagSettingsSchema,
storageSettingsSchema,
retrySettingsSchema,
loggingSettingsSchema,
loggingConfigSchema, // Legacy alias
cacheConfigSchema,
securitySettingsSchema,
taskPrioritySchema,
taskComplexitySchema,
logLevelSchema,
storageTypeSchema,
tagNamingConventionSchema,
bufferEncodingSchema,
type ConfigurationSchema,
type PartialConfigurationSchema
} from './validation.js';
// Re-export the main schema as the default export for convenience
export { configurationSchema as default } from './validation.js';

View File

@@ -0,0 +1,238 @@
/**
* @fileoverview Zod validation schemas for configuration interfaces
*/
import { z } from 'zod';
import { TaskPriority, TaskComplexity } from '../types/index.js';
// ============================================================================
// Enum Schemas
// ============================================================================
/**
* Task priority validation schema
*/
export const taskPrioritySchema = z.enum(['low', 'medium', 'high', 'critical']);
/**
* Task complexity validation schema
*/
export const taskComplexitySchema = z.enum([
'simple',
'moderate',
'complex',
'very-complex'
]);
/**
* Log level validation schema
*/
export const logLevelSchema = z.enum(['error', 'warn', 'info', 'debug']);
/**
* Storage type validation schema
*/
export const storageTypeSchema = z.enum(['file', 'memory', 'database']);
/**
* Tag naming convention validation schema
*/
export const tagNamingConventionSchema = z.enum([
'kebab-case',
'camelCase',
'snake_case'
]);
/**
* Buffer encoding validation schema
*/
export const bufferEncodingSchema = z.enum([
'ascii',
'utf8',
'utf-8',
'utf16le',
'ucs2',
'ucs-2',
'base64',
'base64url',
'latin1',
'binary',
'hex'
]);
// ============================================================================
// Sub-interface Schemas
// ============================================================================
/**
* Model configuration validation schema
*/
export const modelConfigSchema = z.object({
main: z.string().min(1, 'Main model name is required'),
research: z.string().min(1).optional(),
fallback: z.string().min(1, 'Fallback model name is required')
});
/**
* Provider configuration validation schema
*/
export const providerConfigSchema = z.object({
name: z.string().min(1, 'Provider name is required'),
apiKey: z.string().optional(),
baseUrl: z.string().url().optional(),
options: z.record(z.unknown()).optional(),
enabled: z.boolean().optional().default(true)
});
/**
* Task settings validation schema
*/
export const taskSettingsSchema = z.object({
defaultPriority: taskPrioritySchema,
defaultComplexity: taskComplexitySchema,
maxSubtasks: z.number().int().min(1).max(100),
maxConcurrentTasks: z.number().int().min(1).max(50),
autoGenerateIds: z.boolean(),
taskIdPrefix: z.string().optional(),
validateDependencies: z.boolean(),
enableTimestamps: z.boolean(),
enableEffortTracking: z.boolean()
});
/**
* Tag settings validation schema
*/
export const tagSettingsSchema = z.object({
enableTags: z.boolean(),
defaultTag: z.string().min(1),
maxTagsPerTask: z.number().int().min(1).max(50),
autoCreateFromBranch: z.boolean(),
tagNamingConvention: tagNamingConventionSchema
});
/**
* Storage settings validation schema
*/
export const storageSettingsSchema = z.object({
type: storageTypeSchema,
basePath: z.string().optional(),
enableBackup: z.boolean(),
maxBackups: z.number().int().min(0).max(100),
enableCompression: z.boolean(),
encoding: bufferEncodingSchema,
atomicOperations: z.boolean()
});
/**
* Retry settings validation schema
*/
export const retrySettingsSchema = z.object({
retryAttempts: z.number().int().min(0).max(10),
retryDelay: z.number().int().min(100).max(60000),
maxRetryDelay: z.number().int().min(1000).max(300000),
backoffMultiplier: z.number().min(1).max(10),
requestTimeout: z.number().int().min(1000).max(600000),
retryOnNetworkError: z.boolean(),
retryOnRateLimit: z.boolean()
});
/**
* Logging settings validation schema
*/
export const loggingSettingsSchema = z.object({
enabled: z.boolean(),
level: logLevelSchema,
filePath: z.string().optional(),
logRequests: z.boolean(),
logPerformance: z.boolean(),
logStackTraces: z.boolean(),
maxFileSize: z.number().min(1).max(1000),
maxFiles: z.number().int().min(1).max(100)
});
/**
* Security settings validation schema
*/
export const securitySettingsSchema = z.object({
validateApiKeys: z.boolean(),
enableRateLimit: z.boolean(),
maxRequestsPerMinute: z.number().int().min(1).max(10000),
sanitizeInputs: z.boolean(),
maxPromptLength: z.number().int().min(100).max(1000000),
allowedFileExtensions: z.array(z.string().regex(/^\.[a-zA-Z0-9]+$/)),
enableCors: z.boolean()
});
// ============================================================================
// Main Configuration Schema
// ============================================================================
/**
* Base configuration object schema (without refinements)
*/
const baseConfigurationSchema = z.object({
projectPath: z.string().min(1, 'Project path is required'),
aiProvider: z.string().min(1, 'AI provider is required'),
apiKeys: z.record(z.string()),
models: modelConfigSchema,
providers: z.record(providerConfigSchema),
tasks: taskSettingsSchema,
tags: tagSettingsSchema,
storage: storageSettingsSchema,
retry: retrySettingsSchema,
logging: loggingSettingsSchema,
security: securitySettingsSchema,
custom: z.record(z.unknown()).optional(),
version: z.string().min(1, 'Version is required'),
lastUpdated: z.string().min(1, 'Last updated timestamp is required')
});
/**
* Main configuration validation schema with custom refinements
*/
export const configurationSchema = baseConfigurationSchema.refine(
(data) => {
// Custom validation: maxRetryDelay should be >= retryDelay
return data.retry.maxRetryDelay >= data.retry.retryDelay;
},
{
message: 'maxRetryDelay must be greater than or equal to retryDelay',
path: ['retry', 'maxRetryDelay']
}
);
/**
* Partial configuration validation schema for updates
*/
export const partialConfigurationSchema = baseConfigurationSchema.partial();
// ============================================================================
// Legacy/Alias Exports (for backwards compatibility)
// ============================================================================
/**
* Alias for loggingSettingsSchema (for backwards compatibility)
* @deprecated Use loggingSettingsSchema instead
*/
export const loggingConfigSchema = loggingSettingsSchema;
/**
* Cache configuration validation schema (stub - not implemented in IConfiguration yet)
* This is exported for consistency with config-schema.ts exports
*/
export const cacheConfigSchema = z
.object({
enabled: z.boolean().optional().default(false),
ttl: z.number().int().min(1).optional().default(300),
maxSize: z.number().int().min(1).optional().default(1000)
})
.optional();
// ============================================================================
// Type exports for runtime validation
// ============================================================================
export type ConfigurationSchema = z.infer<typeof configurationSchema>;
export type PartialConfigurationSchema = z.infer<
typeof partialConfigurationSchema
>;

View File

@@ -0,0 +1,59 @@
/**
* @fileoverview Custom error classes for the tm-core package
* This file exports all custom error types and error handling utilities
*/
// Error implementations will be defined here
// export * from './task-errors.js';
// export * from './storage-errors.js';
// export * from './provider-errors.js';
// export * from './validation-errors.js';
// Placeholder exports - these will be implemented in later tasks
/**
* Base error class for all tm-core errors
* @deprecated This is a placeholder class that will be properly implemented in later tasks
*/
export class TmCoreError extends Error {
constructor(
message: string,
public code?: string
) {
super(message);
this.name = 'TmCoreError';
}
}
/**
* Error thrown when a task is not found
* @deprecated This is a placeholder class that will be properly implemented in later tasks
*/
export class TaskNotFoundError extends TmCoreError {
constructor(taskId: string) {
super(`Task not found: ${taskId}`, 'TASK_NOT_FOUND');
this.name = 'TaskNotFoundError';
}
}
/**
* Error thrown when validation fails
* @deprecated This is a placeholder class that will be properly implemented in later tasks
*/
export class ValidationError extends TmCoreError {
constructor(message: string) {
super(message, 'VALIDATION_ERROR');
this.name = 'ValidationError';
}
}
/**
* Error thrown when storage operations fail
* @deprecated This is a placeholder class that will be properly implemented in later tasks
*/
export class StorageError extends TmCoreError {
constructor(message: string) {
super(message, 'STORAGE_ERROR');
this.name = 'StorageError';
}
}

View File

@@ -0,0 +1,324 @@
/**
* @fileoverview Base error class for Task Master operations
* Provides comprehensive error handling with metadata, context, and serialization support
*/
/**
* Error codes used throughout the Task Master system
*/
export const ERROR_CODES = {
// File system errors
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
FILE_READ_ERROR: 'FILE_READ_ERROR',
FILE_WRITE_ERROR: 'FILE_WRITE_ERROR',
// Parsing errors
PARSE_ERROR: 'PARSE_ERROR',
JSON_PARSE_ERROR: 'JSON_PARSE_ERROR',
YAML_PARSE_ERROR: 'YAML_PARSE_ERROR',
// Validation errors
VALIDATION_ERROR: 'VALIDATION_ERROR',
SCHEMA_VALIDATION_ERROR: 'SCHEMA_VALIDATION_ERROR',
TYPE_VALIDATION_ERROR: 'TYPE_VALIDATION_ERROR',
// API and network errors
API_ERROR: 'API_ERROR',
NETWORK_ERROR: 'NETWORK_ERROR',
AUTHENTICATION_ERROR: 'AUTHENTICATION_ERROR',
AUTHORIZATION_ERROR: 'AUTHORIZATION_ERROR',
// Task management errors
TASK_NOT_FOUND: 'TASK_NOT_FOUND',
TASK_DEPENDENCY_ERROR: 'TASK_DEPENDENCY_ERROR',
TASK_STATUS_ERROR: 'TASK_STATUS_ERROR',
// Storage errors
STORAGE_ERROR: 'STORAGE_ERROR',
DATABASE_ERROR: 'DATABASE_ERROR',
// Configuration errors
CONFIG_ERROR: 'CONFIG_ERROR',
MISSING_CONFIGURATION: 'MISSING_CONFIGURATION',
INVALID_CONFIGURATION: 'INVALID_CONFIGURATION',
// Provider errors
PROVIDER_ERROR: 'PROVIDER_ERROR',
PROVIDER_NOT_FOUND: 'PROVIDER_NOT_FOUND',
PROVIDER_INITIALIZATION_ERROR: 'PROVIDER_INITIALIZATION_ERROR',
// Generic errors
INTERNAL_ERROR: 'INTERNAL_ERROR',
UNKNOWN_ERROR: 'UNKNOWN_ERROR'
} as const;
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
/**
* Error context interface for additional error metadata
*/
export interface ErrorContext {
/** Additional details about the error */
details?: any;
/** Error timestamp */
timestamp?: Date;
/** Operation that failed */
operation?: string;
/** Resource identifier related to the error */
resource?: string;
/** Stack of operations leading to the error */
operationStack?: string[];
/** User-safe message for display */
userMessage?: string;
/** Internal error identifier for debugging */
errorId?: string;
/** Additional metadata */
metadata?: Record<string, any>;
}
/**
* Serializable error representation
*/
export interface SerializableError {
name: string;
message: string;
code: string;
context: ErrorContext;
stack?: string;
cause?: SerializableError;
}
/**
* Base error class for all Task Master operations
*
* Provides comprehensive error handling with:
* - Error codes for programmatic handling
* - Rich context and metadata support
* - Error chaining with cause property
* - Serialization for logging and transport
* - Sanitization for user-facing messages
*
* @example
* ```typescript
* try {
* // Some operation that might fail
* throw new TaskMasterError(
* 'Failed to parse task file',
* ERROR_CODES.PARSE_ERROR,
* {
* details: { filename: 'tasks.json', line: 42 },
* operation: 'parseTaskFile',
* userMessage: 'There was an error reading your task file'
* }
* );
* } catch (error) {
* console.error(error.toJSON());
* throw new TaskMasterError(
* 'Operation failed',
* ERROR_CODES.INTERNAL_ERROR,
* { operation: 'processTask' },
* error
* );
* }
* ```
*/
export class TaskMasterError extends Error {
/** Error code for programmatic handling */
public readonly code: string;
/** Rich context and metadata */
public readonly context: ErrorContext;
/** Original error that caused this error (for error chaining) */
public readonly cause?: Error;
/** Timestamp when error was created */
public readonly timestamp: Date;
/**
* Create a new TaskMasterError
*
* @param message - Human-readable error message
* @param code - Error code from ERROR_CODES
* @param context - Additional error context and metadata
* @param cause - Original error that caused this error (for chaining)
*/
constructor(
message: string,
code: string = ERROR_CODES.UNKNOWN_ERROR,
context: ErrorContext = {},
cause?: Error
) {
super(message);
// Set error name
this.name = 'TaskMasterError';
// Set properties
this.code = code;
this.cause = cause;
this.timestamp = new Date();
// Merge context with defaults
this.context = {
timestamp: this.timestamp,
...context
};
// Fix prototype chain for proper instanceof checks
Object.setPrototypeOf(this, TaskMasterError.prototype);
// Maintain proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TaskMasterError);
}
// If we have a cause error, append its stack trace
if (cause && cause.stack) {
this.stack = this.stack + '\nCaused by: ' + cause.stack;
}
}
/**
* Get a user-friendly error message
* Falls back to the main message if no user message is provided
*/
public getUserMessage(): string {
return this.context.userMessage || this.message;
}
/**
* Get sanitized error details safe for user display
* Removes sensitive information and internal details
*/
public getSanitizedDetails(): Record<string, any> {
const { details, userMessage, resource, operation } = this.context;
return {
code: this.code,
message: this.getUserMessage(),
...(resource && { resource }),
...(operation && { operation }),
...(details &&
typeof details === 'object' &&
!this.containsSensitiveInfo(details) && { details })
};
}
/**
* Check if error details contain potentially sensitive information
*/
private containsSensitiveInfo(obj: any): boolean {
if (typeof obj !== 'object' || obj === null) return false;
const sensitiveKeys = [
'password',
'token',
'key',
'secret',
'auth',
'credential'
];
const objString = JSON.stringify(obj).toLowerCase();
return sensitiveKeys.some((key) => objString.includes(key));
}
/**
* Convert error to JSON for serialization
* Includes all error information for logging and debugging
*/
public toJSON(): SerializableError {
const result: SerializableError = {
name: this.name,
message: this.message,
code: this.code,
context: this.context,
stack: this.stack
};
// Include serialized cause if present
if (this.cause) {
if (this.cause instanceof TaskMasterError) {
result.cause = this.cause.toJSON();
} else {
result.cause = {
name: this.cause.name,
message: this.cause.message,
code: ERROR_CODES.UNKNOWN_ERROR,
context: {},
stack: this.cause.stack
};
}
}
return result;
}
/**
* Convert error to string representation
* Provides formatted output for logging and debugging
*/
public toString(): string {
let result = `${this.name}[${this.code}]: ${this.message}`;
if (this.context.operation) {
result += ` (operation: ${this.context.operation})`;
}
if (this.context.resource) {
result += ` (resource: ${this.context.resource})`;
}
if (this.cause) {
result += `\nCaused by: ${this.cause.toString()}`;
}
return result;
}
/**
* Check if this error is of a specific code
*/
public is(code: string): boolean {
return this.code === code;
}
/**
* Check if this error or any error in its cause chain is of a specific code
*/
public hasCode(code: string): boolean {
if (this.is(code)) return true;
if (this.cause instanceof TaskMasterError) {
return this.cause.hasCode(code);
}
return false;
}
/**
* Create a new error with additional context
*/
public withContext(
additionalContext: Partial<ErrorContext>
): TaskMasterError {
return new TaskMasterError(
this.message,
this.code,
{ ...this.context, ...additionalContext },
this.cause
);
}
/**
* Create a new error wrapping this one as the cause
*/
public wrap(
message: string,
code: string = ERROR_CODES.INTERNAL_ERROR,
context: ErrorContext = {}
): TaskMasterError {
return new TaskMasterError(message, code, context, this);
}
}

View File

@@ -0,0 +1,30 @@
/**
* @fileoverview Main entry point for the tm-core package
* This file exports all public APIs from the core Task Master library
*/
// Re-export types
export type * from './types/index';
// Re-export interfaces
export type * from './interfaces/index';
export * from './interfaces/index';
// Re-export providers
export * from './providers/index';
// Re-export storage
export * from './storage/index';
// Re-export parser
export * from './parser/index';
// Re-export utilities
export * from './utils/index';
// Re-export errors
export * from './errors/index';
// Package metadata
export const version = '1.0.0';
export const name = '@task-master/tm-core';

View File

@@ -0,0 +1,423 @@
/**
* @fileoverview AI Provider interface definitions for the tm-core package
* This file defines the contract for all AI provider implementations
*/
/**
* Options for AI completion requests
*/
export interface AIOptions {
/** Temperature for response randomness (0.0 to 1.0) */
temperature?: number;
/** Maximum number of tokens to generate */
maxTokens?: number;
/** Whether to use streaming responses */
stream?: boolean;
/** Top-p sampling parameter (0.0 to 1.0) */
topP?: number;
/** Frequency penalty to reduce repetition (-2.0 to 2.0) */
frequencyPenalty?: number;
/** Presence penalty to encourage new topics (-2.0 to 2.0) */
presencePenalty?: number;
/** Stop sequences to halt generation */
stop?: string | string[];
/** Custom system prompt override */
systemPrompt?: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Number of retry attempts on failure */
retries?: number;
}
/**
* Response from AI completion request
*/
export interface AIResponse {
/** Generated text content */
content: string;
/** Token count for the request */
inputTokens: number;
/** Token count for the response */
outputTokens: number;
/** Total tokens used */
totalTokens: number;
/** Cost in USD (if available) */
cost?: number;
/** Model used for generation */
model: string;
/** Provider name */
provider: string;
/** Response timestamp */
timestamp: string;
/** Request duration in milliseconds */
duration: number;
/** Whether the response was cached */
cached?: boolean;
/** Finish reason (completed, length, stop, etc.) */
finishReason?: string;
}
/**
* AI model information
*/
export interface AIModel {
/** Model identifier */
id: string;
/** Human-readable model name */
name: string;
/** Model description */
description?: string;
/** Maximum context length in tokens */
contextLength: number;
/** Input cost per 1K tokens in USD */
inputCostPer1K?: number;
/** Output cost per 1K tokens in USD */
outputCostPer1K?: number;
/** Whether the model supports function calling */
supportsFunctions?: boolean;
/** Whether the model supports vision/image inputs */
supportsVision?: boolean;
/** Whether the model supports streaming */
supportsStreaming?: boolean;
}
/**
* Provider capabilities and metadata
*/
export interface ProviderInfo {
/** Provider name */
name: string;
/** Provider display name */
displayName: string;
/** Provider description */
description?: string;
/** Base API URL */
baseUrl?: string;
/** Available models */
models: AIModel[];
/** Default model ID */
defaultModel: string;
/** Whether the provider requires an API key */
requiresApiKey: boolean;
/** Supported features */
features: {
streaming?: boolean;
functions?: boolean;
vision?: boolean;
embeddings?: boolean;
};
}
/**
* Interface for AI provider implementations
* All AI providers must implement this interface
*/
export interface IAIProvider {
/**
* Generate a text completion from a prompt
* @param prompt - Input prompt text
* @param options - Optional generation parameters
* @returns Promise that resolves to AI response
*/
generateCompletion(prompt: string, options?: AIOptions): Promise<AIResponse>;
/**
* Generate a streaming completion (if supported)
* @param prompt - Input prompt text
* @param options - Optional generation parameters
* @returns AsyncIterator of response chunks
*/
generateStreamingCompletion(
prompt: string,
options?: AIOptions
): AsyncIterator<Partial<AIResponse>>;
/**
* Calculate token count for given text
* @param text - Text to count tokens for
* @param model - Optional model to use for counting
* @returns Number of tokens
*/
calculateTokens(text: string, model?: string): number;
/**
* Get the provider name
* @returns Provider name string
*/
getName(): string;
/**
* Get current model being used
* @returns Current model ID
*/
getModel(): string;
/**
* Set the model to use for requests
* @param model - Model ID to use
*/
setModel(model: string): void;
/**
* Get the default model for this provider
* @returns Default model ID
*/
getDefaultModel(): string;
/**
* Check if the provider is available and configured
* @returns Promise that resolves to availability status
*/
isAvailable(): Promise<boolean>;
/**
* Get provider information and capabilities
* @returns Provider information object
*/
getProviderInfo(): ProviderInfo;
/**
* Get available models for this provider
* @returns Array of available models
*/
getAvailableModels(): AIModel[];
/**
* Validate API key or credentials
* @returns Promise that resolves to validation status
*/
validateCredentials(): Promise<boolean>;
/**
* Get usage statistics if available
* @returns Promise that resolves to usage stats or null
*/
getUsageStats(): Promise<ProviderUsageStats | null>;
/**
* Initialize the provider (set up connections, validate config, etc.)
* @returns Promise that resolves when initialization is complete
*/
initialize(): Promise<void>;
/**
* Clean up and close provider connections
* @returns Promise that resolves when cleanup is complete
*/
close(): Promise<void>;
}
/**
* Usage statistics for a provider
*/
export interface ProviderUsageStats {
/** Total requests made */
totalRequests: number;
/** Total tokens consumed */
totalTokens: number;
/** Total cost in USD */
totalCost: number;
/** Requests today */
requestsToday: number;
/** Tokens used today */
tokensToday: number;
/** Cost today */
costToday: number;
/** Average response time in milliseconds */
averageResponseTime: number;
/** Success rate (0.0 to 1.0) */
successRate: number;
/** Last request timestamp */
lastRequestAt?: string;
/** Rate limit information if available */
rateLimits?: {
requestsPerMinute: number;
tokensPerMinute: number;
requestsRemaining: number;
tokensRemaining: number;
resetTime: string;
};
}
/**
* Configuration for AI provider instances
*/
export interface AIProviderConfig {
/** API key for the provider */
apiKey: string;
/** Base URL override */
baseUrl?: string;
/** Default model to use */
model?: string;
/** Default generation options */
defaultOptions?: AIOptions;
/** Request timeout in milliseconds */
timeout?: number;
/** Maximum retry attempts */
maxRetries?: number;
/** Custom headers to include in requests */
headers?: Record<string, string>;
/** Enable request/response logging */
enableLogging?: boolean;
/** Enable usage tracking */
enableUsageTracking?: boolean;
}
/**
* Abstract base class for AI provider implementations
* Provides common functionality and enforces the interface
*/
export abstract class BaseAIProvider implements IAIProvider {
protected config: AIProviderConfig;
protected currentModel: string;
protected usageStats: ProviderUsageStats | null = null;
constructor(config: AIProviderConfig) {
this.config = config;
this.currentModel = config.model || this.getDefaultModel();
if (config.enableUsageTracking) {
this.initializeUsageTracking();
}
}
// Abstract methods that must be implemented by concrete classes
abstract generateCompletion(
prompt: string,
options?: AIOptions
): Promise<AIResponse>;
abstract generateStreamingCompletion(
prompt: string,
options?: AIOptions
): AsyncIterator<Partial<AIResponse>>;
abstract calculateTokens(text: string, model?: string): number;
abstract getName(): string;
abstract getDefaultModel(): string;
abstract isAvailable(): Promise<boolean>;
abstract getProviderInfo(): ProviderInfo;
abstract validateCredentials(): Promise<boolean>;
abstract initialize(): Promise<void>;
abstract close(): Promise<void>;
// Implemented methods with common functionality
getModel(): string {
return this.currentModel;
}
setModel(model: string): void {
const availableModels = this.getAvailableModels();
const modelExists = availableModels.some((m) => m.id === model);
if (!modelExists) {
throw new Error(
`Model "${model}" is not available for provider "${this.getName()}"`
);
}
this.currentModel = model;
}
getAvailableModels(): AIModel[] {
return this.getProviderInfo().models;
}
async getUsageStats(): Promise<ProviderUsageStats | null> {
return this.usageStats;
}
/**
* Initialize usage tracking
*/
protected initializeUsageTracking(): void {
this.usageStats = {
totalRequests: 0,
totalTokens: 0,
totalCost: 0,
requestsToday: 0,
tokensToday: 0,
costToday: 0,
averageResponseTime: 0,
successRate: 1.0
};
}
/**
* Update usage statistics after a request
* @param response - AI response to record
* @param duration - Request duration in milliseconds
* @param success - Whether the request was successful
*/
protected updateUsageStats(
response: AIResponse,
duration: number,
success: boolean
): void {
if (!this.usageStats) return;
this.usageStats.totalRequests++;
this.usageStats.totalTokens += response.totalTokens;
if (response.cost) {
this.usageStats.totalCost += response.cost;
}
// Update daily stats (simplified - would need proper date tracking)
this.usageStats.requestsToday++;
this.usageStats.tokensToday += response.totalTokens;
if (response.cost) {
this.usageStats.costToday += response.cost;
}
// Update average response time
const totalTime =
this.usageStats.averageResponseTime * (this.usageStats.totalRequests - 1);
this.usageStats.averageResponseTime =
(totalTime + duration) / this.usageStats.totalRequests;
// Update success rate
const successCount = Math.floor(
this.usageStats.successRate * (this.usageStats.totalRequests - 1)
);
const newSuccessCount = successCount + (success ? 1 : 0);
this.usageStats.successRate =
newSuccessCount / this.usageStats.totalRequests;
this.usageStats.lastRequestAt = new Date().toISOString();
}
/**
* Merge user options with default options
* @param userOptions - User-provided options
* @returns Merged options object
*/
protected mergeOptions(userOptions?: AIOptions): AIOptions {
return {
temperature: 0.7,
maxTokens: 2000,
stream: false,
topP: 1.0,
frequencyPenalty: 0.0,
presencePenalty: 0.0,
timeout: 30000,
retries: 3,
...this.config.defaultOptions,
...userOptions
};
}
/**
* Validate prompt input
* @param prompt - Prompt to validate
* @throws Error if prompt is invalid
*/
protected validatePrompt(prompt: string): void {
if (!prompt || typeof prompt !== 'string') {
throw new Error('Prompt must be a non-empty string');
}
if (prompt.trim().length === 0) {
throw new Error('Prompt cannot be empty or only whitespace');
}
}
}

View File

@@ -0,0 +1,409 @@
/**
* @fileoverview Configuration interface definitions for the tm-core package
* This file defines the contract for configuration management
*/
import type { TaskPriority, TaskComplexity } from '../types/index';
/**
* Model configuration for different AI roles
*/
export interface ModelConfig {
/** Primary model for task generation and updates */
main: string;
/** Research model for enhanced task analysis (optional) */
research?: string;
/** Fallback model when primary fails */
fallback: string;
}
/**
* AI provider configuration
*/
export interface ProviderConfig {
/** Provider name (e.g., 'anthropic', 'openai', 'perplexity') */
name: string;
/** API key for the provider */
apiKey?: string;
/** Base URL override */
baseUrl?: string;
/** Custom configuration options */
options?: Record<string, unknown>;
/** Whether this provider is enabled */
enabled?: boolean;
}
/**
* Task generation and management settings
*/
export interface TaskSettings {
/** Default priority for new tasks */
defaultPriority: TaskPriority;
/** Default complexity for analysis */
defaultComplexity: TaskComplexity;
/** Maximum number of subtasks per task */
maxSubtasks: number;
/** Maximum number of concurrent tasks */
maxConcurrentTasks: number;
/** Enable automatic task ID generation */
autoGenerateIds: boolean;
/** Task ID prefix (e.g., 'TASK-', 'TM-') */
taskIdPrefix?: string;
/** Enable task dependency validation */
validateDependencies: boolean;
/** Enable automatic timestamps */
enableTimestamps: boolean;
/** Enable effort tracking */
enableEffortTracking: boolean;
}
/**
* Tag and context management settings
*/
export interface TagSettings {
/** Enable tag-based task organization */
enableTags: boolean;
/** Default tag for new tasks */
defaultTag: string;
/** Maximum number of tags per task */
maxTagsPerTask: number;
/** Enable automatic tag creation from Git branches */
autoCreateFromBranch: boolean;
/** Tag naming convention (kebab-case, camelCase, snake_case) */
tagNamingConvention: 'kebab-case' | 'camelCase' | 'snake_case';
}
/**
* Storage and persistence settings
*/
export interface StorageSettings {
/** Storage backend type */
type: 'file' | 'memory' | 'database';
/** Base path for file storage */
basePath?: string;
/** Enable automatic backups */
enableBackup: boolean;
/** Maximum number of backups to retain */
maxBackups: number;
/** Enable compression for storage */
enableCompression: boolean;
/** File encoding for text files */
encoding: BufferEncoding;
/** Enable atomic file operations */
atomicOperations: boolean;
}
/**
* Retry and resilience settings
*/
export interface RetrySettings {
/** Number of retry attempts for failed operations */
retryAttempts: number;
/** Base delay between retries in milliseconds */
retryDelay: number;
/** Maximum delay between retries in milliseconds */
maxRetryDelay: number;
/** Exponential backoff multiplier */
backoffMultiplier: number;
/** Request timeout in milliseconds */
requestTimeout: number;
/** Enable retry for network errors */
retryOnNetworkError: boolean;
/** Enable retry for rate limit errors */
retryOnRateLimit: boolean;
}
/**
* Logging and debugging settings
*/
export interface LoggingSettings {
/** Enable logging */
enabled: boolean;
/** Log level (error, warn, info, debug) */
level: 'error' | 'warn' | 'info' | 'debug';
/** Log file path (optional) */
filePath?: string;
/** Enable request/response logging */
logRequests: boolean;
/** Enable performance metrics logging */
logPerformance: boolean;
/** Enable error stack traces */
logStackTraces: boolean;
/** Maximum log file size in MB */
maxFileSize: number;
/** Maximum number of log files to retain */
maxFiles: number;
}
/**
* Security and validation settings
*/
export interface SecuritySettings {
/** Enable API key validation */
validateApiKeys: boolean;
/** Enable request rate limiting */
enableRateLimit: boolean;
/** Maximum requests per minute */
maxRequestsPerMinute: number;
/** Enable input sanitization */
sanitizeInputs: boolean;
/** Maximum prompt length in characters */
maxPromptLength: number;
/** Allowed file extensions for imports */
allowedFileExtensions: string[];
/** Enable CORS protection */
enableCors: boolean;
}
/**
* Main configuration interface for Task Master core
*/
export interface IConfiguration {
/** Project root path */
projectPath: string;
/** Current AI provider name */
aiProvider: string;
/** API keys for different providers */
apiKeys: Record<string, string>;
/** Model configuration for different roles */
models: ModelConfig;
/** Provider configurations */
providers: Record<string, ProviderConfig>;
/** Task management settings */
tasks: TaskSettings;
/** Tag and context settings */
tags: TagSettings;
/** Storage configuration */
storage: StorageSettings;
/** Retry and resilience settings */
retry: RetrySettings;
/** Logging configuration */
logging: LoggingSettings;
/** Security settings */
security: SecuritySettings;
/** Custom user-defined settings */
custom?: Record<string, unknown>;
/** Configuration version for migration purposes */
version: string;
/** Last updated timestamp */
lastUpdated: string;
}
/**
* Partial configuration for updates (all fields optional)
*/
export type PartialConfiguration = Partial<IConfiguration>;
/**
* Configuration validation result
*/
export interface ConfigValidationResult {
/** Whether the configuration is valid */
isValid: boolean;
/** Array of error messages */
errors: string[];
/** Array of warning messages */
warnings: string[];
/** Suggested fixes */
suggestions?: string[];
}
/**
* Environment variable configuration mapping
*/
export interface EnvironmentConfig {
/** Mapping of environment variables to config paths */
variables: Record<string, string>;
/** Prefix for environment variables */
prefix: string;
/** Whether to override existing config with env vars */
override: boolean;
}
/**
* Configuration schema definition for validation
*/
export interface ConfigSchema {
/** Schema for the main configuration */
properties: Record<string, ConfigProperty>;
/** Required properties */
required: string[];
/** Additional properties allowed */
additionalProperties: boolean;
}
/**
* Configuration property schema
*/
export interface ConfigProperty {
/** Property type */
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
/** Property description */
description?: string;
/** Default value */
default?: unknown;
/** Allowed values for enums */
enum?: unknown[];
/** Minimum value (for numbers) */
minimum?: number;
/** Maximum value (for numbers) */
maximum?: number;
/** Pattern for string validation */
pattern?: string;
/** Nested properties (for objects) */
properties?: Record<string, ConfigProperty>;
/** Array item type (for arrays) */
items?: ConfigProperty;
/** Whether the property is required */
required?: boolean;
}
/**
* Default configuration factory
*/
export interface IConfigurationFactory {
/**
* Create a default configuration
* @param projectPath - Project root path
* @returns Default configuration object
*/
createDefault(projectPath: string): IConfiguration;
/**
* Merge configurations with precedence
* @param base - Base configuration
* @param override - Override configuration
* @returns Merged configuration
*/
merge(base: IConfiguration, override: PartialConfiguration): IConfiguration;
/**
* Validate configuration against schema
* @param config - Configuration to validate
* @returns Validation result
*/
validate(config: IConfiguration): ConfigValidationResult;
/**
* Load configuration from environment variables
* @param envConfig - Environment variable mapping
* @returns Partial configuration from environment
*/
loadFromEnvironment(envConfig: EnvironmentConfig): PartialConfiguration;
/**
* Get configuration schema
* @returns Configuration schema definition
*/
getSchema(): ConfigSchema;
}
/**
* Configuration manager interface
*/
export interface IConfigurationManager {
/**
* Load configuration from file or create default
* @param configPath - Path to configuration file
* @returns Promise that resolves to configuration
*/
load(configPath?: string): Promise<IConfiguration>;
/**
* Save configuration to file
* @param config - Configuration to save
* @param configPath - Optional path override
* @returns Promise that resolves when save is complete
*/
save(config: IConfiguration, configPath?: string): Promise<void>;
/**
* Update configuration with partial changes
* @param updates - Partial configuration updates
* @returns Promise that resolves to updated configuration
*/
update(updates: PartialConfiguration): Promise<IConfiguration>;
/**
* Get current configuration
* @returns Current configuration object
*/
getConfig(): IConfiguration;
/**
* Watch for configuration changes
* @param callback - Function to call when config changes
* @returns Function to stop watching
*/
watch(callback: (config: IConfiguration) => void): () => void;
/**
* Validate current configuration
* @returns Validation result
*/
validate(): ConfigValidationResult;
/**
* Reset configuration to defaults
* @returns Promise that resolves when reset is complete
*/
reset(): Promise<void>;
}
/**
* Constants for default configuration values
*/
export const DEFAULT_CONFIG_VALUES = {
MODELS: {
MAIN: 'claude-3-5-sonnet-20241022',
FALLBACK: 'gpt-4o-mini'
},
TASKS: {
DEFAULT_PRIORITY: 'medium' as TaskPriority,
DEFAULT_COMPLEXITY: 'moderate' as TaskComplexity,
MAX_SUBTASKS: 20,
MAX_CONCURRENT: 5,
TASK_ID_PREFIX: 'TASK-'
},
TAGS: {
DEFAULT_TAG: 'master',
MAX_TAGS_PER_TASK: 10,
NAMING_CONVENTION: 'kebab-case' as const
},
STORAGE: {
TYPE: 'file' as const,
ENCODING: 'utf8' as BufferEncoding,
MAX_BACKUPS: 5
},
RETRY: {
ATTEMPTS: 3,
DELAY: 1000,
MAX_DELAY: 30000,
BACKOFF_MULTIPLIER: 2,
TIMEOUT: 30000
},
LOGGING: {
LEVEL: 'info' as const,
MAX_FILE_SIZE: 10,
MAX_FILES: 5
},
SECURITY: {
MAX_REQUESTS_PER_MINUTE: 60,
MAX_PROMPT_LENGTH: 100000,
ALLOWED_EXTENSIONS: ['.txt', '.md', '.json']
},
VERSION: '1.0.0'
} as const;

View File

@@ -0,0 +1,16 @@
/**
* @fileoverview Interface definitions index for the tm-core package
* This file exports all interface definitions from their respective modules
*/
// Storage interfaces
export type * from './storage.interface';
export * from './storage.interface';
// AI Provider interfaces
export type * from './ai-provider.interface';
export * from './ai-provider.interface';
// Configuration interfaces
export type * from './configuration.interface';
export * from './configuration.interface';

View File

@@ -0,0 +1,238 @@
/**
* @fileoverview Storage interface definitions for the tm-core package
* This file defines the contract for all storage implementations
*/
import type { Task, TaskMetadata } from '../types/index';
/**
* Interface for storage operations on tasks
* All storage implementations must implement this interface
*/
export interface IStorage {
/**
* Load all tasks from storage, optionally filtered by tag
* @param tag - Optional tag to filter tasks by
* @returns Promise that resolves to an array of tasks
*/
loadTasks(tag?: string): Promise<Task[]>;
/**
* Save tasks to storage, replacing existing tasks
* @param tasks - Array of tasks to save
* @param tag - Optional tag context for the tasks
* @returns Promise that resolves when save is complete
*/
saveTasks(tasks: Task[], tag?: string): Promise<void>;
/**
* Append new tasks to existing storage without replacing
* @param tasks - Array of tasks to append
* @param tag - Optional tag context for the tasks
* @returns Promise that resolves when append is complete
*/
appendTasks(tasks: Task[], tag?: string): Promise<void>;
/**
* Update a specific task by ID
* @param taskId - ID of the task to update
* @param updates - Partial task object with fields to update
* @param tag - Optional tag context for the task
* @returns Promise that resolves when update is complete
*/
updateTask(
taskId: string,
updates: Partial<Task>,
tag?: string
): Promise<void>;
/**
* Delete a task by ID
* @param taskId - ID of the task to delete
* @param tag - Optional tag context for the task
* @returns Promise that resolves when deletion is complete
*/
deleteTask(taskId: string, tag?: string): Promise<void>;
/**
* Check if tasks exist in storage for the given tag
* @param tag - Optional tag to check existence for
* @returns Promise that resolves to boolean indicating existence
*/
exists(tag?: string): Promise<boolean>;
/**
* Load metadata about the task collection
* @param tag - Optional tag to get metadata for
* @returns Promise that resolves to task metadata
*/
loadMetadata(tag?: string): Promise<TaskMetadata | null>;
/**
* Save metadata about the task collection
* @param metadata - Metadata object to save
* @param tag - Optional tag context for the metadata
* @returns Promise that resolves when save is complete
*/
saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;
/**
* Get all available tags in storage
* @returns Promise that resolves to array of available tags
*/
getAllTags(): Promise<string[]>;
/**
* Delete all tasks and metadata for a specific tag
* @param tag - Tag to delete
* @returns Promise that resolves when deletion is complete
*/
deleteTag(tag: string): Promise<void>;
/**
* Rename a tag (move all tasks from old tag to new tag)
* @param oldTag - Current tag name
* @param newTag - New tag name
* @returns Promise that resolves when rename is complete
*/
renameTag(oldTag: string, newTag: string): Promise<void>;
/**
* Copy all tasks from one tag to another
* @param sourceTag - Source tag to copy from
* @param targetTag - Target tag to copy to
* @returns Promise that resolves when copy is complete
*/
copyTag(sourceTag: string, targetTag: string): Promise<void>;
/**
* Initialize storage (create necessary directories, files, etc.)
* @returns Promise that resolves when initialization is complete
*/
initialize(): Promise<void>;
/**
* Clean up and close storage connections
* @returns Promise that resolves when cleanup is complete
*/
close(): Promise<void>;
/**
* Get storage statistics (file sizes, task counts, etc.)
* @returns Promise that resolves to storage statistics
*/
getStats(): Promise<StorageStats>;
}
/**
* Storage statistics interface
*/
export interface StorageStats {
/** Total number of tasks across all tags */
totalTasks: number;
/** Total number of tags */
totalTags: number;
/** Storage size in bytes */
storageSize: number;
/** Last modified timestamp */
lastModified: string;
/** Available tags with task counts */
tagStats: Array<{
tag: string;
taskCount: number;
lastModified: string;
}>;
}
/**
* Configuration options for storage implementations
*/
export interface StorageConfig {
/** Base path for storage */
basePath: string;
/** Enable backup creation */
enableBackup?: boolean;
/** Maximum number of backups to keep */
maxBackups?: number;
/** Enable compression for storage */
enableCompression?: boolean;
/** File encoding (default: utf8) */
encoding?: BufferEncoding;
/** Enable atomic writes */
atomicWrites?: boolean;
}
/**
* Base abstract class for storage implementations
* Provides common functionality and enforces the interface
*/
export abstract class BaseStorage implements IStorage {
protected config: StorageConfig;
constructor(config: StorageConfig) {
this.config = config;
}
// Abstract methods that must be implemented by concrete classes
abstract loadTasks(tag?: string): Promise<Task[]>;
abstract saveTasks(tasks: Task[], tag?: string): Promise<void>;
abstract appendTasks(tasks: Task[], tag?: string): Promise<void>;
abstract updateTask(
taskId: string,
updates: Partial<Task>,
tag?: string
): Promise<void>;
abstract deleteTask(taskId: string, tag?: string): Promise<void>;
abstract exists(tag?: string): Promise<boolean>;
abstract loadMetadata(tag?: string): Promise<TaskMetadata | null>;
abstract saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;
abstract getAllTags(): Promise<string[]>;
abstract deleteTag(tag: string): Promise<void>;
abstract renameTag(oldTag: string, newTag: string): Promise<void>;
abstract copyTag(sourceTag: string, targetTag: string): Promise<void>;
abstract initialize(): Promise<void>;
abstract close(): Promise<void>;
abstract getStats(): Promise<StorageStats>;
/**
* Utility method to generate backup filename
* @param originalPath - Original file path
* @returns Backup file path with timestamp
*/
protected generateBackupPath(originalPath: string): string {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const parts = originalPath.split('.');
const extension = parts.pop();
const baseName = parts.join('.');
return `${baseName}.backup.${timestamp}.${extension}`;
}
/**
* Utility method to validate task data before storage operations
* @param task - Task to validate
* @throws Error if task is invalid
*/
protected validateTask(task: Task): void {
if (!task.id) {
throw new Error('Task ID is required');
}
if (!task.title) {
throw new Error('Task title is required');
}
if (!task.description) {
throw new Error('Task description is required');
}
if (!task.status) {
throw new Error('Task status is required');
}
}
/**
* Utility method to sanitize tag names for file system safety
* @param tag - Tag name to sanitize
* @returns Sanitized tag name
*/
protected sanitizeTag(tag: string): string {
return tag.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
}
}

View File

@@ -0,0 +1,39 @@
/**
* @fileoverview Task parsing functionality for the tm-core package
* This file exports all parsing-related classes and functions
*/
import type { PlaceholderTask } from '../types/index';
// Parser implementations will be defined here
// export * from './prd-parser.js';
// export * from './task-parser.js';
// export * from './markdown-parser.js';
// Placeholder exports - these will be implemented in later tasks
export interface TaskParser {
parse(content: string): Promise<PlaceholderTask[]>;
validate(content: string): Promise<boolean>;
}
/**
* @deprecated This is a placeholder class that will be properly implemented in later tasks
*/
export class PlaceholderParser implements TaskParser {
async parse(content: string): Promise<PlaceholderTask[]> {
// Simple placeholder parsing logic
const lines = content
.split('\n')
.filter((line) => line.trim().startsWith('-'));
return lines.map((line, index) => ({
id: `task-${index + 1}`,
title: line.trim().replace(/^-\s*/, ''),
status: 'pending' as const,
priority: 'medium' as const
}));
}
async validate(content: string): Promise<boolean> {
return content.trim().length > 0;
}
}

View File

@@ -0,0 +1,76 @@
/**
* @fileoverview Base provider implementation for AI providers in tm-core
* Provides common functionality and properties for all AI provider implementations
*/
import {
IAIProvider,
AIOptions,
AIResponse,
AIModel,
ProviderInfo,
ProviderUsageStats
} from '../interfaces/ai-provider.interface.js';
/**
* Configuration interface for BaseProvider
*/
export interface BaseProviderConfig {
/** API key for the provider */
apiKey: string;
/** Optional model ID to use */
model?: string;
}
/**
* Abstract base class providing common functionality for all AI providers
* Implements the IAIProvider interface with shared properties and basic methods
*/
export abstract class BaseProvider implements IAIProvider {
/** API key for authentication */
protected apiKey: string;
/** Current model being used */
protected model: string;
/** Maximum number of retry attempts */
protected maxRetries: number = 3;
/** Delay between retries in milliseconds */
protected retryDelay: number = 1000;
/**
* Constructor for BaseProvider
* @param config - Configuration object with apiKey and optional model
*/
constructor(config: BaseProviderConfig) {
this.apiKey = config.apiKey;
this.model = config.model || this.getDefaultModel();
}
/**
* Get the currently configured model
* @returns Current model ID
*/
getModel(): string {
return this.model;
}
// Abstract methods that concrete providers must implement
abstract generateCompletion(
prompt: string,
options?: AIOptions
): Promise<AIResponse>;
abstract generateStreamingCompletion(
prompt: string,
options?: AIOptions
): AsyncIterator<Partial<AIResponse>>;
abstract calculateTokens(text: string, model?: string): number;
abstract getName(): string;
abstract setModel(model: string): void;
abstract getDefaultModel(): string;
abstract isAvailable(): Promise<boolean>;
abstract getProviderInfo(): ProviderInfo;
abstract getAvailableModels(): AIModel[];
abstract validateCredentials(): Promise<boolean>;
abstract getUsageStats(): Promise<ProviderUsageStats | null>;
abstract initialize(): Promise<void>;
abstract close(): Promise<void>;
}

View File

@@ -0,0 +1,27 @@
/**
* @fileoverview AI provider implementations for the tm-core package
* This file exports all AI provider classes and interfaces
*/
// Provider interfaces and implementations
export * from './base-provider.js';
// export * from './anthropic-provider.js';
// export * from './openai-provider.js';
// export * from './perplexity-provider.js';
// Placeholder exports - these will be implemented in later tasks
export interface AIProvider {
name: string;
generateResponse(prompt: string): Promise<string>;
}
/**
* @deprecated This is a placeholder class that will be properly implemented in later tasks
*/
export class PlaceholderProvider implements AIProvider {
name = 'placeholder';
async generateResponse(prompt: string): Promise<string> {
return `Placeholder response for: ${prompt}`;
}
}

View File

@@ -0,0 +1,356 @@
/**
* File-based storage implementation for Task Master
*/
import { promises as fs } from 'fs';
import path from 'path';
import type { Task, TaskMetadata } from '../types/index.js';
import { BaseStorage, type StorageStats } from './storage.interface.js';
/**
* File storage data structure
*/
interface FileStorageData {
tasks: Task[];
metadata: TaskMetadata;
}
/**
* File-based storage implementation using JSON files
*/
export class FileStorage extends BaseStorage {
private readonly projectPath: string;
private readonly basePath: string;
private readonly tasksDir: string;
private fileLocks: Map<string, Promise<void>> = new Map();
constructor(projectPath: string, config = {}) {
super(config);
this.projectPath = projectPath;
this.basePath = path.join(projectPath, '.taskmaster');
this.tasksDir = path.join(this.basePath, 'tasks');
}
/**
* Initialize storage by creating necessary directories
*/
async initialize(): Promise<void> {
await this.ensureDirectoryExists();
}
/**
* Close storage and cleanup resources
*/
async close(): Promise<void> {
// Wait for any pending file operations
const locks = Array.from(this.fileLocks.values());
if (locks.length > 0) {
await Promise.all(locks);
}
this.fileLocks.clear();
}
/**
* Get statistics about the storage
*/
async getStats(): Promise<StorageStats> {
const tags = await this.getAllTags();
let totalTasks = 0;
let lastModified = '';
for (const tag of tags) {
const filePath = this.getTasksPath(tag === 'default' ? undefined : tag);
try {
const stats = await fs.stat(filePath);
const data = await this.readJsonFile(filePath);
if (data?.tasks) {
totalTasks += data.tasks.length;
}
if (stats.mtime.toISOString() > lastModified) {
lastModified = stats.mtime.toISOString();
}
} catch {
// Ignore missing files
}
}
return {
totalTasks,
totalTags: tags.length,
lastModified: lastModified || new Date().toISOString()
};
}
/**
* Load tasks from file
*/
async loadTasks(tag?: string): Promise<Task[]> {
const filePath = this.getTasksPath(tag);
try {
const data = await this.readJsonFile(filePath);
return data?.tasks || [];
} catch (error: any) {
if (error.code === 'ENOENT') {
return []; // File doesn't exist, return empty array
}
throw new Error(`Failed to load tasks: ${error.message}`);
}
}
/**
* Save tasks to file
*/
async saveTasks(tasks: Task[], tag?: string): Promise<void> {
const filePath = this.getTasksPath(tag);
// Ensure directory exists
await this.ensureDirectoryExists();
// Create data structure with metadata
const data: FileStorageData = {
tasks,
metadata: {
version: '1.0.0',
lastModified: new Date().toISOString(),
taskCount: tasks.length,
completedCount: tasks.filter((t) => t.status === 'done').length,
tags: tag ? [tag] : []
}
};
// Write with file locking
await this.writeJsonFile(filePath, data);
}
/**
* Check if tasks file exists
*/
async exists(tag?: string): Promise<boolean> {
const filePath = this.getTasksPath(tag);
try {
await fs.access(filePath, fs.constants.F_OK);
return true;
} catch {
return false;
}
}
/**
* Get all available tags
*/
async getAllTags(): Promise<string[]> {
try {
await this.ensureDirectoryExists();
const files = await fs.readdir(this.tasksDir);
const tags: string[] = [];
for (const file of files) {
if (file.endsWith('.json')) {
if (file === 'tasks.json') {
tags.push('default');
} else if (!file.includes('.backup.')) {
// Extract tag name from filename (remove .json extension)
tags.push(file.slice(0, -5));
}
}
}
return tags;
} catch (error: any) {
if (error.code === 'ENOENT') {
return [];
}
throw new Error(`Failed to get tags: ${error.message}`);
}
}
/**
* Load metadata from file
*/
async loadMetadata(tag?: string): Promise<TaskMetadata | null> {
const filePath = this.getTasksPath(tag);
try {
const data = await this.readJsonFile(filePath);
return data?.metadata || null;
} catch (error: any) {
if (error.code === 'ENOENT') {
return null;
}
throw new Error(`Failed to load metadata: ${error.message}`);
}
}
/**
* Save metadata (stored with tasks)
*/
async saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void> {
const tasks = await this.loadTasks(tag);
const filePath = this.getTasksPath(tag);
const data: FileStorageData = {
tasks,
metadata
};
await this.writeJsonFile(filePath, data);
}
// ============================================================================
// Private Helper Methods
// ============================================================================
/**
* Get the file path for tasks based on tag
*/
private getTasksPath(tag?: string): string {
if (tag) {
const sanitizedTag = this.sanitizeTag(tag);
return path.join(this.tasksDir, `${sanitizedTag}.json`);
}
return path.join(this.tasksDir, 'tasks.json');
}
/**
* Ensure the storage directory structure exists
*/
private async ensureDirectoryExists(): Promise<void> {
try {
await fs.mkdir(this.tasksDir, { recursive: true });
} catch (error: any) {
throw new Error(`Failed to create storage directory: ${error.message}`);
}
}
/**
* Read and parse JSON file with error handling
*/
private async readJsonFile(
filePath: string
): Promise<FileStorageData | null> {
try {
const content = await fs.readFile(filePath, 'utf-8');
return JSON.parse(content);
} catch (error: any) {
if (error.code === 'ENOENT') {
throw error; // Re-throw ENOENT for caller to handle
}
if (error instanceof SyntaxError) {
throw new Error(`Invalid JSON in file ${filePath}: ${error.message}`);
}
throw new Error(`Failed to read file ${filePath}: ${error.message}`);
}
}
/**
* Write JSON file with atomic operation using temp file
*/
private async writeJsonFile(
filePath: string,
data: FileStorageData
): Promise<void> {
// Use file locking to prevent concurrent writes
const lockKey = filePath;
const existingLock = this.fileLocks.get(lockKey);
if (existingLock) {
await existingLock;
}
const lockPromise = this.performWrite(filePath, data);
this.fileLocks.set(lockKey, lockPromise);
try {
await lockPromise;
} finally {
this.fileLocks.delete(lockKey);
}
}
/**
* Perform the actual write operation
*/
private async performWrite(
filePath: string,
data: FileStorageData
): Promise<void> {
const tempPath = `${filePath}.tmp`;
try {
// Write to temp file first
const content = JSON.stringify(data, null, 2);
await fs.writeFile(tempPath, content, 'utf-8');
// Create backup if configured
if (this.config.autoBackup && (await this.exists())) {
await this.createBackup(filePath);
}
// Atomic rename
await fs.rename(tempPath, filePath);
} catch (error: any) {
// Clean up temp file if it exists
try {
await fs.unlink(tempPath);
} catch {
// Ignore cleanup errors
}
throw new Error(`Failed to write file ${filePath}: ${error.message}`);
}
}
/**
* Create a backup of the file
*/
private async createBackup(filePath: string): Promise<void> {
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = this.getBackupPath(filePath, timestamp);
await fs.copyFile(filePath, backupPath);
// Clean up old backups if needed
if (this.config.maxBackups) {
await this.cleanupOldBackups(filePath);
}
} catch {
// Backup failures are non-critical
}
}
/**
* Remove old backup files beyond the max limit
*/
private async cleanupOldBackups(originalPath: string): Promise<void> {
const dir = path.dirname(originalPath);
const basename = path.basename(originalPath, '.json');
try {
const files = await fs.readdir(dir);
const backupFiles = files
.filter(
(f) => f.startsWith(`${basename}.backup.`) && f.endsWith('.json')
)
.sort()
.reverse();
// Remove backups beyond the limit
const toRemove = backupFiles.slice(this.config.maxBackups!);
for (const file of toRemove) {
try {
await fs.unlink(path.join(dir, file));
} catch {
// Ignore individual file deletion errors
}
}
} catch {
// Cleanup failures are non-critical
}
}
}
// Export as default for convenience
export default FileStorage;

View File

@@ -0,0 +1,40 @@
/**
* @fileoverview Storage layer for the tm-core package
* This file exports all storage-related classes and interfaces
*/
// Storage implementations will be defined here
// export * from './file-storage.js';
// export * from './memory-storage.js';
// export * from './storage-interface.js';
// Placeholder exports - these will be implemented in later tasks
export interface StorageAdapter {
read(path: string): Promise<string | null>;
write(path: string, data: string): Promise<void>;
exists(path: string): Promise<boolean>;
delete(path: string): Promise<void>;
}
/**
* @deprecated This is a placeholder class that will be properly implemented in later tasks
*/
export class PlaceholderStorage implements StorageAdapter {
private data = new Map<string, string>();
async read(path: string): Promise<string | null> {
return this.data.get(path) || null;
}
async write(path: string, data: string): Promise<void> {
this.data.set(path, data);
}
async exists(path: string): Promise<boolean> {
return this.data.has(path);
}
async delete(path: string): Promise<void> {
this.data.delete(path);
}
}

View File

@@ -0,0 +1,289 @@
/**
* Storage interface and base implementation for Task Master
*/
import type {
Task,
TaskMetadata,
TaskFilter,
TaskSortOptions
} from '../types/index.js';
/**
* Storage statistics
*/
export interface StorageStats {
totalTasks: number;
totalTags: number;
lastModified: string;
storageSize?: number;
}
/**
* Storage configuration options
*/
export interface StorageConfig {
basePath?: string;
autoBackup?: boolean;
backupInterval?: number;
maxBackups?: number;
compression?: boolean;
}
/**
* Core storage interface for task persistence
*/
export interface IStorage {
// Core task operations
loadTasks(tag?: string): Promise<Task[]>;
saveTasks(tasks: Task[], tag?: string): Promise<void>;
appendTasks(tasks: Task[], tag?: string): Promise<void>;
updateTask(
taskId: string,
updates: Partial<Task>,
tag?: string
): Promise<boolean>;
deleteTask(taskId: string, tag?: string): Promise<boolean>;
exists(tag?: string): Promise<boolean>;
// Metadata operations
loadMetadata(tag?: string): Promise<TaskMetadata | null>;
saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;
// Tag management
getAllTags(): Promise<string[]>;
deleteTag(tag: string): Promise<boolean>;
renameTag(oldTag: string, newTag: string): Promise<boolean>;
copyTag(sourceTag: string, targetTag: string): Promise<boolean>;
// Advanced operations
searchTasks(filter: TaskFilter, tag?: string): Promise<Task[]>;
sortTasks(tasks: Task[], options: TaskSortOptions): Task[];
// Lifecycle methods
initialize(): Promise<void>;
close(): Promise<void>;
getStats(): Promise<StorageStats>;
}
/**
* Abstract base class for storage implementations
*/
export abstract class BaseStorage implements IStorage {
protected config: StorageConfig;
constructor(config: StorageConfig = {}) {
this.config = {
autoBackup: false,
backupInterval: 3600000, // 1 hour
maxBackups: 10,
compression: false,
...config
};
}
// Abstract methods that must be implemented by subclasses
abstract loadTasks(tag?: string): Promise<Task[]>;
abstract saveTasks(tasks: Task[], tag?: string): Promise<void>;
abstract exists(tag?: string): Promise<boolean>;
abstract initialize(): Promise<void>;
abstract close(): Promise<void>;
abstract getAllTags(): Promise<string[]>;
abstract getStats(): Promise<StorageStats>;
// Default implementations that can be overridden
async appendTasks(tasks: Task[], tag?: string): Promise<void> {
const existingTasks = await this.loadTasks(tag);
const existingIds = new Set(existingTasks.map((t) => t.id));
const newTasks = tasks.filter((t) => !existingIds.has(t.id));
const mergedTasks = [...existingTasks, ...newTasks];
await this.saveTasks(mergedTasks, tag);
}
async updateTask(
taskId: string,
updates: Partial<Task>,
tag?: string
): Promise<boolean> {
const tasks = await this.loadTasks(tag);
const taskIndex = tasks.findIndex((t) => t.id === taskId);
if (taskIndex === -1) {
return false;
}
tasks[taskIndex] = {
...tasks[taskIndex],
...updates,
id: taskId, // Ensure ID cannot be changed
updatedAt: new Date().toISOString()
};
await this.saveTasks(tasks, tag);
return true;
}
async deleteTask(taskId: string, tag?: string): Promise<boolean> {
const tasks = await this.loadTasks(tag);
const filteredTasks = tasks.filter((t) => t.id !== taskId);
if (tasks.length === filteredTasks.length) {
return false; // Task not found
}
await this.saveTasks(filteredTasks, tag);
return true;
}
async loadMetadata(tag?: string): Promise<TaskMetadata | null> {
const tasks = await this.loadTasks(tag);
if (tasks.length === 0) return null;
const completedCount = tasks.filter((t) => t.status === 'done').length;
return {
version: '1.0.0',
lastModified: new Date().toISOString(),
taskCount: tasks.length,
completedCount
};
}
async saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void> {
// Default implementation: metadata is derived from tasks
// Subclasses can override if they store metadata separately
}
async deleteTag(tag: string): Promise<boolean> {
if (await this.exists(tag)) {
await this.saveTasks([], tag);
return true;
}
return false;
}
async renameTag(oldTag: string, newTag: string): Promise<boolean> {
if (!(await this.exists(oldTag))) {
return false;
}
const tasks = await this.loadTasks(oldTag);
await this.saveTasks(tasks, newTag);
await this.deleteTag(oldTag);
return true;
}
async copyTag(sourceTag: string, targetTag: string): Promise<boolean> {
if (!(await this.exists(sourceTag))) {
return false;
}
const tasks = await this.loadTasks(sourceTag);
await this.saveTasks(tasks, targetTag);
return true;
}
async searchTasks(filter: TaskFilter, tag?: string): Promise<Task[]> {
const tasks = await this.loadTasks(tag);
return tasks.filter((task) => {
// Status filter
if (filter.status) {
const statuses = Array.isArray(filter.status)
? filter.status
: [filter.status];
if (!statuses.includes(task.status)) return false;
}
// Priority filter
if (filter.priority) {
const priorities = Array.isArray(filter.priority)
? filter.priority
: [filter.priority];
if (!priorities.includes(task.priority)) return false;
}
// Tags filter
if (filter.tags && filter.tags.length > 0) {
if (
!task.tags ||
!filter.tags.some((tag) => task.tags?.includes(tag))
) {
return false;
}
}
// Subtasks filter
if (filter.hasSubtasks !== undefined) {
const hasSubtasks = task.subtasks && task.subtasks.length > 0;
if (hasSubtasks !== filter.hasSubtasks) return false;
}
// Search filter
if (filter.search) {
const searchLower = filter.search.toLowerCase();
const inTitle = task.title.toLowerCase().includes(searchLower);
const inDescription = task.description
.toLowerCase()
.includes(searchLower);
const inDetails = task.details.toLowerCase().includes(searchLower);
if (!inTitle && !inDescription && !inDetails) return false;
}
// Assignee filter
if (filter.assignee && task.assignee !== filter.assignee) {
return false;
}
// Complexity filter
if (filter.complexity) {
const complexities = Array.isArray(filter.complexity)
? filter.complexity
: [filter.complexity];
if (!task.complexity || !complexities.includes(task.complexity))
return false;
}
return true;
});
}
sortTasks(tasks: Task[], options: TaskSortOptions): Task[] {
return [...tasks].sort((a, b) => {
const aValue = a[options.field];
const bValue = b[options.field];
if (aValue === undefined || bValue === undefined) return 0;
let comparison = 0;
if (aValue < bValue) comparison = -1;
if (aValue > bValue) comparison = 1;
return options.direction === 'asc' ? comparison : -comparison;
});
}
// Helper methods
protected validateTask(task: Task): void {
if (!task.id || typeof task.id !== 'string') {
throw new Error('Task must have a valid string ID');
}
if (!task.title || typeof task.title !== 'string') {
throw new Error('Task must have a valid title');
}
if (!task.status) {
throw new Error('Task must have a valid status');
}
}
protected sanitizeTag(tag: string): string {
// Remove or replace characters that might cause filesystem issues
return tag.replace(/[^a-zA-Z0-9-_]/g, '_').toLowerCase();
}
protected getBackupPath(originalPath: string, timestamp: string): string {
const parts = originalPath.split('.');
const ext = parts.pop();
return `${parts.join('.')}.backup.${timestamp}.${ext}`;
}
}

View File

@@ -0,0 +1,228 @@
/**
* Core type definitions for Task Master
*/
// ============================================================================
// Type Literals
// ============================================================================
/**
* Task status values
*/
export type TaskStatus =
| 'pending'
| 'in-progress'
| 'done'
| 'deferred'
| 'cancelled'
| 'blocked'
| 'review';
/**
* Task priority levels
*/
export type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
/**
* Task complexity levels
*/
export type TaskComplexity = 'simple' | 'moderate' | 'complex' | 'very-complex';
// ============================================================================
// Core Interfaces
// ============================================================================
/**
* Base task interface
*/
export interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
dependencies: string[];
details: string;
testStrategy: string;
subtasks: Subtask[];
// Optional enhanced properties
createdAt?: string;
updatedAt?: string;
effort?: number;
actualEffort?: number;
tags?: string[];
assignee?: string;
complexity?: TaskComplexity;
}
/**
* Subtask interface extending Task with numeric ID
*/
export interface Subtask extends Omit<Task, 'id' | 'subtasks'> {
id: number;
parentId: string;
subtasks?: never; // Subtasks cannot have their own subtasks
}
/**
* Task metadata for tracking overall project state
*/
export interface TaskMetadata {
version: string;
lastModified: string;
taskCount: number;
completedCount: number;
projectName?: string;
description?: string;
tags?: string[];
}
/**
* Task collection with metadata
*/
export interface TaskCollection {
tasks: Task[];
metadata: TaskMetadata;
}
// ============================================================================
// Utility Types
// ============================================================================
/**
* Type for creating a new task (without generated fields)
*/
export type CreateTask = Omit<
Task,
'id' | 'createdAt' | 'updatedAt' | 'subtasks'
> & {
subtasks?: Omit<Subtask, 'id' | 'parentId' | 'createdAt' | 'updatedAt'>[];
};
/**
* Type for updating a task (all fields optional except ID)
*/
export type UpdateTask = Partial<Omit<Task, 'id'>> & {
id: string;
};
/**
* Type for task filters
*/
export interface TaskFilter {
status?: TaskStatus | TaskStatus[];
priority?: TaskPriority | TaskPriority[];
tags?: string[];
hasSubtasks?: boolean;
search?: string;
assignee?: string;
complexity?: TaskComplexity | TaskComplexity[];
}
/**
* Type for sort options
*/
export interface TaskSortOptions {
field: keyof Task;
direction: 'asc' | 'desc';
}
// ============================================================================
// Type Guards
// ============================================================================
/**
* Type guard to check if a value is a valid TaskStatus
*/
export function isTaskStatus(value: unknown): value is TaskStatus {
return (
typeof value === 'string' &&
[
'pending',
'in-progress',
'done',
'deferred',
'cancelled',
'blocked',
'review'
].includes(value)
);
}
/**
* Type guard to check if a value is a valid TaskPriority
*/
export function isTaskPriority(value: unknown): value is TaskPriority {
return (
typeof value === 'string' &&
['low', 'medium', 'high', 'critical'].includes(value)
);
}
/**
* Type guard to check if a value is a valid TaskComplexity
*/
export function isTaskComplexity(value: unknown): value is TaskComplexity {
return (
typeof value === 'string' &&
['simple', 'moderate', 'complex', 'very-complex'].includes(value)
);
}
/**
* Type guard to check if an object is a Task
*/
export function isTask(obj: unknown): obj is Task {
if (!obj || typeof obj !== 'object') return false;
const task = obj as Record<string, unknown>;
return (
typeof task.id === 'string' &&
typeof task.title === 'string' &&
typeof task.description === 'string' &&
isTaskStatus(task.status) &&
isTaskPriority(task.priority) &&
Array.isArray(task.dependencies) &&
typeof task.details === 'string' &&
typeof task.testStrategy === 'string' &&
Array.isArray(task.subtasks)
);
}
/**
* Type guard to check if an object is a Subtask
*/
export function isSubtask(obj: unknown): obj is Subtask {
if (!obj || typeof obj !== 'object') return false;
const subtask = obj as Record<string, unknown>;
return (
typeof subtask.id === 'number' &&
typeof subtask.parentId === 'string' &&
typeof subtask.title === 'string' &&
typeof subtask.description === 'string' &&
isTaskStatus(subtask.status) &&
isTaskPriority(subtask.priority) &&
!('subtasks' in subtask)
);
}
// ============================================================================
// Deprecated Types (for backwards compatibility)
// ============================================================================
/**
* @deprecated Use TaskStatus instead
*/
export type Status = TaskStatus;
/**
* @deprecated Use TaskPriority instead
*/
export type Priority = TaskPriority;
/**
* @deprecated Use TaskComplexity instead
*/
export type Complexity = TaskComplexity;

View File

@@ -0,0 +1,9 @@
/**
* @fileoverview Legacy type definitions for backwards compatibility
* These types are deprecated and will be removed in future versions
*/
/**
* @deprecated Use string directly instead. This will be removed in a future version.
*/
export type TaskId = string;

View File

@@ -0,0 +1,142 @@
/**
* @fileoverview ID generation utilities for Task Master
* Provides functions to generate unique identifiers for tasks and subtasks
*/
import { randomBytes } from 'crypto';
/**
* Generates a unique task ID using the format: TASK-{timestamp}-{random}
*
* @returns A unique task ID string
* @example
* ```typescript
* const taskId = generateTaskId();
* // Returns something like: "TASK-1704067200000-A7B3"
* ```
*/
export function generateTaskId(): string {
const timestamp = Date.now();
const random = generateRandomString(4);
return `TASK-${timestamp}-${random}`;
}
/**
* Generates a subtask ID using the format: {parentId}.{sequential}
*
* @param parentId - The ID of the parent task
* @param existingSubtasks - Array of existing subtask IDs to determine the next sequential number
* @returns A unique subtask ID string
* @example
* ```typescript
* const subtaskId = generateSubtaskId("TASK-123-A7B3", ["TASK-123-A7B3.1"]);
* // Returns: "TASK-123-A7B3.2"
* ```
*/
export function generateSubtaskId(
parentId: string,
existingSubtasks: string[] = []
): string {
// Find existing subtasks for this parent
const parentSubtasks = existingSubtasks.filter((id) =>
id.startsWith(`${parentId}.`)
);
// Extract sequential numbers and find the highest
const sequentialNumbers = parentSubtasks
.map((id) => {
const parts = id.split('.');
const lastPart = parts[parts.length - 1];
return parseInt(lastPart, 10);
})
.filter((num) => !isNaN(num))
.sort((a, b) => a - b);
// Determine the next sequential number
const nextSequential =
sequentialNumbers.length > 0 ? Math.max(...sequentialNumbers) + 1 : 1;
return `${parentId}.${nextSequential}`;
}
/**
* Generates a random alphanumeric string of specified length
* Uses crypto.randomBytes for cryptographically secure randomness
*
* @param length - The desired length of the random string
* @returns A random alphanumeric string
* @internal
*/
function generateRandomString(length: number): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const bytes = randomBytes(length);
let result = '';
for (let i = 0; i < length; i++) {
result += chars[bytes[i] % chars.length];
}
return result;
}
/**
* Validates a task ID format
*
* @param id - The ID to validate
* @returns True if the ID matches the expected task ID format
* @example
* ```typescript
* isValidTaskId("TASK-1704067200000-A7B3"); // true
* isValidTaskId("invalid-id"); // false
* ```
*/
export function isValidTaskId(id: string): boolean {
const taskIdRegex = /^TASK-\d{13}-[A-Z0-9]{4}$/;
return taskIdRegex.test(id);
}
/**
* Validates a subtask ID format
*
* @param id - The ID to validate
* @returns True if the ID matches the expected subtask ID format
* @example
* ```typescript
* isValidSubtaskId("TASK-1704067200000-A7B3.1"); // true
* isValidSubtaskId("TASK-1704067200000-A7B3.1.2"); // true (nested subtask)
* isValidSubtaskId("invalid.id"); // false
* ```
*/
export function isValidSubtaskId(id: string): boolean {
const parts = id.split('.');
if (parts.length < 2) return false;
// First part should be a valid task ID
const taskIdPart = parts[0];
if (!isValidTaskId(taskIdPart)) return false;
// Remaining parts should be positive integers
const sequentialParts = parts.slice(1);
return sequentialParts.every((part) => {
const num = parseInt(part, 10);
return !isNaN(num) && num > 0 && part === num.toString();
});
}
/**
* Extracts the parent task ID from a subtask ID
*
* @param subtaskId - The subtask ID
* @returns The parent task ID, or null if the input is not a valid subtask ID
* @example
* ```typescript
* getParentTaskId("TASK-1704067200000-A7B3.1.2"); // "TASK-1704067200000-A7B3"
* getParentTaskId("TASK-1704067200000-A7B3"); // null (not a subtask)
* ```
*/
export function getParentTaskId(subtaskId: string): string | null {
if (!isValidSubtaskId(subtaskId)) return null;
const parts = subtaskId.split('.');
return parts[0];
}

View File

@@ -0,0 +1,44 @@
/**
* @fileoverview Utility functions for the tm-core package
* This file exports all utility functions and helper classes
*/
// Utility implementations will be defined here
// export * from './validation.js';
// export * from './formatting.js';
// export * from './file-utils.js';
// export * from './async-utils.js';
// Placeholder exports - these will be implemented in later tasks
/**
* Generates a unique ID for tasks
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function generateTaskId(): string {
return `task-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}
/**
* Validates a task ID format
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function isValidTaskId(id: string): boolean {
return typeof id === 'string' && id.length > 0;
}
/**
* Formats a date for task timestamps
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function formatDate(date: Date = new Date()): string {
return date.toISOString();
}
/**
* Deep clones an object
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}

View File

@@ -0,0 +1,22 @@
/**
* Jest setup file for tm-core package
* This file is executed before running tests and can be used to configure
* testing utilities, global mocks, and test environment setup.
*/
// Configure test environment
process.env.NODE_ENV = 'test';
// Global test utilities can be added here
// Custom matchers and global types can be defined here in the future
// Set up any global mocks or configurations here
beforeEach(() => {
// Reset any global state before each test
jest.clearAllMocks();
});
afterEach(() => {
// Clean up after each test
jest.restoreAllMocks();
});

View File

@@ -0,0 +1,150 @@
/**
* Smoke tests to verify basic package functionality and imports
*/
import {
generateTaskId,
isValidTaskId,
formatDate,
PlaceholderProvider,
PlaceholderStorage,
PlaceholderParser,
TmCoreError,
TaskNotFoundError,
ValidationError,
StorageError,
version,
name
} from '@/index';
import type {
TaskId,
TaskStatus,
TaskPriority,
PlaceholderTask
} from '@/types/index';
describe('tm-core smoke tests', () => {
describe('package metadata', () => {
it('should export correct package name and version', () => {
expect(name).toBe('@task-master/tm-core');
expect(version).toBe('1.0.0');
});
});
describe('utility functions', () => {
it('should generate valid task IDs', () => {
const id1 = generateTaskId();
const id2 = generateTaskId();
expect(typeof id1).toBe('string');
expect(typeof id2).toBe('string');
expect(id1).not.toBe(id2); // Should be unique
expect(isValidTaskId(id1)).toBe(true);
expect(isValidTaskId('')).toBe(false);
});
it('should format dates', () => {
const date = new Date('2023-01-01T00:00:00.000Z');
const formatted = formatDate(date);
expect(formatted).toBe('2023-01-01T00:00:00.000Z');
});
});
describe('placeholder provider', () => {
it('should create and use placeholder provider', async () => {
const provider = new PlaceholderProvider();
expect(provider.name).toBe('placeholder');
const response = await provider.generateResponse('test prompt');
expect(response).toContain('test prompt');
});
});
describe('placeholder storage', () => {
it('should perform basic storage operations', async () => {
const storage = new PlaceholderStorage();
const testPath = 'test/path';
const testData = 'test data';
// Initially should not exist
expect(await storage.exists(testPath)).toBe(false);
expect(await storage.read(testPath)).toBe(null);
// Write and verify
await storage.write(testPath, testData);
expect(await storage.exists(testPath)).toBe(true);
expect(await storage.read(testPath)).toBe(testData);
// Delete and verify
await storage.delete(testPath);
expect(await storage.exists(testPath)).toBe(false);
});
});
describe('placeholder parser', () => {
it('should parse simple task lists', async () => {
const parser = new PlaceholderParser();
const content = `
- Task 1
- Task 2
- Task 3
`;
const isValid = await parser.validate(content);
expect(isValid).toBe(true);
const tasks = await parser.parse(content);
expect(tasks).toHaveLength(3);
expect(tasks[0]?.title).toBe('Task 1');
expect(tasks[1]?.title).toBe('Task 2');
expect(tasks[2]?.title).toBe('Task 3');
tasks.forEach((task) => {
expect(task.status).toBe('pending');
expect(task.priority).toBe('medium');
});
});
});
describe('error classes', () => {
it('should create and throw custom errors', () => {
const baseError = new TmCoreError('Base error');
expect(baseError.name).toBe('TmCoreError');
expect(baseError.message).toBe('Base error');
const taskNotFound = new TaskNotFoundError('task-123');
expect(taskNotFound.name).toBe('TaskNotFoundError');
expect(taskNotFound.code).toBe('TASK_NOT_FOUND');
expect(taskNotFound.message).toContain('task-123');
const validationError = new ValidationError('Invalid data');
expect(validationError.name).toBe('ValidationError');
expect(validationError.code).toBe('VALIDATION_ERROR');
const storageError = new StorageError('Storage failed');
expect(storageError.name).toBe('StorageError');
expect(storageError.code).toBe('STORAGE_ERROR');
});
});
describe('type definitions', () => {
it('should have correct types available', () => {
// These are compile-time checks that verify types exist
const taskId: TaskId = 'test-id';
const status: TaskStatus = 'pending';
const priority: TaskPriority = 'high';
const task: PlaceholderTask = {
id: taskId,
title: 'Test Task',
status: status,
priority: priority
};
expect(task.id).toBe('test-id');
expect(task.status).toBe('pending');
expect(task.priority).toBe('high');
});
});
});

View File

@@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"paths": {
"@/*": ["./src/*"],
"@/types": ["./src/types/index"],
"@/providers": ["./src/providers/index"],
"@/storage": ["./src/storage/index"],
"@/parser": ["./src/parser/index"],
"@/utils": ["./src/utils/index"],
"@/errors": ["./src/errors/index"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
}

View File

@@ -0,0 +1,27 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: {
index: 'src/index.ts',
'types/index': 'src/types/index.ts',
'providers/index': 'src/providers/index.ts',
'storage/index': 'src/storage/index.ts',
'parser/index': 'src/parser/index.ts',
'utils/index': 'src/utils/index.ts',
'errors/index': 'src/errors/index.ts'
},
format: ['cjs', 'esm'],
dts: true,
sourcemap: true,
clean: true,
splitting: false,
treeshake: true,
minify: false,
target: 'es2022',
tsconfig: './tsconfig.json',
outDir: 'dist',
external: ['zod'],
esbuildOptions(options) {
options.conditions = ['module'];
}
});