* Load permission events from both sources, normalized to * { ts, tool, target, decision }. * * audit.jsonl entries (event === 'permission-request') predate the dedicated * log; to avoid double counting we only include audit entries older than the * earliest record in permission-events.jsonl.
(root)
| 59 | * earliest record in permission-events.jsonl. |
| 60 | */ |
| 61 | function loadEvents(root) { |
| 62 | const telemetryDir = path.join(root, '.planning', 'telemetry'); |
| 63 | const dedicated = readJsonl(path.join(telemetryDir, 'permission-events.jsonl')) |
| 64 | .filter((e) => e && e.ts) |
| 65 | .map((e) => ({ |
| 66 | ts: e.ts, |
| 67 | tool: e.tool || 'unknown', |
| 68 | target: e.target || '', |
| 69 | decision: e.decision || 'deferred', |
| 70 | })); |
| 71 | |
| 72 | const earliestDedicated = dedicated.length |
| 73 | ? dedicated.reduce((min, e) => (e.ts < min ? e.ts : min), dedicated[0].ts) |
| 74 | : null; |
| 75 | |
| 76 | const historical = readJsonl(path.join(telemetryDir, 'audit.jsonl')) |
| 77 | .filter((e) => e && e.event === 'permission-request' && e.timestamp) |
| 78 | .filter((e) => earliestDedicated === null || e.timestamp < earliestDedicated) |
| 79 | .map((e) => ({ |
| 80 | ts: e.timestamp, |
| 81 | tool: e.tool || 'unknown', |
| 82 | target: e.target || '', |
| 83 | decision: e.decision || 'deferred', |
| 84 | })); |
| 85 | |
| 86 | return historical.concat(dedicated).sort((a, b) => (a.ts < b.ts ? -1 : 1)); |
| 87 | } |
| 88 | |
| 89 | function countBy(events, keyFn) { |
| 90 | const counts = new Map(); |