@geanatz/cortex-mcp 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/dist/errors/errors.d.ts +109 -0
  4. package/dist/errors/errors.js +199 -0
  5. package/dist/errors/index.d.ts +4 -0
  6. package/dist/errors/index.js +4 -0
  7. package/dist/features/task-management/models/artifact.d.ts +169 -0
  8. package/dist/features/task-management/models/artifact.js +155 -0
  9. package/dist/features/task-management/models/config.d.ts +54 -0
  10. package/dist/features/task-management/models/config.js +54 -0
  11. package/dist/features/task-management/models/index.d.ts +6 -0
  12. package/dist/features/task-management/models/index.js +6 -0
  13. package/dist/features/task-management/models/task.d.ts +173 -0
  14. package/dist/features/task-management/models/task.js +84 -0
  15. package/dist/features/task-management/storage/file-storage.d.ts +130 -0
  16. package/dist/features/task-management/storage/file-storage.js +575 -0
  17. package/dist/features/task-management/storage/index.d.ts +5 -0
  18. package/dist/features/task-management/storage/index.js +5 -0
  19. package/dist/features/task-management/storage/storage.d.ts +159 -0
  20. package/dist/features/task-management/storage/storage.js +37 -0
  21. package/dist/features/task-management/tools/artifacts/index.d.ts +6 -0
  22. package/dist/features/task-management/tools/artifacts/index.js +174 -0
  23. package/dist/features/task-management/tools/base/handlers.d.ts +7 -0
  24. package/dist/features/task-management/tools/base/handlers.js +15 -0
  25. package/dist/features/task-management/tools/base/index.d.ts +3 -0
  26. package/dist/features/task-management/tools/base/index.js +3 -0
  27. package/dist/features/task-management/tools/base/schemas.d.ts +3 -0
  28. package/dist/features/task-management/tools/base/schemas.js +6 -0
  29. package/dist/features/task-management/tools/base/types.d.ts +13 -0
  30. package/dist/features/task-management/tools/base/types.js +1 -0
  31. package/dist/features/task-management/tools/tasks/index.d.ts +10 -0
  32. package/dist/features/task-management/tools/tasks/index.js +500 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +57 -0
  35. package/dist/server.d.ts +11 -0
  36. package/dist/server.js +61 -0
  37. package/dist/types/common.d.ts +10 -0
  38. package/dist/types/common.js +1 -0
  39. package/dist/types/index.d.ts +5 -0
  40. package/dist/types/index.js +5 -0
  41. package/dist/utils/cache.d.ts +104 -0
  42. package/dist/utils/cache.js +196 -0
  43. package/dist/utils/file-utils.d.ts +101 -0
  44. package/dist/utils/file-utils.js +270 -0
  45. package/dist/utils/index.d.ts +12 -0
  46. package/dist/utils/index.js +12 -0
  47. package/dist/utils/logger.d.ts +77 -0
  48. package/dist/utils/logger.js +173 -0
  49. package/dist/utils/response-builder.d.ts +4 -0
  50. package/dist/utils/response-builder.js +19 -0
  51. package/dist/utils/storage-config.d.ts +29 -0
  52. package/dist/utils/storage-config.js +51 -0
  53. package/dist/utils/string-utils.d.ts +2 -0
  54. package/dist/utils/string-utils.js +16 -0
  55. package/dist/utils/validation.d.ts +9 -0
  56. package/dist/utils/validation.js +9 -0
  57. package/dist/utils/version.d.ts +9 -0
  58. package/dist/utils/version.js +41 -0
  59. package/package.json +60 -0
@@ -0,0 +1,159 @@
1
+ import { Task, TaskHierarchy, TaskFilters, CreateTaskInput, UpdateTaskInput, Subtask, AddSubtaskInput, UpdateSubtaskInput } from '../models/task.js';
2
+ import { CURRENT_STORAGE_VERSION } from '../models/config.js';
3
+ import { Artifact, CreateArtifactInput, UpdateArtifactInput, ArtifactPhase, TaskArtifacts } from '../models/artifact.js';
4
+ export { CURRENT_STORAGE_VERSION };
5
+ /**
6
+ * Storage operation result
7
+ */
8
+ export interface StorageResult<T> {
9
+ readonly success: boolean;
10
+ readonly data?: T;
11
+ readonly error?: string;
12
+ }
13
+ /**
14
+ * Storage statistics
15
+ */
16
+ export interface StorageStats {
17
+ readonly taskCount: number;
18
+ readonly artifactCount: number;
19
+ readonly cacheHitRate?: number;
20
+ readonly lastAccessed?: Date;
21
+ }
22
+ /**
23
+ * Storage interface for the task management system
24
+ *
25
+ * Simplified storage model:
26
+ * - Each parent task has its own folder: .cortex/tasks/{number}-{slug}/
27
+ * - .task.json contains parent task + subtasks array
28
+ * - Subtasks are NOT separate folders - they're stored inline
29
+ * - No dependsOn - simplified model
30
+ * - Single level nesting only
31
+ *
32
+ * Artifacts stored in .cortex/tasks/{number}-{slug}/{phase}.md
33
+ * Each phase (explore, search, plan, build, test) has its own artifact file
34
+ * Artifacts belong to the entire task hierarchy (parent + subtasks)
35
+ */
36
+ export interface Storage {
37
+ /**
38
+ * Initialize the storage system
39
+ * Must be called before any other operations
40
+ */
41
+ initialize(): Promise<void>;
42
+ /**
43
+ * Check if storage is initialized
44
+ */
45
+ isInitialized(): boolean;
46
+ /**
47
+ * Get all parent tasks
48
+ */
49
+ getTasks(): Promise<readonly Task[]>;
50
+ /**
51
+ * Get tasks with filters
52
+ */
53
+ getTasksFiltered(filters: TaskFilters): Promise<readonly Task[]>;
54
+ /**
55
+ * Get a single task by ID
56
+ */
57
+ getTask(id: string): Promise<Task | null>;
58
+ /**
59
+ * Create a new parent task
60
+ */
61
+ createTask(input: CreateTaskInput): Promise<Task>;
62
+ /**
63
+ * Update an existing task (parent fields or subtask operations)
64
+ */
65
+ updateTask(id: string, updates: UpdateTaskInput): Promise<Task | null>;
66
+ /**
67
+ * Delete a task and all its subtasks
68
+ */
69
+ deleteTask(id: string): Promise<boolean>;
70
+ /**
71
+ * Check if a task exists
72
+ */
73
+ taskExists(id: string): Promise<boolean>;
74
+ /**
75
+ * Add a subtask to a parent task
76
+ */
77
+ addSubtask(taskId: string, input: AddSubtaskInput): Promise<Subtask | null>;
78
+ /**
79
+ * Update a subtask
80
+ */
81
+ updateSubtask(taskId: string, input: UpdateSubtaskInput): Promise<Subtask | null>;
82
+ /**
83
+ * Remove a subtask by ID
84
+ */
85
+ removeSubtask(taskId: string, subtaskId: string): Promise<boolean>;
86
+ /**
87
+ * Get task hierarchy (parent with subtasks)
88
+ */
89
+ getTaskHierarchy(): Promise<readonly TaskHierarchy[]>;
90
+ /**
91
+ * Get a single artifact for a task
92
+ */
93
+ getArtifact(taskId: string, phase: ArtifactPhase): Promise<Artifact | null>;
94
+ /**
95
+ * Get all artifacts for a task
96
+ */
97
+ getAllArtifacts(taskId: string): Promise<TaskArtifacts>;
98
+ /**
99
+ * Create a new artifact (overwrites existing)
100
+ */
101
+ createArtifact(taskId: string, phase: ArtifactPhase, input: CreateArtifactInput): Promise<Artifact>;
102
+ /**
103
+ * Update an existing artifact
104
+ */
105
+ updateArtifact(taskId: string, phase: ArtifactPhase, input: UpdateArtifactInput): Promise<Artifact | null>;
106
+ /**
107
+ * Delete an artifact
108
+ */
109
+ deleteArtifact(taskId: string, phase: ArtifactPhase): Promise<boolean>;
110
+ /**
111
+ * Check if an artifact exists
112
+ */
113
+ artifactExists(taskId: string, phase: ArtifactPhase): Promise<boolean>;
114
+ /**
115
+ * Get storage version
116
+ */
117
+ getVersion(): string;
118
+ /**
119
+ * Get storage statistics
120
+ */
121
+ getStats(): Promise<StorageStats>;
122
+ /**
123
+ * Clear all caches
124
+ */
125
+ clearCache(): void;
126
+ /**
127
+ * Get the working directory
128
+ */
129
+ getWorkingDirectory(): string;
130
+ }
131
+ /**
132
+ * Abstract base storage class with common functionality
133
+ */
134
+ export declare abstract class BaseStorage implements Storage {
135
+ protected initialized: boolean;
136
+ abstract initialize(): Promise<void>;
137
+ abstract getTasks(): Promise<readonly Task[]>;
138
+ abstract getTask(id: string): Promise<Task | null>;
139
+ abstract createTask(input: CreateTaskInput): Promise<Task>;
140
+ abstract updateTask(id: string, updates: UpdateTaskInput): Promise<Task | null>;
141
+ abstract deleteTask(id: string): Promise<boolean>;
142
+ abstract addSubtask(taskId: string, input: AddSubtaskInput): Promise<Subtask | null>;
143
+ abstract updateSubtask(taskId: string, input: UpdateSubtaskInput): Promise<Subtask | null>;
144
+ abstract removeSubtask(taskId: string, subtaskId: string): Promise<boolean>;
145
+ abstract getTaskHierarchy(): Promise<readonly TaskHierarchy[]>;
146
+ abstract getArtifact(taskId: string, phase: ArtifactPhase): Promise<Artifact | null>;
147
+ abstract getAllArtifacts(taskId: string): Promise<TaskArtifacts>;
148
+ abstract createArtifact(taskId: string, phase: ArtifactPhase, input: CreateArtifactInput): Promise<Artifact>;
149
+ abstract updateArtifact(taskId: string, phase: ArtifactPhase, input: UpdateArtifactInput): Promise<Artifact | null>;
150
+ abstract deleteArtifact(taskId: string, phase: ArtifactPhase): Promise<boolean>;
151
+ abstract getStats(): Promise<StorageStats>;
152
+ abstract clearCache(): void;
153
+ abstract getWorkingDirectory(): string;
154
+ isInitialized(): boolean;
155
+ getVersion(): string;
156
+ getTasksFiltered(filters: TaskFilters): Promise<readonly Task[]>;
157
+ taskExists(id: string): Promise<boolean>;
158
+ artifactExists(taskId: string, phase: ArtifactPhase): Promise<boolean>;
159
+ }
@@ -0,0 +1,37 @@
1
+ import { CURRENT_STORAGE_VERSION } from '../models/config.js';
2
+ // Re-export config types for convenience
3
+ export { CURRENT_STORAGE_VERSION };
4
+ /**
5
+ * Abstract base storage class with common functionality
6
+ */
7
+ export class BaseStorage {
8
+ initialized = false;
9
+ isInitialized() {
10
+ return this.initialized;
11
+ }
12
+ getVersion() {
13
+ return CURRENT_STORAGE_VERSION;
14
+ }
15
+ async getTasksFiltered(filters) {
16
+ let tasks = await this.getTasks();
17
+ if (filters.status) {
18
+ const statuses = Array.isArray(filters.status) ? filters.status : [filters.status];
19
+ tasks = tasks.filter(t => statuses.includes(t.status));
20
+ }
21
+ if (filters.tags && filters.tags.length > 0) {
22
+ tasks = tasks.filter(t => t.tags && filters.tags.some(tag => t.tags.includes(tag)));
23
+ }
24
+ if (filters.includeDone === false) {
25
+ tasks = tasks.filter(t => t.status !== 'done');
26
+ }
27
+ return tasks;
28
+ }
29
+ async taskExists(id) {
30
+ const task = await this.getTask(id);
31
+ return task !== null;
32
+ }
33
+ async artifactExists(taskId, phase) {
34
+ const artifact = await this.getArtifact(taskId, phase);
35
+ return artifact !== null;
36
+ }
37
+ }
@@ -0,0 +1,6 @@
1
+ import { Storage } from '../../storage/storage.js';
2
+ import { StorageConfig } from '../../../../utils/storage-config.js';
3
+ import { ToolDefinition } from '../../tools/base/types.js';
4
+ type StorageFactory = (workingDirectory: string, config: StorageConfig) => Promise<Storage>;
5
+ export declare function createArtifactTools(config: StorageConfig, createStorage: StorageFactory): ToolDefinition[];
6
+ export {};
@@ -0,0 +1,174 @@
1
+ import { z } from 'zod';
2
+ import { getWorkingDirectoryDescription } from '../../../../utils/storage-config.js';
3
+ import { createErrorResponse } from '../../../../utils/response-builder.js';
4
+ import { createLogger } from '../../../../utils/logger.js';
5
+ import { ARTIFACT_PHASES, OPERATION_DESCRIPTIONS, PHASE_DESCRIPTIONS } from '../../models/artifact.js';
6
+ import { withErrorHandling } from '../../tools/base/handlers.js';
7
+ import { workingDirectorySchema, taskIdSchema } from '../../tools/base/schemas.js';
8
+ const logger = createLogger('artifact-tools');
9
+ function createCreateHandler(phase, config, createStorage) {
10
+ return async (params) => {
11
+ try {
12
+ const { workingDirectory, taskId, content, status, retries, error } = params;
13
+ if (!taskId || taskId.trim().length === 0) {
14
+ return {
15
+ content: [{ type: 'text', text: 'Error: Task ID is required.' }],
16
+ isError: true
17
+ };
18
+ }
19
+ if (!content || content.trim().length === 0) {
20
+ return {
21
+ content: [{ type: 'text', text: 'Error: Content is required.' }],
22
+ isError: true
23
+ };
24
+ }
25
+ const storage = await createStorage(workingDirectory, config);
26
+ const artifact = await storage.createArtifact(taskId.trim(), phase, {
27
+ content: content.trim(),
28
+ status,
29
+ retries,
30
+ error
31
+ });
32
+ const phaseUpper = phase.charAt(0).toUpperCase() + phase.slice(1);
33
+ const truncated = artifact.content.length > 500
34
+ ? artifact.content.substring(0, 500) + '...\n\n*(truncated - use get_task to see full content)*'
35
+ : artifact.content;
36
+ return {
37
+ content: [{
38
+ type: 'text',
39
+ text: `**${phaseUpper} artifact created for task "${taskId}"**\n\n**Phase:** ${artifact.metadata.phase}\n**Status:** ${artifact.metadata.status}\n**Created:** ${new Date(artifact.metadata.createdAt).toLocaleString()}\n\n---\n\n${truncated}`
40
+ }]
41
+ };
42
+ }
43
+ catch (err) {
44
+ return createErrorResponse(err);
45
+ }
46
+ };
47
+ }
48
+ function createUpdateHandler(phase, config, createStorage) {
49
+ return async (params) => {
50
+ try {
51
+ const { workingDirectory, taskId, content, status, retries, error } = params;
52
+ if (!taskId || taskId.trim().length === 0) {
53
+ return {
54
+ content: [{ type: 'text', text: 'Error: Task ID is required.' }],
55
+ isError: true
56
+ };
57
+ }
58
+ const storage = await createStorage(workingDirectory, config);
59
+ const artifact = await storage.updateArtifact(taskId.trim(), phase, {
60
+ content: content?.trim(),
61
+ status,
62
+ retries,
63
+ error
64
+ });
65
+ if (!artifact) {
66
+ const phaseUpper = phase.charAt(0).toUpperCase() + phase.slice(1);
67
+ return {
68
+ content: [{
69
+ type: 'text',
70
+ text: `Error: ${phaseUpper} artifact not found for task "${taskId}". Use create_${phase} to create it first.`
71
+ }],
72
+ isError: true
73
+ };
74
+ }
75
+ const phaseUpper = phase.charAt(0).toUpperCase() + phase.slice(1);
76
+ const extras = (artifact.metadata.retries !== undefined ? `\n**Retries:** ${artifact.metadata.retries}` : '') + (artifact.metadata.error ? `\n**Error:** ${artifact.metadata.error}` : '');
77
+ return {
78
+ content: [{
79
+ type: 'text',
80
+ text: `**${phaseUpper} artifact updated for task "${taskId}"**\n\n**Phase:** ${artifact.metadata.phase}\n**Status:** ${artifact.metadata.status}\n**Updated:** ${new Date(artifact.metadata.updatedAt).toLocaleString()}${extras}`
81
+ }]
82
+ };
83
+ }
84
+ catch (err) {
85
+ return createErrorResponse(err);
86
+ }
87
+ };
88
+ }
89
+ function createDeleteHandler(phase, config, createStorage) {
90
+ return async (params) => {
91
+ try {
92
+ const { workingDirectory, taskId, confirm } = params;
93
+ if (!taskId || taskId.trim().length === 0) {
94
+ return {
95
+ content: [{ type: 'text', text: 'Error: Task ID is required.' }],
96
+ isError: true
97
+ };
98
+ }
99
+ if (!confirm) {
100
+ return {
101
+ content: [{
102
+ type: 'text',
103
+ text: `Error: Deletion not confirmed. Set confirm=true to delete the ${phase} artifact.`
104
+ }],
105
+ isError: true
106
+ };
107
+ }
108
+ const storage = await createStorage(workingDirectory, config);
109
+ const deleted = await storage.deleteArtifact(taskId.trim(), phase);
110
+ if (!deleted) {
111
+ const phaseUpper = phase.charAt(0).toUpperCase() + phase.slice(1);
112
+ return {
113
+ content: [{
114
+ type: 'text',
115
+ text: `${phaseUpper} artifact not found for task "${taskId}". Nothing to delete.`
116
+ }]
117
+ };
118
+ }
119
+ const phaseUpper = phase.charAt(0).toUpperCase() + phase.slice(1);
120
+ return {
121
+ content: [{
122
+ type: 'text',
123
+ text: `**${phaseUpper} artifact deleted from task "${taskId}"**\n\nThe ${phase} phase has been reset and can be re-run.`
124
+ }]
125
+ };
126
+ }
127
+ catch (err) {
128
+ return createErrorResponse(err);
129
+ }
130
+ };
131
+ }
132
+ export function createArtifactTools(config, createStorage) {
133
+ const tools = [];
134
+ const wdSchema = workingDirectorySchema.describe(getWorkingDirectoryDescription(config));
135
+ for (const phase of ARTIFACT_PHASES) {
136
+ tools.push({
137
+ name: `create_${phase}`,
138
+ description: OPERATION_DESCRIPTIONS.create[phase],
139
+ parameters: {
140
+ workingDirectory: wdSchema,
141
+ taskId: taskIdSchema.describe('The ID of the task to create the artifact for'),
142
+ content: z.string().describe(`Markdown content for the ${phase} artifact. ${PHASE_DESCRIPTIONS[phase]}`),
143
+ status: z.enum(['pending', 'in-progress', 'completed', 'failed', 'skipped']).optional().describe('Status of this phase (defaults to "completed")'),
144
+ retries: z.number().min(0).optional().describe('Number of retry attempts for this phase'),
145
+ error: z.string().optional().describe('Error message if status is "failed"')
146
+ },
147
+ handler: withErrorHandling(createCreateHandler(phase, config, createStorage))
148
+ });
149
+ tools.push({
150
+ name: `update_${phase}`,
151
+ description: OPERATION_DESCRIPTIONS.update[phase],
152
+ parameters: {
153
+ workingDirectory: wdSchema,
154
+ taskId: taskIdSchema.describe('The ID of the task to update the artifact for'),
155
+ content: z.string().optional().describe(`Updated markdown content for the ${phase} artifact`),
156
+ status: z.enum(['pending', 'in-progress', 'completed', 'failed', 'skipped']).optional().describe('Updated status of this phase'),
157
+ retries: z.number().min(0).optional().describe('Updated number of retry attempts'),
158
+ error: z.string().optional().describe('Updated error message')
159
+ },
160
+ handler: withErrorHandling(createUpdateHandler(phase, config, createStorage))
161
+ });
162
+ tools.push({
163
+ name: `delete_${phase}`,
164
+ description: OPERATION_DESCRIPTIONS.delete[phase],
165
+ parameters: {
166
+ workingDirectory: wdSchema,
167
+ taskId: taskIdSchema.describe('The ID of the task to delete the artifact from'),
168
+ confirm: z.boolean().describe('Must be set to true to confirm deletion (safety measure)')
169
+ },
170
+ handler: withErrorHandling(createDeleteHandler(phase, config, createStorage))
171
+ });
172
+ }
173
+ return tools;
174
+ }
@@ -0,0 +1,7 @@
1
+ import { ToolHandler } from './types.js';
2
+ import { McpToolResponse } from '../../../../types/common.js';
3
+ /**
4
+ * Wrap a tool handler with top-level error handling.
5
+ * Any thrown error is converted to an MCP error response.
6
+ */
7
+ export declare function withErrorHandling<TInput, TOutput extends McpToolResponse>(handler: ToolHandler<TInput, TOutput>): ToolHandler<TInput, TOutput>;
@@ -0,0 +1,15 @@
1
+ import { createErrorResponse } from '../../../../utils/response-builder.js';
2
+ /**
3
+ * Wrap a tool handler with top-level error handling.
4
+ * Any thrown error is converted to an MCP error response.
5
+ */
6
+ export function withErrorHandling(handler) {
7
+ return async (input) => {
8
+ try {
9
+ return await handler(input);
10
+ }
11
+ catch (error) {
12
+ return createErrorResponse(error);
13
+ }
14
+ };
15
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './schemas.js';
3
+ export * from './handlers.js';
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './schemas.js';
3
+ export * from './handlers.js';
@@ -0,0 +1,3 @@
1
+ import { z } from 'zod';
2
+ export declare const workingDirectorySchema: z.ZodString;
3
+ export declare const taskIdSchema: z.ZodString;
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ import { ValidationLimits } from '../../../../utils/validation.js';
3
+ export const workingDirectorySchema = z.string().min(1, 'Working directory is required');
4
+ export const taskIdSchema = z.string()
5
+ .min(1, 'Task ID is required')
6
+ .max(ValidationLimits.TASK_ID_MAX_LENGTH);
@@ -0,0 +1,13 @@
1
+ import { McpToolResponse } from '../../../../types/common.js';
2
+ /**
3
+ * Definition for a tool that can be registered with the MCP server.
4
+ * The handler uses a permissive input type because the MCP SDK
5
+ * validates params against the zod schemas at the protocol layer.
6
+ */
7
+ export interface ToolDefinition {
8
+ readonly name: string;
9
+ readonly description: string;
10
+ readonly parameters: Record<string, unknown>;
11
+ readonly handler: (params: any) => Promise<McpToolResponse>;
12
+ }
13
+ export type ToolHandler<TInput = Record<string, unknown>, TOutput = McpToolResponse> = (input: TInput) => Promise<TOutput>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { Storage } from '../../storage/storage.js';
2
+ import { ToolDefinition } from '../base/types.js';
3
+ import { StorageConfig } from '../../../../utils/storage-config.js';
4
+ type StorageFactory = (workingDirectory: string, config: StorageConfig) => Promise<Storage>;
5
+ /**
6
+ * Create all task management tools using the same factory pattern as artifact tools.
7
+ * Each tool creates its own storage instance per-call using the provided factory.
8
+ */
9
+ export declare function createTaskTools(config: StorageConfig, createStorage: StorageFactory): ToolDefinition[];
10
+ export {};