agents-reverse-engineer 0.3.5 → 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.
- package/dist/ai/backends/claude.d.ts +92 -0
- package/dist/ai/backends/claude.d.ts.map +1 -0
- package/dist/ai/backends/claude.js +213 -0
- package/dist/ai/backends/claude.js.map +1 -0
- package/dist/ai/backends/gemini.d.ts +53 -0
- package/dist/ai/backends/gemini.d.ts.map +1 -0
- package/dist/ai/backends/gemini.js +66 -0
- package/dist/ai/backends/gemini.js.map +1 -0
- package/dist/ai/backends/opencode.d.ts +53 -0
- package/dist/ai/backends/opencode.d.ts.map +1 -0
- package/dist/ai/backends/opencode.js +66 -0
- package/dist/ai/backends/opencode.js.map +1 -0
- package/dist/ai/index.d.ts +39 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +54 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/pricing.d.ts +84 -0
- package/dist/ai/pricing.d.ts.map +1 -0
- package/dist/ai/pricing.js +149 -0
- package/dist/ai/pricing.js.map +1 -0
- package/dist/ai/pricing.test.d.ts +2 -0
- package/dist/ai/pricing.test.d.ts.map +1 -0
- package/dist/ai/pricing.test.js +164 -0
- package/dist/ai/pricing.test.js.map +1 -0
- package/dist/ai/registry.d.ts +128 -0
- package/dist/ai/registry.d.ts.map +1 -0
- package/dist/ai/registry.js +192 -0
- package/dist/ai/registry.js.map +1 -0
- package/dist/ai/retry.d.ts +77 -0
- package/dist/ai/retry.d.ts.map +1 -0
- package/dist/ai/retry.js +100 -0
- package/dist/ai/retry.js.map +1 -0
- package/dist/ai/service.d.ts +124 -0
- package/dist/ai/service.d.ts.map +1 -0
- package/dist/ai/service.js +239 -0
- package/dist/ai/service.js.map +1 -0
- package/dist/ai/subprocess.d.ts +45 -0
- package/dist/ai/subprocess.d.ts.map +1 -0
- package/dist/ai/subprocess.js +90 -0
- package/dist/ai/subprocess.js.map +1 -0
- package/dist/ai/telemetry/cleanup.d.ts +30 -0
- package/dist/ai/telemetry/cleanup.d.ts.map +1 -0
- package/dist/ai/telemetry/cleanup.js +56 -0
- package/dist/ai/telemetry/cleanup.js.map +1 -0
- package/dist/ai/telemetry/logger.d.ts +76 -0
- package/dist/ai/telemetry/logger.d.ts.map +1 -0
- package/dist/ai/telemetry/logger.js +130 -0
- package/dist/ai/telemetry/logger.js.map +1 -0
- package/dist/ai/telemetry/run-log.d.ts +29 -0
- package/dist/ai/telemetry/run-log.d.ts.map +1 -0
- package/dist/ai/telemetry/run-log.js +43 -0
- package/dist/ai/telemetry/run-log.js.map +1 -0
- package/dist/ai/types.d.ts +235 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +34 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/cli/discover.d.ts.map +1 -1
- package/dist/cli/discover.js +0 -2
- package/dist/cli/discover.js.map +1 -1
- package/dist/cli/generate.d.ts +22 -14
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +91 -50
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/index.js +12 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/update.d.ts +13 -4
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +93 -131
- package/dist/cli/update.js.map +1 -1
- package/dist/config/defaults.d.ts +2 -2
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +175 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +36 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generation/executor.d.ts.map +1 -1
- package/dist/generation/executor.js +2 -5
- package/dist/generation/executor.js.map +1 -1
- package/dist/generation/prompts/builder.d.ts +10 -0
- package/dist/generation/prompts/builder.d.ts.map +1 -1
- package/dist/generation/prompts/builder.js +77 -1
- package/dist/generation/prompts/builder.js.map +1 -1
- package/dist/generation/prompts/index.d.ts +1 -1
- package/dist/generation/prompts/index.d.ts.map +1 -1
- package/dist/generation/prompts/index.js +1 -1
- package/dist/generation/prompts/index.js.map +1 -1
- package/dist/generation/prompts/templates.d.ts +5 -0
- package/dist/generation/prompts/templates.d.ts.map +1 -1
- package/dist/generation/prompts/templates.js +94 -7
- package/dist/generation/prompts/templates.js.map +1 -1
- package/dist/generation/prompts/types.d.ts +2 -2
- package/dist/generation/prompts/types.js +1 -1
- package/dist/generation/writers/agents-md.d.ts +3 -59
- package/dist/generation/writers/agents-md.d.ts.map +1 -1
- package/dist/generation/writers/agents-md.js +11 -249
- package/dist/generation/writers/agents-md.js.map +1 -1
- package/dist/generation/writers/index.d.ts +1 -1
- package/dist/generation/writers/index.d.ts.map +1 -1
- package/dist/generation/writers/index.js +1 -1
- package/dist/generation/writers/index.js.map +1 -1
- package/dist/installer/uninstall.d.ts.map +1 -1
- package/dist/installer/uninstall.js +41 -1
- package/dist/installer/uninstall.js.map +1 -1
- package/dist/integration/templates.d.ts.map +1 -1
- package/dist/integration/templates.js +37 -4
- package/dist/integration/templates.js.map +1 -1
- package/dist/orchestration/index.d.ts +27 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +34 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/pool.d.ts +62 -0
- package/dist/orchestration/pool.d.ts.map +1 -0
- package/dist/orchestration/pool.js +75 -0
- package/dist/orchestration/pool.js.map +1 -0
- package/dist/orchestration/progress.d.ts +124 -0
- package/dist/orchestration/progress.d.ts.map +1 -0
- package/dist/orchestration/progress.js +214 -0
- package/dist/orchestration/progress.js.map +1 -0
- package/dist/orchestration/runner.d.ts +76 -0
- package/dist/orchestration/runner.d.ts.map +1 -0
- package/dist/orchestration/runner.js +494 -0
- package/dist/orchestration/runner.js.map +1 -0
- package/dist/orchestration/types.d.ts +123 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/orchestration/types.js +11 -0
- package/dist/orchestration/types.js.map +1 -0
- package/dist/quality/density/validator.d.ts +38 -0
- package/dist/quality/density/validator.d.ts.map +1 -0
- package/dist/quality/density/validator.js +61 -0
- package/dist/quality/density/validator.js.map +1 -0
- package/dist/quality/inconsistency/code-vs-code.d.ts +26 -0
- package/dist/quality/inconsistency/code-vs-code.d.ts.map +1 -0
- package/dist/quality/inconsistency/code-vs-code.js +51 -0
- package/dist/quality/inconsistency/code-vs-code.js.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.d.ts +35 -0
- package/dist/quality/inconsistency/code-vs-doc.d.ts.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.js +59 -0
- package/dist/quality/inconsistency/code-vs-doc.js.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.test.d.ts +2 -0
- package/dist/quality/inconsistency/code-vs-doc.test.d.ts.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.test.js +196 -0
- package/dist/quality/inconsistency/code-vs-doc.test.js.map +1 -0
- package/dist/quality/inconsistency/reporter.d.ts +49 -0
- package/dist/quality/inconsistency/reporter.d.ts.map +1 -0
- package/dist/quality/inconsistency/reporter.js +99 -0
- package/dist/quality/inconsistency/reporter.js.map +1 -0
- package/dist/quality/index.d.ts +15 -0
- package/dist/quality/index.d.ts.map +1 -0
- package/dist/quality/index.js +25 -0
- package/dist/quality/index.js.map +1 -0
- package/dist/quality/types.d.ts +63 -0
- package/dist/quality/types.d.ts.map +1 -0
- package/dist/quality/types.js +5 -0
- package/dist/quality/types.js.map +1 -0
- package/dist/update/orchestrator.d.ts.map +1 -1
- package/dist/update/orchestrator.js +2 -1
- package/dist/update/orchestrator.js.map +1 -1
- package/dist/update/orphan-cleaner.js +1 -1
- package/dist/update/orphan-cleaner.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI service orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* The {@link AIService} class is the main entry point for making AI calls.
|
|
5
|
+
* It ties together the subprocess wrapper, retry logic, backend selection,
|
|
6
|
+
* and telemetry logging into a clean `call()` method.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { AIServiceError } from './types.js';
|
|
11
|
+
import { runSubprocess } from './subprocess.js';
|
|
12
|
+
import { withRetry, DEFAULT_RETRY_OPTIONS } from './retry.js';
|
|
13
|
+
import { TelemetryLogger } from './telemetry/logger.js';
|
|
14
|
+
import { writeRunLog } from './telemetry/run-log.js';
|
|
15
|
+
import { cleanupOldLogs } from './telemetry/cleanup.js';
|
|
16
|
+
import { estimateCost } from './pricing.js';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Rate-limit detection patterns
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/** Patterns in stderr that indicate a transient rate-limit error */
|
|
21
|
+
const RATE_LIMIT_PATTERNS = [
|
|
22
|
+
'rate limit',
|
|
23
|
+
'429',
|
|
24
|
+
'too many requests',
|
|
25
|
+
'overloaded',
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Check whether stderr text contains rate-limit indicators.
|
|
29
|
+
*
|
|
30
|
+
* @param stderr - Standard error output from the subprocess
|
|
31
|
+
* @returns `true` if any rate-limit pattern matches (case-insensitive)
|
|
32
|
+
*/
|
|
33
|
+
function isRateLimitStderr(stderr) {
|
|
34
|
+
const lower = stderr.toLowerCase();
|
|
35
|
+
return RATE_LIMIT_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// AIService
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Orchestrates AI CLI calls with retry, timeout, and telemetry.
|
|
42
|
+
*
|
|
43
|
+
* Create one instance per CLI run. Call {@link call} for each AI invocation.
|
|
44
|
+
* Call {@link finalize} at the end to write the run log and clean up old files.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { AIService } from './service.js';
|
|
49
|
+
* import { resolveBackend, createBackendRegistry } from './registry.js';
|
|
50
|
+
*
|
|
51
|
+
* const registry = createBackendRegistry();
|
|
52
|
+
* const backend = await resolveBackend(registry, 'auto');
|
|
53
|
+
* const service = new AIService(backend, {
|
|
54
|
+
* timeoutMs: 120_000,
|
|
55
|
+
* maxRetries: 3,
|
|
56
|
+
* telemetry: { keepRuns: 10 },
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* const response = await service.call({ prompt: 'Summarize this codebase' });
|
|
60
|
+
* console.log(response.text);
|
|
61
|
+
*
|
|
62
|
+
* const { logPath, summary } = await service.finalize('/path/to/project');
|
|
63
|
+
* console.log(`Log written to ${logPath}, cost: $${summary.totalCostUsd}`);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export class AIService {
|
|
67
|
+
/** The backend adapter used for CLI invocations */
|
|
68
|
+
backend;
|
|
69
|
+
/** Service configuration */
|
|
70
|
+
options;
|
|
71
|
+
/** In-memory telemetry logger for this run */
|
|
72
|
+
logger;
|
|
73
|
+
/** Running count of calls made (used for entry tracking) */
|
|
74
|
+
callCount = 0;
|
|
75
|
+
/** Set of model IDs for which an unknown-pricing warning has already been emitted */
|
|
76
|
+
warnedModels = new Set();
|
|
77
|
+
/**
|
|
78
|
+
* Create a new AI service instance.
|
|
79
|
+
*
|
|
80
|
+
* @param backend - The resolved backend adapter
|
|
81
|
+
* @param options - Service configuration (timeout, retries, telemetry)
|
|
82
|
+
*/
|
|
83
|
+
constructor(backend, options) {
|
|
84
|
+
this.backend = backend;
|
|
85
|
+
this.options = options;
|
|
86
|
+
this.logger = new TelemetryLogger(new Date().toISOString());
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Make an AI call with retry logic and telemetry recording.
|
|
90
|
+
*
|
|
91
|
+
* The call flow:
|
|
92
|
+
* 1. Build CLI args via the backend adapter
|
|
93
|
+
* 2. Wrap the subprocess invocation in retry logic
|
|
94
|
+
* 3. On success: parse response via backend, record telemetry entry
|
|
95
|
+
* 4. On failure: record error telemetry entry, throw the error
|
|
96
|
+
*
|
|
97
|
+
* Retries are attempted for `RATE_LIMIT` and `TIMEOUT` errors only.
|
|
98
|
+
* All other errors are treated as permanent failures.
|
|
99
|
+
*
|
|
100
|
+
* @param options - The call options (prompt, model, timeout, etc.)
|
|
101
|
+
* @returns The normalized AI response
|
|
102
|
+
* @throws {AIServiceError} On timeout, rate limit exhaustion, parse error, or subprocess failure
|
|
103
|
+
*/
|
|
104
|
+
async call(options) {
|
|
105
|
+
this.callCount++;
|
|
106
|
+
const callStart = Date.now();
|
|
107
|
+
const timestamp = new Date().toISOString();
|
|
108
|
+
const args = this.backend.buildArgs(options);
|
|
109
|
+
const timeoutMs = options.timeoutMs ?? this.options.timeoutMs;
|
|
110
|
+
let retryCount = 0;
|
|
111
|
+
try {
|
|
112
|
+
const response = await withRetry(async () => {
|
|
113
|
+
const result = await runSubprocess(this.backend.cliCommand, args, {
|
|
114
|
+
timeoutMs,
|
|
115
|
+
input: options.prompt,
|
|
116
|
+
});
|
|
117
|
+
if (result.timedOut) {
|
|
118
|
+
throw new AIServiceError('TIMEOUT', 'Subprocess timed out');
|
|
119
|
+
}
|
|
120
|
+
if (result.exitCode !== 0) {
|
|
121
|
+
if (isRateLimitStderr(result.stderr)) {
|
|
122
|
+
throw new AIServiceError('RATE_LIMIT', `Rate limited by ${this.backend.name}: ${result.stderr.slice(0, 200)}`);
|
|
123
|
+
}
|
|
124
|
+
throw new AIServiceError('SUBPROCESS_ERROR', `${this.backend.name} CLI exited with code ${result.exitCode}: ${result.stderr.slice(0, 500)}`);
|
|
125
|
+
}
|
|
126
|
+
// Parse the response -- wrap in try/catch for parse errors
|
|
127
|
+
try {
|
|
128
|
+
return this.backend.parseResponse(result.stdout, result.durationMs, result.exitCode);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof AIServiceError) {
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
throw new AIServiceError('PARSE_ERROR', `Failed to parse response: ${message}`);
|
|
136
|
+
}
|
|
137
|
+
}, {
|
|
138
|
+
...DEFAULT_RETRY_OPTIONS,
|
|
139
|
+
maxRetries: this.options.maxRetries,
|
|
140
|
+
isRetryable: (error) => {
|
|
141
|
+
return (error instanceof AIServiceError &&
|
|
142
|
+
(error.code === 'RATE_LIMIT' || error.code === 'TIMEOUT'));
|
|
143
|
+
},
|
|
144
|
+
onRetry: (_attempt, _error) => {
|
|
145
|
+
retryCount++;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
// Compute cost via pricing engine
|
|
149
|
+
const costResult = estimateCost(response.model, response.inputTokens, response.outputTokens, response.costUsd > 0 ? response.costUsd : undefined, this.options.pricingOverrides);
|
|
150
|
+
// Warn once per unknown model (stderr to preserve JSON stdout)
|
|
151
|
+
if (costResult.source === 'unavailable' && !this.warnedModels.has(response.model)) {
|
|
152
|
+
this.warnedModels.add(response.model);
|
|
153
|
+
console.error(`Warning: No pricing data for model "${response.model}". Cost shown as N/A. Add pricing in config under ai.pricing.`);
|
|
154
|
+
}
|
|
155
|
+
// Record successful call
|
|
156
|
+
this.logger.addEntry({
|
|
157
|
+
timestamp,
|
|
158
|
+
prompt: options.prompt,
|
|
159
|
+
systemPrompt: options.systemPrompt,
|
|
160
|
+
response: response.text,
|
|
161
|
+
model: response.model,
|
|
162
|
+
inputTokens: response.inputTokens,
|
|
163
|
+
outputTokens: response.outputTokens,
|
|
164
|
+
cacheReadTokens: response.cacheReadTokens,
|
|
165
|
+
cacheCreationTokens: response.cacheCreationTokens,
|
|
166
|
+
costUsd: costResult.costUsd,
|
|
167
|
+
latencyMs: response.durationMs,
|
|
168
|
+
exitCode: response.exitCode,
|
|
169
|
+
retryCount,
|
|
170
|
+
thinking: 'not supported',
|
|
171
|
+
filesRead: [],
|
|
172
|
+
costSource: costResult.source,
|
|
173
|
+
});
|
|
174
|
+
return response;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
// Record failed call
|
|
178
|
+
const latencyMs = Date.now() - callStart;
|
|
179
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
180
|
+
this.logger.addEntry({
|
|
181
|
+
timestamp,
|
|
182
|
+
prompt: options.prompt,
|
|
183
|
+
systemPrompt: options.systemPrompt,
|
|
184
|
+
response: '',
|
|
185
|
+
model: options.model ?? 'unknown',
|
|
186
|
+
inputTokens: 0,
|
|
187
|
+
outputTokens: 0,
|
|
188
|
+
cacheReadTokens: 0,
|
|
189
|
+
cacheCreationTokens: 0,
|
|
190
|
+
costUsd: 0,
|
|
191
|
+
latencyMs,
|
|
192
|
+
exitCode: 1,
|
|
193
|
+
error: errorMessage,
|
|
194
|
+
retryCount,
|
|
195
|
+
thinking: 'not supported',
|
|
196
|
+
filesRead: [],
|
|
197
|
+
costSource: 'unavailable',
|
|
198
|
+
});
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Finalize the run: write the run log to disk and clean up old files.
|
|
204
|
+
*
|
|
205
|
+
* Call this once at the end of a CLI invocation, after all `call()`
|
|
206
|
+
* invocations have completed (or failed).
|
|
207
|
+
*
|
|
208
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
209
|
+
* @returns The log file path and the run summary
|
|
210
|
+
*/
|
|
211
|
+
async finalize(projectRoot) {
|
|
212
|
+
const runLog = this.logger.toRunLog();
|
|
213
|
+
const logPath = await writeRunLog(projectRoot, runLog);
|
|
214
|
+
await cleanupOldLogs(projectRoot, this.options.telemetry.keepRuns);
|
|
215
|
+
return { logPath, summary: runLog.summary };
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Attach file-read metadata to the most recent telemetry entry.
|
|
219
|
+
*
|
|
220
|
+
* Called by the command runner after an AI call completes, to record
|
|
221
|
+
* which source files were sent as context for that call.
|
|
222
|
+
*
|
|
223
|
+
* @param filesRead - Array of file-read records (path + size)
|
|
224
|
+
*/
|
|
225
|
+
addFilesReadToLastEntry(filesRead) {
|
|
226
|
+
this.logger.setFilesReadOnLastEntry(filesRead);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get the current run summary without finalizing.
|
|
230
|
+
*
|
|
231
|
+
* Useful for displaying progress during a run.
|
|
232
|
+
*
|
|
233
|
+
* @returns Current summary statistics
|
|
234
|
+
*/
|
|
235
|
+
getSummary() {
|
|
236
|
+
return this.logger.getSummary();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/ai/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level subprocess wrapper for AI CLI invocations.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY place in the codebase that spawns AI CLI processes.
|
|
5
|
+
* Centralizes timeout enforcement, stdin piping, zombie prevention,
|
|
6
|
+
* and exit code extraction.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { SubprocessResult } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Spawn a CLI subprocess with timeout enforcement and stdin piping.
|
|
13
|
+
*
|
|
14
|
+
* Always resolves -- never rejects. Errors are captured in the returned
|
|
15
|
+
* {@link SubprocessResult} fields (`exitCode`, `timedOut`, `stderr`) so
|
|
16
|
+
* that callers can decide how to handle failures.
|
|
17
|
+
*
|
|
18
|
+
* @param command - The CLI executable to run (e.g., "claude", "gemini")
|
|
19
|
+
* @param args - Argument array passed to the executable
|
|
20
|
+
* @param options - Timeout and optional stdin input
|
|
21
|
+
* @returns Resolved result with stdout, stderr, exit code, timing, and timeout flag
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { runSubprocess } from './subprocess.js';
|
|
26
|
+
*
|
|
27
|
+
* const result = await runSubprocess('claude', ['-p', '--output-format', 'json'], {
|
|
28
|
+
* timeoutMs: 120_000,
|
|
29
|
+
* input: 'Summarize this codebase',
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* if (result.timedOut) {
|
|
33
|
+
* console.error('CLI timed out after 120s');
|
|
34
|
+
* } else if (result.exitCode !== 0) {
|
|
35
|
+
* console.error('CLI failed:', result.stderr);
|
|
36
|
+
* } else {
|
|
37
|
+
* const response = JSON.parse(result.stdout);
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function runSubprocess(command: string, args: string[], options: {
|
|
42
|
+
timeoutMs: number;
|
|
43
|
+
input?: string;
|
|
44
|
+
}): Promise<SubprocessResult>;
|
|
45
|
+
//# sourceMappingURL=subprocess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subprocess.d.ts","sourceRoot":"","sources":["../../src/ai/subprocess.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAAC,gBAAgB,CAAC,CAuD3B"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level subprocess wrapper for AI CLI invocations.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY place in the codebase that spawns AI CLI processes.
|
|
5
|
+
* Centralizes timeout enforcement, stdin piping, zombie prevention,
|
|
6
|
+
* and exit code extraction.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from 'node:child_process';
|
|
11
|
+
/**
|
|
12
|
+
* Spawn a CLI subprocess with timeout enforcement and stdin piping.
|
|
13
|
+
*
|
|
14
|
+
* Always resolves -- never rejects. Errors are captured in the returned
|
|
15
|
+
* {@link SubprocessResult} fields (`exitCode`, `timedOut`, `stderr`) so
|
|
16
|
+
* that callers can decide how to handle failures.
|
|
17
|
+
*
|
|
18
|
+
* @param command - The CLI executable to run (e.g., "claude", "gemini")
|
|
19
|
+
* @param args - Argument array passed to the executable
|
|
20
|
+
* @param options - Timeout and optional stdin input
|
|
21
|
+
* @returns Resolved result with stdout, stderr, exit code, timing, and timeout flag
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { runSubprocess } from './subprocess.js';
|
|
26
|
+
*
|
|
27
|
+
* const result = await runSubprocess('claude', ['-p', '--output-format', 'json'], {
|
|
28
|
+
* timeoutMs: 120_000,
|
|
29
|
+
* input: 'Summarize this codebase',
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* if (result.timedOut) {
|
|
33
|
+
* console.error('CLI timed out after 120s');
|
|
34
|
+
* } else if (result.exitCode !== 0) {
|
|
35
|
+
* console.error('CLI failed:', result.stderr);
|
|
36
|
+
* } else {
|
|
37
|
+
* const response = JSON.parse(result.stdout);
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function runSubprocess(command, args, options) {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
const child = execFile(command, args, {
|
|
45
|
+
timeout: options.timeoutMs,
|
|
46
|
+
killSignal: 'SIGTERM',
|
|
47
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB for large AI responses
|
|
48
|
+
encoding: 'utf-8',
|
|
49
|
+
}, (error, stdout, stderr) => {
|
|
50
|
+
const durationMs = Date.now() - startTime;
|
|
51
|
+
// Detect timeout: execFile sets `killed = true` when the process
|
|
52
|
+
// is terminated due to exceeding the timeout option.
|
|
53
|
+
const timedOut = error !== null && 'killed' in error && error.killed === true;
|
|
54
|
+
// Extract exit code from the error or child process.
|
|
55
|
+
// execFile puts the exit code in error.code when the process exits
|
|
56
|
+
// with a non-zero code, but error.code can also be a string like
|
|
57
|
+
// 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'. Fall back to child.exitCode,
|
|
58
|
+
// then default to 1 for unknown failures and 0 for no error.
|
|
59
|
+
let exitCode;
|
|
60
|
+
if (error === null) {
|
|
61
|
+
exitCode = 0;
|
|
62
|
+
}
|
|
63
|
+
else if (typeof error.code === 'number') {
|
|
64
|
+
exitCode = error.code;
|
|
65
|
+
}
|
|
66
|
+
else if (child.exitCode !== null) {
|
|
67
|
+
exitCode = child.exitCode;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
exitCode = 1;
|
|
71
|
+
}
|
|
72
|
+
resolve({
|
|
73
|
+
stdout: stdout ?? '',
|
|
74
|
+
stderr: stderr ?? '',
|
|
75
|
+
exitCode,
|
|
76
|
+
signal: (error !== null && 'signal' in error ? error.signal : null) ?? null,
|
|
77
|
+
durationMs,
|
|
78
|
+
timedOut,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// Write prompt to stdin if provided, then close the stream.
|
|
82
|
+
// IMPORTANT: Always call .end() -- the child process blocks waiting
|
|
83
|
+
// for EOF on stdin otherwise (see RESEARCH.md Pitfall 1).
|
|
84
|
+
if (options.input !== undefined && child.stdin !== null) {
|
|
85
|
+
child.stdin.write(options.input);
|
|
86
|
+
child.stdin.end();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=subprocess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subprocess.js","sourceRoot":"","sources":["../../src/ai/subprocess.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,IAAc,EACd,OAA8C;IAE9C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,MAAM,KAAK,GAAG,QAAQ,CACpB,OAAO,EACP,IAAI,EACJ;YACE,OAAO,EAAE,OAAO,CAAC,SAAS;YAC1B,UAAU,EAAE,SAAS;YACrB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,8BAA8B;YAC3D,QAAQ,EAAE,OAAO;SAClB,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE1C,iEAAiE;YACjE,qDAAqD;YACrD,MAAM,QAAQ,GAAG,KAAK,KAAK,IAAI,IAAI,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC;YAE9E,qDAAqD;YACrD,mEAAmE;YACnE,iEAAiE;YACjE,oEAAoE;YACpE,6DAA6D;YAC7D,IAAI,QAAgB,CAAC;YACrB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,QAAQ,GAAG,CAAC,CAAC;YACf,CAAC;iBAAM,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC1C,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC;YACxB,CAAC;iBAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACnC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,CAAC,CAAC;YACf,CAAC;YAED,OAAO,CAAC;gBACN,MAAM,EAAE,MAAM,IAAI,EAAE;gBACpB,MAAM,EAAE,MAAM,IAAI,EAAE;gBACpB,QAAQ;gBACR,MAAM,EAAE,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI;gBACrF,UAAU;gBACV,QAAQ;aACT,CAAC,CAAC;QACL,CAAC,CACF,CAAC;QAEF,4DAA4D;QAC5D,oEAAoE;QACpE,0DAA0D;QAC1D,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACxD,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACjC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACpB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry log cleanup utility.
|
|
3
|
+
*
|
|
4
|
+
* Removes old run log files from `.agents-reverse-engineer/logs/`,
|
|
5
|
+
* keeping only the N most recent. Files are sorted lexicographically
|
|
6
|
+
* by name, which works correctly because filenames contain ISO timestamps.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Remove old telemetry log files, keeping only the most recent ones.
|
|
12
|
+
*
|
|
13
|
+
* Reads the logs directory, filters for files matching `run-*.json`,
|
|
14
|
+
* sorts newest-first (lexicographic sort on ISO timestamp filenames),
|
|
15
|
+
* and deletes everything beyond `keepCount`.
|
|
16
|
+
*
|
|
17
|
+
* If the logs directory does not exist, returns 0 without error.
|
|
18
|
+
*
|
|
19
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
20
|
+
* @param keepCount - Number of most recent log files to retain
|
|
21
|
+
* @returns Number of files deleted
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const deleted = await cleanupOldLogs('/home/user/project', 10);
|
|
26
|
+
* console.log(`Cleaned up ${deleted} old log files`);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function cleanupOldLogs(projectRoot: string, keepCount: number): Promise<number>;
|
|
30
|
+
//# sourceMappingURL=cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../../src/ai/telemetry/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4B5F"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry log cleanup utility.
|
|
3
|
+
*
|
|
4
|
+
* Removes old run log files from `.agents-reverse-engineer/logs/`,
|
|
5
|
+
* keeping only the N most recent. Files are sorted lexicographically
|
|
6
|
+
* by name, which works correctly because filenames contain ISO timestamps.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'node:fs/promises';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
/** Directory name for telemetry log files (relative to project root) */
|
|
13
|
+
const LOGS_DIR = '.agents-reverse-engineer/logs';
|
|
14
|
+
/**
|
|
15
|
+
* Remove old telemetry log files, keeping only the most recent ones.
|
|
16
|
+
*
|
|
17
|
+
* Reads the logs directory, filters for files matching `run-*.json`,
|
|
18
|
+
* sorts newest-first (lexicographic sort on ISO timestamp filenames),
|
|
19
|
+
* and deletes everything beyond `keepCount`.
|
|
20
|
+
*
|
|
21
|
+
* If the logs directory does not exist, returns 0 without error.
|
|
22
|
+
*
|
|
23
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
24
|
+
* @param keepCount - Number of most recent log files to retain
|
|
25
|
+
* @returns Number of files deleted
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const deleted = await cleanupOldLogs('/home/user/project', 10);
|
|
30
|
+
* console.log(`Cleaned up ${deleted} old log files`);
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export async function cleanupOldLogs(projectRoot, keepCount) {
|
|
34
|
+
const logsDir = path.join(projectRoot, LOGS_DIR);
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
const allEntries = await fs.readdir(logsDir);
|
|
38
|
+
entries = allEntries.filter((name) => name.startsWith('run-') && name.endsWith('.json'));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// Directory doesn't exist -- nothing to clean up
|
|
42
|
+
if (error.code === 'ENOENT') {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
// Sort lexicographically (newest first) and find files to delete
|
|
48
|
+
entries.sort();
|
|
49
|
+
entries.reverse();
|
|
50
|
+
const toDelete = entries.slice(keepCount);
|
|
51
|
+
for (const filename of toDelete) {
|
|
52
|
+
await fs.unlink(path.join(logsDir, filename));
|
|
53
|
+
}
|
|
54
|
+
return toDelete.length;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../../src/ai/telemetry/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,wEAAwE;AACxE,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AAEjD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,WAAmB,EAAE,SAAiB;IACzE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAEjD,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,GAAG,UAAU,CAAC,MAAM,CACzB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAC5D,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iDAAiD;QACjD,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IAED,iEAAiE;IACjE,OAAO,CAAC,IAAI,EAAE,CAAC;IACf,OAAO,CAAC,OAAO,EAAE,CAAC;IAElB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAE1C,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC;QAChC,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory telemetry logger for AI service calls.
|
|
3
|
+
*
|
|
4
|
+
* Accumulates {@link TelemetryEntry} instances during a run and computes
|
|
5
|
+
* aggregate summaries. The logger is created once per CLI invocation and
|
|
6
|
+
* finalized when the run completes.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { TelemetryEntry, RunLog, FileRead } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Accumulates per-call telemetry entries in memory and produces a
|
|
13
|
+
* complete {@link RunLog} when the run finishes.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const logger = new TelemetryLogger('2026-02-07T12:00:00.000Z');
|
|
18
|
+
* logger.addEntry(entry);
|
|
19
|
+
* const summary = logger.getSummary();
|
|
20
|
+
* const runLog = logger.toRunLog();
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare class TelemetryLogger {
|
|
24
|
+
/** Unique identifier for this run (ISO timestamp-based) */
|
|
25
|
+
readonly runId: string;
|
|
26
|
+
/** ISO 8601 timestamp when the run started */
|
|
27
|
+
readonly startTime: string;
|
|
28
|
+
/** Accumulated telemetry entries */
|
|
29
|
+
private readonly entries;
|
|
30
|
+
/**
|
|
31
|
+
* Create a new telemetry logger for a run.
|
|
32
|
+
*
|
|
33
|
+
* @param runId - Unique run identifier (typically an ISO timestamp)
|
|
34
|
+
*/
|
|
35
|
+
constructor(runId: string);
|
|
36
|
+
/**
|
|
37
|
+
* Record a telemetry entry for a completed AI call.
|
|
38
|
+
*
|
|
39
|
+
* @param entry - The telemetry entry to record
|
|
40
|
+
*/
|
|
41
|
+
addEntry(entry: TelemetryEntry): void;
|
|
42
|
+
/**
|
|
43
|
+
* Get all recorded entries as a read-only array.
|
|
44
|
+
*
|
|
45
|
+
* @returns Immutable view of the accumulated entries
|
|
46
|
+
*/
|
|
47
|
+
getEntries(): readonly TelemetryEntry[];
|
|
48
|
+
/**
|
|
49
|
+
* Update the most recent entry's filesRead array.
|
|
50
|
+
*
|
|
51
|
+
* Called by the AI service after the command runner attaches file
|
|
52
|
+
* metadata to the last call.
|
|
53
|
+
*
|
|
54
|
+
* @param filesRead - Array of file-read records to attach
|
|
55
|
+
*/
|
|
56
|
+
setFilesReadOnLastEntry(filesRead: FileRead[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Compute aggregate summary statistics from all recorded entries.
|
|
59
|
+
*
|
|
60
|
+
* Totals are computed on every call (not cached) so the summary
|
|
61
|
+
* always reflects the current state of the entries array.
|
|
62
|
+
*
|
|
63
|
+
* @returns Summary with totals for calls, tokens, cost, duration, and errors
|
|
64
|
+
*/
|
|
65
|
+
getSummary(): RunLog['summary'];
|
|
66
|
+
/**
|
|
67
|
+
* Assemble the complete {@link RunLog} for this run.
|
|
68
|
+
*
|
|
69
|
+
* Sets `endTime` to the current time, includes all entries, and
|
|
70
|
+
* computes the summary. Call this once when the run is finished.
|
|
71
|
+
*
|
|
72
|
+
* @returns Complete run log ready for serialization
|
|
73
|
+
*/
|
|
74
|
+
toRunLog(): RunLog;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/ai/telemetry/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;;GAWG;AACH,qBAAa,eAAe;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,oCAAoC;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAEhD;;;;OAIG;gBACS,KAAK,EAAE,MAAM;IAKzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAIrC;;;;OAIG;IACH,UAAU,IAAI,SAAS,cAAc,EAAE;IAIvC;;;;;;;OAOG;IACH,uBAAuB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI;IAKpD;;;;;;;OAOG;IACH,UAAU,IAAI,MAAM,CAAC,SAAS,CAAC;IAwC/B;;;;;;;OAOG;IACH,QAAQ,IAAI,MAAM;CASnB"}
|