@aaac/observability 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -3,13 +3,18 @@ import {
3
3
  DEFAULT_DB_PATH,
4
4
  SqliteQueryAdapter,
5
5
  createPipeline,
6
+ emitHumanInstruction,
7
+ emitPromotionPr,
8
+ emitQualityGateResult,
9
+ evaluateMapping,
6
10
  generateId,
7
- isoToUnixNano
8
- } from "../chunk-BL7QW46X.js";
11
+ isoToUnixNano,
12
+ loadEventMappingConfig
13
+ } from "../chunk-3H35HIMU.js";
9
14
 
10
15
  // src/cli/index.ts
11
16
  import { readFileSync } from "fs";
12
- import { dirname, resolve } from "path";
17
+ import { dirname as dirname2, resolve } from "path";
13
18
  import { fileURLToPath } from "url";
14
19
 
15
20
  // src/generated/program.ts
@@ -88,6 +93,34 @@ command_sets:
88
93
  stderr:
89
94
  format: text
90
95
 
96
+ record-hook:
97
+ summary: Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)
98
+ arguments:
99
+ - name: hook-name
100
+ index: 0
101
+ required: true
102
+ schema: { type: string }
103
+ description: "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)"
104
+ options:
105
+ - name: mapping-config
106
+ schema: { type: string }
107
+ description: "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)"
108
+ - name: db
109
+ schema: { type: string }
110
+ description: Path to observability SQLite database (overrides default)
111
+ exits:
112
+ '0':
113
+ description: Hook recorded (or no-op when stdin/mapping unavailable) \u2014 always fail-open
114
+ stdout:
115
+ format: json
116
+ schema:
117
+ type: object
118
+ properties:
119
+ recorded:
120
+ type: integer
121
+ fallback:
122
+ type: boolean
123
+
91
124
  query:
92
125
  summary: Query the observability store via QueryAdapter
93
126
  options:
@@ -137,7 +170,7 @@ command_sets:
137
170
  stderr:
138
171
  format: text
139
172
  `;
140
- var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "AaaC Observability CLI",\n "version": "0.1.0",\n "description": "aaac-observ \u2014 external event registration and query for @aaac/observability"\n },\n "command_sets": {\n "aaac-observ": {\n "summary": "aaac-observ \u2014 register external events and query the observability store",\n "executable": "aaac-observ",\n "commands": {\n "record": {\n "summary": "Register an external event into the observability pipeline (thin entrypoint for EventCollector.registerExternalEvent)",\n "options": [\n {\n "name": "event-type",\n "aliases": [\n "t"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Event type (e.g. promotion.commit, process.edit, agent.session)"\n },\n {\n "name": "lifecycle",\n "aliases": [\n "l"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "open",\n "close",\n "event",\n "instant"\n ]\n },\n "description": "Event lifecycle phase: open | close | event | instant"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID (auto-generated if omitted)"\n },\n {\n "name": "parent-span-id",\n "schema": {\n "type": "string"\n },\n "description": "Parent span ID for hierarchy"\n },\n {\n "name": "session-id",\n "schema": {\n "type": "string"\n },\n "description": "Session ID to associate with the event"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for distributed tracing"\n },\n {\n "name": "source",\n "schema": {\n "type": "string"\n },\n "description": "Event source identifier (default: external)"\n },\n {\n "name": "attr",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Attribute as key=value (repeatable, e.g. --attr git.commit=abc123)"\n },\n {\n "name": "link",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Cross-axis link as targetSpanId:linkType[:targetTraceId] (repeatable)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Event registered successfully",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "eventId": {\n "type": "string"\n },\n "spanId": {\n "type": "string"\n }\n }\n }\n }\n },\n "1": {\n "description": "Registration failed (validation error or write error)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "query": {\n "summary": "Query the observability store via QueryAdapter",\n "options": [\n {\n "name": "kind",\n "aliases": [\n "k"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "trace",\n "span",\n "search",\n "links"\n ]\n },\n "description": "Query kind: trace | span | search | links"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for trace queries (--kind trace)"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID for span/links queries (--kind span or --kind links)"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Event type filter for search queries (--kind search)"\n },\n {\n "name": "task-id",\n "schema": {\n "type": "string"\n },\n "description": "Task ID filter for search queries (--kind search)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Start time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "End time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "direction",\n "aliases": [\n "d"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "forward",\n "reverse",\n "both"\n ]\n },\n "description": "Link traversal direction for links queries (default: both)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Query succeeded \u2014 results as JSON array",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object"\n }\n }\n },\n "1": {\n "description": "Query failed",\n "stderr": {\n "format": "text"\n }\n }\n }\n }\n }\n }\n }\n}';
173
+ var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "AaaC Observability CLI",\n "version": "0.1.0",\n "description": "aaac-observ \u2014 external event registration and query for @aaac/observability"\n },\n "command_sets": {\n "aaac-observ": {\n "summary": "aaac-observ \u2014 register external events and query the observability store",\n "executable": "aaac-observ",\n "commands": {\n "record": {\n "summary": "Register an external event into the observability pipeline (thin entrypoint for EventCollector.registerExternalEvent)",\n "options": [\n {\n "name": "event-type",\n "aliases": [\n "t"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Event type (e.g. promotion.commit, process.edit, agent.session)"\n },\n {\n "name": "lifecycle",\n "aliases": [\n "l"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "open",\n "close",\n "event",\n "instant"\n ]\n },\n "description": "Event lifecycle phase: open | close | event | instant"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID (auto-generated if omitted)"\n },\n {\n "name": "parent-span-id",\n "schema": {\n "type": "string"\n },\n "description": "Parent span ID for hierarchy"\n },\n {\n "name": "session-id",\n "schema": {\n "type": "string"\n },\n "description": "Session ID to associate with the event"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for distributed tracing"\n },\n {\n "name": "source",\n "schema": {\n "type": "string"\n },\n "description": "Event source identifier (default: external)"\n },\n {\n "name": "attr",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Attribute as key=value (repeatable, e.g. --attr git.commit=abc123)"\n },\n {\n "name": "link",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Cross-axis link as targetSpanId:linkType[:targetTraceId] (repeatable)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Event registered successfully",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "eventId": {\n "type": "string"\n },\n "spanId": {\n "type": "string"\n }\n }\n }\n }\n },\n "1": {\n "description": "Registration failed (validation error or write error)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "record-hook": {\n "summary": "Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)",\n "arguments": [\n {\n "name": "hook-name",\n "index": 0,\n "required": true,\n "schema": {\n "type": "string"\n },\n "description": "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)"\n }\n ],\n "options": [\n {\n "name": "mapping-config",\n "schema": {\n "type": "string"\n },\n "description": "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Hook recorded (or no-op when stdin/mapping unavailable) \u2014 always fail-open",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "recorded": {\n "type": "integer"\n },\n "fallback": {\n "type": "boolean"\n }\n }\n }\n }\n }\n }\n },\n "query": {\n "summary": "Query the observability store via QueryAdapter",\n "options": [\n {\n "name": "kind",\n "aliases": [\n "k"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "trace",\n "span",\n "search",\n "links"\n ]\n },\n "description": "Query kind: trace | span | search | links"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for trace queries (--kind trace)"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID for span/links queries (--kind span or --kind links)"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Event type filter for search queries (--kind search)"\n },\n {\n "name": "task-id",\n "schema": {\n "type": "string"\n },\n "description": "Task ID filter for search queries (--kind search)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Start time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "End time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "direction",\n "aliases": [\n "d"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "forward",\n "reverse",\n "both"\n ]\n },\n "description": "Link traversal direction for links queries (default: both)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Query succeeded \u2014 results as JSON array",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object"\n }\n }\n },\n "1": {\n "description": "Query failed",\n "stderr": {\n "format": "text"\n }\n }\n }\n }\n }\n }\n }\n}';
141
174
 
142
175
  // src/generated/program.ts
143
176
  function createProgram(handlers2, version) {
@@ -146,6 +179,9 @@ function createProgram(handlers2, version) {
146
179
  program2.command("record").description("Register an external event into the observability pipeline (thin entrypoint for EventCollector.registerExternalEvent)").option("-t, --event-type <value>", "Event type (e.g. promotion.commit, process.edit, agent.session)").option("-l, --lifecycle <value>", "Event lifecycle phase: open | close | event | instant").option("-s, --span-id <value>", "Span ID (auto-generated if omitted)").option("--parent-span-id <value>", "Parent span ID for hierarchy").option("--session-id <value>", "Session ID to associate with the event").option("--trace-id <value>", "Trace ID for distributed tracing").option("--source <value>", "Event source identifier (default: external)").option("--attr <value>", "Attribute as key=value (repeatable, e.g. --attr git.commit=abc123)").option("--link <value>", "Cross-axis link as targetSpanId:linkType[:targetTraceId] (repeatable)").option("--db <value>", "Path to observability SQLite database (overrides default)").action(async (opts, cmd) => {
147
180
  await handlers2.record(opts, cmd.optsWithGlobals());
148
181
  });
182
+ program2.command("record-hook").description("Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)").argument("<hook-name>", "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)").option("--mapping-config <value>", "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)").option("--db <value>", "Path to observability SQLite database (overrides default)").action(async (hookName, opts, cmd) => {
183
+ await handlers2.recordHook(hookName, opts, cmd.optsWithGlobals());
184
+ });
149
185
  program2.command("query").description("Query the observability store via QueryAdapter").option("-k, --kind <value>", "Query kind: trace | span | search | links").option("--trace-id <value>", "Trace ID for trace queries (--kind trace)").option("-s, --span-id <value>", "Span ID for span/links queries (--kind span or --kind links)").option("--event-type <value>", "Event type filter for search queries (--kind search)").option("--task-id <value>", "Task ID filter for search queries (--kind search)").option("--from <value>", "Start time in ISO 8601 format for search queries (--kind search)").option("--to <value>", "End time in ISO 8601 format for search queries (--kind search)").option("-d, --direction <value>", "Link traversal direction for links queries (default: both)").option("--db <value>", "Path to observability SQLite database (overrides default)").action(async (opts, cmd) => {
150
186
  await handlers2.query(opts, cmd.optsWithGlobals());
151
187
  });
@@ -166,7 +202,7 @@ function createProgram(handlers2, version) {
166
202
  type: "cli-contracts/extract",
167
203
  extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
168
204
  specVersion: doc.cli_contracts ?? "0.1.0",
169
- commands: ["aaac-observ.record", "aaac-observ.query"]
205
+ commands: ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.query"]
170
206
  };
171
207
  }
172
208
  Object.assign(out, doc);
@@ -183,7 +219,7 @@ function createProgram(handlers2, version) {
183
219
  yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
184
220
  yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
185
221
  yamlLines.push("commands:");
186
- for (const id of ["aaac-observ.record", "aaac-observ.query"]) {
222
+ for (const id of ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.query"]) {
187
223
  yamlLines.push(" - " + id);
188
224
  }
189
225
  }
@@ -296,10 +332,14 @@ async function handleRecord(options, _parentOpts) {
296
332
  afterCorrelate: (event) => {
297
333
  capturedEventId = event.id;
298
334
  return event;
299
- },
300
- // Disable enrichment rules for external CLI events to keep the write
301
- // path lightweight (no Enricher cache, no artifact lookups, no derived events)
302
- enrichRules: []
335
+ }
336
+ // Enable the default enrichment rules (R1–R5). This is the path the git
337
+ // post-commit hook uses to record promotion.commit (close) with
338
+ // committed_files; R5 (cross-axis) must run here to add the
339
+ // materializes_as_commit link to task spans whose modified files intersect
340
+ // the commit. R3/R4 accumulation persists in the SQLite-backed cache of the
341
+ // same db file, so R5 sees it across the process boundary (#143 / #140).
342
+ // Enricher is fail-open, so a rule error never blocks recording.
303
343
  });
304
344
  try {
305
345
  collector.registerExternalEvent({
@@ -324,6 +364,197 @@ async function handleRecord(options, _parentOpts) {
324
364
  process.stdout.write(JSON.stringify(result) + "\n");
325
365
  }
326
366
 
367
+ // src/cli/record-hook.ts
368
+ import { mkdir, rename, unlink, writeFile } from "fs/promises";
369
+ import { dirname, join } from "path";
370
+ var GIT_CONTEXT_COMMAND_RE = /\bgit\s+(?:commit|push|merge)\b/;
371
+ var GIT_HOOK_NAMES = /* @__PURE__ */ new Set([
372
+ "pre-commit",
373
+ "post-commit",
374
+ "pre-push",
375
+ "post-merge",
376
+ "post-checkout"
377
+ ]);
378
+ function defaultMappingConfigPath(dbPath) {
379
+ return join(dirname(dbPath), "config", "event-mapping.json");
380
+ }
381
+ function contextFilePath(dbPath) {
382
+ return join(dirname(dbPath), ".observ-context.json");
383
+ }
384
+ async function readStdin() {
385
+ if (process.stdin.isTTY) return "";
386
+ return new Promise((resolve2) => {
387
+ let data = "";
388
+ let settled = false;
389
+ const finish = () => {
390
+ if (settled) return;
391
+ settled = true;
392
+ resolve2(data);
393
+ };
394
+ try {
395
+ process.stdin.setEncoding("utf8");
396
+ process.stdin.on("data", (chunk) => {
397
+ data += chunk;
398
+ });
399
+ process.stdin.on("end", finish);
400
+ process.stdin.on("error", finish);
401
+ process.stdin.on("close", finish);
402
+ const t = setTimeout(finish, 1e3);
403
+ if (typeof t.unref === "function") t.unref();
404
+ } catch {
405
+ finish();
406
+ }
407
+ });
408
+ }
409
+ function parseHookInput(raw) {
410
+ const trimmed = raw.trim();
411
+ if (!trimmed) return {};
412
+ try {
413
+ const parsed = JSON.parse(trimmed);
414
+ return parsed !== null && typeof parsed === "object" ? parsed : {};
415
+ } catch {
416
+ return {};
417
+ }
418
+ }
419
+ function asString(value) {
420
+ return typeof value === "string" && value.length > 0 ? value : void 0;
421
+ }
422
+ function asNumber(value) {
423
+ if (typeof value === "number" && Number.isFinite(value)) return value;
424
+ if (typeof value === "string" && value.trim() !== "") {
425
+ const n = Number(value);
426
+ if (Number.isFinite(n)) return n;
427
+ }
428
+ return void 0;
429
+ }
430
+ function emitMappedSpans(collector, hook, rule, hookInput, sessionId) {
431
+ const { spans, links } = evaluateMapping(rule, hookInput);
432
+ const linksBySpan = /* @__PURE__ */ new Map();
433
+ for (const link of links) {
434
+ const arr = linksBySpan.get(link.fromSpanId) ?? [];
435
+ arr.push({ targetSpanId: link.toSpanId, linkType: link.linkType });
436
+ linksBySpan.set(link.fromSpanId, arr);
437
+ }
438
+ const parentConversationId = hook === "subagentStart" ? asString(hookInput.parent_conversation_id) : void 0;
439
+ for (const span of spans) {
440
+ const attributes = { ...span.attributes };
441
+ if (sessionId) attributes["session_id"] = sessionId;
442
+ if (parentConversationId) {
443
+ attributes["parent_conversation_id"] = parentConversationId;
444
+ }
445
+ collector.emit({
446
+ source: "cursor-hook",
447
+ eventType: span.eventType,
448
+ lifecycle: span.lifecycle,
449
+ spanId: span.spanId,
450
+ parentSpanId: span.parentSpanId,
451
+ attributes,
452
+ links: linksBySpan.get(span.spanId) ?? []
453
+ });
454
+ }
455
+ return spans.length;
456
+ }
457
+ function emitGenericFallback(collector, hook, sessionId) {
458
+ const eventType = (GIT_HOOK_NAMES.has(hook) ? "git." : "cursor.") + hook;
459
+ const attributes = { "hook.name": hook };
460
+ if (sessionId) attributes["session_id"] = sessionId;
461
+ collector.emit({
462
+ source: "cursor-hook",
463
+ eventType,
464
+ lifecycle: "instant",
465
+ attributes
466
+ });
467
+ return 1;
468
+ }
469
+ function emitHumanEvents(collector, hook, hookInput, sessionId) {
470
+ let count = 0;
471
+ if (hook === "beforeSubmitPrompt") {
472
+ const prompt = asString(hookInput.prompt);
473
+ if (prompt !== void 0) {
474
+ const attachments = Array.isArray(hookInput.attachments) ? hookInput.attachments : [];
475
+ emitHumanInstruction(collector, { sessionId, prompt, attachments });
476
+ count += 1;
477
+ }
478
+ } else if (hook === "afterShellExecution") {
479
+ const command = asString(hookInput.command);
480
+ if (command !== void 0) {
481
+ const exitCode = asNumber(hookInput.exit_code) ?? asNumber(hookInput.exitCode) ?? 0;
482
+ const durationMs = asNumber(hookInput.duration);
483
+ if (emitQualityGateResult(collector, { sessionId, command, exitCode, durationMs })) {
484
+ count += 1;
485
+ }
486
+ const output = asString(hookInput.output) ?? "";
487
+ if (emitPromotionPr(collector, { sessionId, command, output })) {
488
+ count += 1;
489
+ }
490
+ }
491
+ }
492
+ return count;
493
+ }
494
+ async function manageContextFile(hook, hookInput, sessionId, dbPath) {
495
+ const command = asString(hookInput.command) ?? "";
496
+ const isGitContextCommand = GIT_CONTEXT_COMMAND_RE.test(command);
497
+ const file = contextFilePath(dbPath);
498
+ if (hook === "beforeShellExecution" && isGitContextCommand) {
499
+ await mkdir(dirname(file), { recursive: true });
500
+ const payload = JSON.stringify({
501
+ session_id: sessionId || null,
502
+ nonce: generateId(),
503
+ created_at_ms: Date.now()
504
+ }) + "\n";
505
+ const tmp = `${file}.${process.pid}.${generateId()}.tmp`;
506
+ await writeFile(tmp, payload, "utf8");
507
+ await rename(tmp, file);
508
+ } else if (hook === "afterShellExecution" && isGitContextCommand) {
509
+ await unlink(file).catch(() => {
510
+ });
511
+ }
512
+ }
513
+ async function runRecordHook(args) {
514
+ const { hook, hookInput, dbPath, mappingConfigPath } = args;
515
+ const sessionId = asString(hookInput.conversation_id) ?? "";
516
+ await manageContextFile(hook, hookInput, sessionId, dbPath).catch(() => {
517
+ });
518
+ let config;
519
+ try {
520
+ config = await loadEventMappingConfig(mappingConfigPath);
521
+ } catch {
522
+ config = void 0;
523
+ }
524
+ const rule = config?.mappings?.[hook];
525
+ let recorded = 0;
526
+ let fallback = false;
527
+ const { collector, sink } = createPipeline({ dbPath });
528
+ try {
529
+ if (rule) {
530
+ recorded += emitMappedSpans(collector, hook, rule, hookInput, sessionId);
531
+ } else {
532
+ recorded += emitGenericFallback(collector, hook, sessionId);
533
+ fallback = true;
534
+ }
535
+ recorded += emitHumanEvents(collector, hook, hookInput, sessionId);
536
+ } finally {
537
+ sink.close();
538
+ }
539
+ return { recorded, fallback };
540
+ }
541
+ async function handleRecordHook(hookName, options, _parentOpts) {
542
+ const hook = hookName ?? "unknown";
543
+ const dbPath = options.db ?? DEFAULT_DB_PATH;
544
+ const mappingConfigPath = options.mappingConfig ?? defaultMappingConfigPath(dbPath);
545
+ let result = { recorded: 0, fallback: false };
546
+ try {
547
+ const hookInput = parseHookInput(await readStdin());
548
+ result = await runRecordHook({ hook, hookInput, dbPath, mappingConfigPath });
549
+ } catch (err) {
550
+ process.stderr.write(
551
+ `record-hook: ${err instanceof Error ? err.message : String(err)}
552
+ `
553
+ );
554
+ }
555
+ process.stdout.write(JSON.stringify(result) + "\n");
556
+ }
557
+
327
558
  // src/cli/query.ts
328
559
  function bigIntReplacer(_key, value) {
329
560
  return typeof value === "bigint" ? value.toString() : value;
@@ -412,11 +643,12 @@ async function handleQuery(options, _parentOpts) {
412
643
  // src/cli/handlers.ts
413
644
  var handlers = {
414
645
  record: handleRecord,
646
+ recordHook: handleRecordHook,
415
647
  query: handleQuery
416
648
  };
417
649
 
418
650
  // src/cli/index.ts
419
- var __dirname = dirname(fileURLToPath(import.meta.url));
651
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
420
652
  var pkg = JSON.parse(
421
653
  readFileSync(resolve(__dirname, "../../package.json"), "utf8")
422
654
  );