octocode-cli 1.5.1 → 1.5.3
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/README.md +28 -9
- package/out/chunks/chunk-375ZNU3A.js +317 -0
- package/out/chunks/{chunk-V64AXUWB.js → chunk-AYILFPX4.js} +1 -1
- package/out/chunks/chunk-BRHVQB64.js +10 -0
- package/out/chunks/{command-help-specs-JZXVSLZ5.js → chunk-KWJUBVXJ.js} +1 -1
- package/out/chunks/chunk-OMXWZJBJ.js +100 -0
- package/out/chunks/command-help-specs-RVJK6V6M.js +8 -0
- package/out/chunks/commands-JFDCC4Q7.js +8 -0
- package/out/chunks/{help-P7TCOYAJ.js → help-H3WGUJO7.js} +1 -1
- package/out/chunks/main-help-BUTDD4IR.js +10 -0
- package/out/chunks/{spinner-URV2OX6O.js → spinner-DXD4JQIU.js} +1 -1
- package/out/chunks/tool-command-L6PVHUNS.js +8 -0
- package/out/octocode-cli.js +1 -1
- package/package.json +2 -2
- package/skills/octocode-brainstorming/scripts/tavily-search.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/cdp-runner.mjs +0 -2
- package/skills/octocode-chrome-devtools/scripts/cdp-sandbox.mjs +1 -5
- package/skills/octocode-chrome-devtools/scripts/cdp-template.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/open-browser.mjs +0 -3
- package/skills/octocode-chrome-devtools/scripts/sourcemap-resolver.mjs +1 -4
- package/skills/octocode-chrome-devtools/scripts/undercover.mjs +0 -2
- package/skills/octocode-engineer/SKILL.md +7 -33
- package/skills/octocode-engineer/references/cli-reference.md +5 -11
- package/skills/octocode-engineer/references/externals.md +0 -4
- package/skills/octocode-engineer/references/quality-indicators.md +10 -50
- package/skills/octocode-engineer/references/tool-workflows.md +10 -13
- package/skills/octocode-engineer/src/analysis/dependency-summary.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/metrics.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/search-main.ts +2 -15
- package/skills/octocode-engineer/src/ast/search.ts +1 -7
- package/skills/octocode-engineer/src/ast/tree-sitter.test.ts +4 -4
- package/skills/octocode-engineer/src/ast/tree-sitter.ts +2 -2
- package/skills/octocode-engineer/src/collectors/chains.ts +3 -10
- package/skills/octocode-engineer/src/collectors/prototype-pollution.ts +3 -3
- package/skills/octocode-engineer/src/collectors/security.ts +3 -3
- package/skills/octocode-engineer/src/common/ensure-deps.ts +3 -20
- package/skills/octocode-engineer/src/detectors/code-quality.ts +0 -1
- package/skills/octocode-engineer/src/index.test.ts +1 -1
- package/skills/octocode-engineer/src/index.ts +1 -11
- package/skills/octocode-engineer/src/pipeline/affected.ts +2 -7
- package/skills/octocode-engineer/src/pipeline/baseline.ts +2 -8
- package/skills/octocode-engineer/src/pipeline/cache.ts +2 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.test.ts +1 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.ts +0 -9
- package/skills/octocode-engineer/src/pipeline/create-options.ts +2 -15
- package/skills/octocode-engineer/src/pipeline/reporters.ts +2 -12
- package/skills/octocode-engineer/src/reporting/summary-md.test.ts +2 -2
- package/skills/octocode-engineer/src/run.ts +1 -7
- package/skills/octocode-engineer/src/types/constants.ts +0 -5
- package/skills/octocode-engineer/src/types/core.ts +0 -5
- package/skills/octocode-news/eslint.config.mjs +1 -6
- package/skills/octocode-news/src/rss-core.ts +2 -4
- package/skills/octocode-research/ecosystem.config.cjs +0 -31
- package/skills/octocode-research/package.json +1 -1
- package/skills/octocode-research/scripts/server.js +272 -230
- package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +0 -24
- package/skills/octocode-research/src/__tests__/integration/routes.test.ts +0 -10
- package/skills/octocode-research/src/__tests__/integration/serverLifecycle.test.ts +5 -22
- package/skills/octocode-research/src/__tests__/integration/toolsRoutes.test.ts +0 -27
- package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +0 -29
- package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +1 -12
- package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +1 -9
- package/skills/octocode-research/src/__tests__/unit/logger.test.ts +0 -7
- package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +1 -7
- package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +0 -11
- package/skills/octocode-research/src/__tests__/unit/retry.test.ts +1 -18
- package/skills/octocode-research/src/__tests__/unit/schemas.test.ts +2 -25
- package/skills/octocode-research/src/index.ts +0 -27
- package/skills/octocode-research/src/mcpCache.ts +3 -18
- package/skills/octocode-research/src/middleware/errorHandler.ts +1 -4
- package/skills/octocode-research/src/middleware/logger.ts +1 -6
- package/skills/octocode-research/src/middleware/queryParser.ts +3 -22
- package/skills/octocode-research/src/routes/github.ts +0 -11
- package/skills/octocode-research/src/routes/local.ts +0 -12
- package/skills/octocode-research/src/routes/lsp.ts +1 -10
- package/skills/octocode-research/src/routes/package.ts +0 -2
- package/skills/octocode-research/src/routes/prompts.ts +2 -32
- package/skills/octocode-research/src/routes/tools.ts +12 -177
- package/skills/octocode-research/src/server-init.ts +0 -32
- package/skills/octocode-research/src/server.ts +8 -37
- package/skills/octocode-research/src/types/errorGuards.ts +0 -15
- package/skills/octocode-research/src/types/guards.ts +6 -27
- package/skills/octocode-research/src/types/mcp.ts +1 -8
- package/skills/octocode-research/src/types/responses.ts +0 -15
- package/skills/octocode-research/src/utils/asyncTimeout.ts +1 -63
- package/skills/octocode-research/src/utils/circuitBreaker.ts +43 -166
- package/skills/octocode-research/src/utils/colors.ts +3 -17
- package/skills/octocode-research/src/utils/errorQueue.ts +6 -28
- package/skills/octocode-research/src/utils/logger.ts +11 -93
- package/skills/octocode-research/src/utils/resilience.ts +22 -77
- package/skills/octocode-research/src/utils/responseBuilder.ts +26 -78
- package/skills/octocode-research/src/utils/responseFactory.ts +0 -7
- package/skills/octocode-research/src/utils/responseParser.ts +11 -59
- package/skills/octocode-research/src/utils/retry.ts +12 -75
- package/skills/octocode-research/src/utils/routeFactory.ts +10 -61
- package/skills/octocode-research/src/utils/url.ts +0 -5
- package/skills/octocode-research/src/validation/httpPreprocess.ts +15 -61
- package/skills/octocode-research/src/validation/schemas.ts +14 -101
- package/skills/octocode-research/src/validation/toolCallSchema.ts +5 -65
- package/skills/octocode-research/tsdown.config.ts +2 -7
- package/skills/octocode-search-skill/SKILL.md +9 -2
- package/skills/octocode-search-skill/references/discovery-surfaces.md +2 -1
- package/skills/octocode-slides/scripts/navbridge.js +1 -14
- package/skills/octocode-slides/scripts/presenter.js +2 -61
- package/skills/octocode-stats/scripts/build_dashboard.mjs +1 -19
- package/out/chunks/chunk-7476PETK.js +0 -309
- package/out/chunks/chunk-OQBJTZWK.js +0 -60
- package/out/chunks/chunk-UCZCF3BQ.js +0 -9
- package/out/chunks/commands-XBFPLHSQ.js +0 -8
- package/out/chunks/main-help-ULF5PAQY.js +0 -10
- package/out/chunks/tool-command-M6VA7P2F.js +0 -8
- package/skills/octocode-research/AGENTS.md +0 -349
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bounded queue for fire-and-forget operation errors.
|
|
3
|
-
* Provides visibility into async errors without blocking main flow.
|
|
4
|
-
*
|
|
5
|
-
* @module utils/errorQueue
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
interface QueuedError {
|
|
9
2
|
timestamp: Date;
|
|
10
3
|
error: Error;
|
|
11
4
|
context?: string;
|
|
12
5
|
}
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
* Bounded error queue that stores recent errors from async operations.
|
|
16
|
-
* Prevents unbounded memory growth while maintaining error visibility.
|
|
17
|
-
*/
|
|
7
|
+
|
|
18
8
|
class ErrorQueue {
|
|
19
9
|
private errors: QueuedError[] = [];
|
|
20
10
|
private readonly maxSize: number;
|
|
@@ -23,10 +13,7 @@ class ErrorQueue {
|
|
|
23
13
|
this.maxSize = maxSize;
|
|
24
14
|
}
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
* Add an error to the queue.
|
|
28
|
-
* If queue is full, oldest error is removed.
|
|
29
|
-
*/
|
|
16
|
+
|
|
30
17
|
push(error: unknown, context?: string): void {
|
|
31
18
|
const normalizedError =
|
|
32
19
|
error instanceof Error ? error : new Error(String(error));
|
|
@@ -37,35 +24,26 @@ class ErrorQueue {
|
|
|
37
24
|
context,
|
|
38
25
|
});
|
|
39
26
|
|
|
40
|
-
// Bounded: remove oldest when full
|
|
41
27
|
if (this.errors.length > this.maxSize) {
|
|
42
28
|
this.errors.shift();
|
|
43
29
|
}
|
|
44
30
|
}
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
* Get the most recent errors.
|
|
48
|
-
*/
|
|
32
|
+
|
|
49
33
|
getRecent(count = 10): QueuedError[] {
|
|
50
34
|
return this.errors.slice(-count);
|
|
51
35
|
}
|
|
52
36
|
|
|
53
|
-
|
|
54
|
-
* Clear all errors from the queue.
|
|
55
|
-
*/
|
|
37
|
+
|
|
56
38
|
clear(): void {
|
|
57
39
|
this.errors = [];
|
|
58
40
|
}
|
|
59
41
|
|
|
60
|
-
|
|
61
|
-
* Get current queue size.
|
|
62
|
-
*/
|
|
42
|
+
|
|
63
43
|
get size(): number {
|
|
64
44
|
return this.errors.length;
|
|
65
45
|
}
|
|
66
46
|
}
|
|
67
47
|
|
|
68
|
-
|
|
69
|
-
* Global error queue instance for fire-and-forget operations.
|
|
70
|
-
*/
|
|
48
|
+
|
|
71
49
|
export const errorQueue = new ErrorQueue();
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
import { errorLog, warnLog } from './colors.js';
|
|
2
2
|
import { errorQueue } from './errorQueue.js';
|
|
3
|
-
|
|
4
|
-
* Cross-platform logging utility for Octocode Research Server
|
|
5
|
-
*
|
|
6
|
-
* Logs to ~/.octocode/logs/ (or %USERPROFILE%\.octocode\logs on Windows)
|
|
7
|
-
* - errors.log: All errors and warnings
|
|
8
|
-
* - tools.log: Tool invocation data and results
|
|
9
|
-
*
|
|
10
|
-
* PERFORMANCE: Uses async file operations to avoid blocking event loop
|
|
11
|
-
*/
|
|
3
|
+
|
|
12
4
|
|
|
13
5
|
import fs from 'node:fs';
|
|
14
6
|
import { promises as fsAsync } from 'node:fs';
|
|
15
7
|
import path from 'node:path';
|
|
16
8
|
import os from 'node:os';
|
|
17
9
|
|
|
18
|
-
// ============================================================================
|
|
19
|
-
// Configuration
|
|
20
|
-
// ============================================================================
|
|
21
10
|
|
|
22
11
|
const HOME = os.homedir();
|
|
23
12
|
const OCTOCODE_DIR = process.env.OCTOCODE_HOME || path.join(HOME, '.octocode');
|
|
@@ -25,24 +14,16 @@ const LOGS_DIR = path.join(OCTOCODE_DIR, 'logs');
|
|
|
25
14
|
const ERROR_LOG = path.join(LOGS_DIR, 'errors.log');
|
|
26
15
|
const TOOLS_LOG = path.join(LOGS_DIR, 'tools.log');
|
|
27
16
|
|
|
28
|
-
// Max log file size before rotation (10MB)
|
|
29
17
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
30
18
|
|
|
31
|
-
// Max size for JSON.stringify to prevent memory spikes (100KB)
|
|
32
19
|
const MAX_LOG_DATA_SIZE = 100 * 1024;
|
|
33
20
|
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// Directory Initialization (async with sync fallback for startup)
|
|
36
|
-
// ============================================================================
|
|
37
21
|
|
|
38
22
|
let initialized = false;
|
|
39
23
|
let fileLoggingEnabled = true;
|
|
40
24
|
let initPromise: Promise<void> | null = null;
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
* Ensure the logs directory exists (async).
|
|
44
|
-
* Safe to call multiple times (idempotent).
|
|
45
|
-
*/
|
|
26
|
+
|
|
46
27
|
async function ensureLogsDirAsync(): Promise<void> {
|
|
47
28
|
if (initialized) return;
|
|
48
29
|
if (initPromise) return initPromise;
|
|
@@ -64,9 +45,7 @@ async function ensureLogsDirAsync(): Promise<void> {
|
|
|
64
45
|
return initPromise;
|
|
65
46
|
}
|
|
66
47
|
|
|
67
|
-
|
|
68
|
-
* Sync initialization for startup only (blocking but necessary)
|
|
69
|
-
*/
|
|
48
|
+
|
|
70
49
|
function ensureLogsDirSync(): void {
|
|
71
50
|
if (initialized) return;
|
|
72
51
|
|
|
@@ -87,13 +66,7 @@ function ensureLogsDirSync(): void {
|
|
|
87
66
|
}
|
|
88
67
|
}
|
|
89
68
|
|
|
90
|
-
// ============================================================================
|
|
91
|
-
// Log Rotation (async)
|
|
92
|
-
// ============================================================================
|
|
93
69
|
|
|
94
|
-
/**
|
|
95
|
-
* Rotate log file if it exceeds max size (async).
|
|
96
|
-
*/
|
|
97
70
|
async function rotateIfNeededAsync(logPath: string): Promise<void> {
|
|
98
71
|
try {
|
|
99
72
|
const stats = await fsAsync.stat(logPath).catch(() => null);
|
|
@@ -107,13 +80,11 @@ async function rotateIfNeededAsync(logPath: string): Promise<void> {
|
|
|
107
80
|
await fsAsync.rename(logPath, rotatedPath);
|
|
108
81
|
await cleanupOldLogsAsync(base, ext, 5);
|
|
109
82
|
} catch {
|
|
110
|
-
|
|
83
|
+
void 0;
|
|
111
84
|
}
|
|
112
85
|
}
|
|
113
86
|
|
|
114
|
-
|
|
115
|
-
* Remove old rotated log files, keeping only the most recent ones (async).
|
|
116
|
-
*/
|
|
87
|
+
|
|
117
88
|
async function cleanupOldLogsAsync(baseName: string, ext: string, keep: number): Promise<void> {
|
|
118
89
|
try {
|
|
119
90
|
const files = await fsAsync.readdir(LOGS_DIR);
|
|
@@ -122,24 +93,16 @@ async function cleanupOldLogsAsync(baseName: string, ext: string, keep: number):
|
|
|
122
93
|
.sort()
|
|
123
94
|
.reverse();
|
|
124
95
|
|
|
125
|
-
// Remove files beyond the keep limit
|
|
126
96
|
const toDelete = rotatedFiles.slice(keep);
|
|
127
97
|
await Promise.all(
|
|
128
98
|
toDelete.map((f) => fsAsync.unlink(path.join(LOGS_DIR, f)).catch(err => errorQueue.push(err, 'cleanupOldLogs')))
|
|
129
99
|
);
|
|
130
100
|
} catch {
|
|
131
|
-
|
|
101
|
+
void 0;
|
|
132
102
|
}
|
|
133
103
|
}
|
|
134
104
|
|
|
135
|
-
// ============================================================================
|
|
136
|
-
// Log Entry Formatting
|
|
137
|
-
// ============================================================================
|
|
138
105
|
|
|
139
|
-
/**
|
|
140
|
-
* Safely stringify data with size limit to prevent memory spikes.
|
|
141
|
-
* Uses single-pass approach with size tracking to avoid double serialization.
|
|
142
|
-
*/
|
|
143
106
|
function safeStringify(data: unknown): string {
|
|
144
107
|
if (data === undefined) return '';
|
|
145
108
|
|
|
@@ -148,9 +111,7 @@ function safeStringify(data: unknown): string {
|
|
|
148
111
|
let truncated = false;
|
|
149
112
|
const seen = new WeakSet<object>();
|
|
150
113
|
|
|
151
|
-
// Custom replacer that tracks size and detects circular refs
|
|
152
114
|
const replacer = (_key: string, value: unknown): unknown => {
|
|
153
|
-
// Check for circular references
|
|
154
115
|
if (value !== null && typeof value === 'object') {
|
|
155
116
|
if (seen.has(value)) {
|
|
156
117
|
return '[Circular]';
|
|
@@ -158,11 +119,9 @@ function safeStringify(data: unknown): string {
|
|
|
158
119
|
seen.add(value);
|
|
159
120
|
}
|
|
160
121
|
|
|
161
|
-
// Estimate size contribution (rough but fast)
|
|
162
122
|
const valueStr = typeof value === 'string' ? value : String(value ?? '');
|
|
163
|
-
size += valueStr.length + 10;
|
|
123
|
+
size += valueStr.length + 10;
|
|
164
124
|
|
|
165
|
-
// If we've exceeded limit, mark as truncated and return placeholder
|
|
166
125
|
if (size > MAX_LOG_DATA_SIZE && !truncated) {
|
|
167
126
|
truncated = true;
|
|
168
127
|
}
|
|
@@ -172,7 +131,6 @@ function safeStringify(data: unknown): string {
|
|
|
172
131
|
|
|
173
132
|
const result = JSON.stringify(data, replacer, 2);
|
|
174
133
|
|
|
175
|
-
// If truncated during serialization, return truncation notice
|
|
176
134
|
if (truncated) {
|
|
177
135
|
return JSON.stringify({
|
|
178
136
|
_truncated: true,
|
|
@@ -187,47 +145,30 @@ function safeStringify(data: unknown): string {
|
|
|
187
145
|
}
|
|
188
146
|
}
|
|
189
147
|
|
|
190
|
-
|
|
191
|
-
* Format a log entry with timestamp and level.
|
|
192
|
-
*/
|
|
148
|
+
|
|
193
149
|
function formatLogEntry(level: string, message: string, data?: unknown): string {
|
|
194
150
|
const timestamp = new Date().toISOString();
|
|
195
151
|
const dataStr = data !== undefined ? `\n${safeStringify(data)}` : '';
|
|
196
152
|
return `[${timestamp}] [${level}] ${message}${dataStr}\n`;
|
|
197
153
|
}
|
|
198
154
|
|
|
199
|
-
// ============================================================================
|
|
200
|
-
// Core Logging Functions (async with fire-and-forget pattern)
|
|
201
|
-
// ============================================================================
|
|
202
155
|
|
|
203
|
-
/**
|
|
204
|
-
* Write to a log file asynchronously (fire-and-forget).
|
|
205
|
-
* Never blocks the event loop.
|
|
206
|
-
*/
|
|
207
156
|
function writeLogAsync(logPath: string, entry: string): void {
|
|
208
157
|
if (!fileLoggingEnabled) return;
|
|
209
158
|
|
|
210
|
-
// Fire and forget - don't await
|
|
211
159
|
(async () => {
|
|
212
160
|
try {
|
|
213
161
|
await ensureLogsDirAsync();
|
|
214
162
|
await rotateIfNeededAsync(logPath);
|
|
215
163
|
await fsAsync.appendFile(logPath, entry, { encoding: 'utf-8' });
|
|
216
164
|
} catch (err) {
|
|
217
|
-
// Disable file logging on persistent failure
|
|
218
165
|
fileLoggingEnabled = false;
|
|
219
166
|
process.stderr.write(`[Logger] File write failed, disabling: ${err}\n`);
|
|
220
167
|
}
|
|
221
168
|
})();
|
|
222
169
|
}
|
|
223
170
|
|
|
224
|
-
// ============================================================================
|
|
225
|
-
// Public API - Error Logger
|
|
226
|
-
// ============================================================================
|
|
227
171
|
|
|
228
|
-
/**
|
|
229
|
-
* Log an error message (async, non-blocking).
|
|
230
|
-
*/
|
|
231
172
|
export function logError(message: string, error?: Error | unknown): void {
|
|
232
173
|
const errorData =
|
|
233
174
|
error instanceof Error
|
|
@@ -237,27 +178,19 @@ export function logError(message: string, error?: Error | unknown): void {
|
|
|
237
178
|
const entry = formatLogEntry('ERROR', message, errorData);
|
|
238
179
|
writeLogAsync(ERROR_LOG, entry);
|
|
239
180
|
|
|
240
|
-
// Also write to console for visibility (redact stack in production)
|
|
241
181
|
const consoleError = process.env.NODE_ENV === 'development'
|
|
242
182
|
? error
|
|
243
183
|
: (error instanceof Error ? error.message : String(error || ''));
|
|
244
|
-
// Use %s format specifier to prevent format string injection from user-controlled message
|
|
245
184
|
console.error('%s %o', errorLog('[ERROR] ' + message), consoleError);
|
|
246
185
|
}
|
|
247
186
|
|
|
248
|
-
|
|
249
|
-
* Log a warning message (async, non-blocking).
|
|
250
|
-
*/
|
|
187
|
+
|
|
251
188
|
export function logWarn(message: string, data?: unknown): void {
|
|
252
189
|
const entry = formatLogEntry('WARN', message, data);
|
|
253
190
|
writeLogAsync(ERROR_LOG, entry);
|
|
254
|
-
// Use %s format specifier to prevent format string injection from user-controlled message
|
|
255
191
|
console.warn('%s', warnLog('[WARN] ' + message));
|
|
256
192
|
}
|
|
257
193
|
|
|
258
|
-
// ============================================================================
|
|
259
|
-
// Public API - Tools Logger
|
|
260
|
-
// ============================================================================
|
|
261
194
|
|
|
262
195
|
interface ToolLogEntry {
|
|
263
196
|
tool: string;
|
|
@@ -276,35 +209,20 @@ export function logToolCall(entry: ToolLogEntry): void {
|
|
|
276
209
|
writeLogAsync(TOOLS_LOG, logEntry);
|
|
277
210
|
}
|
|
278
211
|
|
|
279
|
-
// ============================================================================
|
|
280
|
-
// Utility Functions
|
|
281
|
-
// ============================================================================
|
|
282
212
|
|
|
283
|
-
/**
|
|
284
|
-
* Get the path to the logs directory.
|
|
285
|
-
*/
|
|
286
213
|
export function getLogsPath(): string {
|
|
287
214
|
return LOGS_DIR;
|
|
288
215
|
}
|
|
289
216
|
|
|
290
|
-
|
|
291
|
-
* Initialize logger synchronously (call at startup).
|
|
292
|
-
* After startup, all operations are async.
|
|
293
|
-
*/
|
|
217
|
+
|
|
294
218
|
export function initializeLogger(): void {
|
|
295
219
|
ensureLogsDirSync();
|
|
296
220
|
}
|
|
297
221
|
|
|
298
|
-
// ============================================================================
|
|
299
|
-
// Security Utilities
|
|
300
|
-
// ============================================================================
|
|
301
222
|
|
|
302
223
|
const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'credential', 'api_key', 'apikey'];
|
|
303
224
|
|
|
304
|
-
|
|
305
|
-
* Sanitize query parameters by redacting sensitive values.
|
|
306
|
-
* Prevents accidental exposure of secrets in logs.
|
|
307
|
-
*/
|
|
225
|
+
|
|
308
226
|
export function sanitizeQueryParams(query: Record<string, unknown>): Record<string, unknown> {
|
|
309
227
|
const sanitized: Record<string, unknown> = {};
|
|
310
228
|
|
|
@@ -1,60 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resilience utilities combining circuit breaker + retry + timeout for external API calls.
|
|
3
|
-
*
|
|
4
|
-
* @module utils/resilience
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { withCircuitBreaker } from './circuitBreaker.js';
|
|
8
2
|
import { withRetry, RETRY_CONFIGS } from './retry.js';
|
|
9
3
|
import { withTimeout } from './asyncTimeout.js';
|
|
10
4
|
|
|
11
|
-
|
|
5
|
+
|
|
12
6
|
const DEFAULT_TOOL_TIMEOUT_MS = 30000;
|
|
13
7
|
|
|
14
|
-
|
|
8
|
+
|
|
15
9
|
const TIMEOUT_CONFIGS = {
|
|
16
|
-
github: 60000,
|
|
17
|
-
local: 30000,
|
|
18
|
-
lsp: 30000,
|
|
19
|
-
package: 30000,
|
|
10
|
+
github: 60000,
|
|
11
|
+
local: 30000,
|
|
12
|
+
lsp: 30000,
|
|
13
|
+
package: 30000,
|
|
20
14
|
} as const;
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
* Per-tool circuit breaker mapping.
|
|
24
|
-
* Isolates failures so one failing tool doesn't block unrelated tools.
|
|
25
|
-
*
|
|
26
|
-
* Grouping rationale:
|
|
27
|
-
* - GitHub: Split by API endpoint (search/content/pulls have different rate limits)
|
|
28
|
-
* - LSP: Split by operation weight (navigation vs hierarchy)
|
|
29
|
-
* - Local: Unified (same failure mode - filesystem)
|
|
30
|
-
* - Package: Unified (could split npm/pypi later)
|
|
31
|
-
*/
|
|
16
|
+
|
|
32
17
|
const TOOL_CIRCUIT_MAP: Record<string, string> = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
lspCallHierarchy: 'lsp:hierarchy', // Heavier, recursive operation
|
|
44
|
-
|
|
45
|
-
// Local - unified (same failure mode: filesystem)
|
|
18
|
+
githubSearchCode: 'github:search',
|
|
19
|
+
githubSearchRepositories: 'github:search',
|
|
20
|
+
githubSearchPullRequests: 'github:pulls',
|
|
21
|
+
githubGetFileContent: 'github:content',
|
|
22
|
+
githubViewRepoStructure: 'github:content',
|
|
23
|
+
|
|
24
|
+
lspGotoDefinition: 'lsp:navigation',
|
|
25
|
+
lspFindReferences: 'lsp:navigation',
|
|
26
|
+
lspCallHierarchy: 'lsp:hierarchy',
|
|
27
|
+
|
|
46
28
|
localSearchCode: 'local',
|
|
47
29
|
localGetFileContent: 'local',
|
|
48
30
|
localFindFiles: 'local',
|
|
49
31
|
localViewStructure: 'local',
|
|
50
32
|
|
|
51
|
-
// Package - unified (could split npm/pypi later if needed)
|
|
52
33
|
packageSearch: 'package',
|
|
53
34
|
};
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
* Pre-configured resilience strategies
|
|
57
|
-
*/
|
|
36
|
+
|
|
58
37
|
const RESILIENCE_CONFIGS = {
|
|
59
38
|
github: {
|
|
60
39
|
retry: RETRY_CONFIGS.github,
|
|
@@ -72,30 +51,6 @@ const RESILIENCE_CONFIGS = {
|
|
|
72
51
|
|
|
73
52
|
type ResilienceCategory = keyof typeof RESILIENCE_CONFIGS;
|
|
74
53
|
|
|
75
|
-
/**
|
|
76
|
-
* Execute an operation with combined timeout + circuit breaker + retry protection.
|
|
77
|
-
*
|
|
78
|
-
* The protection layers work as follows:
|
|
79
|
-
* 1. TIMEOUT: Wraps entire operation to prevent hanging (outermost)
|
|
80
|
-
* 2. CIRCUIT BREAKER: If circuit is OPEN, fail fast (no retries/timeout)
|
|
81
|
-
* 3. RETRY: If circuit is CLOSED/HALF-OPEN, retry with backoff (innermost)
|
|
82
|
-
*
|
|
83
|
-
* Failures (including timeouts) contribute to circuit state.
|
|
84
|
-
*
|
|
85
|
-
* @param category - Resilience category ('github', 'local', 'lsp')
|
|
86
|
-
* @param operation - Async operation to execute
|
|
87
|
-
* @param context - Context for logging
|
|
88
|
-
* @returns Operation result
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```typescript
|
|
92
|
-
* const result = await withResilience(
|
|
93
|
-
* 'github',
|
|
94
|
-
* () => githubSearchCode({ queries }),
|
|
95
|
-
* { tool: 'githubSearchCode' }
|
|
96
|
-
* );
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
54
|
async function withResilience<T>(
|
|
100
55
|
category: ResilienceCategory,
|
|
101
56
|
operation: () => Promise<T>,
|
|
@@ -105,10 +60,8 @@ async function withResilience<T>(
|
|
|
105
60
|
const timeoutMs = TIMEOUT_CONFIGS[category] || DEFAULT_TOOL_TIMEOUT_MS;
|
|
106
61
|
const toolName = context?.tool || category;
|
|
107
62
|
|
|
108
|
-
// Get tool-specific circuit name (isolates failures per tool/endpoint)
|
|
109
63
|
const circuitName = TOOL_CIRCUIT_MAP[toolName] || category;
|
|
110
64
|
|
|
111
|
-
// Timeout wraps circuit breaker wraps retry
|
|
112
65
|
return withTimeout(
|
|
113
66
|
() => withCircuitBreaker(circuitName, async () => {
|
|
114
67
|
return withRetry(operation, config.retry, context);
|
|
@@ -118,9 +71,7 @@ async function withResilience<T>(
|
|
|
118
71
|
);
|
|
119
72
|
}
|
|
120
73
|
|
|
121
|
-
|
|
122
|
-
* Execute GitHub API call with resilience protection
|
|
123
|
-
*/
|
|
74
|
+
|
|
124
75
|
export async function withGitHubResilience<T>(
|
|
125
76
|
operation: () => Promise<T>,
|
|
126
77
|
toolName: string
|
|
@@ -128,9 +79,7 @@ export async function withGitHubResilience<T>(
|
|
|
128
79
|
return withResilience('github', operation, { tool: toolName });
|
|
129
80
|
}
|
|
130
81
|
|
|
131
|
-
|
|
132
|
-
* Execute LSP call with resilience protection
|
|
133
|
-
*/
|
|
82
|
+
|
|
134
83
|
export async function withLspResilience<T>(
|
|
135
84
|
operation: () => Promise<T>,
|
|
136
85
|
toolName: string
|
|
@@ -138,9 +87,7 @@ export async function withLspResilience<T>(
|
|
|
138
87
|
return withResilience('lsp', operation, { tool: toolName });
|
|
139
88
|
}
|
|
140
89
|
|
|
141
|
-
|
|
142
|
-
* Execute local tool call with resilience protection
|
|
143
|
-
*/
|
|
90
|
+
|
|
144
91
|
export async function withLocalResilience<T>(
|
|
145
92
|
operation: () => Promise<T>,
|
|
146
93
|
toolName: string
|
|
@@ -148,9 +95,7 @@ export async function withLocalResilience<T>(
|
|
|
148
95
|
return withResilience('local', operation, { tool: toolName });
|
|
149
96
|
}
|
|
150
97
|
|
|
151
|
-
|
|
152
|
-
* Execute package API call (npm/PyPI) with resilience protection
|
|
153
|
-
*/
|
|
98
|
+
|
|
154
99
|
export async function withPackageResilience<T>(
|
|
155
100
|
operation: () => Promise<T>,
|
|
156
101
|
toolName: string
|