@claudetools/tools 0.8.3 → 0.8.5

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 (69) hide show
  1. package/dist/cli.js +41 -0
  2. package/dist/context/deduplication.d.ts +72 -0
  3. package/dist/context/deduplication.js +77 -0
  4. package/dist/context/deduplication.test.d.ts +6 -0
  5. package/dist/context/deduplication.test.js +84 -0
  6. package/dist/context/emergency-eviction.d.ts +73 -0
  7. package/dist/context/emergency-eviction.example.d.ts +13 -0
  8. package/dist/context/emergency-eviction.example.js +94 -0
  9. package/dist/context/emergency-eviction.js +226 -0
  10. package/dist/context/eviction-engine.d.ts +76 -0
  11. package/dist/context/eviction-engine.example.d.ts +7 -0
  12. package/dist/context/eviction-engine.example.js +144 -0
  13. package/dist/context/eviction-engine.js +176 -0
  14. package/dist/context/example-usage.d.ts +1 -0
  15. package/dist/context/example-usage.js +128 -0
  16. package/dist/context/exchange-summariser.d.ts +80 -0
  17. package/dist/context/exchange-summariser.js +261 -0
  18. package/dist/context/health-monitor.d.ts +97 -0
  19. package/dist/context/health-monitor.example.d.ts +1 -0
  20. package/dist/context/health-monitor.example.js +164 -0
  21. package/dist/context/health-monitor.js +210 -0
  22. package/dist/context/importance-scorer.d.ts +94 -0
  23. package/dist/context/importance-scorer.example.d.ts +1 -0
  24. package/dist/context/importance-scorer.example.js +140 -0
  25. package/dist/context/importance-scorer.js +187 -0
  26. package/dist/context/index.d.ts +9 -0
  27. package/dist/context/index.js +16 -0
  28. package/dist/context/session-helper.d.ts +10 -0
  29. package/dist/context/session-helper.js +51 -0
  30. package/dist/context/session-store.d.ts +94 -0
  31. package/dist/context/session-store.js +286 -0
  32. package/dist/context/usage-estimator.d.ts +131 -0
  33. package/dist/context/usage-estimator.js +260 -0
  34. package/dist/context/usage-estimator.test.d.ts +1 -0
  35. package/dist/context/usage-estimator.test.js +208 -0
  36. package/dist/context-cli.d.ts +16 -0
  37. package/dist/context-cli.js +309 -0
  38. package/dist/handlers/codedna-handlers.d.ts +1 -1
  39. package/dist/handlers/tool-handlers.js +215 -13
  40. package/dist/helpers/api-client.d.ts +5 -1
  41. package/dist/helpers/api-client.js +3 -1
  42. package/dist/helpers/circuit-breaker.d.ts +28 -0
  43. package/dist/helpers/circuit-breaker.js +97 -0
  44. package/dist/helpers/compact-formatter.d.ts +2 -0
  45. package/dist/helpers/compact-formatter.js +6 -0
  46. package/dist/helpers/error-tracking.js +1 -1
  47. package/dist/helpers/tasks-retry.d.ts +9 -0
  48. package/dist/helpers/tasks-retry.js +30 -0
  49. package/dist/helpers/tasks.d.ts +91 -5
  50. package/dist/helpers/tasks.js +261 -16
  51. package/dist/helpers/usage-analytics.js +1 -1
  52. package/dist/hooks/index.d.ts +4 -0
  53. package/dist/hooks/index.js +6 -0
  54. package/dist/hooks/post-tool-use-hook-cli.d.ts +2 -0
  55. package/dist/hooks/post-tool-use-hook-cli.js +34 -0
  56. package/dist/hooks/post-tool-use.d.ts +67 -0
  57. package/dist/hooks/post-tool-use.js +234 -0
  58. package/dist/hooks/stop-hook-cli.d.ts +2 -0
  59. package/dist/hooks/stop-hook-cli.js +34 -0
  60. package/dist/hooks/stop.d.ts +64 -0
  61. package/dist/hooks/stop.js +192 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +2 -0
  64. package/dist/logger.d.ts +1 -1
  65. package/dist/logger.js +4 -0
  66. package/dist/setup.js +206 -2
  67. package/dist/tools.js +107 -2
  68. package/package.json +19 -18
  69. package/scripts/verify-prompt-compliance.sh +0 -0
@@ -0,0 +1,97 @@
1
+ // =============================================================================
2
+ // Circuit Breaker for Worker Failure Tracking
3
+ // =============================================================================
4
+ //
5
+ // Implements the circuit breaker pattern to prevent dispatching tasks to
6
+ // failing workers. Tracks failures per worker type and opens circuit after
7
+ // threshold is reached. Circuit auto-closes after cooldown period.
8
+ //
9
+ // Circuit States:
10
+ // - CLOSED: Normal operation, tasks dispatched
11
+ // - OPEN: Too many failures, tasks NOT dispatched to this worker type
12
+ // - HALF_OPEN: Cooldown complete, attempting recovery (not implemented yet)
13
+ //
14
+ // Configuration
15
+ const FAILURE_THRESHOLD = 3; // Open circuit after N failures
16
+ const FAILURE_WINDOW_MS = 5 * 60 * 1000; // 5 minutes window for counting failures
17
+ const COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes cooldown before auto-close
18
+ // In-memory circuit state (per worker type)
19
+ const circuits = new Map();
20
+ /**
21
+ * Record a worker failure
22
+ * Increments failure count and potentially opens the circuit
23
+ */
24
+ export function recordWorkerFailure(workerType) {
25
+ const now = Date.now();
26
+ const state = circuits.get(workerType) || {
27
+ failures: 0,
28
+ lastFailure: 0,
29
+ openedAt: null,
30
+ };
31
+ // If circuit is already open, don't count new failures
32
+ if (state.openedAt !== null) {
33
+ return;
34
+ }
35
+ // Check if last failure was outside the window
36
+ if (now - state.lastFailure > FAILURE_WINDOW_MS) {
37
+ // Reset failure count (new window)
38
+ state.failures = 1;
39
+ }
40
+ else {
41
+ // Within window, increment
42
+ state.failures++;
43
+ }
44
+ state.lastFailure = now;
45
+ // Check if we should open the circuit
46
+ if (state.failures >= FAILURE_THRESHOLD) {
47
+ state.openedAt = now;
48
+ console.warn(`[Circuit Breaker] OPENED for worker type: ${workerType} (${state.failures} failures)`);
49
+ }
50
+ circuits.set(workerType, state);
51
+ }
52
+ /**
53
+ * Check if circuit is open for a worker type
54
+ * Auto-closes circuit if cooldown period has elapsed
55
+ */
56
+ export function isCircuitOpen(workerType) {
57
+ const state = circuits.get(workerType);
58
+ // No failures recorded = closed
59
+ if (!state || state.openedAt === null) {
60
+ return false;
61
+ }
62
+ const now = Date.now();
63
+ const timeSinceOpened = now - state.openedAt;
64
+ // Check if cooldown has elapsed
65
+ if (timeSinceOpened >= COOLDOWN_MS) {
66
+ // Auto-close circuit
67
+ state.openedAt = null;
68
+ state.failures = 0;
69
+ circuits.set(workerType, state);
70
+ console.info(`[Circuit Breaker] AUTO-CLOSED for worker type: ${workerType} (cooldown elapsed)`);
71
+ return false;
72
+ }
73
+ // Still in cooldown
74
+ return true;
75
+ }
76
+ /**
77
+ * Get circuit status for all worker types
78
+ * Useful for monitoring and debugging
79
+ */
80
+ export function getCircuitStatus() {
81
+ // Return a copy of the current state
82
+ return new Map(circuits);
83
+ }
84
+ /**
85
+ * Manually reset a circuit (for testing or admin override)
86
+ */
87
+ export function resetCircuit(workerType) {
88
+ circuits.delete(workerType);
89
+ console.info(`[Circuit Breaker] RESET for worker type: ${workerType}`);
90
+ }
91
+ /**
92
+ * Reset all circuits (for testing)
93
+ */
94
+ export function resetAllCircuits() {
95
+ circuits.clear();
96
+ console.info('[Circuit Breaker] RESET ALL circuits');
97
+ }
@@ -49,3 +49,5 @@ export declare function compactMemoryIndex(entries: {
49
49
  relevance: number;
50
50
  is_critical?: boolean;
51
51
  }[]): string;
52
+ /** Compact task handoff */
53
+ export declare function compactTaskHandoff(task: Task, handedOff: boolean, newWorkerType: string): string;
@@ -128,3 +128,9 @@ export function compactMemoryIndex(entries) {
128
128
  return `${i + 1}. ${critical}${e.summary.slice(0, 60)} (${rel}%) [${shortId(e.id)}]`;
129
129
  }).join('\n');
130
130
  }
131
+ /** Compact task handoff */
132
+ export function compactTaskHandoff(task, handedOff, newWorkerType) {
133
+ if (!handedOff)
134
+ return `❌ Failed to hand off: ${task.title}`;
135
+ return `🔄 Handed off: ${task.title} → ${newWorkerType} (ready for re-dispatch)`;
136
+ }
@@ -28,7 +28,7 @@ export class CodeDNAErrorTracker {
28
28
  // Store error in memory system as a fact
29
29
  const userId = DEFAULT_USER_ID;
30
30
  const projectId = error.projectId || resolveProjectId();
31
- await storeFact(projectId, 'CodeDNA', 'ERROR_OCCURRED', error.operation, JSON.stringify(errorRecord), userId);
31
+ await storeFact(projectId, 'CodeDNA', 'ERROR_OCCURRED', error.operation, JSON.stringify(errorRecord), { userId });
32
32
  // Log to console for immediate visibility
33
33
  console.error('[CodeDNA Error]', {
34
34
  operation: error.operation,
@@ -7,12 +7,19 @@ export interface TimedOutTask {
7
7
  export interface RetryMetadata {
8
8
  retryCount: number;
9
9
  lastFailedAt: string;
10
+ lastRetryAt?: string;
10
11
  lastError?: string;
11
12
  failureHistory: Array<{
12
13
  timestamp: string;
13
14
  error?: string;
14
15
  }>;
15
16
  }
17
+ /**
18
+ * Calculate exponential backoff delay in milliseconds
19
+ * Formula: delay_ms = min(1000 * 2^retry_count, 300000)
20
+ * Max delay is 5 minutes (300000ms)
21
+ */
22
+ export declare function calculateBackoffDelay(retryCount: number): number;
16
23
  /**
17
24
  * Detect tasks that have timed out (lock expired while in_progress)
18
25
  * These are likely abandoned or failed tasks
@@ -28,8 +35,10 @@ export declare function retryTask(userId: string, projectId: string, taskId: str
28
35
  task: Task;
29
36
  retryCount: number;
30
37
  retriesRemaining: number;
38
+ retryAfter?: string;
31
39
  };
32
40
  error?: string;
41
+ retryAfter?: string;
33
42
  }>;
34
43
  /**
35
44
  * Mark a task as failed with context
@@ -11,10 +11,22 @@ function getRetryMetadata(task) {
11
11
  return {
12
12
  retryCount: metadata.retryCount || 0,
13
13
  lastFailedAt: metadata.lastFailedAt || '',
14
+ lastRetryAt: metadata.lastRetryAt,
14
15
  lastError: metadata.lastError,
15
16
  failureHistory: metadata.failureHistory || [],
16
17
  };
17
18
  }
19
+ /**
20
+ * Calculate exponential backoff delay in milliseconds
21
+ * Formula: delay_ms = min(1000 * 2^retry_count, 300000)
22
+ * Max delay is 5 minutes (300000ms)
23
+ */
24
+ export function calculateBackoffDelay(retryCount) {
25
+ const baseDelay = 1000; // 1 second
26
+ const maxDelay = 300000; // 5 minutes
27
+ const delay = baseDelay * Math.pow(2, retryCount);
28
+ return Math.min(delay, maxDelay);
29
+ }
18
30
  /**
19
31
  * Update retry metadata
20
32
  */
@@ -27,6 +39,7 @@ function updateRetryMetadata(metadata, error) {
27
39
  ...metadata,
28
40
  retryCount,
29
41
  lastFailedAt: timestamp,
42
+ lastRetryAt: timestamp,
30
43
  lastError: error,
31
44
  failureHistory: [
32
45
  ...failureHistory,
@@ -90,6 +103,23 @@ export async function retryTask(userId, projectId, taskId, maxRetries = 3, error
90
103
  error: `Retry limit exceeded (${maxRetries} retries)`,
91
104
  };
92
105
  }
106
+ // Check exponential backoff window
107
+ if (retryMetadata.lastRetryAt && retryMetadata.retryCount > 0) {
108
+ const lastRetryTime = new Date(retryMetadata.lastRetryAt);
109
+ const now = new Date();
110
+ const timeSinceLastRetry = now.getTime() - lastRetryTime.getTime();
111
+ const backoffDelay = calculateBackoffDelay(retryMetadata.retryCount);
112
+ if (timeSinceLastRetry < backoffDelay) {
113
+ const remainingBackoff = backoffDelay - timeSinceLastRetry;
114
+ const retryAfterTime = new Date(now.getTime() + remainingBackoff);
115
+ const retryAfter = retryAfterTime.toISOString();
116
+ return {
117
+ success: false,
118
+ error: `Task is in backoff window. Please wait ${Math.ceil(remainingBackoff / 1000)} seconds before retrying.`,
119
+ retryAfter,
120
+ };
121
+ }
122
+ }
93
123
  // Update metadata with failure info
94
124
  const updatedMetadata = updateRetryMetadata(task.metadata || {}, errorContext);
95
125
  // Update task with retry metadata and reset status
@@ -127,15 +127,30 @@ 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;
130
+ export declare const EFFORT_WEIGHTS: {
131
+ readonly xs: 1;
132
+ readonly s: 2;
133
+ readonly m: 4;
134
+ readonly l: 8;
135
+ readonly xl: 16;
136
+ };
137
+ export declare const LOCK_DURATION_BY_EFFORT: {
138
+ readonly xs: 15;
139
+ readonly s: 30;
140
+ readonly m: 60;
141
+ readonly l: 120;
142
+ readonly xl: 240;
143
+ };
144
+ export declare const DEFAULT_CAPACITY_BUDGET = 20;
131
145
  /**
132
- * Get count of currently active tasks
133
- * Returns both in_progress and claimed (locked) task counts
146
+ * Get effort-weighted load of currently active tasks
147
+ * Returns both in_progress and claimed (locked) task counts, plus effort-weighted load
134
148
  */
135
149
  export declare function getActiveTaskCount(userId: string, projectId: string, epicId?: string): Promise<{
136
150
  inProgress: number;
137
151
  claimed: number;
138
152
  total: number;
153
+ effortLoad: number;
139
154
  }>;
140
155
  /**
141
156
  * Get all tasks ready for parallel dispatch
@@ -143,9 +158,17 @@ export declare function getActiveTaskCount(userId: string, projectId: string, ep
143
158
  * - Excludes already claimed tasks
144
159
  * - Resolves dependencies (only returns unblocked tasks)
145
160
  * - Matches each to appropriate expert worker
146
- * - Respects max parallel limit by considering currently active tasks
161
+ * - Respects effort-weighted capacity budget
147
162
  */
148
- export declare function getDispatchableTasks(userId: string, projectId: string, epicId?: string, maxParallel?: number): Promise<DispatchableTask[]>;
163
+ export declare function getDispatchableTasks(userId: string, projectId: string, epicId?: string, capacityBudget?: number): Promise<DispatchableTask[]>;
164
+ /**
165
+ * Check if task lock is about to expire and return warning
166
+ */
167
+ export declare function getLockExpiryWarning(lockExpiresAt: string | null): {
168
+ warning: boolean;
169
+ minutes_remaining: number;
170
+ message?: string;
171
+ };
149
172
  /**
150
173
  * Get full execution context for a worker agent
151
174
  */
@@ -156,11 +179,41 @@ export declare function getExecutionContext(userId: string, projectId: string, t
156
179
  context: TaskContext[];
157
180
  parentTask?: Task;
158
181
  siblingTasks?: Task[];
182
+ lockWarning?: {
183
+ warning: boolean;
184
+ minutes_remaining: number;
185
+ message?: string;
186
+ };
159
187
  }>;
160
188
  /**
161
189
  * Find newly unblocked tasks after a completion and update their status to ready
162
190
  */
163
191
  export declare function resolveTaskDependencies(userId: string, projectId: string, completedTaskId: string, epicId?: string): Promise<Task[]>;
192
+ /**
193
+ * Aggregate work_log context from all tasks in an epic
194
+ * Used by orchestrator to synthesize final results
195
+ */
196
+ export declare function getEpicAggregate(userId: string, projectId: string, epicId: string, includePending?: boolean): Promise<{
197
+ epic: {
198
+ id: string;
199
+ title: string;
200
+ description: string;
201
+ status: string;
202
+ };
203
+ tasks: Array<{
204
+ id: string;
205
+ title: string;
206
+ status: string;
207
+ work_log: string | null;
208
+ completed_at: string | null;
209
+ }>;
210
+ summary_stats: {
211
+ total: number;
212
+ completed: number;
213
+ in_progress: number;
214
+ pending: number;
215
+ };
216
+ }>;
164
217
  /**
165
218
  * Get epic status with progress tracking
166
219
  * Auto-completes epic if all child tasks are done
@@ -173,3 +226,36 @@ export declare function getEpicStatus(userId: string, projectId: string, epicId:
173
226
  allComplete: boolean;
174
227
  autoCompleted: boolean;
175
228
  }>;
229
+ export declare function handoffTask(userId: string, projectId: string, taskId: string, agentId: string, newWorkerType: string, reason: string): Promise<{
230
+ success: boolean;
231
+ data: {
232
+ task: Task;
233
+ handed_off: boolean;
234
+ new_worker_type: string;
235
+ };
236
+ }>;
237
+ /**
238
+ * Revise an existing epic by adding/removing/updating tasks
239
+ * Enables iterative planning without recreating the entire epic
240
+ */
241
+ export declare function reviseEpic(userId: string, projectId: string, epicId: string, changes: {
242
+ add_tasks?: {
243
+ title: string;
244
+ description?: string;
245
+ effort?: string;
246
+ domain?: string;
247
+ blocked_by?: string[];
248
+ }[];
249
+ remove_task_ids?: string[];
250
+ update_tasks?: {
251
+ task_id: string;
252
+ title?: string;
253
+ description?: string;
254
+ effort?: string;
255
+ }[];
256
+ }): Promise<{
257
+ epic: Task;
258
+ added: Task[];
259
+ removed: string[];
260
+ updated: Task[];
261
+ }>;