@claudetools/tools 0.8.4 → 0.8.6
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.
- package/dist/cli.js +14 -0
- package/dist/handlers/tool-handlers.js +140 -19
- package/dist/helpers/api-client.d.ts +2 -0
- package/dist/helpers/api-client.js +12 -1
- package/dist/helpers/circuit-breaker.d.ts +28 -0
- package/dist/helpers/circuit-breaker.js +97 -0
- package/dist/helpers/compact-formatter.d.ts +2 -0
- package/dist/helpers/compact-formatter.js +6 -0
- package/dist/helpers/tasks-retry.d.ts +9 -0
- package/dist/helpers/tasks-retry.js +30 -0
- package/dist/helpers/tasks.d.ts +91 -5
- package/dist/helpers/tasks.js +261 -16
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/test-runner.d.ts +33 -0
- package/dist/test-runner.js +149 -0
- package/dist/testing/example.d.ts +1 -0
- package/dist/testing/example.js +63 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +13 -0
- package/dist/testing/test-runner.d.ts +220 -0
- package/dist/testing/test-runner.js +302 -0
- package/dist/tools.js +103 -2
- package/package.json +1 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a single test execution
|
|
3
|
+
*/
|
|
4
|
+
export interface TestResult {
|
|
5
|
+
/** Test name/description */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Whether the test passed */
|
|
8
|
+
passed: boolean;
|
|
9
|
+
/** Duration in milliseconds */
|
|
10
|
+
duration_ms: number;
|
|
11
|
+
/** Error details if test failed */
|
|
12
|
+
error?: {
|
|
13
|
+
message: string;
|
|
14
|
+
stack?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Summary of test suite execution
|
|
19
|
+
*/
|
|
20
|
+
export interface TestSuiteSummary {
|
|
21
|
+
/** Total number of tests */
|
|
22
|
+
total: number;
|
|
23
|
+
/** Number of passed tests */
|
|
24
|
+
passed: number;
|
|
25
|
+
/** Number of failed tests */
|
|
26
|
+
failed: number;
|
|
27
|
+
/** Total duration in milliseconds */
|
|
28
|
+
total_duration_ms: number;
|
|
29
|
+
/** Average duration per test in milliseconds */
|
|
30
|
+
avg_duration_ms: number;
|
|
31
|
+
/** Pass rate as percentage (0-100) */
|
|
32
|
+
pass_rate: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Complete test suite results
|
|
36
|
+
*/
|
|
37
|
+
export interface TestSuite {
|
|
38
|
+
/** Suite name */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Individual test results */
|
|
41
|
+
tests: TestResult[];
|
|
42
|
+
/** Aggregated summary */
|
|
43
|
+
summary: TestSuiteSummary;
|
|
44
|
+
/** Timestamp when suite started */
|
|
45
|
+
started_at: string;
|
|
46
|
+
/** Timestamp when suite completed */
|
|
47
|
+
completed_at: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Test function signature
|
|
51
|
+
*/
|
|
52
|
+
export type TestFunction = () => Promise<void> | void;
|
|
53
|
+
/**
|
|
54
|
+
* Test definition
|
|
55
|
+
*/
|
|
56
|
+
export interface TestDefinition {
|
|
57
|
+
name: string;
|
|
58
|
+
fn: TestFunction;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run a single test with timing and error handling
|
|
62
|
+
*
|
|
63
|
+
* @param name - Test name/description
|
|
64
|
+
* @param fn - Test function to execute
|
|
65
|
+
* @returns Test result with pass/fail, duration, and error details
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const result = await runTest('should store and retrieve fact', async () => {
|
|
70
|
+
* const fact = await storeFact('test', 'RELATES_TO', 'value');
|
|
71
|
+
* assert(fact !== null);
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* if (!result.passed) {
|
|
75
|
+
* console.error(`Test failed: ${result.error?.message}`);
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function runTest(name: string, fn: TestFunction): Promise<TestResult>;
|
|
80
|
+
/**
|
|
81
|
+
* Run a suite of tests
|
|
82
|
+
*
|
|
83
|
+
* @param name - Suite name
|
|
84
|
+
* @param tests - Array of test definitions
|
|
85
|
+
* @returns Complete test suite results with summary
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const suite = await runSuite('Memory Integration Tests', [
|
|
90
|
+
* { name: 'should store fact', fn: async () => { ... } },
|
|
91
|
+
* { name: 'should search facts', fn: async () => { ... } },
|
|
92
|
+
* { name: 'should inject context', fn: async () => { ... } },
|
|
93
|
+
* ]);
|
|
94
|
+
*
|
|
95
|
+
* console.log(formatResults(suite));
|
|
96
|
+
*
|
|
97
|
+
* if (suite.summary.failed > 0) {
|
|
98
|
+
* process.exit(1);
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare function runSuite(name: string, tests: TestDefinition[]): Promise<TestSuite>;
|
|
103
|
+
/**
|
|
104
|
+
* Format test suite results as human-readable text
|
|
105
|
+
*
|
|
106
|
+
* @param suite - Test suite results
|
|
107
|
+
* @returns Formatted string with test results and summary
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const suite = await runSuite('My Tests', [...]);
|
|
112
|
+
* console.log(formatResults(suite));
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* Output:
|
|
116
|
+
* ```
|
|
117
|
+
* ================================================================================
|
|
118
|
+
* Test Suite: My Tests
|
|
119
|
+
* ================================================================================
|
|
120
|
+
*
|
|
121
|
+
* ✓ should pass test 1 (45.32ms)
|
|
122
|
+
* ✗ should fail test 2 (12.18ms)
|
|
123
|
+
* Error: Expected value to be true
|
|
124
|
+
*
|
|
125
|
+
* --------------------------------------------------------------------------------
|
|
126
|
+
* Summary
|
|
127
|
+
* --------------------------------------------------------------------------------
|
|
128
|
+
* Total: 2
|
|
129
|
+
* Passed: 1
|
|
130
|
+
* Failed: 1
|
|
131
|
+
* Pass Rate: 50.00%
|
|
132
|
+
* Duration: 57.50ms (avg: 28.75ms)
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare function formatResults(suite: TestSuite): string;
|
|
136
|
+
/**
|
|
137
|
+
* Format test suite results as JSON
|
|
138
|
+
*
|
|
139
|
+
* @param suite - Test suite results
|
|
140
|
+
* @param pretty - Whether to pretty-print the JSON (default: true)
|
|
141
|
+
* @returns JSON string
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* const suite = await runSuite('My Tests', [...]);
|
|
146
|
+
* const json = formatResultsJson(suite);
|
|
147
|
+
* await fs.writeFile('test-results.json', json);
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* Output:
|
|
151
|
+
* ```json
|
|
152
|
+
* {
|
|
153
|
+
* "name": "My Tests",
|
|
154
|
+
* "started_at": "2025-01-01T12:00:00.000Z",
|
|
155
|
+
* "completed_at": "2025-01-01T12:00:01.234Z",
|
|
156
|
+
* "summary": {
|
|
157
|
+
* "total": 5,
|
|
158
|
+
* "passed": 4,
|
|
159
|
+
* "failed": 1,
|
|
160
|
+
* "pass_rate": 80.00,
|
|
161
|
+
* "total_duration_ms": 1234.56,
|
|
162
|
+
* "avg_duration_ms": 246.91
|
|
163
|
+
* },
|
|
164
|
+
* "tests": [...]
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export declare function formatResultsJson(suite: TestSuite, pretty?: boolean): string;
|
|
169
|
+
/**
|
|
170
|
+
* Create a simple assertion helper for tests
|
|
171
|
+
*
|
|
172
|
+
* @param condition - Condition to check
|
|
173
|
+
* @param message - Error message if assertion fails
|
|
174
|
+
* @throws Error if condition is false
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```typescript
|
|
178
|
+
* await runTest('should work', async () => {
|
|
179
|
+
* const result = await someOperation();
|
|
180
|
+
* assert(result !== null, 'Result should not be null');
|
|
181
|
+
* assert(result.value === 'expected', 'Value should be expected');
|
|
182
|
+
* });
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export declare function assert(condition: boolean, message?: string): asserts condition;
|
|
186
|
+
/**
|
|
187
|
+
* Deep equality assertion
|
|
188
|
+
*
|
|
189
|
+
* @param actual - Actual value
|
|
190
|
+
* @param expected - Expected value
|
|
191
|
+
* @param message - Error message if assertion fails
|
|
192
|
+
* @throws Error if values are not equal
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* await runTest('should match object', async () => {
|
|
197
|
+
* const result = await getUser();
|
|
198
|
+
* assertEqual(result, { id: '123', name: 'Test' });
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export declare function assertEqual<T>(actual: T, expected: T, message?: string): void;
|
|
203
|
+
/**
|
|
204
|
+
* Async error assertion helper
|
|
205
|
+
*
|
|
206
|
+
* @param fn - Function that should throw
|
|
207
|
+
* @param expectedMessage - Optional expected error message
|
|
208
|
+
* @throws Error if function doesn't throw
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```typescript
|
|
212
|
+
* await runTest('should reject invalid input', async () => {
|
|
213
|
+
* await assertThrows(
|
|
214
|
+
* async () => await validateInput('invalid'),
|
|
215
|
+
* 'Invalid input format'
|
|
216
|
+
* );
|
|
217
|
+
* });
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
export declare function assertThrows(fn: () => Promise<void> | void, expectedMessage?: string): Promise<void>;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Agent-Based Integration Test Runner
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Core test harness for running integration tests against the ClaudeTools
|
|
6
|
+
// memory system. Provides result collection, timing, and reporting.
|
|
7
|
+
//
|
|
8
|
+
/**
|
|
9
|
+
* Run a single test with timing and error handling
|
|
10
|
+
*
|
|
11
|
+
* @param name - Test name/description
|
|
12
|
+
* @param fn - Test function to execute
|
|
13
|
+
* @returns Test result with pass/fail, duration, and error details
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const result = await runTest('should store and retrieve fact', async () => {
|
|
18
|
+
* const fact = await storeFact('test', 'RELATES_TO', 'value');
|
|
19
|
+
* assert(fact !== null);
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* if (!result.passed) {
|
|
23
|
+
* console.error(`Test failed: ${result.error?.message}`);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function runTest(name, fn) {
|
|
28
|
+
const startTime = performance.now();
|
|
29
|
+
try {
|
|
30
|
+
await fn();
|
|
31
|
+
const duration_ms = performance.now() - startTime;
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
passed: true,
|
|
35
|
+
duration_ms: Math.round(duration_ms * 100) / 100, // Round to 2 decimal places
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const duration_ms = performance.now() - startTime;
|
|
40
|
+
const err = error;
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
passed: false,
|
|
44
|
+
duration_ms: Math.round(duration_ms * 100) / 100,
|
|
45
|
+
error: {
|
|
46
|
+
message: err.message || String(error),
|
|
47
|
+
stack: err.stack,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Calculate summary statistics from test results
|
|
54
|
+
*
|
|
55
|
+
* @param tests - Array of test results
|
|
56
|
+
* @returns Aggregated summary with counts, duration, and pass rate
|
|
57
|
+
*/
|
|
58
|
+
function calculateSummary(tests) {
|
|
59
|
+
const total = tests.length;
|
|
60
|
+
const passed = tests.filter(t => t.passed).length;
|
|
61
|
+
const failed = total - passed;
|
|
62
|
+
const total_duration_ms = tests.reduce((sum, t) => sum + t.duration_ms, 0);
|
|
63
|
+
const avg_duration_ms = total > 0
|
|
64
|
+
? Math.round((total_duration_ms / total) * 100) / 100
|
|
65
|
+
: 0;
|
|
66
|
+
const pass_rate = total > 0
|
|
67
|
+
? Math.round((passed / total) * 10000) / 100 // Round to 2 decimal places
|
|
68
|
+
: 0;
|
|
69
|
+
return {
|
|
70
|
+
total,
|
|
71
|
+
passed,
|
|
72
|
+
failed,
|
|
73
|
+
total_duration_ms: Math.round(total_duration_ms * 100) / 100,
|
|
74
|
+
avg_duration_ms,
|
|
75
|
+
pass_rate,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Run a suite of tests
|
|
80
|
+
*
|
|
81
|
+
* @param name - Suite name
|
|
82
|
+
* @param tests - Array of test definitions
|
|
83
|
+
* @returns Complete test suite results with summary
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const suite = await runSuite('Memory Integration Tests', [
|
|
88
|
+
* { name: 'should store fact', fn: async () => { ... } },
|
|
89
|
+
* { name: 'should search facts', fn: async () => { ... } },
|
|
90
|
+
* { name: 'should inject context', fn: async () => { ... } },
|
|
91
|
+
* ]);
|
|
92
|
+
*
|
|
93
|
+
* console.log(formatResults(suite));
|
|
94
|
+
*
|
|
95
|
+
* if (suite.summary.failed > 0) {
|
|
96
|
+
* process.exit(1);
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export async function runSuite(name, tests) {
|
|
101
|
+
const started_at = new Date().toISOString();
|
|
102
|
+
// Run all tests sequentially
|
|
103
|
+
const results = [];
|
|
104
|
+
for (const test of tests) {
|
|
105
|
+
const result = await runTest(test.name, test.fn);
|
|
106
|
+
results.push(result);
|
|
107
|
+
}
|
|
108
|
+
const completed_at = new Date().toISOString();
|
|
109
|
+
const summary = calculateSummary(results);
|
|
110
|
+
return {
|
|
111
|
+
name,
|
|
112
|
+
tests: results,
|
|
113
|
+
summary,
|
|
114
|
+
started_at,
|
|
115
|
+
completed_at,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Format test suite results as human-readable text
|
|
120
|
+
*
|
|
121
|
+
* @param suite - Test suite results
|
|
122
|
+
* @returns Formatted string with test results and summary
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const suite = await runSuite('My Tests', [...]);
|
|
127
|
+
* console.log(formatResults(suite));
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* Output:
|
|
131
|
+
* ```
|
|
132
|
+
* ================================================================================
|
|
133
|
+
* Test Suite: My Tests
|
|
134
|
+
* ================================================================================
|
|
135
|
+
*
|
|
136
|
+
* ✓ should pass test 1 (45.32ms)
|
|
137
|
+
* ✗ should fail test 2 (12.18ms)
|
|
138
|
+
* Error: Expected value to be true
|
|
139
|
+
*
|
|
140
|
+
* --------------------------------------------------------------------------------
|
|
141
|
+
* Summary
|
|
142
|
+
* --------------------------------------------------------------------------------
|
|
143
|
+
* Total: 2
|
|
144
|
+
* Passed: 1
|
|
145
|
+
* Failed: 1
|
|
146
|
+
* Pass Rate: 50.00%
|
|
147
|
+
* Duration: 57.50ms (avg: 28.75ms)
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function formatResults(suite) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
// Header
|
|
153
|
+
lines.push('='.repeat(80));
|
|
154
|
+
lines.push(`Test Suite: ${suite.name}`);
|
|
155
|
+
lines.push('='.repeat(80));
|
|
156
|
+
lines.push('');
|
|
157
|
+
// Individual test results
|
|
158
|
+
for (const test of suite.tests) {
|
|
159
|
+
const icon = test.passed ? '✓' : '✗';
|
|
160
|
+
const status = test.passed ? 'PASS' : 'FAIL';
|
|
161
|
+
lines.push(`${icon} ${test.name} (${test.duration_ms}ms)`);
|
|
162
|
+
if (!test.passed && test.error) {
|
|
163
|
+
lines.push(` Error: ${test.error.message}`);
|
|
164
|
+
if (test.error.stack) {
|
|
165
|
+
const stackLines = test.error.stack.split('\n').slice(1, 4); // First 3 stack frames
|
|
166
|
+
stackLines.forEach(line => lines.push(` ${line.trim()}`));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Summary
|
|
171
|
+
lines.push('');
|
|
172
|
+
lines.push('-'.repeat(80));
|
|
173
|
+
lines.push('Summary');
|
|
174
|
+
lines.push('-'.repeat(80));
|
|
175
|
+
lines.push(`Total: ${suite.summary.total}`);
|
|
176
|
+
lines.push(`Passed: ${suite.summary.passed}`);
|
|
177
|
+
lines.push(`Failed: ${suite.summary.failed}`);
|
|
178
|
+
lines.push(`Pass Rate: ${suite.summary.pass_rate}%`);
|
|
179
|
+
lines.push(`Duration: ${suite.summary.total_duration_ms}ms (avg: ${suite.summary.avg_duration_ms}ms)`);
|
|
180
|
+
lines.push('');
|
|
181
|
+
// Overall result
|
|
182
|
+
const overallStatus = suite.summary.failed === 0 ? 'PASSED ✓' : 'FAILED ✗';
|
|
183
|
+
lines.push(`Overall: ${overallStatus}`);
|
|
184
|
+
lines.push('='.repeat(80));
|
|
185
|
+
return lines.join('\n');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Format test suite results as JSON
|
|
189
|
+
*
|
|
190
|
+
* @param suite - Test suite results
|
|
191
|
+
* @param pretty - Whether to pretty-print the JSON (default: true)
|
|
192
|
+
* @returns JSON string
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const suite = await runSuite('My Tests', [...]);
|
|
197
|
+
* const json = formatResultsJson(suite);
|
|
198
|
+
* await fs.writeFile('test-results.json', json);
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* Output:
|
|
202
|
+
* ```json
|
|
203
|
+
* {
|
|
204
|
+
* "name": "My Tests",
|
|
205
|
+
* "started_at": "2025-01-01T12:00:00.000Z",
|
|
206
|
+
* "completed_at": "2025-01-01T12:00:01.234Z",
|
|
207
|
+
* "summary": {
|
|
208
|
+
* "total": 5,
|
|
209
|
+
* "passed": 4,
|
|
210
|
+
* "failed": 1,
|
|
211
|
+
* "pass_rate": 80.00,
|
|
212
|
+
* "total_duration_ms": 1234.56,
|
|
213
|
+
* "avg_duration_ms": 246.91
|
|
214
|
+
* },
|
|
215
|
+
* "tests": [...]
|
|
216
|
+
* }
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export function formatResultsJson(suite, pretty = true) {
|
|
220
|
+
return pretty
|
|
221
|
+
? JSON.stringify(suite, null, 2)
|
|
222
|
+
: JSON.stringify(suite);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Create a simple assertion helper for tests
|
|
226
|
+
*
|
|
227
|
+
* @param condition - Condition to check
|
|
228
|
+
* @param message - Error message if assertion fails
|
|
229
|
+
* @throws Error if condition is false
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* await runTest('should work', async () => {
|
|
234
|
+
* const result = await someOperation();
|
|
235
|
+
* assert(result !== null, 'Result should not be null');
|
|
236
|
+
* assert(result.value === 'expected', 'Value should be expected');
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export function assert(condition, message) {
|
|
241
|
+
if (!condition) {
|
|
242
|
+
throw new Error(message || 'Assertion failed');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Deep equality assertion
|
|
247
|
+
*
|
|
248
|
+
* @param actual - Actual value
|
|
249
|
+
* @param expected - Expected value
|
|
250
|
+
* @param message - Error message if assertion fails
|
|
251
|
+
* @throws Error if values are not equal
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* await runTest('should match object', async () => {
|
|
256
|
+
* const result = await getUser();
|
|
257
|
+
* assertEqual(result, { id: '123', name: 'Test' });
|
|
258
|
+
* });
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
export function assertEqual(actual, expected, message) {
|
|
262
|
+
const actualJson = JSON.stringify(actual);
|
|
263
|
+
const expectedJson = JSON.stringify(expected);
|
|
264
|
+
if (actualJson !== expectedJson) {
|
|
265
|
+
const msg = message || `Expected ${expectedJson} but got ${actualJson}`;
|
|
266
|
+
throw new Error(msg);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Async error assertion helper
|
|
271
|
+
*
|
|
272
|
+
* @param fn - Function that should throw
|
|
273
|
+
* @param expectedMessage - Optional expected error message
|
|
274
|
+
* @throws Error if function doesn't throw
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* await runTest('should reject invalid input', async () => {
|
|
279
|
+
* await assertThrows(
|
|
280
|
+
* async () => await validateInput('invalid'),
|
|
281
|
+
* 'Invalid input format'
|
|
282
|
+
* );
|
|
283
|
+
* });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export async function assertThrows(fn, expectedMessage) {
|
|
287
|
+
try {
|
|
288
|
+
await fn();
|
|
289
|
+
throw new Error('Expected function to throw an error');
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
if (error instanceof Error && error.message === 'Expected function to throw an error') {
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
if (expectedMessage) {
|
|
296
|
+
const actualMessage = error instanceof Error ? error.message : String(error);
|
|
297
|
+
if (!actualMessage.includes(expectedMessage)) {
|
|
298
|
+
throw new Error(`Expected error message to include "${expectedMessage}" but got "${actualMessage}"`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -465,6 +465,54 @@ High hit rate memories (frequently fetched after being indexed) are good candida
|
|
|
465
465
|
required: ['goal', 'epic_title', 'tasks'],
|
|
466
466
|
},
|
|
467
467
|
},
|
|
468
|
+
{
|
|
469
|
+
name: 'task_plan_revise',
|
|
470
|
+
description: 'Modify an existing epic by adding new tasks, removing tasks, or updating task details. Enables iterative planning without recreating the entire plan.',
|
|
471
|
+
inputSchema: {
|
|
472
|
+
type: 'object',
|
|
473
|
+
properties: {
|
|
474
|
+
epic_id: {
|
|
475
|
+
type: 'string',
|
|
476
|
+
description: 'The ID of the epic to revise',
|
|
477
|
+
},
|
|
478
|
+
add_tasks: {
|
|
479
|
+
type: 'array',
|
|
480
|
+
items: {
|
|
481
|
+
type: 'object',
|
|
482
|
+
properties: {
|
|
483
|
+
title: { type: 'string' },
|
|
484
|
+
description: { type: 'string' },
|
|
485
|
+
effort: { type: 'string', enum: ['xs', 's', 'm', 'l', 'xl'] },
|
|
486
|
+
domain: { type: 'string' },
|
|
487
|
+
blocked_by: { type: 'array', items: { type: 'string' } },
|
|
488
|
+
},
|
|
489
|
+
required: ['title'],
|
|
490
|
+
},
|
|
491
|
+
description: 'New tasks to add to the epic',
|
|
492
|
+
},
|
|
493
|
+
remove_task_ids: {
|
|
494
|
+
type: 'array',
|
|
495
|
+
items: { type: 'string' },
|
|
496
|
+
description: 'Task IDs to cancel (sets status to cancelled)',
|
|
497
|
+
},
|
|
498
|
+
update_tasks: {
|
|
499
|
+
type: 'array',
|
|
500
|
+
items: {
|
|
501
|
+
type: 'object',
|
|
502
|
+
properties: {
|
|
503
|
+
task_id: { type: 'string' },
|
|
504
|
+
title: { type: 'string' },
|
|
505
|
+
description: { type: 'string' },
|
|
506
|
+
effort: { type: 'string', enum: ['xs', 's', 'm', 'l', 'xl'] },
|
|
507
|
+
},
|
|
508
|
+
required: ['task_id'],
|
|
509
|
+
},
|
|
510
|
+
description: 'Tasks to update (adds context about changes)',
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
required: ['epic_id'],
|
|
514
|
+
},
|
|
515
|
+
},
|
|
468
516
|
{
|
|
469
517
|
name: 'task_start',
|
|
470
518
|
description: 'PROACTIVE: When starting work on a task, claim it and get all context. Use this before beginning any task work.',
|
|
@@ -608,7 +656,16 @@ High hit rate memories (frequently fetched after being indexed) are good candida
|
|
|
608
656
|
},
|
|
609
657
|
{
|
|
610
658
|
name: 'task_claim',
|
|
611
|
-
description:
|
|
659
|
+
description: `Claim a task for working on it. Creates a lock preventing other agents from working on it.
|
|
660
|
+
|
|
661
|
+
Lock duration is automatically scaled based on task effort:
|
|
662
|
+
- xs: 15 minutes
|
|
663
|
+
- s: 30 minutes (default if no effort specified)
|
|
664
|
+
- m: 60 minutes (1 hour)
|
|
665
|
+
- l: 120 minutes (2 hours)
|
|
666
|
+
- xl: 240 minutes (4 hours)
|
|
667
|
+
|
|
668
|
+
Explicit lock_duration_minutes parameter overrides the effort-based default.`,
|
|
612
669
|
inputSchema: {
|
|
613
670
|
type: 'object',
|
|
614
671
|
properties: {
|
|
@@ -622,7 +679,7 @@ High hit rate memories (frequently fetched after being indexed) are good candida
|
|
|
622
679
|
},
|
|
623
680
|
lock_duration_minutes: {
|
|
624
681
|
type: 'number',
|
|
625
|
-
description: '
|
|
682
|
+
description: 'Optional: Override effort-based lock duration (in minutes)',
|
|
626
683
|
},
|
|
627
684
|
},
|
|
628
685
|
required: ['task_id', 'agent_id'],
|
|
@@ -739,6 +796,32 @@ High hit rate memories (frequently fetched after being indexed) are good candida
|
|
|
739
796
|
required: ['task_id', 'agent_id'],
|
|
740
797
|
},
|
|
741
798
|
},
|
|
799
|
+
{
|
|
800
|
+
name: 'task_handoff',
|
|
801
|
+
description: 'Hand off a task to a different expert worker type during execution. Releases current lock, updates agent_type, and sets task back to ready status for re-dispatch.',
|
|
802
|
+
inputSchema: {
|
|
803
|
+
type: 'object',
|
|
804
|
+
properties: {
|
|
805
|
+
task_id: {
|
|
806
|
+
type: 'string',
|
|
807
|
+
description: 'The task ID to hand off',
|
|
808
|
+
},
|
|
809
|
+
new_worker_type: {
|
|
810
|
+
type: 'string',
|
|
811
|
+
description: 'Target worker type (e.g., "api-expert", "frontend-expert", "database-expert")',
|
|
812
|
+
},
|
|
813
|
+
reason: {
|
|
814
|
+
type: 'string',
|
|
815
|
+
description: 'Why the handoff is needed (e.g., "requires database expertise", "needs API integration")',
|
|
816
|
+
},
|
|
817
|
+
agent_id: {
|
|
818
|
+
type: 'string',
|
|
819
|
+
description: 'Your agent identifier (default: claude-code)',
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
required: ['task_id', 'new_worker_type', 'reason'],
|
|
823
|
+
},
|
|
824
|
+
},
|
|
742
825
|
// =========================================================================
|
|
743
826
|
// ORCHESTRATION TOOLS
|
|
744
827
|
// =========================================================================
|
|
@@ -921,6 +1004,24 @@ High hit rate memories (frequently fetched after being indexed) are good candida
|
|
|
921
1004
|
required: ['epic_id'],
|
|
922
1005
|
},
|
|
923
1006
|
},
|
|
1007
|
+
{
|
|
1008
|
+
name: 'task_aggregate',
|
|
1009
|
+
description: 'Aggregate work_log context from all sibling tasks under an epic for orchestrator synthesis. Returns structured summary of completed work across all tasks in the epic.',
|
|
1010
|
+
inputSchema: {
|
|
1011
|
+
type: 'object',
|
|
1012
|
+
properties: {
|
|
1013
|
+
epic_id: {
|
|
1014
|
+
type: 'string',
|
|
1015
|
+
description: 'Epic ID to aggregate from',
|
|
1016
|
+
},
|
|
1017
|
+
include_pending: {
|
|
1018
|
+
type: 'boolean',
|
|
1019
|
+
description: 'Include non-completed tasks (default: false)',
|
|
1020
|
+
},
|
|
1021
|
+
},
|
|
1022
|
+
required: ['epic_id'],
|
|
1023
|
+
},
|
|
1024
|
+
},
|
|
924
1025
|
// =========================================================================
|
|
925
1026
|
// CODEBASE MAPPING TOOLS
|
|
926
1027
|
// =========================================================================
|