harness-evolve 1.0.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.
@@ -0,0 +1,437 @@
1
+ // src/hooks/post-tool-use-failure.ts
2
+ import { unlink } from "fs/promises";
3
+ import { join as join3 } from "path";
4
+
5
+ // src/schemas/hook-input.ts
6
+ import { z } from "zod/v4";
7
+ var hookCommonSchema = z.object({
8
+ session_id: z.string(),
9
+ transcript_path: z.string(),
10
+ cwd: z.string(),
11
+ permission_mode: z.string()
12
+ });
13
+ var userPromptSubmitInputSchema = hookCommonSchema.extend({
14
+ hook_event_name: z.literal("UserPromptSubmit"),
15
+ prompt: z.string()
16
+ });
17
+ var preToolUseInputSchema = hookCommonSchema.extend({
18
+ hook_event_name: z.literal("PreToolUse"),
19
+ tool_name: z.string(),
20
+ tool_input: z.record(z.string(), z.unknown()),
21
+ tool_use_id: z.string()
22
+ });
23
+ var postToolUseInputSchema = hookCommonSchema.extend({
24
+ hook_event_name: z.literal("PostToolUse"),
25
+ tool_name: z.string(),
26
+ tool_input: z.record(z.string(), z.unknown()),
27
+ tool_response: z.unknown().optional(),
28
+ tool_use_id: z.string()
29
+ });
30
+ var postToolUseFailureInputSchema = hookCommonSchema.extend({
31
+ hook_event_name: z.literal("PostToolUseFailure"),
32
+ tool_name: z.string(),
33
+ tool_input: z.record(z.string(), z.unknown()),
34
+ tool_use_id: z.string(),
35
+ error: z.string().optional(),
36
+ is_interrupt: z.boolean().optional()
37
+ });
38
+ var permissionRequestInputSchema = hookCommonSchema.extend({
39
+ hook_event_name: z.literal("PermissionRequest"),
40
+ tool_name: z.string(),
41
+ tool_input: z.record(z.string(), z.unknown()),
42
+ permission_suggestions: z.array(z.unknown()).optional()
43
+ });
44
+ var stopInputSchema = hookCommonSchema.extend({
45
+ hook_event_name: z.literal("Stop"),
46
+ stop_hook_active: z.boolean(),
47
+ last_assistant_message: z.string().optional()
48
+ });
49
+
50
+ // src/storage/logger.ts
51
+ import { appendFile } from "fs/promises";
52
+ import { join as join2 } from "path";
53
+
54
+ // src/schemas/log-entry.ts
55
+ import { z as z2 } from "zod/v4";
56
+ var promptEntrySchema = z2.object({
57
+ timestamp: z2.iso.datetime(),
58
+ session_id: z2.string(),
59
+ cwd: z2.string(),
60
+ prompt: z2.string(),
61
+ prompt_length: z2.number(),
62
+ transcript_path: z2.string().optional()
63
+ });
64
+ var toolEntrySchema = z2.object({
65
+ timestamp: z2.iso.datetime(),
66
+ session_id: z2.string(),
67
+ event: z2.enum(["pre", "post", "failure"]),
68
+ tool_name: z2.string(),
69
+ input_summary: z2.string().optional(),
70
+ duration_ms: z2.number().optional(),
71
+ success: z2.boolean().optional()
72
+ });
73
+ var permissionEntrySchema = z2.object({
74
+ timestamp: z2.iso.datetime(),
75
+ session_id: z2.string(),
76
+ tool_name: z2.string(),
77
+ decision: z2.enum(["approved", "denied", "unknown"])
78
+ });
79
+ var sessionEntrySchema = z2.object({
80
+ timestamp: z2.iso.datetime(),
81
+ session_id: z2.string(),
82
+ event: z2.enum(["start", "end"]),
83
+ cwd: z2.string().optional()
84
+ });
85
+
86
+ // src/scrubber/patterns.ts
87
+ var SCRUB_PATTERNS = [
88
+ {
89
+ name: "aws_key",
90
+ regex: /AKIA[0-9A-Z]{16}/g,
91
+ replacement: "[REDACTED:aws_key]"
92
+ },
93
+ {
94
+ name: "aws_secret",
95
+ regex: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)[\s=:]+[A-Za-z0-9/+=]{40}/g,
96
+ replacement: "[REDACTED:aws_secret]"
97
+ },
98
+ {
99
+ name: "github_token",
100
+ regex: /gh[ps]_[A-Za-z0-9_]{36,}/g,
101
+ replacement: "[REDACTED:github_token]"
102
+ },
103
+ {
104
+ name: "github_oauth",
105
+ regex: /gho_[A-Za-z0-9_]{36,}/g,
106
+ replacement: "[REDACTED:github_oauth]"
107
+ },
108
+ {
109
+ name: "bearer_token",
110
+ regex: /[Bb]earer\s+[A-Za-z0-9\-._~+/]+=*/g,
111
+ replacement: "[REDACTED:bearer_token]"
112
+ },
113
+ {
114
+ name: "jwt",
115
+ regex: /eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/g,
116
+ replacement: "[REDACTED:jwt]"
117
+ },
118
+ {
119
+ name: "api_key",
120
+ regex: /(?:api[_-]?key|apikey)[\s=:]+['"]?[A-Za-z0-9\-._]{20,}['"]?/gi,
121
+ replacement: "[REDACTED:api_key]"
122
+ },
123
+ {
124
+ name: "secret",
125
+ regex: /(?:secret|SECRET)[\s=:]+['"]?[A-Za-z0-9\-._]{20,}['"]?/g,
126
+ replacement: "[REDACTED:secret]"
127
+ },
128
+ {
129
+ name: "private_key",
130
+ regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/g,
131
+ replacement: "[REDACTED:private_key]"
132
+ },
133
+ {
134
+ name: "password",
135
+ regex: /(?:password|passwd|pwd)[\s=:]+['"]?[^\s'"]{8,}['"]?/gi,
136
+ replacement: "[REDACTED:password]"
137
+ },
138
+ {
139
+ name: "slack_token",
140
+ regex: /xox[bpors]-[A-Za-z0-9-]{10,}/g,
141
+ replacement: "[REDACTED:slack_token]"
142
+ },
143
+ {
144
+ name: "google_api_key",
145
+ regex: /AIza[0-9A-Za-z\-_]{35}/g,
146
+ replacement: "[REDACTED:google_api_key]"
147
+ },
148
+ {
149
+ name: "stripe_key",
150
+ regex: /[sr]k_(?:test|live)_[A-Za-z0-9]{20,}/g,
151
+ replacement: "[REDACTED:stripe_key]"
152
+ },
153
+ {
154
+ name: "db_url",
155
+ regex: /(?:postgres|mysql|mongodb):\/\/[^\s]+:[^\s]+@[^\s]+/g,
156
+ replacement: "[REDACTED:db_url]"
157
+ }
158
+ // Pattern 15: High-entropy string detection is intentionally omitted.
159
+ // It requires Shannon entropy calculation and is deferred to when
160
+ // config.scrubbing.highEntropyDetection is enabled.
161
+ ];
162
+
163
+ // src/scrubber/scrub.ts
164
+ function scrubString(input, extraPatterns) {
165
+ let result = input;
166
+ const patterns = extraPatterns ? [...SCRUB_PATTERNS, ...extraPatterns] : SCRUB_PATTERNS;
167
+ for (const pattern of patterns) {
168
+ pattern.regex.lastIndex = 0;
169
+ result = result.replace(pattern.regex, pattern.replacement);
170
+ }
171
+ return result;
172
+ }
173
+ function scrubObject(obj, extraPatterns) {
174
+ if (typeof obj === "string") {
175
+ return scrubString(obj, extraPatterns);
176
+ }
177
+ if (Array.isArray(obj)) {
178
+ return obj.map((item) => scrubObject(item, extraPatterns));
179
+ }
180
+ if (obj !== null && typeof obj === "object") {
181
+ const result = {};
182
+ for (const [key, value] of Object.entries(obj)) {
183
+ result[key] = scrubObject(value, extraPatterns);
184
+ }
185
+ return result;
186
+ }
187
+ return obj;
188
+ }
189
+
190
+ // src/storage/dirs.ts
191
+ import { mkdir } from "fs/promises";
192
+ import { join } from "path";
193
+ var BASE_DIR = join(process.env.HOME ?? "", ".harness-evolve");
194
+ var paths = {
195
+ base: BASE_DIR,
196
+ logs: {
197
+ prompts: join(BASE_DIR, "logs", "prompts"),
198
+ tools: join(BASE_DIR, "logs", "tools"),
199
+ permissions: join(BASE_DIR, "logs", "permissions"),
200
+ sessions: join(BASE_DIR, "logs", "sessions")
201
+ },
202
+ analysis: join(BASE_DIR, "analysis"),
203
+ analysisPreProcessed: join(BASE_DIR, "analysis", "pre-processed"),
204
+ summary: join(BASE_DIR, "analysis", "pre-processed", "summary.json"),
205
+ environmentSnapshot: join(BASE_DIR, "analysis", "environment-snapshot.json"),
206
+ analysisResult: join(BASE_DIR, "analysis", "analysis-result.json"),
207
+ pending: join(BASE_DIR, "pending"),
208
+ config: join(BASE_DIR, "config.json"),
209
+ counter: join(BASE_DIR, "counter.json"),
210
+ recommendations: join(BASE_DIR, "recommendations.md"),
211
+ recommendationState: join(BASE_DIR, "analysis", "recommendation-state.json"),
212
+ recommendationArchive: join(BASE_DIR, "analysis", "recommendations-archive"),
213
+ notificationFlag: join(BASE_DIR, "analysis", "has-pending-notifications"),
214
+ autoApplyLog: join(BASE_DIR, "analysis", "auto-apply-log.jsonl"),
215
+ outcomeHistory: join(BASE_DIR, "analysis", "outcome-history.jsonl")
216
+ };
217
+ var initialized = false;
218
+ async function ensureInit() {
219
+ if (initialized) return;
220
+ await mkdir(paths.logs.prompts, { recursive: true });
221
+ await mkdir(paths.logs.tools, { recursive: true });
222
+ await mkdir(paths.logs.permissions, { recursive: true });
223
+ await mkdir(paths.logs.sessions, { recursive: true });
224
+ await mkdir(paths.analysis, { recursive: true });
225
+ await mkdir(paths.analysisPreProcessed, { recursive: true });
226
+ await mkdir(paths.pending, { recursive: true });
227
+ await mkdir(paths.recommendationArchive, { recursive: true });
228
+ initialized = true;
229
+ }
230
+
231
+ // src/storage/logger.ts
232
+ var SCHEMA_MAP = {
233
+ prompts: promptEntrySchema,
234
+ tools: toolEntrySchema,
235
+ permissions: permissionEntrySchema,
236
+ sessions: sessionEntrySchema
237
+ };
238
+ function getLogDir(type) {
239
+ return paths.logs[type];
240
+ }
241
+ function getLogFilePath(type, date) {
242
+ const dateStr = (date ?? /* @__PURE__ */ new Date()).toISOString().slice(0, 10);
243
+ return join2(getLogDir(type), `${dateStr}.jsonl`);
244
+ }
245
+ async function appendLogEntry(type, rawEntry) {
246
+ await ensureInit();
247
+ const schema = SCHEMA_MAP[type];
248
+ const validated = schema.parse(rawEntry);
249
+ const scrubbed = scrubObject(validated);
250
+ const line = JSON.stringify(scrubbed) + "\n";
251
+ const filePath = getLogFilePath(type);
252
+ await appendFile(filePath, line, "utf-8");
253
+ }
254
+
255
+ // src/storage/counter.ts
256
+ import { readFile } from "fs/promises";
257
+ import { lock } from "proper-lockfile";
258
+ import writeFileAtomic from "write-file-atomic";
259
+
260
+ // src/schemas/counter.ts
261
+ import { z as z3 } from "zod/v4";
262
+ var counterSchema = z3.object({
263
+ total: z3.number().default(0),
264
+ session: z3.record(z3.string(), z3.number()).default({}),
265
+ last_analysis: z3.iso.datetime().optional(),
266
+ last_updated: z3.iso.datetime()
267
+ });
268
+
269
+ // src/storage/counter.ts
270
+ async function incrementCounter(sessionId) {
271
+ await ensureInit();
272
+ try {
273
+ await readFile(paths.counter, "utf-8");
274
+ } catch {
275
+ const initial = {
276
+ total: 0,
277
+ session: {},
278
+ last_updated: (/* @__PURE__ */ new Date()).toISOString()
279
+ };
280
+ await writeFileAtomic(paths.counter, JSON.stringify(initial, null, 2));
281
+ }
282
+ const release = await lock(paths.counter, {
283
+ retries: { retries: 50, minTimeout: 20, maxTimeout: 1e3, randomize: true },
284
+ stale: 1e4
285
+ // Consider lock stale after 10 seconds
286
+ });
287
+ try {
288
+ const raw = await readFile(paths.counter, "utf-8");
289
+ const data = counterSchema.parse(JSON.parse(raw));
290
+ data.total += 1;
291
+ data.session[sessionId] = (data.session[sessionId] ?? 0) + 1;
292
+ data.last_updated = (/* @__PURE__ */ new Date()).toISOString();
293
+ await writeFileAtomic(paths.counter, JSON.stringify(data, null, 2));
294
+ return data.total;
295
+ } finally {
296
+ await release();
297
+ }
298
+ }
299
+
300
+ // src/storage/config.ts
301
+ import { readFile as readFile2 } from "fs/promises";
302
+ import writeFileAtomic2 from "write-file-atomic";
303
+
304
+ // src/schemas/config.ts
305
+ import { z as z4 } from "zod/v4";
306
+ var configSchema = z4.object({
307
+ version: z4.number().default(1),
308
+ analysis: z4.object({
309
+ threshold: z4.number().min(1).default(50),
310
+ enabled: z4.boolean().default(true),
311
+ classifierThresholds: z4.record(z4.string(), z4.number()).default({})
312
+ }).default({ threshold: 50, enabled: true, classifierThresholds: {} }),
313
+ hooks: z4.object({
314
+ capturePrompts: z4.boolean().default(true),
315
+ captureTools: z4.boolean().default(true),
316
+ capturePermissions: z4.boolean().default(true),
317
+ captureSessions: z4.boolean().default(true)
318
+ }).default({
319
+ capturePrompts: true,
320
+ captureTools: true,
321
+ capturePermissions: true,
322
+ captureSessions: true
323
+ }),
324
+ scrubbing: z4.object({
325
+ enabled: z4.boolean().default(true),
326
+ highEntropyDetection: z4.boolean().default(false),
327
+ customPatterns: z4.array(z4.object({
328
+ name: z4.string(),
329
+ regex: z4.string(),
330
+ replacement: z4.string()
331
+ })).default([])
332
+ }).default({
333
+ enabled: true,
334
+ highEntropyDetection: false,
335
+ customPatterns: []
336
+ }),
337
+ delivery: z4.object({
338
+ stdoutInjection: z4.boolean().default(true),
339
+ maxTokens: z4.number().default(200),
340
+ fullAuto: z4.boolean().default(false),
341
+ maxRecommendationsInFile: z4.number().default(20),
342
+ archiveAfterDays: z4.number().default(7)
343
+ }).default({
344
+ stdoutInjection: true,
345
+ maxTokens: 200,
346
+ fullAuto: false,
347
+ maxRecommendationsInFile: 20,
348
+ archiveAfterDays: 7
349
+ })
350
+ }).strict();
351
+
352
+ // src/storage/config.ts
353
+ async function loadConfig() {
354
+ try {
355
+ const raw = await readFile2(paths.config, "utf-8");
356
+ return configSchema.parse(JSON.parse(raw));
357
+ } catch {
358
+ const defaults = configSchema.parse({});
359
+ await writeFileAtomic2(paths.config, JSON.stringify(defaults, null, 2));
360
+ return defaults;
361
+ }
362
+ }
363
+
364
+ // src/hooks/shared.ts
365
+ var MAX_LEN = 200;
366
+ function truncate(str, maxLen) {
367
+ return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
368
+ }
369
+ function readFromStream(stream) {
370
+ return new Promise((resolve, reject) => {
371
+ let data = "";
372
+ stream.setEncoding("utf-8");
373
+ stream.on("data", (chunk) => {
374
+ data += chunk;
375
+ });
376
+ stream.on("end", () => resolve(data));
377
+ stream.on("error", reject);
378
+ });
379
+ }
380
+ function readStdin() {
381
+ return readFromStream(process.stdin);
382
+ }
383
+ function summarizeToolInput(toolName, toolInput) {
384
+ switch (toolName) {
385
+ case "Bash":
386
+ return truncate(String(toolInput.command ?? ""), MAX_LEN);
387
+ case "Write":
388
+ case "Edit":
389
+ case "Read":
390
+ return truncate(String(toolInput.file_path ?? ""), MAX_LEN);
391
+ case "Glob":
392
+ return truncate(String(toolInput.pattern ?? ""), MAX_LEN);
393
+ case "Grep":
394
+ return truncate(String(toolInput.pattern ?? ""), MAX_LEN);
395
+ default: {
396
+ const str = JSON.stringify(toolInput);
397
+ return truncate(str, MAX_LEN);
398
+ }
399
+ }
400
+ }
401
+
402
+ // src/hooks/post-tool-use-failure.ts
403
+ async function handlePostToolUseFailure(rawJson) {
404
+ try {
405
+ const config = await loadConfig();
406
+ if (!config.hooks.captureTools) return;
407
+ const input = postToolUseFailureInputSchema.parse(JSON.parse(rawJson));
408
+ const markerPath = join3(paths.pending, `${input.tool_use_id}.ts`);
409
+ try {
410
+ await unlink(markerPath);
411
+ } catch {
412
+ }
413
+ await appendLogEntry("tools", {
414
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
415
+ session_id: input.session_id,
416
+ event: "failure",
417
+ tool_name: input.tool_name,
418
+ input_summary: summarizeToolInput(input.tool_name, input.tool_input),
419
+ success: false
420
+ });
421
+ await incrementCounter(input.session_id);
422
+ } catch {
423
+ }
424
+ }
425
+ async function main() {
426
+ try {
427
+ const raw = await readStdin();
428
+ await handlePostToolUseFailure(raw);
429
+ } catch {
430
+ }
431
+ process.exit(0);
432
+ }
433
+ main();
434
+ export {
435
+ handlePostToolUseFailure
436
+ };
437
+ //# sourceMappingURL=post-tool-use-failure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/post-tool-use-failure.ts","../../src/schemas/hook-input.ts","../../src/storage/logger.ts","../../src/schemas/log-entry.ts","../../src/scrubber/patterns.ts","../../src/scrubber/scrub.ts","../../src/storage/dirs.ts","../../src/storage/counter.ts","../../src/schemas/counter.ts","../../src/storage/config.ts","../../src/schemas/config.ts","../../src/hooks/shared.ts"],"sourcesContent":["// PostToolUseFailure hook handler: captures tool execution failure events.\n// Cleans up any PreToolUse marker file and logs a failure entry.\n\nimport { unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { postToolUseFailureInputSchema } from '../schemas/hook-input.js';\nimport { appendLogEntry } from '../storage/logger.js';\nimport { incrementCounter } from '../storage/counter.js';\nimport { loadConfig } from '../storage/config.js';\nimport { paths } from '../storage/dirs.js';\nimport { readStdin, summarizeToolInput } from './shared.js';\n\n/**\n * Core handler logic, exported for testability.\n * Validates input, cleans up marker file, logs 'failure' tool entry\n * with success=false, increments counter.\n * Swallows all errors to never block Claude Code.\n */\nexport async function handlePostToolUseFailure(rawJson: string): Promise<void> {\n try {\n const config = await loadConfig();\n if (!config.hooks.captureTools) return;\n\n const input = postToolUseFailureInputSchema.parse(JSON.parse(rawJson));\n\n // Clean up marker file if it exists\n const markerPath = join(paths.pending, `${input.tool_use_id}.ts`);\n try {\n await unlink(markerPath);\n } catch {\n // Marker may not exist -- that's fine\n }\n\n await appendLogEntry('tools', {\n timestamp: new Date().toISOString(),\n session_id: input.session_id,\n event: 'failure' as const,\n tool_name: input.tool_name,\n input_summary: summarizeToolInput(input.tool_name, input.tool_input as Record<string, unknown>),\n success: false,\n });\n\n await incrementCounter(input.session_id);\n } catch {\n // Never block Claude Code on capture errors\n }\n}\n\n// Entry point when invoked by Claude Code as a command hook\nasync function main(): Promise<void> {\n try {\n const raw = await readStdin();\n await handlePostToolUseFailure(raw);\n } catch {\n // Never block Claude Code\n }\n process.exit(0);\n}\n\nmain();\n","import { z } from 'zod/v4';\n\n// Common fields present in ALL Claude Code hook events\nexport const hookCommonSchema = z.object({\n session_id: z.string(),\n transcript_path: z.string(),\n cwd: z.string(),\n permission_mode: z.string(),\n});\nexport type HookCommon = z.infer<typeof hookCommonSchema>;\n\n// UserPromptSubmit: fires when user submits a prompt\nexport const userPromptSubmitInputSchema = hookCommonSchema.extend({\n hook_event_name: z.literal('UserPromptSubmit'),\n prompt: z.string(),\n});\nexport type UserPromptSubmitInput = z.infer<typeof userPromptSubmitInputSchema>;\n\n// PreToolUse: fires before a tool is invoked\nexport const preToolUseInputSchema = hookCommonSchema.extend({\n hook_event_name: z.literal('PreToolUse'),\n tool_name: z.string(),\n tool_input: z.record(z.string(), z.unknown()),\n tool_use_id: z.string(),\n});\nexport type PreToolUseInput = z.infer<typeof preToolUseInputSchema>;\n\n// PostToolUse: fires after a tool completes successfully\nexport const postToolUseInputSchema = hookCommonSchema.extend({\n hook_event_name: z.literal('PostToolUse'),\n tool_name: z.string(),\n tool_input: z.record(z.string(), z.unknown()),\n tool_response: z.unknown().optional(),\n tool_use_id: z.string(),\n});\nexport type PostToolUseInput = z.infer<typeof postToolUseInputSchema>;\n\n// PostToolUseFailure: fires after a tool execution fails\nexport const postToolUseFailureInputSchema = hookCommonSchema.extend({\n hook_event_name: z.literal('PostToolUseFailure'),\n tool_name: z.string(),\n tool_input: z.record(z.string(), z.unknown()),\n tool_use_id: z.string(),\n error: z.string().optional(),\n is_interrupt: z.boolean().optional(),\n});\nexport type PostToolUseFailureInput = z.infer<typeof postToolUseFailureInputSchema>;\n\n// PermissionRequest: fires before user sees permission dialog\nexport const permissionRequestInputSchema = hookCommonSchema.extend({\n hook_event_name: z.literal('PermissionRequest'),\n tool_name: z.string(),\n tool_input: z.record(z.string(), z.unknown()),\n permission_suggestions: z.array(z.unknown()).optional(),\n});\nexport type PermissionRequestInput = z.infer<typeof permissionRequestInputSchema>;\n\n// Stop: fires after Claude Code completes a response\nexport const stopInputSchema = hookCommonSchema.extend({\n hook_event_name: z.literal('Stop'),\n stop_hook_active: z.boolean(),\n last_assistant_message: z.string().optional(),\n});\nexport type StopInput = z.infer<typeof stopInputSchema>;\n","// JSONL logger with scrub-before-write pipeline and daily rotation.\n// Pipeline: raw data -> validate schema -> scrub strings -> append JSONL\n// Implements D-01 (scrub on write), D-03 (separate dirs), D-04 (daily rotation).\n\nimport { appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { z } from 'zod/v4';\nimport {\n promptEntrySchema,\n toolEntrySchema,\n permissionEntrySchema,\n sessionEntrySchema,\n} from '../schemas/log-entry.js';\nimport { scrubObject } from '../scrubber/scrub.js';\nimport { paths, ensureInit } from './dirs.js';\n\n// Schema map (schemas are static, safe to capture at module level)\nconst SCHEMA_MAP = {\n prompts: promptEntrySchema,\n tools: toolEntrySchema,\n permissions: permissionEntrySchema,\n sessions: sessionEntrySchema,\n} as const;\n\nexport type LogType = keyof typeof SCHEMA_MAP;\n\n// Resolve directory path at call time (not module load) to support testing\nfunction getLogDir(type: LogType): string {\n return paths.logs[type];\n}\n\nfunction getLogFilePath(type: LogType, date?: Date): string {\n const dateStr = (date ?? new Date()).toISOString().slice(0, 10); // YYYY-MM-DD per D-04\n return join(getLogDir(type), `${dateStr}.jsonl`);\n}\n\n/**\n * Validate, scrub, and append a log entry as a JSONL line.\n *\n * Uses native fs.appendFile (NOT write-file-atomic) because:\n * - JSONL is append-only, not replace\n * - appendFile is atomic for writes <4KB on POSIX\n * - write-file-atomic replaces entire file (anti-pattern for appends)\n */\nexport async function appendLogEntry(type: LogType, rawEntry: unknown): Promise<void> {\n await ensureInit();\n const schema = SCHEMA_MAP[type];\n const validated = (schema as z.ZodType).parse(rawEntry);\n const scrubbed = scrubObject(validated); // D-01: scrub before write\n const line = JSON.stringify(scrubbed) + '\\n';\n const filePath = getLogFilePath(type);\n await appendFile(filePath, line, 'utf-8');\n}\n","import { z } from 'zod/v4';\n\nexport const promptEntrySchema = z.object({\n timestamp: z.iso.datetime(),\n session_id: z.string(),\n cwd: z.string(),\n prompt: z.string(),\n prompt_length: z.number(),\n transcript_path: z.string().optional(),\n});\nexport type PromptEntry = z.infer<typeof promptEntrySchema>;\n\nexport const toolEntrySchema = z.object({\n timestamp: z.iso.datetime(),\n session_id: z.string(),\n event: z.enum(['pre', 'post', 'failure']),\n tool_name: z.string(),\n input_summary: z.string().optional(),\n duration_ms: z.number().optional(),\n success: z.boolean().optional(),\n});\nexport type ToolEntry = z.infer<typeof toolEntrySchema>;\n\nexport const permissionEntrySchema = z.object({\n timestamp: z.iso.datetime(),\n session_id: z.string(),\n tool_name: z.string(),\n decision: z.enum(['approved', 'denied', 'unknown']),\n});\nexport type PermissionEntry = z.infer<typeof permissionEntrySchema>;\n\nexport const sessionEntrySchema = z.object({\n timestamp: z.iso.datetime(),\n session_id: z.string(),\n event: z.enum(['start', 'end']),\n cwd: z.string().optional(),\n});\nexport type SessionEntry = z.infer<typeof sessionEntrySchema>;\n","// Secret detection regex patterns with [REDACTED:type] markers.\n// Based on top-15 patterns from secrets-patterns-db research.\n// 14 regex patterns included; high-entropy detection (pattern 15)\n// is deferred to config.scrubbing.highEntropyDetection enablement.\n\nexport interface ScrubPattern {\n name: string;\n regex: RegExp;\n replacement: string;\n}\n\nexport const SCRUB_PATTERNS: ScrubPattern[] = [\n {\n name: 'aws_key',\n regex: /AKIA[0-9A-Z]{16}/g,\n replacement: '[REDACTED:aws_key]',\n },\n {\n name: 'aws_secret',\n regex: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)[\\s=:]+[A-Za-z0-9/+=]{40}/g,\n replacement: '[REDACTED:aws_secret]',\n },\n {\n name: 'github_token',\n regex: /gh[ps]_[A-Za-z0-9_]{36,}/g,\n replacement: '[REDACTED:github_token]',\n },\n {\n name: 'github_oauth',\n regex: /gho_[A-Za-z0-9_]{36,}/g,\n replacement: '[REDACTED:github_oauth]',\n },\n {\n name: 'bearer_token',\n regex: /[Bb]earer\\s+[A-Za-z0-9\\-._~+/]+=*/g,\n replacement: '[REDACTED:bearer_token]',\n },\n {\n name: 'jwt',\n regex: /eyJ[A-Za-z0-9\\-_]+\\.eyJ[A-Za-z0-9\\-_]+\\.[A-Za-z0-9\\-_]+/g,\n replacement: '[REDACTED:jwt]',\n },\n {\n name: 'api_key',\n regex: /(?:api[_-]?key|apikey)[\\s=:]+['\"]?[A-Za-z0-9\\-._]{20,}['\"]?/gi,\n replacement: '[REDACTED:api_key]',\n },\n {\n name: 'secret',\n regex: /(?:secret|SECRET)[\\s=:]+['\"]?[A-Za-z0-9\\-._]{20,}['\"]?/g,\n replacement: '[REDACTED:secret]',\n },\n {\n name: 'private_key',\n regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/g,\n replacement: '[REDACTED:private_key]',\n },\n {\n name: 'password',\n regex: /(?:password|passwd|pwd)[\\s=:]+['\"]?[^\\s'\"]{8,}['\"]?/gi,\n replacement: '[REDACTED:password]',\n },\n {\n name: 'slack_token',\n regex: /xox[bpors]-[A-Za-z0-9-]{10,}/g,\n replacement: '[REDACTED:slack_token]',\n },\n {\n name: 'google_api_key',\n regex: /AIza[0-9A-Za-z\\-_]{35}/g,\n replacement: '[REDACTED:google_api_key]',\n },\n {\n name: 'stripe_key',\n regex: /[sr]k_(?:test|live)_[A-Za-z0-9]{20,}/g,\n replacement: '[REDACTED:stripe_key]',\n },\n {\n name: 'db_url',\n regex: /(?:postgres|mysql|mongodb):\\/\\/[^\\s]+:[^\\s]+@[^\\s]+/g,\n replacement: '[REDACTED:db_url]',\n },\n // Pattern 15: High-entropy string detection is intentionally omitted.\n // It requires Shannon entropy calculation and is deferred to when\n // config.scrubbing.highEntropyDetection is enabled.\n];\n","// String and object scrubbing functions.\n// Applies regex-based secret detection patterns to replace\n// sensitive values with [REDACTED:type] markers (D-01, D-02).\n\nimport { SCRUB_PATTERNS, type ScrubPattern } from './patterns.js';\n\n/**\n * Scrub secrets from a string using built-in patterns\n * plus optional extra patterns (e.g., user-defined custom patterns).\n */\nexport function scrubString(input: string, extraPatterns?: ScrubPattern[]): string {\n let result = input;\n const patterns = extraPatterns\n ? [...SCRUB_PATTERNS, ...extraPatterns]\n : SCRUB_PATTERNS;\n for (const pattern of patterns) {\n // Reset regex lastIndex for global patterns to ensure\n // correct matching when regex objects are reused across calls\n pattern.regex.lastIndex = 0;\n result = result.replace(pattern.regex, pattern.replacement);\n }\n return result;\n}\n\n/**\n * Recursively scrub all string values in an object/array structure.\n * Non-string primitives (numbers, booleans, null, undefined) pass through unchanged.\n */\nexport function scrubObject<T>(obj: T, extraPatterns?: ScrubPattern[]): T {\n if (typeof obj === 'string') {\n return scrubString(obj, extraPatterns) as T;\n }\n if (Array.isArray(obj)) {\n return obj.map(item => scrubObject(item, extraPatterns)) as T;\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = scrubObject(value, extraPatterns);\n }\n return result as T;\n }\n return obj; // numbers, booleans, null, undefined pass through\n}\n","import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nconst BASE_DIR = join(process.env.HOME ?? '', '.harness-evolve');\n\nexport const paths = {\n base: BASE_DIR,\n logs: {\n prompts: join(BASE_DIR, 'logs', 'prompts'),\n tools: join(BASE_DIR, 'logs', 'tools'),\n permissions: join(BASE_DIR, 'logs', 'permissions'),\n sessions: join(BASE_DIR, 'logs', 'sessions'),\n },\n analysis: join(BASE_DIR, 'analysis'),\n analysisPreProcessed: join(BASE_DIR, 'analysis', 'pre-processed'),\n summary: join(BASE_DIR, 'analysis', 'pre-processed', 'summary.json'),\n environmentSnapshot: join(BASE_DIR, 'analysis', 'environment-snapshot.json'),\n analysisResult: join(BASE_DIR, 'analysis', 'analysis-result.json'),\n pending: join(BASE_DIR, 'pending'),\n config: join(BASE_DIR, 'config.json'),\n counter: join(BASE_DIR, 'counter.json'),\n recommendations: join(BASE_DIR, 'recommendations.md'),\n recommendationState: join(BASE_DIR, 'analysis', 'recommendation-state.json'),\n recommendationArchive: join(BASE_DIR, 'analysis', 'recommendations-archive'),\n notificationFlag: join(BASE_DIR, 'analysis', 'has-pending-notifications'),\n autoApplyLog: join(BASE_DIR, 'analysis', 'auto-apply-log.jsonl'),\n outcomeHistory: join(BASE_DIR, 'analysis', 'outcome-history.jsonl'),\n} as const;\n\nlet initialized = false;\n\nexport async function ensureInit(): Promise<void> {\n if (initialized) return;\n await mkdir(paths.logs.prompts, { recursive: true });\n await mkdir(paths.logs.tools, { recursive: true });\n await mkdir(paths.logs.permissions, { recursive: true });\n await mkdir(paths.logs.sessions, { recursive: true });\n await mkdir(paths.analysis, { recursive: true });\n await mkdir(paths.analysisPreProcessed, { recursive: true });\n await mkdir(paths.pending, { recursive: true });\n await mkdir(paths.recommendationArchive, { recursive: true });\n initialized = true;\n}\n\n// For testing: reset the initialized flag\nexport function resetInit(): void {\n initialized = false;\n}\n","import { readFile } from 'node:fs/promises';\nimport { lock } from 'proper-lockfile';\nimport writeFileAtomic from 'write-file-atomic';\nimport { counterSchema, type Counter } from '../schemas/counter.js';\nimport { paths, ensureInit } from './dirs.js';\n\n/**\n * Read the current counter state from disk.\n * Returns defaults (total=0, session={}) if no counter file exists.\n */\nexport async function readCounter(): Promise<Counter> {\n await ensureInit();\n try {\n const raw = await readFile(paths.counter, 'utf-8');\n return counterSchema.parse(JSON.parse(raw));\n } catch {\n // File doesn't exist or invalid -- return defaults\n return {\n total: 0,\n session: {},\n last_updated: new Date().toISOString(),\n };\n }\n}\n\n/**\n * Atomically increment the interaction counter with cross-process safety.\n *\n * Uses proper-lockfile (mkdir-based, macOS-safe) for cross-process locking\n * and write-file-atomic for crash-safe writes inside the lock.\n *\n * Pattern: ensure-file -> lock -> read -> increment -> atomic-write -> unlock\n */\nexport async function incrementCounter(sessionId: string): Promise<number> {\n await ensureInit();\n\n // Ensure counter file exists before locking (proper-lockfile requires existing file)\n try {\n await readFile(paths.counter, 'utf-8');\n } catch {\n const initial: Counter = {\n total: 0,\n session: {},\n last_updated: new Date().toISOString(),\n };\n await writeFileAtomic(paths.counter, JSON.stringify(initial, null, 2));\n }\n\n const release = await lock(paths.counter, {\n retries: { retries: 50, minTimeout: 20, maxTimeout: 1000, randomize: true },\n stale: 10000, // Consider lock stale after 10 seconds\n });\n\n try {\n const raw = await readFile(paths.counter, 'utf-8');\n const data = counterSchema.parse(JSON.parse(raw));\n data.total += 1;\n data.session[sessionId] = (data.session[sessionId] ?? 0) + 1;\n data.last_updated = new Date().toISOString();\n await writeFileAtomic(paths.counter, JSON.stringify(data, null, 2));\n return data.total;\n } finally {\n await release();\n }\n}\n\n/**\n * Reset the counter to zero. Used for testing and post-analysis reset.\n */\nexport async function resetCounter(): Promise<void> {\n await ensureInit();\n const data: Counter = {\n total: 0,\n session: {},\n last_updated: new Date().toISOString(),\n };\n await writeFileAtomic(paths.counter, JSON.stringify(data, null, 2));\n}\n","import { z } from 'zod/v4';\n\nexport const counterSchema = z.object({\n total: z.number().default(0),\n session: z.record(z.string(), z.number()).default({}),\n last_analysis: z.iso.datetime().optional(),\n last_updated: z.iso.datetime(),\n});\nexport type Counter = z.infer<typeof counterSchema>;\n","import { readFile } from 'node:fs/promises';\nimport writeFileAtomic from 'write-file-atomic';\nimport { configSchema, type Config } from '../schemas/config.js';\nimport { paths } from './dirs.js';\n\nexport async function loadConfig(): Promise<Config> {\n try {\n const raw = await readFile(paths.config, 'utf-8');\n return configSchema.parse(JSON.parse(raw));\n } catch {\n // File doesn't exist, is invalid JSON, or fails strict validation\n const defaults = configSchema.parse({});\n await writeFileAtomic(paths.config, JSON.stringify(defaults, null, 2));\n return defaults;\n }\n}\n","import { z } from 'zod/v4';\n\nexport const configSchema = z.object({\n version: z.number().default(1),\n analysis: z.object({\n threshold: z.number().min(1).default(50),\n enabled: z.boolean().default(true),\n classifierThresholds: z.record(z.string(), z.number()).default({}),\n }).default({ threshold: 50, enabled: true, classifierThresholds: {} }),\n hooks: z.object({\n capturePrompts: z.boolean().default(true),\n captureTools: z.boolean().default(true),\n capturePermissions: z.boolean().default(true),\n captureSessions: z.boolean().default(true),\n }).default({\n capturePrompts: true,\n captureTools: true,\n capturePermissions: true,\n captureSessions: true,\n }),\n scrubbing: z.object({\n enabled: z.boolean().default(true),\n highEntropyDetection: z.boolean().default(false),\n customPatterns: z.array(z.object({\n name: z.string(),\n regex: z.string(),\n replacement: z.string(),\n })).default([]),\n }).default({\n enabled: true,\n highEntropyDetection: false,\n customPatterns: [],\n }),\n delivery: z.object({\n stdoutInjection: z.boolean().default(true),\n maxTokens: z.number().default(200),\n fullAuto: z.boolean().default(false),\n maxRecommendationsInFile: z.number().default(20),\n archiveAfterDays: z.number().default(7),\n }).default({\n stdoutInjection: true,\n maxTokens: 200,\n fullAuto: false,\n maxRecommendationsInFile: 20,\n archiveAfterDays: 7,\n }),\n}).strict();\n\nexport type Config = z.infer<typeof configSchema>;\n","// Shared utilities for hook handlers: stdin reading and tool input summarization\n\nconst MAX_LEN = 200;\n\n/**\n * Truncate a string to maxLen characters, appending '...' if truncated.\n */\nfunction truncate(str: string, maxLen: number): string {\n return str.length > maxLen ? str.slice(0, maxLen) + '...' : str;\n}\n\n/**\n * Read all data from a readable stream into a string buffer.\n * Used for testability -- readStdin() delegates to this with process.stdin.\n */\nexport function readFromStream(stream: NodeJS.ReadableStream): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = '';\n stream.setEncoding('utf-8');\n stream.on('data', (chunk: string) => {\n data += chunk;\n });\n stream.on('end', () => resolve(data));\n stream.on('error', reject);\n });\n}\n\n/**\n * Read all stdin data into a string buffer.\n * Claude Code pipes JSON to hook stdin; this collects all chunks before parsing.\n */\nexport function readStdin(): Promise<string> {\n return readFromStream(process.stdin);\n}\n\n/**\n * Produce a concise summary of tool_input, capped at 200 characters.\n * Prevents Write/Edit tool inputs (with full file content) from bloating logs.\n */\nexport function summarizeToolInput(\n toolName: string,\n toolInput: Record<string, unknown>,\n): string {\n switch (toolName) {\n case 'Bash':\n return truncate(String(toolInput.command ?? ''), MAX_LEN);\n case 'Write':\n case 'Edit':\n case 'Read':\n return truncate(String(toolInput.file_path ?? ''), MAX_LEN);\n case 'Glob':\n return truncate(String(toolInput.pattern ?? ''), MAX_LEN);\n case 'Grep':\n return truncate(String(toolInput.pattern ?? ''), MAX_LEN);\n default: {\n // MCP tools and others: stringify then truncate\n const str = JSON.stringify(toolInput);\n return truncate(str, MAX_LEN);\n }\n }\n}\n"],"mappings":";AAGA,SAAS,cAAc;AACvB,SAAS,QAAAA,aAAY;;;ACJrB,SAAS,SAAS;AAGX,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,YAAY,EAAE,OAAO;AAAA,EACrB,iBAAiB,EAAE,OAAO;AAAA,EAC1B,KAAK,EAAE,OAAO;AAAA,EACd,iBAAiB,EAAE,OAAO;AAC5B,CAAC;AAIM,IAAM,8BAA8B,iBAAiB,OAAO;AAAA,EACjE,iBAAiB,EAAE,QAAQ,kBAAkB;AAAA,EAC7C,QAAQ,EAAE,OAAO;AACnB,CAAC;AAIM,IAAM,wBAAwB,iBAAiB,OAAO;AAAA,EAC3D,iBAAiB,EAAE,QAAQ,YAAY;AAAA,EACvC,WAAW,EAAE,OAAO;AAAA,EACpB,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EAC5C,aAAa,EAAE,OAAO;AACxB,CAAC;AAIM,IAAM,yBAAyB,iBAAiB,OAAO;AAAA,EAC5D,iBAAiB,EAAE,QAAQ,aAAa;AAAA,EACxC,WAAW,EAAE,OAAO;AAAA,EACpB,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EAC5C,eAAe,EAAE,QAAQ,EAAE,SAAS;AAAA,EACpC,aAAa,EAAE,OAAO;AACxB,CAAC;AAIM,IAAM,gCAAgC,iBAAiB,OAAO;AAAA,EACnE,iBAAiB,EAAE,QAAQ,oBAAoB;AAAA,EAC/C,WAAW,EAAE,OAAO;AAAA,EACpB,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EAC5C,aAAa,EAAE,OAAO;AAAA,EACtB,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,cAAc,EAAE,QAAQ,EAAE,SAAS;AACrC,CAAC;AAIM,IAAM,+BAA+B,iBAAiB,OAAO;AAAA,EAClE,iBAAiB,EAAE,QAAQ,mBAAmB;AAAA,EAC9C,WAAW,EAAE,OAAO;AAAA,EACpB,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EAC5C,wBAAwB,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS;AACxD,CAAC;AAIM,IAAM,kBAAkB,iBAAiB,OAAO;AAAA,EACrD,iBAAiB,EAAE,QAAQ,MAAM;AAAA,EACjC,kBAAkB,EAAE,QAAQ;AAAA,EAC5B,wBAAwB,EAAE,OAAO,EAAE,SAAS;AAC9C,CAAC;;;AC1DD,SAAS,kBAAkB;AAC3B,SAAS,QAAAC,aAAY;;;ACLrB,SAAS,KAAAC,UAAS;AAEX,IAAM,oBAAoBA,GAAE,OAAO;AAAA,EACxC,WAAWA,GAAE,IAAI,SAAS;AAAA,EAC1B,YAAYA,GAAE,OAAO;AAAA,EACrB,KAAKA,GAAE,OAAO;AAAA,EACd,QAAQA,GAAE,OAAO;AAAA,EACjB,eAAeA,GAAE,OAAO;AAAA,EACxB,iBAAiBA,GAAE,OAAO,EAAE,SAAS;AACvC,CAAC;AAGM,IAAM,kBAAkBA,GAAE,OAAO;AAAA,EACtC,WAAWA,GAAE,IAAI,SAAS;AAAA,EAC1B,YAAYA,GAAE,OAAO;AAAA,EACrB,OAAOA,GAAE,KAAK,CAAC,OAAO,QAAQ,SAAS,CAAC;AAAA,EACxC,WAAWA,GAAE,OAAO;AAAA,EACpB,eAAeA,GAAE,OAAO,EAAE,SAAS;AAAA,EACnC,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,SAASA,GAAE,QAAQ,EAAE,SAAS;AAChC,CAAC;AAGM,IAAM,wBAAwBA,GAAE,OAAO;AAAA,EAC5C,WAAWA,GAAE,IAAI,SAAS;AAAA,EAC1B,YAAYA,GAAE,OAAO;AAAA,EACrB,WAAWA,GAAE,OAAO;AAAA,EACpB,UAAUA,GAAE,KAAK,CAAC,YAAY,UAAU,SAAS,CAAC;AACpD,CAAC;AAGM,IAAM,qBAAqBA,GAAE,OAAO;AAAA,EACzC,WAAWA,GAAE,IAAI,SAAS;AAAA,EAC1B,YAAYA,GAAE,OAAO;AAAA,EACrB,OAAOA,GAAE,KAAK,CAAC,SAAS,KAAK,CAAC;AAAA,EAC9B,KAAKA,GAAE,OAAO,EAAE,SAAS;AAC3B,CAAC;;;ACzBM,IAAM,iBAAiC;AAAA,EAC5C;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA;AAAA;AAAA;AAIF;;;AC3EO,SAAS,YAAY,OAAe,eAAwC;AACjF,MAAI,SAAS;AACb,QAAM,WAAW,gBACb,CAAC,GAAG,gBAAgB,GAAG,aAAa,IACpC;AACJ,aAAW,WAAW,UAAU;AAG9B,YAAQ,MAAM,YAAY;AAC1B,aAAS,OAAO,QAAQ,QAAQ,OAAO,QAAQ,WAAW;AAAA,EAC5D;AACA,SAAO;AACT;AAMO,SAAS,YAAe,KAAQ,eAAmC;AACxE,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO,YAAY,KAAK,aAAa;AAAA,EACvC;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,UAAQ,YAAY,MAAM,aAAa,CAAC;AAAA,EACzD;AACA,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,UAAM,SAAkC,CAAC;AACzC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,aAAO,GAAG,IAAI,YAAY,OAAO,aAAa;AAAA,IAChD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AC3CA,SAAS,aAAa;AACtB,SAAS,YAAY;AAErB,IAAM,WAAW,KAAK,QAAQ,IAAI,QAAQ,IAAI,iBAAiB;AAExD,IAAM,QAAQ;AAAA,EACnB,MAAM;AAAA,EACN,MAAM;AAAA,IACJ,SAAS,KAAK,UAAU,QAAQ,SAAS;AAAA,IACzC,OAAO,KAAK,UAAU,QAAQ,OAAO;AAAA,IACrC,aAAa,KAAK,UAAU,QAAQ,aAAa;AAAA,IACjD,UAAU,KAAK,UAAU,QAAQ,UAAU;AAAA,EAC7C;AAAA,EACA,UAAU,KAAK,UAAU,UAAU;AAAA,EACnC,sBAAsB,KAAK,UAAU,YAAY,eAAe;AAAA,EAChE,SAAS,KAAK,UAAU,YAAY,iBAAiB,cAAc;AAAA,EACnE,qBAAqB,KAAK,UAAU,YAAY,2BAA2B;AAAA,EAC3E,gBAAgB,KAAK,UAAU,YAAY,sBAAsB;AAAA,EACjE,SAAS,KAAK,UAAU,SAAS;AAAA,EACjC,QAAQ,KAAK,UAAU,aAAa;AAAA,EACpC,SAAS,KAAK,UAAU,cAAc;AAAA,EACtC,iBAAiB,KAAK,UAAU,oBAAoB;AAAA,EACpD,qBAAqB,KAAK,UAAU,YAAY,2BAA2B;AAAA,EAC3E,uBAAuB,KAAK,UAAU,YAAY,yBAAyB;AAAA,EAC3E,kBAAkB,KAAK,UAAU,YAAY,2BAA2B;AAAA,EACxE,cAAc,KAAK,UAAU,YAAY,sBAAsB;AAAA,EAC/D,gBAAgB,KAAK,UAAU,YAAY,uBAAuB;AACpE;AAEA,IAAI,cAAc;AAElB,eAAsB,aAA4B;AAChD,MAAI,YAAa;AACjB,QAAM,MAAM,MAAM,KAAK,SAAS,EAAE,WAAW,KAAK,CAAC;AACnD,QAAM,MAAM,MAAM,KAAK,OAAO,EAAE,WAAW,KAAK,CAAC;AACjD,QAAM,MAAM,MAAM,KAAK,aAAa,EAAE,WAAW,KAAK,CAAC;AACvD,QAAM,MAAM,MAAM,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;AACpD,QAAM,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAC/C,QAAM,MAAM,MAAM,sBAAsB,EAAE,WAAW,KAAK,CAAC;AAC3D,QAAM,MAAM,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAM,MAAM,MAAM,uBAAuB,EAAE,WAAW,KAAK,CAAC;AAC5D,gBAAc;AAChB;;;AJzBA,IAAM,aAAa;AAAA,EACjB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,aAAa;AAAA,EACb,UAAU;AACZ;AAKA,SAAS,UAAU,MAAuB;AACxC,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,eAAe,MAAe,MAAqB;AAC1D,QAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,YAAY,EAAE,MAAM,GAAG,EAAE;AAC9D,SAAOC,MAAK,UAAU,IAAI,GAAG,GAAG,OAAO,QAAQ;AACjD;AAUA,eAAsB,eAAe,MAAe,UAAkC;AACpF,QAAM,WAAW;AACjB,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,YAAa,OAAqB,MAAM,QAAQ;AACtD,QAAM,WAAW,YAAY,SAAS;AACtC,QAAM,OAAO,KAAK,UAAU,QAAQ,IAAI;AACxC,QAAM,WAAW,eAAe,IAAI;AACpC,QAAM,WAAW,UAAU,MAAM,OAAO;AAC1C;;;AKpDA,SAAS,gBAAgB;AACzB,SAAS,YAAY;AACrB,OAAO,qBAAqB;;;ACF5B,SAAS,KAAAC,UAAS;AAEX,IAAM,gBAAgBA,GAAE,OAAO;AAAA,EACpC,OAAOA,GAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EAC3B,SAASA,GAAE,OAAOA,GAAE,OAAO,GAAGA,GAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,EACpD,eAAeA,GAAE,IAAI,SAAS,EAAE,SAAS;AAAA,EACzC,cAAcA,GAAE,IAAI,SAAS;AAC/B,CAAC;;;AD0BD,eAAsB,iBAAiB,WAAoC;AACzE,QAAM,WAAW;AAGjB,MAAI;AACF,UAAM,SAAS,MAAM,SAAS,OAAO;AAAA,EACvC,QAAQ;AACN,UAAM,UAAmB;AAAA,MACvB,OAAO;AAAA,MACP,SAAS,CAAC;AAAA,MACV,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,IACvC;AACA,UAAM,gBAAgB,MAAM,SAAS,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EACvE;AAEA,QAAM,UAAU,MAAM,KAAK,MAAM,SAAS;AAAA,IACxC,SAAS,EAAE,SAAS,IAAI,YAAY,IAAI,YAAY,KAAM,WAAW,KAAK;AAAA,IAC1E,OAAO;AAAA;AAAA,EACT,CAAC;AAED,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,MAAM,SAAS,OAAO;AACjD,UAAM,OAAO,cAAc,MAAM,KAAK,MAAM,GAAG,CAAC;AAChD,SAAK,SAAS;AACd,SAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,KAAK,KAAK;AAC3D,SAAK,gBAAe,oBAAI,KAAK,GAAE,YAAY;AAC3C,UAAM,gBAAgB,MAAM,SAAS,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAClE,WAAO,KAAK;AAAA,EACd,UAAE;AACA,UAAM,QAAQ;AAAA,EAChB;AACF;;;AEhEA,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,sBAAqB;;;ACD5B,SAAS,KAAAC,UAAS;AAEX,IAAM,eAAeA,GAAE,OAAO;AAAA,EACnC,SAASA,GAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EAC7B,UAAUA,GAAE,OAAO;AAAA,IACjB,WAAWA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE;AAAA,IACvC,SAASA,GAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,IACjC,sBAAsBA,GAAE,OAAOA,GAAE,OAAO,GAAGA,GAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,EACnE,CAAC,EAAE,QAAQ,EAAE,WAAW,IAAI,SAAS,MAAM,sBAAsB,CAAC,EAAE,CAAC;AAAA,EACrE,OAAOA,GAAE,OAAO;AAAA,IACd,gBAAgBA,GAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,IACxC,cAAcA,GAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,IACtC,oBAAoBA,GAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,IAC5C,iBAAiBA,GAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,EAC3C,CAAC,EAAE,QAAQ;AAAA,IACT,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,EACnB,CAAC;AAAA,EACD,WAAWA,GAAE,OAAO;AAAA,IAClB,SAASA,GAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,IACjC,sBAAsBA,GAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,IAC/C,gBAAgBA,GAAE,MAAMA,GAAE,OAAO;AAAA,MAC/B,MAAMA,GAAE,OAAO;AAAA,MACf,OAAOA,GAAE,OAAO;AAAA,MAChB,aAAaA,GAAE,OAAO;AAAA,IACxB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,EAChB,CAAC,EAAE,QAAQ;AAAA,IACT,SAAS;AAAA,IACT,sBAAsB;AAAA,IACtB,gBAAgB,CAAC;AAAA,EACnB,CAAC;AAAA,EACD,UAAUA,GAAE,OAAO;AAAA,IACjB,iBAAiBA,GAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,IACzC,WAAWA,GAAE,OAAO,EAAE,QAAQ,GAAG;AAAA,IACjC,UAAUA,GAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,IACnC,0BAA0BA,GAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,IAC/C,kBAAkBA,GAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACxC,CAAC,EAAE,QAAQ;AAAA,IACT,iBAAiB;AAAA,IACjB,WAAW;AAAA,IACX,UAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,kBAAkB;AAAA,EACpB,CAAC;AACH,CAAC,EAAE,OAAO;;;ADzCV,eAAsB,aAA8B;AAClD,MAAI;AACF,UAAM,MAAM,MAAMC,UAAS,MAAM,QAAQ,OAAO;AAChD,WAAO,aAAa,MAAM,KAAK,MAAM,GAAG,CAAC;AAAA,EAC3C,QAAQ;AAEN,UAAM,WAAW,aAAa,MAAM,CAAC,CAAC;AACtC,UAAMC,iBAAgB,MAAM,QAAQ,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AACrE,WAAO;AAAA,EACT;AACF;;;AEbA,IAAM,UAAU;AAKhB,SAAS,SAAS,KAAa,QAAwB;AACrD,SAAO,IAAI,SAAS,SAAS,IAAI,MAAM,GAAG,MAAM,IAAI,QAAQ;AAC9D;AAMO,SAAS,eAAe,QAAgD;AAC7E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,OAAO;AACX,WAAO,YAAY,OAAO;AAC1B,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,cAAQ;AAAA,IACV,CAAC;AACD,WAAO,GAAG,OAAO,MAAM,QAAQ,IAAI,CAAC;AACpC,WAAO,GAAG,SAAS,MAAM;AAAA,EAC3B,CAAC;AACH;AAMO,SAAS,YAA6B;AAC3C,SAAO,eAAe,QAAQ,KAAK;AACrC;AAMO,SAAS,mBACd,UACA,WACQ;AACR,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,SAAS,OAAO,UAAU,WAAW,EAAE,GAAG,OAAO;AAAA,IAC1D,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,SAAS,OAAO,UAAU,aAAa,EAAE,GAAG,OAAO;AAAA,IAC5D,KAAK;AACH,aAAO,SAAS,OAAO,UAAU,WAAW,EAAE,GAAG,OAAO;AAAA,IAC1D,KAAK;AACH,aAAO,SAAS,OAAO,UAAU,WAAW,EAAE,GAAG,OAAO;AAAA,IAC1D,SAAS;AAEP,YAAM,MAAM,KAAK,UAAU,SAAS;AACpC,aAAO,SAAS,KAAK,OAAO;AAAA,IAC9B;AAAA,EACF;AACF;;;AX1CA,eAAsB,yBAAyB,SAAgC;AAC7E,MAAI;AACF,UAAM,SAAS,MAAM,WAAW;AAChC,QAAI,CAAC,OAAO,MAAM,aAAc;AAEhC,UAAM,QAAQ,8BAA8B,MAAM,KAAK,MAAM,OAAO,CAAC;AAGrE,UAAM,aAAaC,MAAK,MAAM,SAAS,GAAG,MAAM,WAAW,KAAK;AAChE,QAAI;AACF,YAAM,OAAO,UAAU;AAAA,IACzB,QAAQ;AAAA,IAER;AAEA,UAAM,eAAe,SAAS;AAAA,MAC5B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,YAAY,MAAM;AAAA,MAClB,OAAO;AAAA,MACP,WAAW,MAAM;AAAA,MACjB,eAAe,mBAAmB,MAAM,WAAW,MAAM,UAAqC;AAAA,MAC9F,SAAS;AAAA,IACX,CAAC;AAED,UAAM,iBAAiB,MAAM,UAAU;AAAA,EACzC,QAAQ;AAAA,EAER;AACF;AAGA,eAAe,OAAsB;AACnC,MAAI;AACF,UAAM,MAAM,MAAM,UAAU;AAC5B,UAAM,yBAAyB,GAAG;AAAA,EACpC,QAAQ;AAAA,EAER;AACA,UAAQ,KAAK,CAAC;AAChB;AAEA,KAAK;","names":["join","join","z","join","z","readFile","writeFileAtomic","z","readFile","writeFileAtomic","join"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Core handler logic, exported for testability.
3
+ * Reads marker file for duration, validates input, logs 'post' tool entry
4
+ * with duration_ms and success=true, increments counter.
5
+ * Swallows all errors to never block Claude Code.
6
+ */
7
+ declare function handlePostToolUse(rawJson: string): Promise<void>;
8
+
9
+ export { handlePostToolUse };