@claudetools/tools 0.3.8 → 0.4.0

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.
@@ -48,3 +48,40 @@ export declare function injectContext(projectId: string, query: string, userId?:
48
48
  reason?: string;
49
49
  };
50
50
  }>;
51
+ export interface CachedDoc {
52
+ library: string;
53
+ context7Id: string | null;
54
+ version: string | null;
55
+ fetchedAt: string;
56
+ size: number;
57
+ mode: 'code' | 'info';
58
+ content: string;
59
+ library_name?: string;
60
+ }
61
+ export interface DocsCacheListResponse {
62
+ libraries: {
63
+ library_id: string;
64
+ library_name: string;
65
+ topics?: string[];
66
+ cached_at: string;
67
+ }[];
68
+ }
69
+ /**
70
+ * List all cached documentation libraries (global cache)
71
+ */
72
+ export declare function listCachedDocs(_projectId?: string): Promise<DocsCacheListResponse>;
73
+ /**
74
+ * Get cached documentation for a specific library
75
+ */
76
+ export declare function getCachedDocs(_projectId: string, // Unused - docs cache is global
77
+ libraryId: string, topic?: string): Promise<CachedDoc | null>;
78
+ /**
79
+ * Ensure documentation is cached (fetches from Context7 if needed)
80
+ */
81
+ export declare function cacheDocs(_projectId: string, // Unused - docs cache is global
82
+ libraryId: string, libraryName: string, _content?: string, // Unused - backend fetches from Context7
83
+ topic?: string): Promise<{
84
+ success: boolean;
85
+ status?: string;
86
+ size?: number;
87
+ }>;
@@ -57,3 +57,66 @@ export async function injectContext(projectId, query, userId = DEFAULT_USER_ID)
57
57
  });
58
58
  return response.data;
59
59
  }
60
+ /**
61
+ * List all cached documentation libraries (global cache)
62
+ */
63
+ export async function listCachedDocs(_projectId // Unused - docs cache is global
64
+ ) {
65
+ try {
66
+ const response = await apiRequest('/api/v1/docs');
67
+ // Transform to expected format
68
+ return {
69
+ libraries: response.data.libraries.map(lib => ({
70
+ library_id: lib.library,
71
+ library_name: lib.library,
72
+ cached_at: new Date().toISOString(), // Not provided by list endpoint
73
+ })),
74
+ };
75
+ }
76
+ catch {
77
+ return { libraries: [] };
78
+ }
79
+ }
80
+ /**
81
+ * Get cached documentation for a specific library
82
+ */
83
+ export async function getCachedDocs(_projectId, // Unused - docs cache is global
84
+ libraryId, topic) {
85
+ try {
86
+ const modeParam = topic ? `?mode=info` : '';
87
+ const response = await apiRequest(`/api/v1/docs/${encodeURIComponent(libraryId)}${modeParam}`);
88
+ if (!response.success || !response.data) {
89
+ return null;
90
+ }
91
+ // Add compatibility alias
92
+ return {
93
+ ...response.data,
94
+ library_name: response.data.library,
95
+ };
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Ensure documentation is cached (fetches from Context7 if needed)
103
+ */
104
+ export async function cacheDocs(_projectId, // Unused - docs cache is global
105
+ libraryId, libraryName, _content, // Unused - backend fetches from Context7
106
+ topic) {
107
+ try {
108
+ const response = await apiRequest('/api/v1/docs/ensure', 'POST', {
109
+ library: libraryName || libraryId,
110
+ mode: topic ? 'info' : 'code',
111
+ topic,
112
+ });
113
+ return {
114
+ success: response.success,
115
+ status: response.data?.status,
116
+ size: response.data?.size,
117
+ };
118
+ }
119
+ catch {
120
+ return { success: false };
121
+ }
122
+ }
@@ -21,7 +21,7 @@ export const DEFAULT_CONFIG = {
21
21
  defaultProjectId: undefined,
22
22
  defaultUserId: 'default',
23
23
  // Context Injection
24
- autoInjectContext: false, // Disabled by default for backward compatibility
24
+ autoInjectContext: true, // Enabled by default - this is the core value prop
25
25
  contextRelevanceThreshold: 0.5,
26
26
  maxContextFacts: 10,
27
27
  // Logging & Verbosity
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Context7-compatible library ID mappings
3
+ * Maps canonical names to context7 library IDs
4
+ */
5
+ export declare const CONTEXT7_LIBRARY_IDS: Record<string, string>;
6
+ export interface DetectedLibrary {
7
+ name: string;
8
+ context7Id?: string;
9
+ mentions: number;
10
+ }
11
+ /**
12
+ * Detect libraries mentioned in text
13
+ * @param text - Text to scan for library mentions
14
+ * @returns Array of detected libraries with mention counts
15
+ */
16
+ export declare function detectLibraries(text: string): DetectedLibrary[];
17
+ /**
18
+ * Detect libraries from goal and task descriptions
19
+ * @param goal - The epic/plan goal
20
+ * @param tasks - Array of task objects with title and description
21
+ * @returns Deduplicated list of detected libraries
22
+ */
23
+ export declare function detectLibrariesFromPlan(goal: string, tasks: {
24
+ title: string;
25
+ description?: string;
26
+ }[]): DetectedLibrary[];
@@ -0,0 +1,145 @@
1
+ // =============================================================================
2
+ // Library/Framework Detection for Task Planning
3
+ // =============================================================================
4
+ /**
5
+ * Common libraries and frameworks to detect in task descriptions
6
+ * Maps lowercase keywords to canonical library names
7
+ */
8
+ const LIBRARY_KEYWORDS = {
9
+ // Frontend frameworks
10
+ 'react': 'React',
11
+ 'reactjs': 'React',
12
+ 'react.js': 'React',
13
+ 'next': 'Next.js',
14
+ 'nextjs': 'Next.js',
15
+ 'next.js': 'Next.js',
16
+ 'vue': 'Vue.js',
17
+ 'vuejs': 'Vue.js',
18
+ 'vue.js': 'Vue.js',
19
+ 'angular': 'Angular',
20
+ 'svelte': 'Svelte',
21
+ 'sveltekit': 'SvelteKit',
22
+ // Backend frameworks
23
+ 'express': 'Express',
24
+ 'expressjs': 'Express',
25
+ 'fastify': 'Fastify',
26
+ 'hono': 'Hono',
27
+ 'koa': 'Koa',
28
+ 'nest': 'NestJS',
29
+ 'nestjs': 'NestJS',
30
+ // Databases & ORMs
31
+ 'supabase': 'Supabase',
32
+ 'firebase': 'Firebase',
33
+ 'prisma': 'Prisma',
34
+ 'drizzle': 'Drizzle',
35
+ 'mongoose': 'Mongoose',
36
+ 'mongodb': 'MongoDB',
37
+ 'postgres': 'PostgreSQL',
38
+ 'postgresql': 'PostgreSQL',
39
+ 'mysql': 'MySQL',
40
+ 'redis': 'Redis',
41
+ // State management
42
+ 'redux': 'Redux',
43
+ 'zustand': 'Zustand',
44
+ 'jotai': 'Jotai',
45
+ 'recoil': 'Recoil',
46
+ 'tanstack': 'TanStack Query',
47
+ 'react-query': 'TanStack Query',
48
+ 'swr': 'SWR',
49
+ // UI libraries
50
+ 'tailwind': 'Tailwind CSS',
51
+ 'tailwindcss': 'Tailwind CSS',
52
+ 'shadcn': 'shadcn/ui',
53
+ 'radix': 'Radix UI',
54
+ 'chakra': 'Chakra UI',
55
+ 'mantine': 'Mantine',
56
+ 'mui': 'Material UI',
57
+ 'material-ui': 'Material UI',
58
+ // Testing
59
+ 'jest': 'Jest',
60
+ 'vitest': 'Vitest',
61
+ 'playwright': 'Playwright',
62
+ 'cypress': 'Cypress',
63
+ 'testing-library': 'Testing Library',
64
+ // Build tools
65
+ 'vite': 'Vite',
66
+ 'webpack': 'Webpack',
67
+ 'esbuild': 'esbuild',
68
+ 'turbopack': 'Turbopack',
69
+ 'turborepo': 'Turborepo',
70
+ // Auth & APIs
71
+ 'auth': 'Authentication',
72
+ 'oauth': 'OAuth',
73
+ 'jwt': 'JWT',
74
+ 'stripe': 'Stripe',
75
+ 'openai': 'OpenAI',
76
+ 'langchain': 'LangChain',
77
+ 'vercel': 'Vercel',
78
+ // Other
79
+ 'typescript': 'TypeScript',
80
+ 'graphql': 'GraphQL',
81
+ 'trpc': 'tRPC',
82
+ 'zod': 'Zod',
83
+ 'socket.io': 'Socket.IO',
84
+ 'websocket': 'WebSocket',
85
+ };
86
+ /**
87
+ * Context7-compatible library ID mappings
88
+ * Maps canonical names to context7 library IDs
89
+ */
90
+ export const CONTEXT7_LIBRARY_IDS = {
91
+ 'React': '/facebook/react',
92
+ 'Next.js': '/vercel/next.js',
93
+ 'Vue.js': '/vuejs/vue',
94
+ 'Supabase': '/supabase/supabase',
95
+ 'Prisma': '/prisma/prisma',
96
+ 'Tailwind CSS': '/tailwindlabs/tailwindcss',
97
+ 'TypeScript': '/microsoft/typescript',
98
+ 'Vitest': '/vitest-dev/vitest',
99
+ 'Zod': '/colinhacks/zod',
100
+ 'tRPC': '/trpc/trpc',
101
+ 'TanStack Query': '/tanstack/query',
102
+ };
103
+ /**
104
+ * Detect libraries mentioned in text
105
+ * @param text - Text to scan for library mentions
106
+ * @returns Array of detected libraries with mention counts
107
+ */
108
+ export function detectLibraries(text) {
109
+ const lowerText = text.toLowerCase();
110
+ const detected = new Map();
111
+ // Scan for each keyword
112
+ for (const [keyword, libraryName] of Object.entries(LIBRARY_KEYWORDS)) {
113
+ // Use word boundary matching to avoid partial matches
114
+ const regex = new RegExp(`\\b${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'gi');
115
+ const matches = lowerText.match(regex);
116
+ if (matches) {
117
+ const current = detected.get(libraryName) || 0;
118
+ detected.set(libraryName, current + matches.length);
119
+ }
120
+ }
121
+ // Convert to array and sort by mention count
122
+ const results = [];
123
+ for (const [name, mentions] of detected) {
124
+ results.push({
125
+ name,
126
+ context7Id: CONTEXT7_LIBRARY_IDS[name],
127
+ mentions,
128
+ });
129
+ }
130
+ return results.sort((a, b) => b.mentions - a.mentions);
131
+ }
132
+ /**
133
+ * Detect libraries from goal and task descriptions
134
+ * @param goal - The epic/plan goal
135
+ * @param tasks - Array of task objects with title and description
136
+ * @returns Deduplicated list of detected libraries
137
+ */
138
+ export function detectLibrariesFromPlan(goal, tasks) {
139
+ // Combine all text
140
+ const allText = [
141
+ goal,
142
+ ...tasks.map(t => `${t.title} ${t.description || ''}`),
143
+ ].join(' ');
144
+ return detectLibraries(allText);
145
+ }
@@ -0,0 +1,49 @@
1
+ import { type Task } from './tasks.js';
2
+ export interface TimedOutTask {
3
+ task: Task;
4
+ lockExpiredAt: string;
5
+ timeSinceExpiry: number;
6
+ }
7
+ export interface RetryMetadata {
8
+ retryCount: number;
9
+ lastFailedAt: string;
10
+ lastError?: string;
11
+ failureHistory: Array<{
12
+ timestamp: string;
13
+ error?: string;
14
+ }>;
15
+ }
16
+ /**
17
+ * Detect tasks that have timed out (lock expired while in_progress)
18
+ * These are likely abandoned or failed tasks
19
+ */
20
+ export declare function detectTimedOutTasks(userId: string, projectId: string): Promise<TimedOutTask[]>;
21
+ /**
22
+ * Retry a failed or timed-out task
23
+ * Returns success: false if retry limit exceeded
24
+ */
25
+ export declare function retryTask(userId: string, projectId: string, taskId: string, maxRetries?: number, errorContext?: string): Promise<{
26
+ success: boolean;
27
+ data?: {
28
+ task: Task;
29
+ retryCount: number;
30
+ retriesRemaining: number;
31
+ };
32
+ error?: string;
33
+ }>;
34
+ /**
35
+ * Mark a task as failed with context
36
+ */
37
+ export declare function failTask(userId: string, projectId: string, taskId: string, errorContext: string, agentId?: string): Promise<{
38
+ success: boolean;
39
+ data: Task;
40
+ }>;
41
+ /**
42
+ * Auto-detect and handle timed-out tasks
43
+ * Returns tasks that were auto-retried
44
+ */
45
+ export declare function autoRetryTimedOutTasks(userId: string, projectId: string, maxRetries?: number): Promise<{
46
+ timedOut: TimedOutTask[];
47
+ retried: Task[];
48
+ failed: Task[];
49
+ }>;
@@ -0,0 +1,168 @@
1
+ // =============================================================================
2
+ // Task Failure Handling & Retry System
3
+ // =============================================================================
4
+ import { apiRequest } from './api-client.js';
5
+ import { getTask, listTasks, updateTaskStatus, addTaskContext } from './tasks.js';
6
+ /**
7
+ * Get retry metadata from task metadata
8
+ */
9
+ function getRetryMetadata(task) {
10
+ const metadata = task.metadata || {};
11
+ return {
12
+ retryCount: metadata.retryCount || 0,
13
+ lastFailedAt: metadata.lastFailedAt || '',
14
+ lastError: metadata.lastError,
15
+ failureHistory: metadata.failureHistory || [],
16
+ };
17
+ }
18
+ /**
19
+ * Update retry metadata
20
+ */
21
+ function updateRetryMetadata(metadata, error) {
22
+ const current = metadata;
23
+ const retryCount = (current.retryCount || 0) + 1;
24
+ const timestamp = new Date().toISOString();
25
+ const failureHistory = (current.failureHistory || []);
26
+ return {
27
+ ...metadata,
28
+ retryCount,
29
+ lastFailedAt: timestamp,
30
+ lastError: error,
31
+ failureHistory: [
32
+ ...failureHistory,
33
+ { timestamp, error },
34
+ ],
35
+ };
36
+ }
37
+ /**
38
+ * Detect tasks that have timed out (lock expired while in_progress)
39
+ * These are likely abandoned or failed tasks
40
+ */
41
+ export async function detectTimedOutTasks(userId, projectId) {
42
+ // Get all in_progress tasks
43
+ const result = await listTasks(userId, projectId, {
44
+ status: 'in_progress',
45
+ limit: 100,
46
+ });
47
+ if (!result.success || !result.data.length) {
48
+ return [];
49
+ }
50
+ const now = new Date();
51
+ const timedOut = [];
52
+ for (const task of result.data) {
53
+ // Check if lock has expired
54
+ if (task.lock_expires_at) {
55
+ const lockExpires = new Date(task.lock_expires_at);
56
+ if (lockExpires < now) {
57
+ timedOut.push({
58
+ task,
59
+ lockExpiredAt: task.lock_expires_at,
60
+ timeSinceExpiry: now.getTime() - lockExpires.getTime(),
61
+ });
62
+ }
63
+ }
64
+ }
65
+ return timedOut;
66
+ }
67
+ /**
68
+ * Retry a failed or timed-out task
69
+ * Returns success: false if retry limit exceeded
70
+ */
71
+ export async function retryTask(userId, projectId, taskId, maxRetries = 3, errorContext) {
72
+ // Get current task
73
+ const taskResult = await getTask(userId, projectId, taskId);
74
+ if (!taskResult.success) {
75
+ return {
76
+ success: false,
77
+ error: 'Task not found',
78
+ };
79
+ }
80
+ const task = taskResult.data;
81
+ const retryMetadata = getRetryMetadata(task);
82
+ // Check retry limit
83
+ if (retryMetadata.retryCount >= maxRetries) {
84
+ // Mark as failed permanently
85
+ await updateTaskStatus(userId, projectId, taskId, 'failed');
86
+ // Add failure context
87
+ await addTaskContext(userId, projectId, taskId, 'work_log', `Task failed permanently after ${retryMetadata.retryCount} retries. Last error: ${errorContext || 'Unknown'}`, 'system');
88
+ return {
89
+ success: false,
90
+ error: `Retry limit exceeded (${maxRetries} retries)`,
91
+ };
92
+ }
93
+ // Update metadata with failure info
94
+ const updatedMetadata = updateRetryMetadata(task.metadata || {}, errorContext);
95
+ // Update task with retry metadata and reset status
96
+ // Using the /api/v1/tasks/:userId/:projectId/:taskId endpoint with POST
97
+ await apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}`, 'POST', {
98
+ status: 'ready',
99
+ assigned_to: null,
100
+ locked_at: null,
101
+ lock_expires_at: null,
102
+ metadata: updatedMetadata,
103
+ });
104
+ // Add retry context
105
+ const newRetryCount = retryMetadata.retryCount + 1;
106
+ await addTaskContext(userId, projectId, taskId, 'work_log', `Task retry ${newRetryCount}/${maxRetries}. Previous failure: ${errorContext || 'Lock timeout'}`, 'system');
107
+ // Get updated task
108
+ const updatedResult = await getTask(userId, projectId, taskId);
109
+ const updatedTask = updatedResult.data;
110
+ return {
111
+ success: true,
112
+ data: {
113
+ task: updatedTask,
114
+ retryCount: newRetryCount,
115
+ retriesRemaining: maxRetries - newRetryCount,
116
+ },
117
+ };
118
+ }
119
+ /**
120
+ * Mark a task as failed with context
121
+ */
122
+ export async function failTask(userId, projectId, taskId, errorContext, agentId) {
123
+ // Get current task to update metadata
124
+ const taskResult = await getTask(userId, projectId, taskId);
125
+ if (!taskResult.success) {
126
+ throw new Error('Task not found');
127
+ }
128
+ const task = taskResult.data;
129
+ const updatedMetadata = updateRetryMetadata(task.metadata || {}, errorContext);
130
+ // Update to failed status with metadata
131
+ await apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}`, 'POST', {
132
+ status: 'failed',
133
+ metadata: updatedMetadata,
134
+ assigned_to: null,
135
+ locked_at: null,
136
+ lock_expires_at: null,
137
+ });
138
+ // Add failure context
139
+ await addTaskContext(userId, projectId, taskId, 'work_log', `Task failed: ${errorContext}`, agentId || 'system');
140
+ // Get updated task
141
+ const result = await getTask(userId, projectId, taskId);
142
+ return result;
143
+ }
144
+ /**
145
+ * Auto-detect and handle timed-out tasks
146
+ * Returns tasks that were auto-retried
147
+ */
148
+ export async function autoRetryTimedOutTasks(userId, projectId, maxRetries = 3) {
149
+ const timedOut = await detectTimedOutTasks(userId, projectId);
150
+ const retried = [];
151
+ const failed = [];
152
+ for (const { task, timeSinceExpiry } of timedOut) {
153
+ const minutesSinceExpiry = Math.floor(timeSinceExpiry / 1000 / 60);
154
+ const errorContext = `Lock timeout: expired ${minutesSinceExpiry} minutes ago`;
155
+ const retryResult = await retryTask(userId, projectId, task.id, maxRetries, errorContext);
156
+ if (retryResult.success && retryResult.data) {
157
+ retried.push(retryResult.data.task);
158
+ }
159
+ else {
160
+ // Task failed permanently
161
+ const failResult = await getTask(userId, projectId, task.id);
162
+ if (failResult.success) {
163
+ failed.push(failResult.data);
164
+ }
165
+ }
166
+ }
167
+ return { timedOut, retried, failed };
168
+ }
@@ -127,12 +127,23 @@ export declare function heartbeatTask(userId: string, projectId: string, taskId:
127
127
  new_expires_at: string;
128
128
  };
129
129
  }>;
130
+ export declare const DEFAULT_MAX_PARALLEL = 5;
131
+ /**
132
+ * Get count of currently active tasks
133
+ * Returns both in_progress and claimed (locked) task counts
134
+ */
135
+ export declare function getActiveTaskCount(userId: string, projectId: string, epicId?: string): Promise<{
136
+ inProgress: number;
137
+ claimed: number;
138
+ total: number;
139
+ }>;
130
140
  /**
131
141
  * Get all tasks ready for parallel dispatch
132
142
  * - Filters for 'ready' status
133
143
  * - Excludes already claimed tasks
134
144
  * - Resolves dependencies (only returns unblocked tasks)
135
145
  * - Matches each to appropriate expert worker
146
+ * - Respects max parallel limit by considering currently active tasks
136
147
  */
137
148
  export declare function getDispatchableTasks(userId: string, projectId: string, epicId?: string, maxParallel?: number): Promise<DispatchableTask[]>;
138
149
  /**
@@ -147,6 +158,18 @@ export declare function getExecutionContext(userId: string, projectId: string, t
147
158
  siblingTasks?: Task[];
148
159
  }>;
149
160
  /**
150
- * Find newly unblocked tasks after a completion
161
+ * Find newly unblocked tasks after a completion and update their status to ready
151
162
  */
152
163
  export declare function resolveTaskDependencies(userId: string, projectId: string, completedTaskId: string, epicId?: string): Promise<Task[]>;
164
+ /**
165
+ * Get epic status with progress tracking
166
+ * Auto-completes epic if all child tasks are done
167
+ */
168
+ export declare function getEpicStatus(userId: string, projectId: string, epicId: string): Promise<{
169
+ epic: Task;
170
+ totalTasks: number;
171
+ byStatus: Record<string, number>;
172
+ percentComplete: number;
173
+ allComplete: boolean;
174
+ autoCompleted: boolean;
175
+ }>;