@agimon-ai/doompi-hook 0.0.1-alpha.85 → 0.0.1-alpha.87

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.
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_hooks = require("../hooks-CReMXrU9.cjs");
5
+ const require_hooks = require("../hooks-BeDKiWFr.cjs");
6
6
  let _agimon_ai_doompi_core_piExtension = require("@agimon-ai/doompi-core/piExtension");
7
7
  let _agimon_ai_doompi_core_extensionFile = require("@agimon-ai/doompi-core/extensionFile");
8
8
  let _agimon_ai_doompi_config_piContext = require("@agimon-ai/doompi-config/piContext");
@@ -1,4 +1,4 @@
1
- import { o as HookExtensionOptions } from "../index-LpLV04fM.mjs";
1
+ import { o as HookExtensionOptions } from "../index-CcfteHKn.mjs";
2
2
  //#region generated/pi.d.ts
3
3
  export declare const extension: import("@agimon-ai/doompi-core/runtimePiExtension").DefinedPiExtension<HookExtensionOptions>;
4
4
  //#endregion
@@ -1,4 +1,4 @@
1
- import { _ as failuresFrom, a as sessionHookPayload, b as toolResultMessages, g as decisionsFrom, h as decisionReason, i as createBashHookRunner, m as additionalContextsFrom, n as selectPluginHooks, o as toolHookPayload, r as createHookTelemetry, s as createHookDocumentReader, t as HOOK_EVENT, u as selectRegistryHooks, v as hookFailureMessage, y as isDenied } from "../hooks-D2s_5hzZ.mjs";
1
+ import { _ as failuresFrom, a as sessionHookPayload, b as toolResultMessages, g as decisionsFrom, h as decisionReason, i as createBashHookRunner, m as additionalContextsFrom, n as selectPluginHooks, o as toolHookPayload, r as createHookTelemetry, s as createHookDocumentReader, t as HOOK_EVENT, u as selectRegistryHooks, v as hookFailureMessage, y as isDenied } from "../hooks-GyYjEZKZ.mjs";
2
2
  import { definePiExtension } from "@agimon-ai/doompi-core/piExtension";
3
3
  import { composeRootHooks, defineCliHook, defineResource, defineRoot } from "@agimon-ai/doompi-core/extensionFile";
4
4
  import { requireDoomConfigContext } from "@agimon-ai/doompi-config/piContext";
@@ -344,6 +344,7 @@ function createBashHookRunner(options = {}) {
344
344
  let timedOut = false;
345
345
  let childExited = false;
346
346
  let timeoutExitCode = null;
347
+ let stdinError;
347
348
  let escalationTimer;
348
349
  const signal = signalOwnedProcess(child.pid, (value) => child.kill(value));
349
350
  const ownedProcessIsAlive = () => {
@@ -383,6 +384,10 @@ function createBashHookRunner(options = {}) {
383
384
  finishTimeout();
384
385
  }, HOOK_TERMINATION_GRACE_MS);
385
386
  }, timeoutSeconds * MILLISECONDS_PER_SECOND);
387
+ child.stdin?.on("error", (error) => {
388
+ if (settled || timedOut || errorCode(error) === "EPIPE") return;
389
+ stdinError = error;
390
+ });
386
391
  child.stdout?.on("data", (chunk) => {
387
392
  stdout += chunk.toString();
388
393
  });
@@ -417,6 +422,16 @@ function createBashHookRunner(options = {}) {
417
422
  } });
418
423
  return;
419
424
  }
425
+ if (stdinError) {
426
+ telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, stdinError, { "hook.reason": "stdin_failed" });
427
+ warn(`[pi-hook] advisory hook could not write stdin: ${stdinError.message}\n`);
428
+ finish({ failure: {
429
+ command: hook.command,
430
+ message: stdinError.message,
431
+ reason: "stdin_failed"
432
+ } });
433
+ return;
434
+ }
420
435
  const jsonLine = stdout.trim().split(LINE_BREAK).reverse().find((line) => line.startsWith("{"));
421
436
  if (!jsonLine) {
422
437
  finish();
@@ -612,4 +627,4 @@ Object.defineProperty(exports, "toolResultMessages", {
612
627
  }
613
628
  });
614
629
 
615
- //# sourceMappingURL=hooks-CReMXrU9.cjs.map
630
+ //# sourceMappingURL=hooks-BeDKiWFr.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-BeDKiWFr.cjs","names":["fs","parseYaml","doomConfigCandidates","spawnProcess","createDoomTelemetry"],"sources":["../src/constants/hookDecisions.ts","../src/services/hookDecisions/index.ts","../src/constants/hookDocuments.ts","../src/constants/telemetry.ts","../src/services/toolNames/index.ts","../src/services/hookRegistry/index.ts","../src/services/hookDocuments/index.ts","../src/constants/hookPayload.ts","../src/services/hookPayload/index.ts","../src/constants/hookRunner.ts","../src/services/hookRunner/index.ts","../src/constants/hookTelemetry.ts","../src/services/hookTelemetry/index.ts","../src/services/pluginHooks/index.ts","../src/constants/hooks.ts"],"sourcesContent":["export const BLOCK_DECISION = 'block';\nexport const DENY_PERMISSION = 'deny';\n","import { BLOCK_DECISION, DENY_PERMISSION } from '../../constants/hookDecisions';\nimport type { HookDecision, HookFailure, HookOutcome } from '../../types/hooks';\n\nexport function decisionsFrom(outcomes: ReadonlyArray<HookOutcome>): HookDecision[] {\n return outcomes.flatMap((outcome) => (outcome.decision ? [outcome.decision] : []));\n}\n\nexport function failuresFrom(outcomes: ReadonlyArray<HookOutcome>): HookFailure[] {\n return outcomes.flatMap((outcome) => (outcome.failure ? [outcome.failure] : []));\n}\n\n/** Both spellings a hook may use to refuse the call it observed. */\nexport function isDenied(decision: HookDecision | undefined): boolean {\n return decision?.decision === BLOCK_DECISION || decision?.hookSpecificOutput?.permissionDecision === DENY_PERMISSION;\n}\n\nexport function decisionReason(decision: HookDecision | undefined): string | undefined {\n return decision?.reason ?? decision?.hookSpecificOutput?.reason;\n}\n\n/** Context a hook wants added to the conversation rather than used to block. */\nexport function additionalContextsFrom(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext)\n .filter((value): value is string => Boolean(value));\n}\n\n/** What a post-tool hook has to say, whichever field it said it in. */\nexport function toolResultMessages(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext ?? decisionReason(decision))\n .filter((value): value is string => Boolean(value));\n}\n\n/**\n * A guardrail that never ran looks exactly like one that passed, so the agent is\n * told which checks were missed and what its options are.\n */\nexport function hookFailureMessage(failures: ReadonlyArray<HookFailure>): string {\n return [\n 'One or more repository hooks did not complete, so their checks were not applied:',\n ...failures.map((failure) => `- ${failure.command}: ${failure.message}`),\n 'Options:',\n '- Inspect the hook command or configuration and rerun after correcting it.',\n '- Continue only if the missed advisory check is acceptable.',\n '- Ask the user before bypassing a hook that may enforce a guardrail.',\n ].join('\\n');\n}\n","export const HOOKS_FILE = 'hooks.yaml';\nexport const FILE_ENCODING = 'utf8';\nexport const FILE_NOT_FOUND_ERROR = 'ENOENT';\nexport const REGISTRY_SOURCE_ATTRIBUTE = 'repository';\n","export const HOOK_TELEMETRY_EVENT = {\n hookFailed: 'doom_pi_hook.failed',\n hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed',\n} as const;\n","/**\n * Pi's tool names translated into the Claude names hook matchers are written\n * against.\n *\n * Hook authors write `matcher: Bash`, and Pi reports `bash`, so a matcher would\n * never fire without this. Only this direction belongs here: rewriting Claude\n * names for Pi is an agent-definition concern the harness owns.\n */\nconst PI_TO_CLAUDE = new Map([\n ['read', 'Read'],\n ['edit', 'Edit'],\n ['write', 'Write'],\n ['bash', 'Bash'],\n ['find', 'Glob'],\n ['grep', 'Grep'],\n ['subagent', 'Agent'],\n]);\n\n/** The Claude name a hook matcher expects for a tool Pi just ran. */\nexport function toClaudeToolName(tool: string): string {\n return PI_TO_CLAUDE.get(tool) ?? tool;\n}\n\n/** Whether a matcher, if the row declares one, accepts the tool that fired. */\nexport function matchesTool(matcher: string | undefined, toolName: string | undefined): boolean {\n if (!toolName || !matcher) return true;\n return new RegExp(matcher).test(toClaudeToolName(toolName));\n}\n","import type { HookDocumentSource, ParsedRegistrySource, RegistryEntry, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/** Which rows a dispatch keeps, once the session's selection is known. */\nexport interface RegistrySelection {\n event: string;\n toolName?: string;\n /** Selected group ids, or undefined for \"every group\", the standalone default. */\n allowedGroups?: readonly string[];\n inSubagent: boolean;\n}\n\n/**\n * Identity of a set of registry sources, for callers that cache the parse.\n *\n * Keyed on contents rather than mtime, so it stays correct for an in-place\n * rewrite and needs no invalidation hook. Reading both files is cheap; parsing\n * is what a cache built on this skips.\n */\nexport function registryCacheKey(sources: ReadonlyArray<HookDocumentSource>): string {\n return sources.map((source) => `${source.baseDirectory} ${source.text}`).join(' ');\n}\n\n/**\n * Flattens parsed registry documents into sorted rows, keeping only bindings\n * that declare a `pi` frontend.\n *\n * A later source replaces the group of the same id outright rather than merging\n * into it, so a repository that redefines a group owns it completely.\n */\nexport function registryEntries(sources: ReadonlyArray<ParsedRegistrySource>): RegistryEntry[] {\n const groups = new Map<string, RegistryEntry[]>();\n let position = 0;\n for (const source of sources) {\n for (const [groupId, group] of Object.entries(source.document.groups ?? {})) {\n const entries: RegistryEntry[] = [];\n for (const hook of group.hooks ?? []) {\n // Every declared hook advances the position, whether or not it is kept,\n // so the sort tiebreaker stays the order the file declares them in.\n position += 1;\n if (!hook.pi) continue;\n entries.push({\n ...hook.pi,\n event: hook.event,\n order: hook.pi.order ?? 0,\n position,\n groupId,\n core: group.core === true,\n baseDirectory: source.baseDirectory,\n });\n }\n groups.set(groupId, entries);\n }\n }\n\n const entries = [...groups.values()].flat();\n entries.sort((left, right) => left.order - right.order || left.position - right.position);\n return entries;\n}\n\n/**\n * The rows one dispatch runs.\n *\n * Group selection changes when /mode switches mid-session, so inclusion is\n * applied per call rather than being baked into a cached parse. Core groups\n * always load; the rest are gated by the layers the harness resolved.\n */\nexport function selectRegistryHooks(\n entries: ReadonlyArray<RegistryEntry>,\n selection: RegistrySelection,\n): ResolvedHook[] {\n const allowed = selection.allowedGroups === undefined ? undefined : new Set(selection.allowedGroups);\n return entries\n .filter((entry) => entry.core || !allowed || allowed.has(entry.groupId))\n .filter((entry) => entry.event === selection.event)\n .filter((entry) => !(entry.skipInSubagent && selection.inSubagent))\n .filter((entry) => matchesTool(entry.matcher, selection.toolName))\n .map((entry) => ({\n hook: { command: entry.command, timeout: entry.timeout },\n // The declaring config's root, so a global hook can reach its own scripts\n // through CLAUDE_PLUGIN_ROOT while still running against this repository.\n root: entry.baseDirectory,\n }));\n}\n","import fs from 'node:fs';\n\nimport { doomConfigCandidates } from '@agimon-ai/doompi-config/layeredConfig';\nimport { parse as parseYaml } from 'yaml';\n\nimport {\n HOOKS_FILE,\n FILE_ENCODING,\n FILE_NOT_FOUND_ERROR,\n REGISTRY_SOURCE_ATTRIBUTE,\n} from '../../constants/hookDocuments';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookDocumentReader,\n HookDocumentSource,\n PluginDocumentRead,\n PluginHookConfig,\n PluginHookDocument,\n PluginHookSourceRef,\n RegistryDocument,\n RegistryEntry,\n RegistryRead,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\nimport { registryCacheKey, registryEntries } from '../hookRegistry';\n\nexport interface HookDocumentReaderOptions {\n telemetry?: HookTelemetry;\n /** Where the global `.doom` directory lives. Defaults to the user's home. */\n homeDirectory?: string;\n readFile?: (filePath: string) => Promise<string>;\n warn?: (message: string) => void;\n}\n\nfunction reason(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction isMissingFile(error: unknown): boolean {\n return (error as NodeJS.ErrnoException | undefined)?.code === FILE_NOT_FOUND_ERROR;\n}\n\n/**\n * Reads `.doom/hooks.yaml` from the global config directory and the repository,\n * and the plugin hook configs the harness resolved.\n *\n * Every file is re-read on every dispatch: it costs almost nothing and keeps an\n * in-place rewrite visible, which an mtime check would miss when two writes land\n * inside the same filesystem timestamp granularity. Both caches are keyed on\n * contents instead, so parsing — the expensive half — is what they skip.\n */\nexport function createHookDocumentReader(options: HookDocumentReaderOptions = {}): HookDocumentReader {\n const readFile = options.readFile ?? ((filePath: string) => fs.promises.readFile(filePath, FILE_ENCODING));\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n let cachedRegistryKey: string | undefined;\n let cachedRegistryEntries: RegistryEntry[] = [];\n const pluginConfigCache = new Map<string, { source: string; config: PluginHookConfig }>();\n\n const readSource = async (candidate: {\n filePath: string;\n baseDirectory: string;\n }): Promise<HookDocumentSource | undefined> => {\n try {\n return { baseDirectory: candidate.baseDirectory, text: await readFile(candidate.filePath) };\n } catch (error) {\n if (isMissingFile(error)) return undefined;\n throw error;\n }\n };\n\n const parseSources = (sources: ReadonlyArray<HookDocumentSource>): RegistryEntry[] => {\n const key = registryCacheKey(sources);\n if (cachedRegistryKey === key) return cachedRegistryEntries;\n cachedRegistryEntries = registryEntries(\n sources.map((source) => ({\n baseDirectory: source.baseDirectory,\n document: (parseYaml(source.text) ?? {}) as RegistryDocument,\n })),\n );\n cachedRegistryKey = key;\n return cachedRegistryEntries;\n };\n\n return {\n async registry(repoRoot: string): Promise<RegistryRead> {\n const candidates = doomConfigCandidates(HOOKS_FILE, repoRoot, options.homeDirectory);\n // Named for the error path only. A registry absent from both locations is\n // a repository with no hooks, not a failure, so only a file that exists\n // and cannot be read or parsed reaches the catch below.\n const registryPath = candidates.map((candidate) => candidate.filePath).join(' and ');\n try {\n const sources = (await Promise.all(candidates.map(readSource))).filter(\n (source): source is HookDocumentSource => source !== undefined,\n );\n return { entries: parseSources(sources) };\n } catch (error) {\n // This empties the registry for the whole dispatch, so every repository\n // hook silently stops running rather than any one of them failing.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookRegistryReadFailed, error, {\n 'hook.registry.source': REGISTRY_SOURCE_ATTRIBUTE,\n });\n warn(`[pi-hook] could not read ${registryPath}: ${reason(error)}\\n`);\n return { entries: [], failure: { command: registryPath, message: reason(error), reason: 'registry_read' } };\n }\n },\n\n async plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead> {\n const documents: PluginHookDocument[] = [];\n const failures: PluginDocumentRead['failures'] = [];\n for (const source of sources) {\n try {\n const text = await readFile(source.configPath);\n const cached = pluginConfigCache.get(source.configPath);\n const config = cached?.source === text ? cached.config : (JSON.parse(text) as PluginHookConfig);\n pluginConfigCache.set(source.configPath, { source: text, config });\n documents.push({ pluginRoot: source.pluginRoot, config });\n } catch (error) {\n failures.push({ command: source.configPath, message: reason(error), reason: 'plugin_config' });\n }\n }\n return { documents, failures };\n },\n };\n}\n","export const TOOL_RESULT_EVENT = 'tool_result';\n","import { TOOL_RESULT_EVENT } from '../../constants/hookPayload';\nimport type { HookPayload, HookToolEvent } from '../../types/hooks';\nimport { toClaudeToolName } from '../toolNames';\n\n/** The payload shape a Claude Code session hook is written against. */\nexport function toolHookPayload(\n event: HookToolEvent,\n hookEventName: string,\n repoRoot: string,\n sessionId: string,\n): HookPayload {\n return {\n session_id: sessionId,\n transcript_path: '',\n cwd: repoRoot,\n hook_event_name: hookEventName,\n tool_name: toClaudeToolName(event.toolName),\n tool_input: event.input,\n ...(event.type === TOOL_RESULT_EVENT ? { tool_response: { success: !event.isError, content: event.content } } : {}),\n };\n}\n\n/** Session lifecycle hooks observe no tool, so they only need where and who. */\nexport function sessionHookPayload(sessionId: string, repoRoot: string): HookPayload {\n return { session_id: sessionId, cwd: repoRoot };\n}\n","export const HOOK_SHELL = '/bin/bash';\nexport const HOOK_SHELL_COMMAND_FLAG = '-c';\nexport const HOOK_TERMINATION_GRACE_MS = 2_000;\nexport const DEFAULT_HOOK_TIMEOUT_SECONDS = 10;\nexport const MILLISECONDS_PER_SECOND = 1_000;\nexport const PROCESS_NOT_FOUND_ERROR = 'ESRCH';\nexport const BROKEN_PIPE_ERROR = 'EPIPE';\nexport const WINDOWS_PLATFORM = 'win32';\nexport const UNKNOWN_EXIT_CODE = -1;\nexport const JSON_LINE_START = '{';\nexport const LINE_BREAK = /\\r?\\n/;\n","import { spawn as spawnProcess } from 'node:child_process';\n\nimport {\n HOOK_SHELL,\n HOOK_SHELL_COMMAND_FLAG,\n HOOK_TERMINATION_GRACE_MS,\n DEFAULT_HOOK_TIMEOUT_SECONDS,\n MILLISECONDS_PER_SECOND,\n PROCESS_NOT_FOUND_ERROR,\n BROKEN_PIPE_ERROR,\n WINDOWS_PLATFORM,\n UNKNOWN_EXIT_CODE,\n JSON_LINE_START,\n LINE_BREAK,\n} from '../../constants/hookRunner';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookCommand,\n HookDecision,\n HookOutcome,\n HookPayload,\n HookRunOptions,\n HookRunner,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\n\nexport interface BashHookRunnerOptions {\n telemetry?: HookTelemetry;\n env?: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n spawn?: typeof spawnProcess;\n warn?: (message: string) => void;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return error instanceof Error && 'code' in error ? String(error.code) : undefined;\n}\n\n/**\n * Runs advisory hook commands through bash and reports what each one decided.\n *\n * A hook is a command the repository owns, so it is spawned in its own process\n * group and terminated as one: a hook that starts a server and stalls should\n * not leave the server behind when the timeout fires.\n */\nexport function createBashHookRunner(options: BashHookRunnerOptions = {}): HookRunner {\n const spawn = options.spawn ?? spawnProcess;\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n const useProcessGroup = (options.platform ?? process.platform) !== WINDOWS_PLATFORM;\n\n const signalOwnedProcess = (\n pid: number | undefined,\n kill: (signal: NodeJS.Signals) => void,\n ): ((signal: NodeJS.Signals) => void) => {\n return (signal) => {\n try {\n if (useProcessGroup && pid !== undefined) {\n process.kill(-pid, signal);\n return;\n }\n kill(signal);\n } catch (error) {\n if (errorCode(error) !== PROCESS_NOT_FOUND_ERROR) {\n process.emitWarning(`Could not signal repository hook process: ${String(error)}`);\n }\n }\n };\n };\n\n return {\n run(hook: HookCommand, payload: HookPayload, runOptions: HookRunOptions): Promise<HookOutcome> {\n const timeoutSeconds = hook.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS;\n const environment = options.env ?? process.env;\n return new Promise((resolve) => {\n // -c, not -lc. Hooks run on every tool call, and a login shell would\n // source the developer's profile each time: per-call latency plus hook\n // behavior that varies with their dotfiles. The child inherits the\n // environment below, so it keeps the PATH the launcher started with.\n const child = spawn(HOOK_SHELL, [HOOK_SHELL_COMMAND_FLAG, hook.command], {\n cwd: runOptions.repoRoot,\n env: {\n ...environment,\n CLAUDE_PROJECT_DIR: runOptions.repoRoot,\n CODEX_REPO_ROOT: runOptions.repoRoot,\n ORIGINAL_REPO_PATH: environment.ORIGINAL_REPO_PATH ?? runOptions.repoRoot,\n ...(runOptions.pluginRoot ? { CLAUDE_PLUGIN_ROOT: runOptions.pluginRoot } : {}),\n },\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: useProcessGroup,\n });\n let stdout = '';\n let stderr = '';\n let settled = false;\n let timedOut = false;\n let childExited = false;\n let timeoutExitCode: number | null = null;\n let stdinError: Error | undefined;\n let escalationTimer: NodeJS.Timeout | undefined;\n\n const signal = signalOwnedProcess(child.pid, (value) => child.kill(value));\n\n const ownedProcessIsAlive = (): boolean => {\n if (!useProcessGroup || child.pid === undefined) return !childExited;\n try {\n process.kill(-child.pid, 0);\n return true;\n } catch (error) {\n return errorCode(error) !== PROCESS_NOT_FOUND_ERROR;\n }\n };\n\n const finish = (outcome: HookOutcome = {}): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (escalationTimer) clearTimeout(escalationTimer);\n resolve(outcome);\n };\n\n const finishTimeout = (): void => {\n const message = `Hook timed out after ${timeoutSeconds} seconds.`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'timeout',\n 'hook.exit_code': timeoutExitCode ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook timed out: ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'timeout' } });\n };\n\n const timer = setTimeout(() => {\n timedOut = true;\n signal('SIGTERM');\n escalationTimer = setTimeout(() => {\n if (ownedProcessIsAlive()) signal('SIGKILL');\n finishTimeout();\n }, HOOK_TERMINATION_GRACE_MS);\n }, timeoutSeconds * MILLISECONDS_PER_SECOND);\n\n child.stdin?.on('error', (error: Error) => {\n // Hooks may exit or close stdin without consuming the entire payload.\n // Keep this listener through settlement: a pending write can fail later.\n if (settled || timedOut || errorCode(error) === BROKEN_PIPE_ERROR) return;\n stdinError = error;\n });\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.once('error', (error: Error) => {\n if (timedOut) return;\n // A guardrail that never ran is indistinguishable from one that\n // passed, which is the whole reason these are worth reporting.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'spawn_failed' });\n warn(`[pi-hook] advisory hook failed: ${error.message}\\n`);\n finish({ failure: { command: hook.command, message: error.message, reason: 'spawn_failed' } });\n });\n child.once('exit', (code: number | null) => {\n childExited = true;\n timeoutExitCode = code;\n if (timedOut) return;\n if (code !== 0) {\n const message = stderr.trim() || `Advisory hook exited with code ${code ?? 'unknown'}`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'non_zero_exit',\n 'hook.exit_code': code ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook failed (${code}): ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'non_zero_exit' } });\n return;\n }\n if (stdinError) {\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, stdinError, { 'hook.reason': 'stdin_failed' });\n warn(`[pi-hook] advisory hook could not write stdin: ${stdinError.message}\\n`);\n finish({ failure: { command: hook.command, message: stdinError.message, reason: 'stdin_failed' } });\n return;\n }\n const jsonLine = stdout\n .trim()\n .split(LINE_BREAK)\n .reverse()\n .find((line) => line.startsWith(JSON_LINE_START));\n if (!jsonLine) {\n finish();\n return;\n }\n try {\n finish({ decision: JSON.parse(jsonLine) as HookDecision });\n } catch (error) {\n // The hook ran and had an opinion, and it was dropped.\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'invalid_json' });\n warn(`[pi-hook] advisory hook returned invalid JSON: ${hook.command}\\n`);\n finish({\n failure: {\n command: hook.command,\n message: error instanceof Error ? error.message : String(error),\n reason: 'invalid_json',\n },\n });\n }\n });\n child.stdin?.end(JSON.stringify(payload));\n });\n },\n };\n}\n","export const SERVICE_NAME = 'doom-pi-hook';\nexport const PACKAGE_NAME = '@agimon-ai/doompi-hook';\n","import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nimport { SERVICE_NAME, PACKAGE_NAME } from '../../constants/hookTelemetry';\nimport type { HookTelemetry } from '../../types/telemetry';\n\nexport interface HookTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport function createHookTelemetry(options: HookTelemetryOptions = {}): HookTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n };\n}\n","import type { PluginHookDocument, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/**\n * The plugin hooks one dispatch runs.\n *\n * Plugin configs are the Claude Code `hooks.json` shape verbatim: an event name\n * maps to groups, each with an optional matcher and a list of commands. Order\n * follows the declaration order of the plugins the harness resolved.\n */\nexport function selectPluginHooks(\n documents: ReadonlyArray<PluginHookDocument>,\n eventName: string,\n toolName?: string,\n): ResolvedHook[] {\n const matches: ResolvedHook[] = [];\n for (const document of documents) {\n for (const group of document.config.hooks?.[eventName] ?? []) {\n if (!matchesTool(group.matcher, toolName)) continue;\n for (const hook of group.hooks ?? []) matches.push({ hook, root: document.pluginRoot });\n }\n }\n return matches;\n}\n","export const HOOK_EVENT = {\n sessionStart: 'SessionStart',\n preToolUse: 'PreToolUse',\n postToolUse: 'PostToolUse',\n stop: 'Stop',\n sessionEnd: 'SessionEnd',\n} as const;\n"],"mappings":"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"}
@@ -321,6 +321,7 @@ function createBashHookRunner(options = {}) {
321
321
  let timedOut = false;
322
322
  let childExited = false;
323
323
  let timeoutExitCode = null;
324
+ let stdinError;
324
325
  let escalationTimer;
325
326
  const signal = signalOwnedProcess(child.pid, (value) => child.kill(value));
326
327
  const ownedProcessIsAlive = () => {
@@ -360,6 +361,10 @@ function createBashHookRunner(options = {}) {
360
361
  finishTimeout();
361
362
  }, HOOK_TERMINATION_GRACE_MS);
362
363
  }, timeoutSeconds * MILLISECONDS_PER_SECOND);
364
+ child.stdin?.on("error", (error) => {
365
+ if (settled || timedOut || errorCode(error) === "EPIPE") return;
366
+ stdinError = error;
367
+ });
363
368
  child.stdout?.on("data", (chunk) => {
364
369
  stdout += chunk.toString();
365
370
  });
@@ -394,6 +399,16 @@ function createBashHookRunner(options = {}) {
394
399
  } });
395
400
  return;
396
401
  }
402
+ if (stdinError) {
403
+ telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, stdinError, { "hook.reason": "stdin_failed" });
404
+ warn(`[pi-hook] advisory hook could not write stdin: ${stdinError.message}\n`);
405
+ finish({ failure: {
406
+ command: hook.command,
407
+ message: stdinError.message,
408
+ reason: "stdin_failed"
409
+ } });
410
+ return;
411
+ }
397
412
  const jsonLine = stdout.trim().split(LINE_BREAK).reverse().find((line) => line.startsWith("{"));
398
413
  if (!jsonLine) {
399
414
  finish();
@@ -470,4 +485,4 @@ const HOOK_EVENT = {
470
485
  //#endregion
471
486
  export { failuresFrom as _, sessionHookPayload as a, toolResultMessages as b, registryCacheKey as c, matchesTool as d, toClaudeToolName as f, decisionsFrom as g, decisionReason as h, createBashHookRunner as i, registryEntries as l, additionalContextsFrom as m, selectPluginHooks as n, toolHookPayload as o, HOOK_TELEMETRY_EVENT as p, createHookTelemetry as r, createHookDocumentReader as s, HOOK_EVENT as t, selectRegistryHooks as u, hookFailureMessage as v, isDenied as y };
472
487
 
473
- //# sourceMappingURL=hooks-D2s_5hzZ.mjs.map
488
+ //# sourceMappingURL=hooks-GyYjEZKZ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-GyYjEZKZ.mjs","names":["parseYaml","spawn","spawnProcess"],"sources":["../src/constants/hookDecisions.ts","../src/services/hookDecisions/index.ts","../src/constants/hookDocuments.ts","../src/constants/telemetry.ts","../src/services/toolNames/index.ts","../src/services/hookRegistry/index.ts","../src/services/hookDocuments/index.ts","../src/constants/hookPayload.ts","../src/services/hookPayload/index.ts","../src/constants/hookRunner.ts","../src/services/hookRunner/index.ts","../src/constants/hookTelemetry.ts","../src/services/hookTelemetry/index.ts","../src/services/pluginHooks/index.ts","../src/constants/hooks.ts"],"sourcesContent":["export const BLOCK_DECISION = 'block';\nexport const DENY_PERMISSION = 'deny';\n","import { BLOCK_DECISION, DENY_PERMISSION } from '../../constants/hookDecisions';\nimport type { HookDecision, HookFailure, HookOutcome } from '../../types/hooks';\n\nexport function decisionsFrom(outcomes: ReadonlyArray<HookOutcome>): HookDecision[] {\n return outcomes.flatMap((outcome) => (outcome.decision ? [outcome.decision] : []));\n}\n\nexport function failuresFrom(outcomes: ReadonlyArray<HookOutcome>): HookFailure[] {\n return outcomes.flatMap((outcome) => (outcome.failure ? [outcome.failure] : []));\n}\n\n/** Both spellings a hook may use to refuse the call it observed. */\nexport function isDenied(decision: HookDecision | undefined): boolean {\n return decision?.decision === BLOCK_DECISION || decision?.hookSpecificOutput?.permissionDecision === DENY_PERMISSION;\n}\n\nexport function decisionReason(decision: HookDecision | undefined): string | undefined {\n return decision?.reason ?? decision?.hookSpecificOutput?.reason;\n}\n\n/** Context a hook wants added to the conversation rather than used to block. */\nexport function additionalContextsFrom(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext)\n .filter((value): value is string => Boolean(value));\n}\n\n/** What a post-tool hook has to say, whichever field it said it in. */\nexport function toolResultMessages(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext ?? decisionReason(decision))\n .filter((value): value is string => Boolean(value));\n}\n\n/**\n * A guardrail that never ran looks exactly like one that passed, so the agent is\n * told which checks were missed and what its options are.\n */\nexport function hookFailureMessage(failures: ReadonlyArray<HookFailure>): string {\n return [\n 'One or more repository hooks did not complete, so their checks were not applied:',\n ...failures.map((failure) => `- ${failure.command}: ${failure.message}`),\n 'Options:',\n '- Inspect the hook command or configuration and rerun after correcting it.',\n '- Continue only if the missed advisory check is acceptable.',\n '- Ask the user before bypassing a hook that may enforce a guardrail.',\n ].join('\\n');\n}\n","export const HOOKS_FILE = 'hooks.yaml';\nexport const FILE_ENCODING = 'utf8';\nexport const FILE_NOT_FOUND_ERROR = 'ENOENT';\nexport const REGISTRY_SOURCE_ATTRIBUTE = 'repository';\n","export const HOOK_TELEMETRY_EVENT = {\n hookFailed: 'doom_pi_hook.failed',\n hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed',\n} as const;\n","/**\n * Pi's tool names translated into the Claude names hook matchers are written\n * against.\n *\n * Hook authors write `matcher: Bash`, and Pi reports `bash`, so a matcher would\n * never fire without this. Only this direction belongs here: rewriting Claude\n * names for Pi is an agent-definition concern the harness owns.\n */\nconst PI_TO_CLAUDE = new Map([\n ['read', 'Read'],\n ['edit', 'Edit'],\n ['write', 'Write'],\n ['bash', 'Bash'],\n ['find', 'Glob'],\n ['grep', 'Grep'],\n ['subagent', 'Agent'],\n]);\n\n/** The Claude name a hook matcher expects for a tool Pi just ran. */\nexport function toClaudeToolName(tool: string): string {\n return PI_TO_CLAUDE.get(tool) ?? tool;\n}\n\n/** Whether a matcher, if the row declares one, accepts the tool that fired. */\nexport function matchesTool(matcher: string | undefined, toolName: string | undefined): boolean {\n if (!toolName || !matcher) return true;\n return new RegExp(matcher).test(toClaudeToolName(toolName));\n}\n","import type { HookDocumentSource, ParsedRegistrySource, RegistryEntry, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/** Which rows a dispatch keeps, once the session's selection is known. */\nexport interface RegistrySelection {\n event: string;\n toolName?: string;\n /** Selected group ids, or undefined for \"every group\", the standalone default. */\n allowedGroups?: readonly string[];\n inSubagent: boolean;\n}\n\n/**\n * Identity of a set of registry sources, for callers that cache the parse.\n *\n * Keyed on contents rather than mtime, so it stays correct for an in-place\n * rewrite and needs no invalidation hook. Reading both files is cheap; parsing\n * is what a cache built on this skips.\n */\nexport function registryCacheKey(sources: ReadonlyArray<HookDocumentSource>): string {\n return sources.map((source) => `${source.baseDirectory} ${source.text}`).join(' ');\n}\n\n/**\n * Flattens parsed registry documents into sorted rows, keeping only bindings\n * that declare a `pi` frontend.\n *\n * A later source replaces the group of the same id outright rather than merging\n * into it, so a repository that redefines a group owns it completely.\n */\nexport function registryEntries(sources: ReadonlyArray<ParsedRegistrySource>): RegistryEntry[] {\n const groups = new Map<string, RegistryEntry[]>();\n let position = 0;\n for (const source of sources) {\n for (const [groupId, group] of Object.entries(source.document.groups ?? {})) {\n const entries: RegistryEntry[] = [];\n for (const hook of group.hooks ?? []) {\n // Every declared hook advances the position, whether or not it is kept,\n // so the sort tiebreaker stays the order the file declares them in.\n position += 1;\n if (!hook.pi) continue;\n entries.push({\n ...hook.pi,\n event: hook.event,\n order: hook.pi.order ?? 0,\n position,\n groupId,\n core: group.core === true,\n baseDirectory: source.baseDirectory,\n });\n }\n groups.set(groupId, entries);\n }\n }\n\n const entries = [...groups.values()].flat();\n entries.sort((left, right) => left.order - right.order || left.position - right.position);\n return entries;\n}\n\n/**\n * The rows one dispatch runs.\n *\n * Group selection changes when /mode switches mid-session, so inclusion is\n * applied per call rather than being baked into a cached parse. Core groups\n * always load; the rest are gated by the layers the harness resolved.\n */\nexport function selectRegistryHooks(\n entries: ReadonlyArray<RegistryEntry>,\n selection: RegistrySelection,\n): ResolvedHook[] {\n const allowed = selection.allowedGroups === undefined ? undefined : new Set(selection.allowedGroups);\n return entries\n .filter((entry) => entry.core || !allowed || allowed.has(entry.groupId))\n .filter((entry) => entry.event === selection.event)\n .filter((entry) => !(entry.skipInSubagent && selection.inSubagent))\n .filter((entry) => matchesTool(entry.matcher, selection.toolName))\n .map((entry) => ({\n hook: { command: entry.command, timeout: entry.timeout },\n // The declaring config's root, so a global hook can reach its own scripts\n // through CLAUDE_PLUGIN_ROOT while still running against this repository.\n root: entry.baseDirectory,\n }));\n}\n","import fs from 'node:fs';\n\nimport { doomConfigCandidates } from '@agimon-ai/doompi-config/layeredConfig';\nimport { parse as parseYaml } from 'yaml';\n\nimport {\n HOOKS_FILE,\n FILE_ENCODING,\n FILE_NOT_FOUND_ERROR,\n REGISTRY_SOURCE_ATTRIBUTE,\n} from '../../constants/hookDocuments';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookDocumentReader,\n HookDocumentSource,\n PluginDocumentRead,\n PluginHookConfig,\n PluginHookDocument,\n PluginHookSourceRef,\n RegistryDocument,\n RegistryEntry,\n RegistryRead,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\nimport { registryCacheKey, registryEntries } from '../hookRegistry';\n\nexport interface HookDocumentReaderOptions {\n telemetry?: HookTelemetry;\n /** Where the global `.doom` directory lives. Defaults to the user's home. */\n homeDirectory?: string;\n readFile?: (filePath: string) => Promise<string>;\n warn?: (message: string) => void;\n}\n\nfunction reason(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction isMissingFile(error: unknown): boolean {\n return (error as NodeJS.ErrnoException | undefined)?.code === FILE_NOT_FOUND_ERROR;\n}\n\n/**\n * Reads `.doom/hooks.yaml` from the global config directory and the repository,\n * and the plugin hook configs the harness resolved.\n *\n * Every file is re-read on every dispatch: it costs almost nothing and keeps an\n * in-place rewrite visible, which an mtime check would miss when two writes land\n * inside the same filesystem timestamp granularity. Both caches are keyed on\n * contents instead, so parsing — the expensive half — is what they skip.\n */\nexport function createHookDocumentReader(options: HookDocumentReaderOptions = {}): HookDocumentReader {\n const readFile = options.readFile ?? ((filePath: string) => fs.promises.readFile(filePath, FILE_ENCODING));\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n let cachedRegistryKey: string | undefined;\n let cachedRegistryEntries: RegistryEntry[] = [];\n const pluginConfigCache = new Map<string, { source: string; config: PluginHookConfig }>();\n\n const readSource = async (candidate: {\n filePath: string;\n baseDirectory: string;\n }): Promise<HookDocumentSource | undefined> => {\n try {\n return { baseDirectory: candidate.baseDirectory, text: await readFile(candidate.filePath) };\n } catch (error) {\n if (isMissingFile(error)) return undefined;\n throw error;\n }\n };\n\n const parseSources = (sources: ReadonlyArray<HookDocumentSource>): RegistryEntry[] => {\n const key = registryCacheKey(sources);\n if (cachedRegistryKey === key) return cachedRegistryEntries;\n cachedRegistryEntries = registryEntries(\n sources.map((source) => ({\n baseDirectory: source.baseDirectory,\n document: (parseYaml(source.text) ?? {}) as RegistryDocument,\n })),\n );\n cachedRegistryKey = key;\n return cachedRegistryEntries;\n };\n\n return {\n async registry(repoRoot: string): Promise<RegistryRead> {\n const candidates = doomConfigCandidates(HOOKS_FILE, repoRoot, options.homeDirectory);\n // Named for the error path only. A registry absent from both locations is\n // a repository with no hooks, not a failure, so only a file that exists\n // and cannot be read or parsed reaches the catch below.\n const registryPath = candidates.map((candidate) => candidate.filePath).join(' and ');\n try {\n const sources = (await Promise.all(candidates.map(readSource))).filter(\n (source): source is HookDocumentSource => source !== undefined,\n );\n return { entries: parseSources(sources) };\n } catch (error) {\n // This empties the registry for the whole dispatch, so every repository\n // hook silently stops running rather than any one of them failing.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookRegistryReadFailed, error, {\n 'hook.registry.source': REGISTRY_SOURCE_ATTRIBUTE,\n });\n warn(`[pi-hook] could not read ${registryPath}: ${reason(error)}\\n`);\n return { entries: [], failure: { command: registryPath, message: reason(error), reason: 'registry_read' } };\n }\n },\n\n async plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead> {\n const documents: PluginHookDocument[] = [];\n const failures: PluginDocumentRead['failures'] = [];\n for (const source of sources) {\n try {\n const text = await readFile(source.configPath);\n const cached = pluginConfigCache.get(source.configPath);\n const config = cached?.source === text ? cached.config : (JSON.parse(text) as PluginHookConfig);\n pluginConfigCache.set(source.configPath, { source: text, config });\n documents.push({ pluginRoot: source.pluginRoot, config });\n } catch (error) {\n failures.push({ command: source.configPath, message: reason(error), reason: 'plugin_config' });\n }\n }\n return { documents, failures };\n },\n };\n}\n","export const TOOL_RESULT_EVENT = 'tool_result';\n","import { TOOL_RESULT_EVENT } from '../../constants/hookPayload';\nimport type { HookPayload, HookToolEvent } from '../../types/hooks';\nimport { toClaudeToolName } from '../toolNames';\n\n/** The payload shape a Claude Code session hook is written against. */\nexport function toolHookPayload(\n event: HookToolEvent,\n hookEventName: string,\n repoRoot: string,\n sessionId: string,\n): HookPayload {\n return {\n session_id: sessionId,\n transcript_path: '',\n cwd: repoRoot,\n hook_event_name: hookEventName,\n tool_name: toClaudeToolName(event.toolName),\n tool_input: event.input,\n ...(event.type === TOOL_RESULT_EVENT ? { tool_response: { success: !event.isError, content: event.content } } : {}),\n };\n}\n\n/** Session lifecycle hooks observe no tool, so they only need where and who. */\nexport function sessionHookPayload(sessionId: string, repoRoot: string): HookPayload {\n return { session_id: sessionId, cwd: repoRoot };\n}\n","export const HOOK_SHELL = '/bin/bash';\nexport const HOOK_SHELL_COMMAND_FLAG = '-c';\nexport const HOOK_TERMINATION_GRACE_MS = 2_000;\nexport const DEFAULT_HOOK_TIMEOUT_SECONDS = 10;\nexport const MILLISECONDS_PER_SECOND = 1_000;\nexport const PROCESS_NOT_FOUND_ERROR = 'ESRCH';\nexport const BROKEN_PIPE_ERROR = 'EPIPE';\nexport const WINDOWS_PLATFORM = 'win32';\nexport const UNKNOWN_EXIT_CODE = -1;\nexport const JSON_LINE_START = '{';\nexport const LINE_BREAK = /\\r?\\n/;\n","import { spawn as spawnProcess } from 'node:child_process';\n\nimport {\n HOOK_SHELL,\n HOOK_SHELL_COMMAND_FLAG,\n HOOK_TERMINATION_GRACE_MS,\n DEFAULT_HOOK_TIMEOUT_SECONDS,\n MILLISECONDS_PER_SECOND,\n PROCESS_NOT_FOUND_ERROR,\n BROKEN_PIPE_ERROR,\n WINDOWS_PLATFORM,\n UNKNOWN_EXIT_CODE,\n JSON_LINE_START,\n LINE_BREAK,\n} from '../../constants/hookRunner';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookCommand,\n HookDecision,\n HookOutcome,\n HookPayload,\n HookRunOptions,\n HookRunner,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\n\nexport interface BashHookRunnerOptions {\n telemetry?: HookTelemetry;\n env?: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n spawn?: typeof spawnProcess;\n warn?: (message: string) => void;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return error instanceof Error && 'code' in error ? String(error.code) : undefined;\n}\n\n/**\n * Runs advisory hook commands through bash and reports what each one decided.\n *\n * A hook is a command the repository owns, so it is spawned in its own process\n * group and terminated as one: a hook that starts a server and stalls should\n * not leave the server behind when the timeout fires.\n */\nexport function createBashHookRunner(options: BashHookRunnerOptions = {}): HookRunner {\n const spawn = options.spawn ?? spawnProcess;\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n const useProcessGroup = (options.platform ?? process.platform) !== WINDOWS_PLATFORM;\n\n const signalOwnedProcess = (\n pid: number | undefined,\n kill: (signal: NodeJS.Signals) => void,\n ): ((signal: NodeJS.Signals) => void) => {\n return (signal) => {\n try {\n if (useProcessGroup && pid !== undefined) {\n process.kill(-pid, signal);\n return;\n }\n kill(signal);\n } catch (error) {\n if (errorCode(error) !== PROCESS_NOT_FOUND_ERROR) {\n process.emitWarning(`Could not signal repository hook process: ${String(error)}`);\n }\n }\n };\n };\n\n return {\n run(hook: HookCommand, payload: HookPayload, runOptions: HookRunOptions): Promise<HookOutcome> {\n const timeoutSeconds = hook.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS;\n const environment = options.env ?? process.env;\n return new Promise((resolve) => {\n // -c, not -lc. Hooks run on every tool call, and a login shell would\n // source the developer's profile each time: per-call latency plus hook\n // behavior that varies with their dotfiles. The child inherits the\n // environment below, so it keeps the PATH the launcher started with.\n const child = spawn(HOOK_SHELL, [HOOK_SHELL_COMMAND_FLAG, hook.command], {\n cwd: runOptions.repoRoot,\n env: {\n ...environment,\n CLAUDE_PROJECT_DIR: runOptions.repoRoot,\n CODEX_REPO_ROOT: runOptions.repoRoot,\n ORIGINAL_REPO_PATH: environment.ORIGINAL_REPO_PATH ?? runOptions.repoRoot,\n ...(runOptions.pluginRoot ? { CLAUDE_PLUGIN_ROOT: runOptions.pluginRoot } : {}),\n },\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: useProcessGroup,\n });\n let stdout = '';\n let stderr = '';\n let settled = false;\n let timedOut = false;\n let childExited = false;\n let timeoutExitCode: number | null = null;\n let stdinError: Error | undefined;\n let escalationTimer: NodeJS.Timeout | undefined;\n\n const signal = signalOwnedProcess(child.pid, (value) => child.kill(value));\n\n const ownedProcessIsAlive = (): boolean => {\n if (!useProcessGroup || child.pid === undefined) return !childExited;\n try {\n process.kill(-child.pid, 0);\n return true;\n } catch (error) {\n return errorCode(error) !== PROCESS_NOT_FOUND_ERROR;\n }\n };\n\n const finish = (outcome: HookOutcome = {}): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (escalationTimer) clearTimeout(escalationTimer);\n resolve(outcome);\n };\n\n const finishTimeout = (): void => {\n const message = `Hook timed out after ${timeoutSeconds} seconds.`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'timeout',\n 'hook.exit_code': timeoutExitCode ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook timed out: ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'timeout' } });\n };\n\n const timer = setTimeout(() => {\n timedOut = true;\n signal('SIGTERM');\n escalationTimer = setTimeout(() => {\n if (ownedProcessIsAlive()) signal('SIGKILL');\n finishTimeout();\n }, HOOK_TERMINATION_GRACE_MS);\n }, timeoutSeconds * MILLISECONDS_PER_SECOND);\n\n child.stdin?.on('error', (error: Error) => {\n // Hooks may exit or close stdin without consuming the entire payload.\n // Keep this listener through settlement: a pending write can fail later.\n if (settled || timedOut || errorCode(error) === BROKEN_PIPE_ERROR) return;\n stdinError = error;\n });\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.once('error', (error: Error) => {\n if (timedOut) return;\n // A guardrail that never ran is indistinguishable from one that\n // passed, which is the whole reason these are worth reporting.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'spawn_failed' });\n warn(`[pi-hook] advisory hook failed: ${error.message}\\n`);\n finish({ failure: { command: hook.command, message: error.message, reason: 'spawn_failed' } });\n });\n child.once('exit', (code: number | null) => {\n childExited = true;\n timeoutExitCode = code;\n if (timedOut) return;\n if (code !== 0) {\n const message = stderr.trim() || `Advisory hook exited with code ${code ?? 'unknown'}`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'non_zero_exit',\n 'hook.exit_code': code ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook failed (${code}): ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'non_zero_exit' } });\n return;\n }\n if (stdinError) {\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, stdinError, { 'hook.reason': 'stdin_failed' });\n warn(`[pi-hook] advisory hook could not write stdin: ${stdinError.message}\\n`);\n finish({ failure: { command: hook.command, message: stdinError.message, reason: 'stdin_failed' } });\n return;\n }\n const jsonLine = stdout\n .trim()\n .split(LINE_BREAK)\n .reverse()\n .find((line) => line.startsWith(JSON_LINE_START));\n if (!jsonLine) {\n finish();\n return;\n }\n try {\n finish({ decision: JSON.parse(jsonLine) as HookDecision });\n } catch (error) {\n // The hook ran and had an opinion, and it was dropped.\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'invalid_json' });\n warn(`[pi-hook] advisory hook returned invalid JSON: ${hook.command}\\n`);\n finish({\n failure: {\n command: hook.command,\n message: error instanceof Error ? error.message : String(error),\n reason: 'invalid_json',\n },\n });\n }\n });\n child.stdin?.end(JSON.stringify(payload));\n });\n },\n };\n}\n","export const SERVICE_NAME = 'doom-pi-hook';\nexport const PACKAGE_NAME = '@agimon-ai/doompi-hook';\n","import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nimport { SERVICE_NAME, PACKAGE_NAME } from '../../constants/hookTelemetry';\nimport type { HookTelemetry } from '../../types/telemetry';\n\nexport interface HookTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport function createHookTelemetry(options: HookTelemetryOptions = {}): HookTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n };\n}\n","import type { PluginHookDocument, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/**\n * The plugin hooks one dispatch runs.\n *\n * Plugin configs are the Claude Code `hooks.json` shape verbatim: an event name\n * maps to groups, each with an optional matcher and a list of commands. Order\n * follows the declaration order of the plugins the harness resolved.\n */\nexport function selectPluginHooks(\n documents: ReadonlyArray<PluginHookDocument>,\n eventName: string,\n toolName?: string,\n): ResolvedHook[] {\n const matches: ResolvedHook[] = [];\n for (const document of documents) {\n for (const group of document.config.hooks?.[eventName] ?? []) {\n if (!matchesTool(group.matcher, toolName)) continue;\n for (const hook of group.hooks ?? []) matches.push({ hook, root: document.pluginRoot });\n }\n }\n return matches;\n}\n","export const HOOK_EVENT = {\n sessionStart: 'SessionStart',\n preToolUse: 'PreToolUse',\n postToolUse: 'PostToolUse',\n stop: 'Stop',\n sessionEnd: 'SessionEnd',\n} as const;\n"],"mappings":"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"}
@@ -93,7 +93,7 @@ interface HookDecision {
93
93
  additionalContext?: string;
94
94
  };
95
95
  }
96
- type HookFailureReason = 'spawn_failed' | 'non_zero_exit' | 'timeout' | 'invalid_json' | 'registry_read' | 'plugin_config';
96
+ type HookFailureReason = 'spawn_failed' | 'stdin_failed' | 'non_zero_exit' | 'timeout' | 'invalid_json' | 'registry_read' | 'plugin_config';
97
97
  interface HookFailure {
98
98
  command: string;
99
99
  message: string;
@@ -294,4 +294,4 @@ declare function toClaudeToolName(tool: string): string;
294
294
  declare function matchesTool(matcher: string | undefined, toolName: string | undefined): boolean;
295
295
  //#endregion
296
296
  export { HookDocumentReader as A, ParsedRegistrySource as B, decisionsFrom as C, toolResultMessages as D, isDenied as E, HookOutcome as F, PluginHookSourceRef as G, PluginHookConfig as H, HookPayload as I, RegistryEntry as J, RegistryBinding as K, HookRunOptions as L, HookEventName as M, HookFailure as N, HookCommand as O, HookFailureReason as P, HOOK_EVENT as Q, HookRunner as R, decisionReason as S, hookFailureMessage as T, PluginHookDocument as U, PluginDocumentRead as V, PluginHookGroup as W, RegistryRead as X, RegistryGroup as Y, ResolvedHook as Z, HookTelemetry as _, createHookTelemetry as a, HOOK_TELEMETRY_EVENT as b, createBashHookRunner as c, registryEntries as d, selectRegistryHooks as f, createHookDocumentReader as g, HookDocumentReaderOptions as h, HookTelemetryOptions as i, HookDocumentSource as j, HookDecision as k, RegistrySelection as l, toolHookPayload as m, toClaudeToolName as n, HookExtensionOptions as o, sessionHookPayload as p, RegistryDocument as q, selectPluginHooks as r, BashHookRunnerOptions as s, matchesTool as t, registryCacheKey as u, HookTelemetryAttributes as v, failuresFrom as w, additionalContextsFrom as x, HookTelemetryEventName as y, HookToolEvent as z };
297
- //# sourceMappingURL=index-LpLV04fM.d.mts.map
297
+ //# sourceMappingURL=index-CcfteHKn.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-LpLV04fM.d.mts","names":[],"sources":["../src/constants/hooks.ts","../src/types/hooks.ts","../src/services/hookDecisions/index.ts","../src/constants/telemetry.ts","../src/types/telemetry.ts","../src/services/hookDocuments/index.ts","../src/services/hookPayload/index.ts","../src/services/hookRegistry/index.ts","../src/services/hookRunner/index.ts","../src/services/hookRuntime/type.ts","../src/services/hookTelemetry/index.ts","../src/services/pluginHooks/index.ts","../src/services/toolNames/index.ts"],"mappings":";;;;;;cAAa;WACX;WACA;WACA;WACA;WACA;;;;;;;;;;;;KCMU,wBAAwB,yBAAyB;UAE5C;EACf;;EAEA;;;UAIe;EACf;EACA;EACA;EACA;EACA;;;UAIe,sBAAsB;EACrC;EACA;;EAEA;EACA;EACA;;EAEA;;UAGe;EACf;EACA,QAAQ;IAAQ;IAAe,KAAK;;;UAGrB;EACf,SAAS,eAAe;;;UAIT;EACf;EACA;;;UAIe;EACf;EACA,UAAU;;UAGK;EACf;EACA,QAAQ;;UAGO;EACf,QAAQ,eAAe;;;UAIR;EACf;EACA,QAAQ;;;UAIO;EACf,MAAM;EACN;;;UAIe;EACf;EACA;EACA;IACE;IACA;IACA;;;KAIQ;UAQK;EACf;EACA;EACA,QAAQ;;UAGO;EACf,WAAW;EACX,UAAU;;;;;;;;UASK;WACN;WACA;WACA;WACA;WACA;;;KAIC,cAAc;UAET;EACf;;EAEA;;;UAIe;EACf,IAAI,MAAM,aAAa,SAAS,aAAa,SAAS,iBAAiB,QAAQ;;;UAIhE;EACf,SAAS;EACT,UAAU;;;UAIK;EACf,WAAW;EACX,UAAU;;;UAIK;WACN;WACA;;;UAIM;EACf,SAAS,mBAAmB,QAAQ;EACpC,QAAQ,kBAAkB,wBAAwB,QAAQ;;;;iBC9J5C,cAAc,UAAU,cAAc,eAAe;iBAIrD,aAAa,UAAU,cAAc,eAAe;;iBAKpD,SAAS,UAAU;iBAInB,eAAe,UAAU;;iBAKzB,uBAAuB,WAAW,cAAc;;iBAOhD,mBAAmB,WAAW,cAAc;;;;;iBAU5C,mBAAmB,UAAU,cAAc;;;cCtC9C;WACX;WACA;;;;;KCCU,iCAAiC,mCAAmC;KACpE,0BAA0B;;;;;;UAOrB;EACf,YAAY,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B;EAClG,cAAc,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B;;;;UCarF;EACf,YAAY;;EAEZ;EACA,YAAY,qBAAqB;EACjC,QAAQ;;;;;;;;;;;iBAoBM,yBAAyB,UAAS,4BAAiC;;;;iBC9CnE,gBACd,OAAO,eACP,uBACA,kBACA,oBACC;;iBAaa,mBAAmB,mBAAmB,mBAAmB;;;;UCnBxD;EACf;EACA;;EAEA;EACA;;;;;;;;;iBAUc,iBAAiB,SAAS,cAAc;;;;;;;;iBAWxC,gBAAgB,SAAS,cAAc,wBAAwB;;;;;;;;iBAqC/D,oBACd,SAAS,cAAc,gBACvB,WAAW,oBACV;;;UC7Cc;EACf,YAAY;EACZ,MAAM,OAAO;EACb,WAAW,OAAO;EAClB,eAAe;EACf,QAAQ;;;;;;;;;iBAcM,qBAAqB,UAAS,wBAA6B;;;UCtC1D;EACf,YAAY;EACZ,SAAS;EACT,YAAY;;;;UCJG;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;iBAGM,oBAAoB,UAAS,uBAA4B;;;;;;;;;;iBCHzD,kBACd,WAAW,cAAc,qBACzB,mBACA,oBACC;;;;iBCKa,iBAAiB;;iBAKjB,YAAY,6BAA6B"}
1
+ {"version":3,"file":"index-CcfteHKn.d.mts","names":[],"sources":["../src/constants/hooks.ts","../src/types/hooks.ts","../src/services/hookDecisions/index.ts","../src/constants/telemetry.ts","../src/types/telemetry.ts","../src/services/hookDocuments/index.ts","../src/services/hookPayload/index.ts","../src/services/hookRegistry/index.ts","../src/services/hookRunner/index.ts","../src/services/hookRuntime/type.ts","../src/services/hookTelemetry/index.ts","../src/services/pluginHooks/index.ts","../src/services/toolNames/index.ts"],"mappings":";;;;;;cAAa;WACX;WACA;WACA;WACA;WACA;;;;;;;;;;;;KCMU,wBAAwB,yBAAyB;UAE5C;EACf;;EAEA;;;UAIe;EACf;EACA;EACA;EACA;EACA;;;UAIe,sBAAsB;EACrC;EACA;;EAEA;EACA;EACA;;EAEA;;UAGe;EACf;EACA,QAAQ;IAAQ;IAAe,KAAK;;;UAGrB;EACf,SAAS,eAAe;;;UAIT;EACf;EACA;;;UAIe;EACf;EACA,UAAU;;UAGK;EACf;EACA,QAAQ;;UAGO;EACf,QAAQ,eAAe;;;UAIR;EACf;EACA,QAAQ;;;UAIO;EACf,MAAM;EACN;;;UAIe;EACf;EACA;EACA;IACE;IACA;IACA;;;KAIQ;UASK;EACf;EACA;EACA,QAAQ;;UAGO;EACf,WAAW;EACX,UAAU;;;;;;;;UASK;WACN;WACA;WACA;WACA;WACA;;;KAIC,cAAc;UAET;EACf;;EAEA;;;UAIe;EACf,IAAI,MAAM,aAAa,SAAS,aAAa,SAAS,iBAAiB,QAAQ;;;UAIhE;EACf,SAAS;EACT,UAAU;;;UAIK;EACf,WAAW;EACX,UAAU;;;UAIK;WACN;WACA;;;UAIM;EACf,SAAS,mBAAmB,QAAQ;EACpC,QAAQ,kBAAkB,wBAAwB,QAAQ;;;;iBC/J5C,cAAc,UAAU,cAAc,eAAe;iBAIrD,aAAa,UAAU,cAAc,eAAe;;iBAKpD,SAAS,UAAU;iBAInB,eAAe,UAAU;;iBAKzB,uBAAuB,WAAW,cAAc;;iBAOhD,mBAAmB,WAAW,cAAc;;;;;iBAU5C,mBAAmB,UAAU,cAAc;;;cCtC9C;WACX;WACA;;;;;KCCU,iCAAiC,mCAAmC;KACpE,0BAA0B;;;;;;UAOrB;EACf,YAAY,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B;EAClG,cAAc,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B;;;;UCarF;EACf,YAAY;;EAEZ;EACA,YAAY,qBAAqB;EACjC,QAAQ;;;;;;;;;;;iBAoBM,yBAAyB,UAAS,4BAAiC;;;;iBC9CnE,gBACd,OAAO,eACP,uBACA,kBACA,oBACC;;iBAaa,mBAAmB,mBAAmB,mBAAmB;;;;UCnBxD;EACf;EACA;;EAEA;EACA;;;;;;;;;iBAUc,iBAAiB,SAAS,cAAc;;;;;;;;iBAWxC,gBAAgB,SAAS,cAAc,wBAAwB;;;;;;;;iBAqC/D,oBACd,SAAS,cAAc,gBACvB,WAAW,oBACV;;;UC5Cc;EACf,YAAY;EACZ,MAAM,OAAO;EACb,WAAW,OAAO;EAClB,eAAe;EACf,QAAQ;;;;;;;;;iBAcM,qBAAqB,UAAS,wBAA6B;;;UCvC1D;EACf,YAAY;EACZ,SAAS;EACT,YAAY;;;;UCJG;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;iBAGM,oBAAoB,UAAS,uBAA4B;;;;;;;;;;iBCHzD,kBACd,WAAW,cAAc,qBACzB,mBACA,oBACC;;;;iBCKa,iBAAiB;;iBAKjB,YAAY,6BAA6B"}
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hooks = require("./hooks-CReMXrU9.cjs");
2
+ const require_hooks = require("./hooks-BeDKiWFr.cjs");
3
3
  exports.HOOK_EVENT = require_hooks.HOOK_EVENT;
4
4
  exports.HOOK_TELEMETRY_EVENT = require_hooks.HOOK_TELEMETRY_EVENT;
5
5
  exports.additionalContextsFrom = require_hooks.additionalContextsFrom;
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as HookDocumentReader, B as ParsedRegistrySource, C as decisionsFrom, D as toolResultMessages, E as isDenied, F as HookOutcome, G as PluginHookSourceRef, H as PluginHookConfig, I as HookPayload, J as RegistryEntry, K as RegistryBinding, L as HookRunOptions, M as HookEventName, N as HookFailure, O as HookCommand, P as HookFailureReason, Q as HOOK_EVENT, R as HookRunner, S as decisionReason, T as hookFailureMessage, U as PluginHookDocument, V as PluginDocumentRead, W as PluginHookGroup, X as RegistryRead, Y as RegistryGroup, Z as ResolvedHook, _ as HookTelemetry, a as createHookTelemetry, b as HOOK_TELEMETRY_EVENT, c as createBashHookRunner, d as registryEntries, f as selectRegistryHooks, g as createHookDocumentReader, h as HookDocumentReaderOptions, i as HookTelemetryOptions, j as HookDocumentSource, k as HookDecision, l as RegistrySelection, m as toolHookPayload, n as toClaudeToolName, o as HookExtensionOptions, p as sessionHookPayload, q as RegistryDocument, r as selectPluginHooks, s as BashHookRunnerOptions, t as matchesTool, u as registryCacheKey, v as HookTelemetryAttributes, w as failuresFrom, x as additionalContextsFrom, y as HookTelemetryEventName, z as HookToolEvent } from "./index-LpLV04fM.mjs";
1
+ import { A as HookDocumentReader, B as ParsedRegistrySource, C as decisionsFrom, D as toolResultMessages, E as isDenied, F as HookOutcome, G as PluginHookSourceRef, H as PluginHookConfig, I as HookPayload, J as RegistryEntry, K as RegistryBinding, L as HookRunOptions, M as HookEventName, N as HookFailure, O as HookCommand, P as HookFailureReason, Q as HOOK_EVENT, R as HookRunner, S as decisionReason, T as hookFailureMessage, U as PluginHookDocument, V as PluginDocumentRead, W as PluginHookGroup, X as RegistryRead, Y as RegistryGroup, Z as ResolvedHook, _ as HookTelemetry, a as createHookTelemetry, b as HOOK_TELEMETRY_EVENT, c as createBashHookRunner, d as registryEntries, f as selectRegistryHooks, g as createHookDocumentReader, h as HookDocumentReaderOptions, i as HookTelemetryOptions, j as HookDocumentSource, k as HookDecision, l as RegistrySelection, m as toolHookPayload, n as toClaudeToolName, o as HookExtensionOptions, p as sessionHookPayload, q as RegistryDocument, r as selectPluginHooks, s as BashHookRunnerOptions, t as matchesTool, u as registryCacheKey, v as HookTelemetryAttributes, w as failuresFrom, x as additionalContextsFrom, y as HookTelemetryEventName, z as HookToolEvent } from "./index-CcfteHKn.mjs";
2
2
  export { type BashHookRunnerOptions, HOOK_EVENT, HOOK_TELEMETRY_EVENT, type HookCommand, type HookDecision, type HookDocumentReader, type HookDocumentReaderOptions, type HookDocumentSource, type HookEventName, type HookExtensionOptions, type HookFailure, type HookFailureReason, type HookOutcome, type HookPayload, type HookRunOptions, type HookRunner, type HookTelemetry, type HookTelemetryAttributes, type HookTelemetryEventName, type HookTelemetryOptions, type HookToolEvent, type ParsedRegistrySource, type PluginDocumentRead, type PluginHookConfig, type PluginHookDocument, type PluginHookGroup, type PluginHookSourceRef, type RegistryBinding, type RegistryDocument, type RegistryEntry, type RegistryGroup, type RegistryRead, type RegistrySelection, type ResolvedHook, additionalContextsFrom, createBashHookRunner, createHookDocumentReader, createHookTelemetry, decisionReason, decisionsFrom, failuresFrom, hookFailureMessage, isDenied, matchesTool, registryCacheKey, registryEntries, selectPluginHooks, selectRegistryHooks, sessionHookPayload, toClaudeToolName, toolHookPayload, toolResultMessages };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as failuresFrom, a as sessionHookPayload, b as toolResultMessages, c as registryCacheKey, d as matchesTool, f as toClaudeToolName, g as decisionsFrom, h as decisionReason, i as createBashHookRunner, l as registryEntries, m as additionalContextsFrom, n as selectPluginHooks, o as toolHookPayload, p as HOOK_TELEMETRY_EVENT, r as createHookTelemetry, s as createHookDocumentReader, t as HOOK_EVENT, u as selectRegistryHooks, v as hookFailureMessage, y as isDenied } from "./hooks-D2s_5hzZ.mjs";
1
+ import { _ as failuresFrom, a as sessionHookPayload, b as toolResultMessages, c as registryCacheKey, d as matchesTool, f as toClaudeToolName, g as decisionsFrom, h as decisionReason, i as createBashHookRunner, l as registryEntries, m as additionalContextsFrom, n as selectPluginHooks, o as toolHookPayload, p as HOOK_TELEMETRY_EVENT, r as createHookTelemetry, s as createHookDocumentReader, t as HOOK_EVENT, u as selectRegistryHooks, v as hookFailureMessage, y as isDenied } from "./hooks-GyYjEZKZ.mjs";
2
2
  export { HOOK_EVENT, HOOK_TELEMETRY_EVENT, additionalContextsFrom, createBashHookRunner, createHookDocumentReader, createHookTelemetry, decisionReason, decisionsFrom, failuresFrom, hookFailureMessage, isDenied, matchesTool, registryCacheKey, registryEntries, selectPluginHooks, selectRegistryHooks, sessionHookPayload, toClaudeToolName, toolHookPayload, toolResultMessages };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-hook",
3
- "version": "0.0.1-alpha.85",
3
+ "version": "0.0.1-alpha.87",
4
4
  "description": "Claude-Code-compatible repository and plugin hook runner for DoomPi sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -66,15 +66,15 @@
66
66
  "access": "public"
67
67
  },
68
68
  "dependencies": {
69
- "@agimon-ai/doompi-config": "0.0.1-alpha.107",
70
- "@agimon-ai/doompi-core": "0.0.1-alpha.108",
71
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.94",
69
+ "@agimon-ai/doompi-config": "0.0.1-alpha.109",
70
+ "@agimon-ai/doompi-core": "0.0.1-alpha.110",
71
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.95",
72
72
  "@deepseek-ai/cordis": "4.0.2",
73
73
  "yaml": "2.9.1"
74
74
  },
75
75
  "devDependencies": {
76
- "@agimon-ai/doompi-build": "0.0.1-alpha.26",
77
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.92",
76
+ "@agimon-ai/doompi-build": "0.0.1-alpha.27",
77
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.94",
78
78
  "@earendil-works/pi-coding-agent": "1.0.0",
79
79
  "@types/node": "26.6.2",
80
80
  "@vitest/coverage-v8": "5.0.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"hooks-CReMXrU9.cjs","names":["fs","parseYaml","doomConfigCandidates","spawnProcess","createDoomTelemetry"],"sources":["../src/constants/hookDecisions.ts","../src/services/hookDecisions/index.ts","../src/constants/hookDocuments.ts","../src/constants/telemetry.ts","../src/services/toolNames/index.ts","../src/services/hookRegistry/index.ts","../src/services/hookDocuments/index.ts","../src/constants/hookPayload.ts","../src/services/hookPayload/index.ts","../src/constants/hookRunner.ts","../src/services/hookRunner/index.ts","../src/constants/hookTelemetry.ts","../src/services/hookTelemetry/index.ts","../src/services/pluginHooks/index.ts","../src/constants/hooks.ts"],"sourcesContent":["export const BLOCK_DECISION = 'block';\nexport const DENY_PERMISSION = 'deny';\n","import { BLOCK_DECISION, DENY_PERMISSION } from '../../constants/hookDecisions';\nimport type { HookDecision, HookFailure, HookOutcome } from '../../types/hooks';\n\nexport function decisionsFrom(outcomes: ReadonlyArray<HookOutcome>): HookDecision[] {\n return outcomes.flatMap((outcome) => (outcome.decision ? [outcome.decision] : []));\n}\n\nexport function failuresFrom(outcomes: ReadonlyArray<HookOutcome>): HookFailure[] {\n return outcomes.flatMap((outcome) => (outcome.failure ? [outcome.failure] : []));\n}\n\n/** Both spellings a hook may use to refuse the call it observed. */\nexport function isDenied(decision: HookDecision | undefined): boolean {\n return decision?.decision === BLOCK_DECISION || decision?.hookSpecificOutput?.permissionDecision === DENY_PERMISSION;\n}\n\nexport function decisionReason(decision: HookDecision | undefined): string | undefined {\n return decision?.reason ?? decision?.hookSpecificOutput?.reason;\n}\n\n/** Context a hook wants added to the conversation rather than used to block. */\nexport function additionalContextsFrom(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext)\n .filter((value): value is string => Boolean(value));\n}\n\n/** What a post-tool hook has to say, whichever field it said it in. */\nexport function toolResultMessages(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext ?? decisionReason(decision))\n .filter((value): value is string => Boolean(value));\n}\n\n/**\n * A guardrail that never ran looks exactly like one that passed, so the agent is\n * told which checks were missed and what its options are.\n */\nexport function hookFailureMessage(failures: ReadonlyArray<HookFailure>): string {\n return [\n 'One or more repository hooks did not complete, so their checks were not applied:',\n ...failures.map((failure) => `- ${failure.command}: ${failure.message}`),\n 'Options:',\n '- Inspect the hook command or configuration and rerun after correcting it.',\n '- Continue only if the missed advisory check is acceptable.',\n '- Ask the user before bypassing a hook that may enforce a guardrail.',\n ].join('\\n');\n}\n","export const HOOKS_FILE = 'hooks.yaml';\nexport const FILE_ENCODING = 'utf8';\nexport const FILE_NOT_FOUND_ERROR = 'ENOENT';\nexport const REGISTRY_SOURCE_ATTRIBUTE = 'repository';\n","export const HOOK_TELEMETRY_EVENT = {\n hookFailed: 'doom_pi_hook.failed',\n hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed',\n} as const;\n","/**\n * Pi's tool names translated into the Claude names hook matchers are written\n * against.\n *\n * Hook authors write `matcher: Bash`, and Pi reports `bash`, so a matcher would\n * never fire without this. Only this direction belongs here: rewriting Claude\n * names for Pi is an agent-definition concern the harness owns.\n */\nconst PI_TO_CLAUDE = new Map([\n ['read', 'Read'],\n ['edit', 'Edit'],\n ['write', 'Write'],\n ['bash', 'Bash'],\n ['find', 'Glob'],\n ['grep', 'Grep'],\n ['subagent', 'Agent'],\n]);\n\n/** The Claude name a hook matcher expects for a tool Pi just ran. */\nexport function toClaudeToolName(tool: string): string {\n return PI_TO_CLAUDE.get(tool) ?? tool;\n}\n\n/** Whether a matcher, if the row declares one, accepts the tool that fired. */\nexport function matchesTool(matcher: string | undefined, toolName: string | undefined): boolean {\n if (!toolName || !matcher) return true;\n return new RegExp(matcher).test(toClaudeToolName(toolName));\n}\n","import type { HookDocumentSource, ParsedRegistrySource, RegistryEntry, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/** Which rows a dispatch keeps, once the session's selection is known. */\nexport interface RegistrySelection {\n event: string;\n toolName?: string;\n /** Selected group ids, or undefined for \"every group\", the standalone default. */\n allowedGroups?: readonly string[];\n inSubagent: boolean;\n}\n\n/**\n * Identity of a set of registry sources, for callers that cache the parse.\n *\n * Keyed on contents rather than mtime, so it stays correct for an in-place\n * rewrite and needs no invalidation hook. Reading both files is cheap; parsing\n * is what a cache built on this skips.\n */\nexport function registryCacheKey(sources: ReadonlyArray<HookDocumentSource>): string {\n return sources.map((source) => `${source.baseDirectory} ${source.text}`).join(' ');\n}\n\n/**\n * Flattens parsed registry documents into sorted rows, keeping only bindings\n * that declare a `pi` frontend.\n *\n * A later source replaces the group of the same id outright rather than merging\n * into it, so a repository that redefines a group owns it completely.\n */\nexport function registryEntries(sources: ReadonlyArray<ParsedRegistrySource>): RegistryEntry[] {\n const groups = new Map<string, RegistryEntry[]>();\n let position = 0;\n for (const source of sources) {\n for (const [groupId, group] of Object.entries(source.document.groups ?? {})) {\n const entries: RegistryEntry[] = [];\n for (const hook of group.hooks ?? []) {\n // Every declared hook advances the position, whether or not it is kept,\n // so the sort tiebreaker stays the order the file declares them in.\n position += 1;\n if (!hook.pi) continue;\n entries.push({\n ...hook.pi,\n event: hook.event,\n order: hook.pi.order ?? 0,\n position,\n groupId,\n core: group.core === true,\n baseDirectory: source.baseDirectory,\n });\n }\n groups.set(groupId, entries);\n }\n }\n\n const entries = [...groups.values()].flat();\n entries.sort((left, right) => left.order - right.order || left.position - right.position);\n return entries;\n}\n\n/**\n * The rows one dispatch runs.\n *\n * Group selection changes when /mode switches mid-session, so inclusion is\n * applied per call rather than being baked into a cached parse. Core groups\n * always load; the rest are gated by the layers the harness resolved.\n */\nexport function selectRegistryHooks(\n entries: ReadonlyArray<RegistryEntry>,\n selection: RegistrySelection,\n): ResolvedHook[] {\n const allowed = selection.allowedGroups === undefined ? undefined : new Set(selection.allowedGroups);\n return entries\n .filter((entry) => entry.core || !allowed || allowed.has(entry.groupId))\n .filter((entry) => entry.event === selection.event)\n .filter((entry) => !(entry.skipInSubagent && selection.inSubagent))\n .filter((entry) => matchesTool(entry.matcher, selection.toolName))\n .map((entry) => ({\n hook: { command: entry.command, timeout: entry.timeout },\n // The declaring config's root, so a global hook can reach its own scripts\n // through CLAUDE_PLUGIN_ROOT while still running against this repository.\n root: entry.baseDirectory,\n }));\n}\n","import fs from 'node:fs';\n\nimport { doomConfigCandidates } from '@agimon-ai/doompi-config/layeredConfig';\nimport { parse as parseYaml } from 'yaml';\n\nimport {\n HOOKS_FILE,\n FILE_ENCODING,\n FILE_NOT_FOUND_ERROR,\n REGISTRY_SOURCE_ATTRIBUTE,\n} from '../../constants/hookDocuments';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookDocumentReader,\n HookDocumentSource,\n PluginDocumentRead,\n PluginHookConfig,\n PluginHookDocument,\n PluginHookSourceRef,\n RegistryDocument,\n RegistryEntry,\n RegistryRead,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\nimport { registryCacheKey, registryEntries } from '../hookRegistry';\n\nexport interface HookDocumentReaderOptions {\n telemetry?: HookTelemetry;\n /** Where the global `.doom` directory lives. Defaults to the user's home. */\n homeDirectory?: string;\n readFile?: (filePath: string) => Promise<string>;\n warn?: (message: string) => void;\n}\n\nfunction reason(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction isMissingFile(error: unknown): boolean {\n return (error as NodeJS.ErrnoException | undefined)?.code === FILE_NOT_FOUND_ERROR;\n}\n\n/**\n * Reads `.doom/hooks.yaml` from the global config directory and the repository,\n * and the plugin hook configs the harness resolved.\n *\n * Every file is re-read on every dispatch: it costs almost nothing and keeps an\n * in-place rewrite visible, which an mtime check would miss when two writes land\n * inside the same filesystem timestamp granularity. Both caches are keyed on\n * contents instead, so parsing — the expensive half — is what they skip.\n */\nexport function createHookDocumentReader(options: HookDocumentReaderOptions = {}): HookDocumentReader {\n const readFile = options.readFile ?? ((filePath: string) => fs.promises.readFile(filePath, FILE_ENCODING));\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n let cachedRegistryKey: string | undefined;\n let cachedRegistryEntries: RegistryEntry[] = [];\n const pluginConfigCache = new Map<string, { source: string; config: PluginHookConfig }>();\n\n const readSource = async (candidate: {\n filePath: string;\n baseDirectory: string;\n }): Promise<HookDocumentSource | undefined> => {\n try {\n return { baseDirectory: candidate.baseDirectory, text: await readFile(candidate.filePath) };\n } catch (error) {\n if (isMissingFile(error)) return undefined;\n throw error;\n }\n };\n\n const parseSources = (sources: ReadonlyArray<HookDocumentSource>): RegistryEntry[] => {\n const key = registryCacheKey(sources);\n if (cachedRegistryKey === key) return cachedRegistryEntries;\n cachedRegistryEntries = registryEntries(\n sources.map((source) => ({\n baseDirectory: source.baseDirectory,\n document: (parseYaml(source.text) ?? {}) as RegistryDocument,\n })),\n );\n cachedRegistryKey = key;\n return cachedRegistryEntries;\n };\n\n return {\n async registry(repoRoot: string): Promise<RegistryRead> {\n const candidates = doomConfigCandidates(HOOKS_FILE, repoRoot, options.homeDirectory);\n // Named for the error path only. A registry absent from both locations is\n // a repository with no hooks, not a failure, so only a file that exists\n // and cannot be read or parsed reaches the catch below.\n const registryPath = candidates.map((candidate) => candidate.filePath).join(' and ');\n try {\n const sources = (await Promise.all(candidates.map(readSource))).filter(\n (source): source is HookDocumentSource => source !== undefined,\n );\n return { entries: parseSources(sources) };\n } catch (error) {\n // This empties the registry for the whole dispatch, so every repository\n // hook silently stops running rather than any one of them failing.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookRegistryReadFailed, error, {\n 'hook.registry.source': REGISTRY_SOURCE_ATTRIBUTE,\n });\n warn(`[pi-hook] could not read ${registryPath}: ${reason(error)}\\n`);\n return { entries: [], failure: { command: registryPath, message: reason(error), reason: 'registry_read' } };\n }\n },\n\n async plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead> {\n const documents: PluginHookDocument[] = [];\n const failures: PluginDocumentRead['failures'] = [];\n for (const source of sources) {\n try {\n const text = await readFile(source.configPath);\n const cached = pluginConfigCache.get(source.configPath);\n const config = cached?.source === text ? cached.config : (JSON.parse(text) as PluginHookConfig);\n pluginConfigCache.set(source.configPath, { source: text, config });\n documents.push({ pluginRoot: source.pluginRoot, config });\n } catch (error) {\n failures.push({ command: source.configPath, message: reason(error), reason: 'plugin_config' });\n }\n }\n return { documents, failures };\n },\n };\n}\n","export const TOOL_RESULT_EVENT = 'tool_result';\n","import { TOOL_RESULT_EVENT } from '../../constants/hookPayload';\nimport type { HookPayload, HookToolEvent } from '../../types/hooks';\nimport { toClaudeToolName } from '../toolNames';\n\n/** The payload shape a Claude Code session hook is written against. */\nexport function toolHookPayload(\n event: HookToolEvent,\n hookEventName: string,\n repoRoot: string,\n sessionId: string,\n): HookPayload {\n return {\n session_id: sessionId,\n transcript_path: '',\n cwd: repoRoot,\n hook_event_name: hookEventName,\n tool_name: toClaudeToolName(event.toolName),\n tool_input: event.input,\n ...(event.type === TOOL_RESULT_EVENT ? { tool_response: { success: !event.isError, content: event.content } } : {}),\n };\n}\n\n/** Session lifecycle hooks observe no tool, so they only need where and who. */\nexport function sessionHookPayload(sessionId: string, repoRoot: string): HookPayload {\n return { session_id: sessionId, cwd: repoRoot };\n}\n","export const HOOK_SHELL = '/bin/bash';\nexport const HOOK_SHELL_COMMAND_FLAG = '-c';\nexport const HOOK_TERMINATION_GRACE_MS = 2_000;\nexport const DEFAULT_HOOK_TIMEOUT_SECONDS = 10;\nexport const MILLISECONDS_PER_SECOND = 1_000;\nexport const PROCESS_NOT_FOUND_ERROR = 'ESRCH';\nexport const WINDOWS_PLATFORM = 'win32';\nexport const UNKNOWN_EXIT_CODE = -1;\nexport const JSON_LINE_START = '{';\nexport const LINE_BREAK = /\\r?\\n/;\n","import { spawn as spawnProcess } from 'node:child_process';\n\nimport {\n HOOK_SHELL,\n HOOK_SHELL_COMMAND_FLAG,\n HOOK_TERMINATION_GRACE_MS,\n DEFAULT_HOOK_TIMEOUT_SECONDS,\n MILLISECONDS_PER_SECOND,\n PROCESS_NOT_FOUND_ERROR,\n WINDOWS_PLATFORM,\n UNKNOWN_EXIT_CODE,\n JSON_LINE_START,\n LINE_BREAK,\n} from '../../constants/hookRunner';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookCommand,\n HookDecision,\n HookOutcome,\n HookPayload,\n HookRunOptions,\n HookRunner,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\n\nexport interface BashHookRunnerOptions {\n telemetry?: HookTelemetry;\n env?: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n spawn?: typeof spawnProcess;\n warn?: (message: string) => void;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return error instanceof Error && 'code' in error ? String(error.code) : undefined;\n}\n\n/**\n * Runs advisory hook commands through bash and reports what each one decided.\n *\n * A hook is a command the repository owns, so it is spawned in its own process\n * group and terminated as one: a hook that starts a server and stalls should\n * not leave the server behind when the timeout fires.\n */\nexport function createBashHookRunner(options: BashHookRunnerOptions = {}): HookRunner {\n const spawn = options.spawn ?? spawnProcess;\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n const useProcessGroup = (options.platform ?? process.platform) !== WINDOWS_PLATFORM;\n\n const signalOwnedProcess = (\n pid: number | undefined,\n kill: (signal: NodeJS.Signals) => void,\n ): ((signal: NodeJS.Signals) => void) => {\n return (signal) => {\n try {\n if (useProcessGroup && pid !== undefined) {\n process.kill(-pid, signal);\n return;\n }\n kill(signal);\n } catch (error) {\n if (errorCode(error) !== PROCESS_NOT_FOUND_ERROR) {\n process.emitWarning(`Could not signal repository hook process: ${String(error)}`);\n }\n }\n };\n };\n\n return {\n run(hook: HookCommand, payload: HookPayload, runOptions: HookRunOptions): Promise<HookOutcome> {\n const timeoutSeconds = hook.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS;\n const environment = options.env ?? process.env;\n return new Promise((resolve) => {\n // -c, not -lc. Hooks run on every tool call, and a login shell would\n // source the developer's profile each time: per-call latency plus hook\n // behavior that varies with their dotfiles. The child inherits the\n // environment below, so it keeps the PATH the launcher started with.\n const child = spawn(HOOK_SHELL, [HOOK_SHELL_COMMAND_FLAG, hook.command], {\n cwd: runOptions.repoRoot,\n env: {\n ...environment,\n CLAUDE_PROJECT_DIR: runOptions.repoRoot,\n CODEX_REPO_ROOT: runOptions.repoRoot,\n ORIGINAL_REPO_PATH: environment.ORIGINAL_REPO_PATH ?? runOptions.repoRoot,\n ...(runOptions.pluginRoot ? { CLAUDE_PLUGIN_ROOT: runOptions.pluginRoot } : {}),\n },\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: useProcessGroup,\n });\n let stdout = '';\n let stderr = '';\n let settled = false;\n let timedOut = false;\n let childExited = false;\n let timeoutExitCode: number | null = null;\n let escalationTimer: NodeJS.Timeout | undefined;\n\n const signal = signalOwnedProcess(child.pid, (value) => child.kill(value));\n\n const ownedProcessIsAlive = (): boolean => {\n if (!useProcessGroup || child.pid === undefined) return !childExited;\n try {\n process.kill(-child.pid, 0);\n return true;\n } catch (error) {\n return errorCode(error) !== PROCESS_NOT_FOUND_ERROR;\n }\n };\n\n const finish = (outcome: HookOutcome = {}): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (escalationTimer) clearTimeout(escalationTimer);\n resolve(outcome);\n };\n\n const finishTimeout = (): void => {\n const message = `Hook timed out after ${timeoutSeconds} seconds.`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'timeout',\n 'hook.exit_code': timeoutExitCode ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook timed out: ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'timeout' } });\n };\n\n const timer = setTimeout(() => {\n timedOut = true;\n signal('SIGTERM');\n escalationTimer = setTimeout(() => {\n if (ownedProcessIsAlive()) signal('SIGKILL');\n finishTimeout();\n }, HOOK_TERMINATION_GRACE_MS);\n }, timeoutSeconds * MILLISECONDS_PER_SECOND);\n\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.once('error', (error: Error) => {\n if (timedOut) return;\n // A guardrail that never ran is indistinguishable from one that\n // passed, which is the whole reason these are worth reporting.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'spawn_failed' });\n warn(`[pi-hook] advisory hook failed: ${error.message}\\n`);\n finish({ failure: { command: hook.command, message: error.message, reason: 'spawn_failed' } });\n });\n child.once('exit', (code: number | null) => {\n childExited = true;\n timeoutExitCode = code;\n if (timedOut) return;\n if (code !== 0) {\n const message = stderr.trim() || `Advisory hook exited with code ${code ?? 'unknown'}`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'non_zero_exit',\n 'hook.exit_code': code ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook failed (${code}): ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'non_zero_exit' } });\n return;\n }\n const jsonLine = stdout\n .trim()\n .split(LINE_BREAK)\n .reverse()\n .find((line) => line.startsWith(JSON_LINE_START));\n if (!jsonLine) {\n finish();\n return;\n }\n try {\n finish({ decision: JSON.parse(jsonLine) as HookDecision });\n } catch (error) {\n // The hook ran and had an opinion, and it was dropped.\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'invalid_json' });\n warn(`[pi-hook] advisory hook returned invalid JSON: ${hook.command}\\n`);\n finish({\n failure: {\n command: hook.command,\n message: error instanceof Error ? error.message : String(error),\n reason: 'invalid_json',\n },\n });\n }\n });\n child.stdin?.end(JSON.stringify(payload));\n });\n },\n };\n}\n","export const SERVICE_NAME = 'doom-pi-hook';\nexport const PACKAGE_NAME = '@agimon-ai/doompi-hook';\n","import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nimport { SERVICE_NAME, PACKAGE_NAME } from '../../constants/hookTelemetry';\nimport type { HookTelemetry } from '../../types/telemetry';\n\nexport interface HookTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport function createHookTelemetry(options: HookTelemetryOptions = {}): HookTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n };\n}\n","import type { PluginHookDocument, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/**\n * The plugin hooks one dispatch runs.\n *\n * Plugin configs are the Claude Code `hooks.json` shape verbatim: an event name\n * maps to groups, each with an optional matcher and a list of commands. Order\n * follows the declaration order of the plugins the harness resolved.\n */\nexport function selectPluginHooks(\n documents: ReadonlyArray<PluginHookDocument>,\n eventName: string,\n toolName?: string,\n): ResolvedHook[] {\n const matches: ResolvedHook[] = [];\n for (const document of documents) {\n for (const group of document.config.hooks?.[eventName] ?? []) {\n if (!matchesTool(group.matcher, toolName)) continue;\n for (const hook of group.hooks ?? []) matches.push({ hook, root: document.pluginRoot });\n }\n }\n return matches;\n}\n","export const HOOK_EVENT = {\n sessionStart: 'SessionStart',\n preToolUse: 'PreToolUse',\n postToolUse: 'PostToolUse',\n stop: 'Stop',\n sessionEnd: 'SessionEnd',\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACGA,SAAgB,cAAc,UAAsD;CAClF,OAAO,SAAS,SAAS,YAAa,QAAQ,WAAW,CAAC,QAAQ,QAAQ,IAAI,CAAC,CAAE;AACnF;AAEA,SAAgB,aAAa,UAAqD;CAChF,OAAO,SAAS,SAAS,YAAa,QAAQ,UAAU,CAAC,QAAQ,OAAO,IAAI,CAAC,CAAE;AACjF;;AAGA,SAAgB,SAAS,UAA6C;CACpE,OAAO,UAAU,aAAA,WAA+B,UAAU,oBAAoB,uBAAA;AAChF;AAEA,SAAgB,eAAe,UAAwD;CACrF,OAAO,UAAU,UAAU,UAAU,oBAAoB;AAC3D;;AAGA,SAAgB,uBAAuB,WAAkD;CACvF,OAAO,UACJ,KAAK,aAAa,SAAS,oBAAoB,iBAAiB,CAAC,CACjE,QAAQ,UAA2B,QAAQ,KAAK,CAAC;AACtD;;AAGA,SAAgB,mBAAmB,WAAkD;CACnF,OAAO,UACJ,KAAK,aAAa,SAAS,oBAAoB,qBAAqB,eAAe,QAAQ,CAAC,CAAC,CAC7F,QAAQ,UAA2B,QAAQ,KAAK,CAAC;AACtD;;;;;AAMA,SAAgB,mBAAmB,UAA8C;CAC/E,OAAO;EACL;EACA,GAAG,SAAS,KAAK,YAAY,KAAK,QAAQ,QAAQ,IAAI,QAAQ,SAAS;EACvE;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;;;AC/CA,MAAa,aAAa;AAE1B,MAAa,uBAAuB;AACpC,MAAa,4BAA4B;;;ACHzC,MAAa,uBAAuB;CAClC,YAAY;CACZ,wBAAwB;AAC1B;;;;;;;;;;;ACKA,MAAM,+BAAe,IAAI,IAAI;CAC3B,CAAC,QAAQ,MAAM;CACf,CAAC,QAAQ,MAAM;CACf,CAAC,SAAS,OAAO;CACjB,CAAC,QAAQ,MAAM;CACf,CAAC,QAAQ,MAAM;CACf,CAAC,QAAQ,MAAM;CACf,CAAC,YAAY,OAAO;AACtB,CAAC;;AAGD,SAAgB,iBAAiB,MAAsB;CACrD,OAAO,aAAa,IAAI,IAAI,KAAK;AACnC;;AAGA,SAAgB,YAAY,SAA6B,UAAuC;CAC9F,IAAI,CAAC,YAAY,CAAC,SAAS,OAAO;CAClC,OAAO,IAAI,OAAO,OAAO,CAAC,CAAC,KAAK,iBAAiB,QAAQ,CAAC;AAC5D;;;;;;;;;;ACRA,SAAgB,iBAAiB,SAAoD;CACnF,OAAO,QAAQ,KAAK,WAAW,GAAG,OAAO,cAAc,GAAG,OAAO,MAAM,CAAC,CAAC,KAAK,GAAG;AACnF;;;;;;;;AASA,SAAgB,gBAAgB,SAA+D;CAC7F,MAAM,yBAAS,IAAI,IAA6B;CAChD,IAAI,WAAW;CACf,KAAK,MAAM,UAAU,SACnB,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,OAAO,SAAS,UAAU,CAAC,CAAC,GAAG;EAC3E,MAAM,UAA2B,CAAC;EAClC,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG;GAGpC,YAAY;GACZ,IAAI,CAAC,KAAK,IAAI;GACd,QAAQ,KAAK;IACX,GAAG,KAAK;IACR,OAAO,KAAK;IACZ,OAAO,KAAK,GAAG,SAAS;IACxB;IACA;IACA,MAAM,MAAM,SAAS;IACrB,eAAe,OAAO;GACxB,CAAC;EACH;EACA,OAAO,IAAI,SAAS,OAAO;CAC7B;CAGF,MAAM,UAAU,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK;CAC1C,QAAQ,MAAM,MAAM,UAAU,KAAK,QAAQ,MAAM,SAAS,KAAK,WAAW,MAAM,QAAQ;CACxF,OAAO;AACT;;;;;;;;AASA,SAAgB,oBACd,SACA,WACgB;CAChB,MAAM,UAAU,UAAU,kBAAkB,KAAA,IAAY,KAAA,IAAY,IAAI,IAAI,UAAU,aAAa;CACnG,OAAO,QACJ,QAAQ,UAAU,MAAM,QAAQ,CAAC,WAAW,QAAQ,IAAI,MAAM,OAAO,CAAC,CAAC,CACvE,QAAQ,UAAU,MAAM,UAAU,UAAU,KAAK,CAAC,CAClD,QAAQ,UAAU,EAAE,MAAM,kBAAkB,UAAU,WAAW,CAAC,CAClE,QAAQ,UAAU,YAAY,MAAM,SAAS,UAAU,QAAQ,CAAC,CAAC,CACjE,KAAK,WAAW;EACf,MAAM;GAAE,SAAS,MAAM;GAAS,SAAS,MAAM;EAAQ;EAGvD,MAAM,MAAM;CACd,EAAE;AACN;;;ACjDA,SAAS,OAAO,OAAwB;CACtC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,SAAS,cAAc,OAAyB;CAC9C,OAAQ,OAA6C,SAAS;AAChE;;;;;;;;;;AAWA,SAAgB,yBAAyB,UAAqC,CAAC,GAAuB;CACpG,MAAM,WAAW,QAAQ,cAAc,aAAqBA,QAAAA,QAAG,SAAS,SAAS,UAAA,MAAuB;CACxG,MAAM,YAAY,QAAQ;CAC1B,MAAM,OAAO,QAAQ,UAAU,YAAoB,KAAK,QAAQ,OAAO,MAAM,OAAO;CACpF,IAAI;CACJ,IAAI,wBAAyC,CAAC;CAC9C,MAAM,oCAAoB,IAAI,IAA0D;CAExF,MAAM,aAAa,OAAO,cAGqB;EAC7C,IAAI;GACF,OAAO;IAAE,eAAe,UAAU;IAAe,MAAM,MAAM,SAAS,UAAU,QAAQ;GAAE;EAC5F,SAAS,OAAO;GACd,IAAI,cAAc,KAAK,GAAG,OAAO,KAAA;GACjC,MAAM;EACR;CACF;CAEA,MAAM,gBAAgB,YAAgE;EACpF,MAAM,MAAM,iBAAiB,OAAO;EACpC,IAAI,sBAAsB,KAAK,OAAO;EACtC,wBAAwB,gBACtB,QAAQ,KAAK,YAAY;GACvB,eAAe,OAAO;GACtB,WAAA,GAAWC,KAAAA,MAAAA,CAAU,OAAO,IAAI,KAAK,CAAC;EACxC,EAAE,CACJ;EACA,oBAAoB;EACpB,OAAO;CACT;CAEA,OAAO;EACL,MAAM,SAAS,UAAyC;GACtD,MAAM,cAAA,GAAaC,uCAAAA,qBAAAA,CAAqB,YAAY,UAAU,QAAQ,aAAa;GAInF,MAAM,eAAe,WAAW,KAAK,cAAc,UAAU,QAAQ,CAAC,CAAC,KAAK,OAAO;GACnF,IAAI;IACF,MAAM,WAAW,MAAM,QAAQ,IAAI,WAAW,IAAI,UAAU,CAAC,EAAA,CAAG,QAC7D,WAAyC,WAAW,KAAA,CACvD;IACA,OAAO,EAAE,SAAS,aAAa,OAAO,EAAE;GAC1C,SAAS,OAAO;IAGd,WAAgB,YAAY,qBAAqB,wBAAwB,OAAO,EAC9E,wBAAwB,0BAC1B,CAAC;IACD,KAAK,4BAA4B,aAAa,IAAI,OAAO,KAAK,EAAE,GAAG;IACnE,OAAO;KAAE,SAAS,CAAC;KAAG,SAAS;MAAE,SAAS;MAAc,SAAS,OAAO,KAAK;MAAG,QAAQ;KAAgB;IAAE;GAC5G;EACF;EAEA,MAAM,QAAQ,SAAsE;GAClF,MAAM,YAAkC,CAAC;GACzC,MAAM,WAA2C,CAAC;GAClD,KAAK,MAAM,UAAU,SACnB,IAAI;IACF,MAAM,OAAO,MAAM,SAAS,OAAO,UAAU;IAC7C,MAAM,SAAS,kBAAkB,IAAI,OAAO,UAAU;IACtD,MAAM,SAAS,QAAQ,WAAW,OAAO,OAAO,SAAU,KAAK,MAAM,IAAI;IACzE,kBAAkB,IAAI,OAAO,YAAY;KAAE,QAAQ;KAAM;IAAO,CAAC;IACjE,UAAU,KAAK;KAAE,YAAY,OAAO;KAAY;IAAO,CAAC;GAC1D,SAAS,OAAO;IACd,SAAS,KAAK;KAAE,SAAS,OAAO;KAAY,SAAS,OAAO,KAAK;KAAG,QAAQ;IAAgB,CAAC;GAC/F;GAEF,OAAO;IAAE;IAAW;GAAS;EAC/B;CACF;AACF;;;;AEvHA,SAAgB,gBACd,OACA,eACA,UACA,WACa;CACb,OAAO;EACL,YAAY;EACZ,iBAAiB;EACjB,KAAK;EACL,iBAAiB;EACjB,WAAW,iBAAiB,MAAM,QAAQ;EAC1C,YAAY,MAAM;EAClB,GAAI,MAAM,SAAA,gBAA6B,EAAE,eAAe;GAAE,SAAS,CAAC,MAAM;GAAS,SAAS,MAAM;EAAQ,EAAE,IAAI,CAAC;CACnH;AACF;;AAGA,SAAgB,mBAAmB,WAAmB,UAA+B;CACnF,OAAO;EAAE,YAAY;EAAW,KAAK;CAAS;AAChD;;;ACzBA,MAAa,aAAa;AAE1B,MAAa,4BAA4B;AAEzC,MAAa,0BAA0B;AACvC,MAAa,0BAA0B;AACvC,MAAa,mBAAmB;AAGhC,MAAa,aAAa;;;ACwB1B,SAAS,UAAU,OAAoC;CACrD,OAAO,iBAAiB,SAAS,UAAU,QAAQ,OAAO,MAAM,IAAI,IAAI,KAAA;AAC1E;;;;;;;;AASA,SAAgB,qBAAqB,UAAiC,CAAC,GAAe;CACpF,MAAM,QAAQ,QAAQ,SAASC,mBAAAA;CAC/B,MAAM,YAAY,QAAQ;CAC1B,MAAM,OAAO,QAAQ,UAAU,YAAoB,KAAK,QAAQ,OAAO,MAAM,OAAO;CACpF,MAAM,mBAAmB,QAAQ,YAAY,QAAQ,cAAc;CAEnE,MAAM,sBACJ,KACA,SACuC;EACvC,QAAQ,WAAW;GACjB,IAAI;IACF,IAAI,mBAAmB,QAAQ,KAAA,GAAW;KACxC,QAAQ,KAAK,CAAC,KAAK,MAAM;KACzB;IACF;IACA,KAAK,MAAM;GACb,SAAS,OAAO;IACd,IAAI,UAAU,KAAK,MAAA,SACjB,QAAQ,YAAY,6CAA6C,OAAO,KAAK,GAAG;GAEpF;EACF;CACF;CAEA,OAAO,EACL,IAAI,MAAmB,SAAsB,YAAkD;EAC7F,MAAM,iBAAiB,KAAK,WAAA;EAC5B,MAAM,cAAc,QAAQ,OAAO,QAAQ;EAC3C,OAAO,IAAI,SAAS,YAAY;GAK9B,MAAM,QAAQ,MAAM,YAAY,CAAA,MAA0B,KAAK,OAAO,GAAG;IACvE,KAAK,WAAW;IAChB,KAAK;KACH,GAAG;KACH,oBAAoB,WAAW;KAC/B,iBAAiB,WAAW;KAC5B,oBAAoB,YAAY,sBAAsB,WAAW;KACjE,GAAI,WAAW,aAAa,EAAE,oBAAoB,WAAW,WAAW,IAAI,CAAC;IAC/E;IACA,OAAO;KAAC;KAAQ;KAAQ;IAAM;IAC9B,UAAU;GACZ,CAAC;GACD,IAAI,SAAS;GACb,IAAI,SAAS;GACb,IAAI,UAAU;GACd,IAAI,WAAW;GACf,IAAI,cAAc;GAClB,IAAI,kBAAiC;GACrC,IAAI;GAEJ,MAAM,SAAS,mBAAmB,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,CAAC;GAEzE,MAAM,4BAAqC;IACzC,IAAI,CAAC,mBAAmB,MAAM,QAAQ,KAAA,GAAW,OAAO,CAAC;IACzD,IAAI;KACF,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC;KAC1B,OAAO;IACT,SAAS,OAAO;KACd,OAAO,UAAU,KAAK,MAAM;IAC9B;GACF;GAEA,MAAM,UAAU,UAAuB,CAAC,MAAY;IAClD,IAAI,SAAS;IACb,UAAU;IACV,aAAa,KAAK;IAClB,IAAI,iBAAiB,aAAa,eAAe;IACjD,QAAQ,OAAO;GACjB;GAEA,MAAM,sBAA4B;IAChC,MAAM,UAAU,wBAAwB,eAAe;IACvD,WAAgB,cAAc,qBAAqB,YAAY,SAAS;KACtE,eAAe;KACf,kBAAkB,mBAAA;IACpB,CAAC;IACD,KAAK,sCAAsC,KAAK,QAAQ,IAAI,QAAQ;IACpE,OAAO,EAAE,SAAS;KAAE,SAAS,KAAK;KAAS;KAAS,QAAQ;IAAU,EAAE,CAAC;GAC3E;GAEA,MAAM,QAAQ,iBAAiB;IAC7B,WAAW;IACX,OAAO,SAAS;IAChB,kBAAkB,iBAAiB;KACjC,IAAI,oBAAoB,GAAG,OAAO,SAAS;KAC3C,cAAc;IAChB,GAAG,yBAAyB;GAC9B,GAAG,iBAAiB,uBAAuB;GAE3C,MAAM,QAAQ,GAAG,SAAS,UAAkB;IAC1C,UAAU,MAAM,SAAS;GAC3B,CAAC;GACD,MAAM,QAAQ,GAAG,SAAS,UAAkB;IAC1C,UAAU,MAAM,SAAS;GAC3B,CAAC;GACD,MAAM,KAAK,UAAU,UAAiB;IACpC,IAAI,UAAU;IAGd,WAAgB,YAAY,qBAAqB,YAAY,OAAO,EAAE,eAAe,eAAe,CAAC;IACrG,KAAK,mCAAmC,MAAM,QAAQ,GAAG;IACzD,OAAO,EAAE,SAAS;KAAE,SAAS,KAAK;KAAS,SAAS,MAAM;KAAS,QAAQ;IAAe,EAAE,CAAC;GAC/F,CAAC;GACD,MAAM,KAAK,SAAS,SAAwB;IAC1C,cAAc;IACd,kBAAkB;IAClB,IAAI,UAAU;IACd,IAAI,SAAS,GAAG;KACd,MAAM,UAAU,OAAO,KAAK,KAAK,kCAAkC,QAAQ;KAC3E,WAAgB,cAAc,qBAAqB,YAAY,SAAS;MACtE,eAAe;MACf,kBAAkB,QAAA;KACpB,CAAC;KACD,KAAK,mCAAmC,KAAK,KAAK,KAAK,QAAQ,IAAI,QAAQ;KAC3E,OAAO,EAAE,SAAS;MAAE,SAAS,KAAK;MAAS;MAAS,QAAQ;KAAgB,EAAE,CAAC;KAC/E;IACF;IACA,MAAM,WAAW,OACd,KAAK,CAAC,CACN,MAAM,UAAU,CAAC,CACjB,QAAQ,CAAC,CACT,MAAM,SAAS,KAAK,WAAA,GAA0B,CAAC;IAClD,IAAI,CAAC,UAAU;KACb,OAAO;KACP;IACF;IACA,IAAI;KACF,OAAO,EAAE,UAAU,KAAK,MAAM,QAAQ,EAAkB,CAAC;IAC3D,SAAS,OAAO;KAEd,WAAgB,cAAc,qBAAqB,YAAY,OAAO,EAAE,eAAe,eAAe,CAAC;KACvG,KAAK,kDAAkD,KAAK,QAAQ,GAAG;KACvE,OAAO,EACL,SAAS;MACP,SAAS,KAAK;MACd,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;MAC9D,QAAQ;KACV,EACF,CAAC;IACH;GACF,CAAC;GACD,MAAM,OAAO,IAAI,KAAK,UAAU,OAAO,CAAC;EAC1C,CAAC;CACH,EACF;AACF;;;ACjMA,MAAa,eAAe;AAC5B,MAAa,eAAe;;;ACY5B,SAAgB,oBAAoB,UAAgC,CAAC,GAAkB;CACrF,MAAM,aAAA,GAA2BC,4BAAAA,oBAAAA,CAAoB;EACnD,aAAa;EACb,aAAa;EACb,KAAK,QAAQ;EACb,eAAe,QAAQ;EACvB,KAAK,QAAQ;EACb,kBAAkB,QAAQ;EAC1B,MAAM,QAAQ;EACd,YAAY;EACZ,cAAc;CAChB,CAAC;CACD,OAAO;EACL,cAAc,OAAO,OAAO,eAAe,UAAU,YAAY,OAAO,OAAO,UAAU;EACzF,gBAAgB,OAAO,OAAO,eAAe,UAAU,cAAc,OAAO,OAAO,UAAU;CAC/F;AACF;;;;;;;;;;ACnBA,SAAgB,kBACd,WACA,WACA,UACgB;CAChB,MAAM,UAA0B,CAAC;CACjC,KAAK,MAAM,YAAY,WACrB,KAAK,MAAM,SAAS,SAAS,OAAO,QAAQ,cAAc,CAAC,GAAG;EAC5D,IAAI,CAAC,YAAY,MAAM,SAAS,QAAQ,GAAG;EAC3C,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG,QAAQ,KAAK;GAAE;GAAM,MAAM,SAAS;EAAW,CAAC;CACxF;CAEF,OAAO;AACT;;;ACvBA,MAAa,aAAa;CACxB,cAAc;CACd,YAAY;CACZ,aAAa;CACb,MAAM;CACN,YAAY;AACd"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hooks-D2s_5hzZ.mjs","names":["parseYaml","spawn","spawnProcess"],"sources":["../src/constants/hookDecisions.ts","../src/services/hookDecisions/index.ts","../src/constants/hookDocuments.ts","../src/constants/telemetry.ts","../src/services/toolNames/index.ts","../src/services/hookRegistry/index.ts","../src/services/hookDocuments/index.ts","../src/constants/hookPayload.ts","../src/services/hookPayload/index.ts","../src/constants/hookRunner.ts","../src/services/hookRunner/index.ts","../src/constants/hookTelemetry.ts","../src/services/hookTelemetry/index.ts","../src/services/pluginHooks/index.ts","../src/constants/hooks.ts"],"sourcesContent":["export const BLOCK_DECISION = 'block';\nexport const DENY_PERMISSION = 'deny';\n","import { BLOCK_DECISION, DENY_PERMISSION } from '../../constants/hookDecisions';\nimport type { HookDecision, HookFailure, HookOutcome } from '../../types/hooks';\n\nexport function decisionsFrom(outcomes: ReadonlyArray<HookOutcome>): HookDecision[] {\n return outcomes.flatMap((outcome) => (outcome.decision ? [outcome.decision] : []));\n}\n\nexport function failuresFrom(outcomes: ReadonlyArray<HookOutcome>): HookFailure[] {\n return outcomes.flatMap((outcome) => (outcome.failure ? [outcome.failure] : []));\n}\n\n/** Both spellings a hook may use to refuse the call it observed. */\nexport function isDenied(decision: HookDecision | undefined): boolean {\n return decision?.decision === BLOCK_DECISION || decision?.hookSpecificOutput?.permissionDecision === DENY_PERMISSION;\n}\n\nexport function decisionReason(decision: HookDecision | undefined): string | undefined {\n return decision?.reason ?? decision?.hookSpecificOutput?.reason;\n}\n\n/** Context a hook wants added to the conversation rather than used to block. */\nexport function additionalContextsFrom(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext)\n .filter((value): value is string => Boolean(value));\n}\n\n/** What a post-tool hook has to say, whichever field it said it in. */\nexport function toolResultMessages(decisions: ReadonlyArray<HookDecision>): string[] {\n return decisions\n .map((decision) => decision.hookSpecificOutput?.additionalContext ?? decisionReason(decision))\n .filter((value): value is string => Boolean(value));\n}\n\n/**\n * A guardrail that never ran looks exactly like one that passed, so the agent is\n * told which checks were missed and what its options are.\n */\nexport function hookFailureMessage(failures: ReadonlyArray<HookFailure>): string {\n return [\n 'One or more repository hooks did not complete, so their checks were not applied:',\n ...failures.map((failure) => `- ${failure.command}: ${failure.message}`),\n 'Options:',\n '- Inspect the hook command or configuration and rerun after correcting it.',\n '- Continue only if the missed advisory check is acceptable.',\n '- Ask the user before bypassing a hook that may enforce a guardrail.',\n ].join('\\n');\n}\n","export const HOOKS_FILE = 'hooks.yaml';\nexport const FILE_ENCODING = 'utf8';\nexport const FILE_NOT_FOUND_ERROR = 'ENOENT';\nexport const REGISTRY_SOURCE_ATTRIBUTE = 'repository';\n","export const HOOK_TELEMETRY_EVENT = {\n hookFailed: 'doom_pi_hook.failed',\n hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed',\n} as const;\n","/**\n * Pi's tool names translated into the Claude names hook matchers are written\n * against.\n *\n * Hook authors write `matcher: Bash`, and Pi reports `bash`, so a matcher would\n * never fire without this. Only this direction belongs here: rewriting Claude\n * names for Pi is an agent-definition concern the harness owns.\n */\nconst PI_TO_CLAUDE = new Map([\n ['read', 'Read'],\n ['edit', 'Edit'],\n ['write', 'Write'],\n ['bash', 'Bash'],\n ['find', 'Glob'],\n ['grep', 'Grep'],\n ['subagent', 'Agent'],\n]);\n\n/** The Claude name a hook matcher expects for a tool Pi just ran. */\nexport function toClaudeToolName(tool: string): string {\n return PI_TO_CLAUDE.get(tool) ?? tool;\n}\n\n/** Whether a matcher, if the row declares one, accepts the tool that fired. */\nexport function matchesTool(matcher: string | undefined, toolName: string | undefined): boolean {\n if (!toolName || !matcher) return true;\n return new RegExp(matcher).test(toClaudeToolName(toolName));\n}\n","import type { HookDocumentSource, ParsedRegistrySource, RegistryEntry, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/** Which rows a dispatch keeps, once the session's selection is known. */\nexport interface RegistrySelection {\n event: string;\n toolName?: string;\n /** Selected group ids, or undefined for \"every group\", the standalone default. */\n allowedGroups?: readonly string[];\n inSubagent: boolean;\n}\n\n/**\n * Identity of a set of registry sources, for callers that cache the parse.\n *\n * Keyed on contents rather than mtime, so it stays correct for an in-place\n * rewrite and needs no invalidation hook. Reading both files is cheap; parsing\n * is what a cache built on this skips.\n */\nexport function registryCacheKey(sources: ReadonlyArray<HookDocumentSource>): string {\n return sources.map((source) => `${source.baseDirectory} ${source.text}`).join(' ');\n}\n\n/**\n * Flattens parsed registry documents into sorted rows, keeping only bindings\n * that declare a `pi` frontend.\n *\n * A later source replaces the group of the same id outright rather than merging\n * into it, so a repository that redefines a group owns it completely.\n */\nexport function registryEntries(sources: ReadonlyArray<ParsedRegistrySource>): RegistryEntry[] {\n const groups = new Map<string, RegistryEntry[]>();\n let position = 0;\n for (const source of sources) {\n for (const [groupId, group] of Object.entries(source.document.groups ?? {})) {\n const entries: RegistryEntry[] = [];\n for (const hook of group.hooks ?? []) {\n // Every declared hook advances the position, whether or not it is kept,\n // so the sort tiebreaker stays the order the file declares them in.\n position += 1;\n if (!hook.pi) continue;\n entries.push({\n ...hook.pi,\n event: hook.event,\n order: hook.pi.order ?? 0,\n position,\n groupId,\n core: group.core === true,\n baseDirectory: source.baseDirectory,\n });\n }\n groups.set(groupId, entries);\n }\n }\n\n const entries = [...groups.values()].flat();\n entries.sort((left, right) => left.order - right.order || left.position - right.position);\n return entries;\n}\n\n/**\n * The rows one dispatch runs.\n *\n * Group selection changes when /mode switches mid-session, so inclusion is\n * applied per call rather than being baked into a cached parse. Core groups\n * always load; the rest are gated by the layers the harness resolved.\n */\nexport function selectRegistryHooks(\n entries: ReadonlyArray<RegistryEntry>,\n selection: RegistrySelection,\n): ResolvedHook[] {\n const allowed = selection.allowedGroups === undefined ? undefined : new Set(selection.allowedGroups);\n return entries\n .filter((entry) => entry.core || !allowed || allowed.has(entry.groupId))\n .filter((entry) => entry.event === selection.event)\n .filter((entry) => !(entry.skipInSubagent && selection.inSubagent))\n .filter((entry) => matchesTool(entry.matcher, selection.toolName))\n .map((entry) => ({\n hook: { command: entry.command, timeout: entry.timeout },\n // The declaring config's root, so a global hook can reach its own scripts\n // through CLAUDE_PLUGIN_ROOT while still running against this repository.\n root: entry.baseDirectory,\n }));\n}\n","import fs from 'node:fs';\n\nimport { doomConfigCandidates } from '@agimon-ai/doompi-config/layeredConfig';\nimport { parse as parseYaml } from 'yaml';\n\nimport {\n HOOKS_FILE,\n FILE_ENCODING,\n FILE_NOT_FOUND_ERROR,\n REGISTRY_SOURCE_ATTRIBUTE,\n} from '../../constants/hookDocuments';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookDocumentReader,\n HookDocumentSource,\n PluginDocumentRead,\n PluginHookConfig,\n PluginHookDocument,\n PluginHookSourceRef,\n RegistryDocument,\n RegistryEntry,\n RegistryRead,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\nimport { registryCacheKey, registryEntries } from '../hookRegistry';\n\nexport interface HookDocumentReaderOptions {\n telemetry?: HookTelemetry;\n /** Where the global `.doom` directory lives. Defaults to the user's home. */\n homeDirectory?: string;\n readFile?: (filePath: string) => Promise<string>;\n warn?: (message: string) => void;\n}\n\nfunction reason(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction isMissingFile(error: unknown): boolean {\n return (error as NodeJS.ErrnoException | undefined)?.code === FILE_NOT_FOUND_ERROR;\n}\n\n/**\n * Reads `.doom/hooks.yaml` from the global config directory and the repository,\n * and the plugin hook configs the harness resolved.\n *\n * Every file is re-read on every dispatch: it costs almost nothing and keeps an\n * in-place rewrite visible, which an mtime check would miss when two writes land\n * inside the same filesystem timestamp granularity. Both caches are keyed on\n * contents instead, so parsing — the expensive half — is what they skip.\n */\nexport function createHookDocumentReader(options: HookDocumentReaderOptions = {}): HookDocumentReader {\n const readFile = options.readFile ?? ((filePath: string) => fs.promises.readFile(filePath, FILE_ENCODING));\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n let cachedRegistryKey: string | undefined;\n let cachedRegistryEntries: RegistryEntry[] = [];\n const pluginConfigCache = new Map<string, { source: string; config: PluginHookConfig }>();\n\n const readSource = async (candidate: {\n filePath: string;\n baseDirectory: string;\n }): Promise<HookDocumentSource | undefined> => {\n try {\n return { baseDirectory: candidate.baseDirectory, text: await readFile(candidate.filePath) };\n } catch (error) {\n if (isMissingFile(error)) return undefined;\n throw error;\n }\n };\n\n const parseSources = (sources: ReadonlyArray<HookDocumentSource>): RegistryEntry[] => {\n const key = registryCacheKey(sources);\n if (cachedRegistryKey === key) return cachedRegistryEntries;\n cachedRegistryEntries = registryEntries(\n sources.map((source) => ({\n baseDirectory: source.baseDirectory,\n document: (parseYaml(source.text) ?? {}) as RegistryDocument,\n })),\n );\n cachedRegistryKey = key;\n return cachedRegistryEntries;\n };\n\n return {\n async registry(repoRoot: string): Promise<RegistryRead> {\n const candidates = doomConfigCandidates(HOOKS_FILE, repoRoot, options.homeDirectory);\n // Named for the error path only. A registry absent from both locations is\n // a repository with no hooks, not a failure, so only a file that exists\n // and cannot be read or parsed reaches the catch below.\n const registryPath = candidates.map((candidate) => candidate.filePath).join(' and ');\n try {\n const sources = (await Promise.all(candidates.map(readSource))).filter(\n (source): source is HookDocumentSource => source !== undefined,\n );\n return { entries: parseSources(sources) };\n } catch (error) {\n // This empties the registry for the whole dispatch, so every repository\n // hook silently stops running rather than any one of them failing.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookRegistryReadFailed, error, {\n 'hook.registry.source': REGISTRY_SOURCE_ATTRIBUTE,\n });\n warn(`[pi-hook] could not read ${registryPath}: ${reason(error)}\\n`);\n return { entries: [], failure: { command: registryPath, message: reason(error), reason: 'registry_read' } };\n }\n },\n\n async plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead> {\n const documents: PluginHookDocument[] = [];\n const failures: PluginDocumentRead['failures'] = [];\n for (const source of sources) {\n try {\n const text = await readFile(source.configPath);\n const cached = pluginConfigCache.get(source.configPath);\n const config = cached?.source === text ? cached.config : (JSON.parse(text) as PluginHookConfig);\n pluginConfigCache.set(source.configPath, { source: text, config });\n documents.push({ pluginRoot: source.pluginRoot, config });\n } catch (error) {\n failures.push({ command: source.configPath, message: reason(error), reason: 'plugin_config' });\n }\n }\n return { documents, failures };\n },\n };\n}\n","export const TOOL_RESULT_EVENT = 'tool_result';\n","import { TOOL_RESULT_EVENT } from '../../constants/hookPayload';\nimport type { HookPayload, HookToolEvent } from '../../types/hooks';\nimport { toClaudeToolName } from '../toolNames';\n\n/** The payload shape a Claude Code session hook is written against. */\nexport function toolHookPayload(\n event: HookToolEvent,\n hookEventName: string,\n repoRoot: string,\n sessionId: string,\n): HookPayload {\n return {\n session_id: sessionId,\n transcript_path: '',\n cwd: repoRoot,\n hook_event_name: hookEventName,\n tool_name: toClaudeToolName(event.toolName),\n tool_input: event.input,\n ...(event.type === TOOL_RESULT_EVENT ? { tool_response: { success: !event.isError, content: event.content } } : {}),\n };\n}\n\n/** Session lifecycle hooks observe no tool, so they only need where and who. */\nexport function sessionHookPayload(sessionId: string, repoRoot: string): HookPayload {\n return { session_id: sessionId, cwd: repoRoot };\n}\n","export const HOOK_SHELL = '/bin/bash';\nexport const HOOK_SHELL_COMMAND_FLAG = '-c';\nexport const HOOK_TERMINATION_GRACE_MS = 2_000;\nexport const DEFAULT_HOOK_TIMEOUT_SECONDS = 10;\nexport const MILLISECONDS_PER_SECOND = 1_000;\nexport const PROCESS_NOT_FOUND_ERROR = 'ESRCH';\nexport const WINDOWS_PLATFORM = 'win32';\nexport const UNKNOWN_EXIT_CODE = -1;\nexport const JSON_LINE_START = '{';\nexport const LINE_BREAK = /\\r?\\n/;\n","import { spawn as spawnProcess } from 'node:child_process';\n\nimport {\n HOOK_SHELL,\n HOOK_SHELL_COMMAND_FLAG,\n HOOK_TERMINATION_GRACE_MS,\n DEFAULT_HOOK_TIMEOUT_SECONDS,\n MILLISECONDS_PER_SECOND,\n PROCESS_NOT_FOUND_ERROR,\n WINDOWS_PLATFORM,\n UNKNOWN_EXIT_CODE,\n JSON_LINE_START,\n LINE_BREAK,\n} from '../../constants/hookRunner';\nimport { HOOK_TELEMETRY_EVENT } from '../../constants/telemetry';\nimport type {\n HookCommand,\n HookDecision,\n HookOutcome,\n HookPayload,\n HookRunOptions,\n HookRunner,\n} from '../../types/hooks';\nimport { type HookTelemetry } from '../../types/telemetry';\n\nexport interface BashHookRunnerOptions {\n telemetry?: HookTelemetry;\n env?: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n spawn?: typeof spawnProcess;\n warn?: (message: string) => void;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return error instanceof Error && 'code' in error ? String(error.code) : undefined;\n}\n\n/**\n * Runs advisory hook commands through bash and reports what each one decided.\n *\n * A hook is a command the repository owns, so it is spawned in its own process\n * group and terminated as one: a hook that starts a server and stalls should\n * not leave the server behind when the timeout fires.\n */\nexport function createBashHookRunner(options: BashHookRunnerOptions = {}): HookRunner {\n const spawn = options.spawn ?? spawnProcess;\n const telemetry = options.telemetry;\n const warn = options.warn ?? ((message: string) => void process.stderr.write(message));\n const useProcessGroup = (options.platform ?? process.platform) !== WINDOWS_PLATFORM;\n\n const signalOwnedProcess = (\n pid: number | undefined,\n kill: (signal: NodeJS.Signals) => void,\n ): ((signal: NodeJS.Signals) => void) => {\n return (signal) => {\n try {\n if (useProcessGroup && pid !== undefined) {\n process.kill(-pid, signal);\n return;\n }\n kill(signal);\n } catch (error) {\n if (errorCode(error) !== PROCESS_NOT_FOUND_ERROR) {\n process.emitWarning(`Could not signal repository hook process: ${String(error)}`);\n }\n }\n };\n };\n\n return {\n run(hook: HookCommand, payload: HookPayload, runOptions: HookRunOptions): Promise<HookOutcome> {\n const timeoutSeconds = hook.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS;\n const environment = options.env ?? process.env;\n return new Promise((resolve) => {\n // -c, not -lc. Hooks run on every tool call, and a login shell would\n // source the developer's profile each time: per-call latency plus hook\n // behavior that varies with their dotfiles. The child inherits the\n // environment below, so it keeps the PATH the launcher started with.\n const child = spawn(HOOK_SHELL, [HOOK_SHELL_COMMAND_FLAG, hook.command], {\n cwd: runOptions.repoRoot,\n env: {\n ...environment,\n CLAUDE_PROJECT_DIR: runOptions.repoRoot,\n CODEX_REPO_ROOT: runOptions.repoRoot,\n ORIGINAL_REPO_PATH: environment.ORIGINAL_REPO_PATH ?? runOptions.repoRoot,\n ...(runOptions.pluginRoot ? { CLAUDE_PLUGIN_ROOT: runOptions.pluginRoot } : {}),\n },\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: useProcessGroup,\n });\n let stdout = '';\n let stderr = '';\n let settled = false;\n let timedOut = false;\n let childExited = false;\n let timeoutExitCode: number | null = null;\n let escalationTimer: NodeJS.Timeout | undefined;\n\n const signal = signalOwnedProcess(child.pid, (value) => child.kill(value));\n\n const ownedProcessIsAlive = (): boolean => {\n if (!useProcessGroup || child.pid === undefined) return !childExited;\n try {\n process.kill(-child.pid, 0);\n return true;\n } catch (error) {\n return errorCode(error) !== PROCESS_NOT_FOUND_ERROR;\n }\n };\n\n const finish = (outcome: HookOutcome = {}): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (escalationTimer) clearTimeout(escalationTimer);\n resolve(outcome);\n };\n\n const finishTimeout = (): void => {\n const message = `Hook timed out after ${timeoutSeconds} seconds.`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'timeout',\n 'hook.exit_code': timeoutExitCode ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook timed out: ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'timeout' } });\n };\n\n const timer = setTimeout(() => {\n timedOut = true;\n signal('SIGTERM');\n escalationTimer = setTimeout(() => {\n if (ownedProcessIsAlive()) signal('SIGKILL');\n finishTimeout();\n }, HOOK_TERMINATION_GRACE_MS);\n }, timeoutSeconds * MILLISECONDS_PER_SECOND);\n\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.once('error', (error: Error) => {\n if (timedOut) return;\n // A guardrail that never ran is indistinguishable from one that\n // passed, which is the whole reason these are worth reporting.\n void telemetry?.recordError(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'spawn_failed' });\n warn(`[pi-hook] advisory hook failed: ${error.message}\\n`);\n finish({ failure: { command: hook.command, message: error.message, reason: 'spawn_failed' } });\n });\n child.once('exit', (code: number | null) => {\n childExited = true;\n timeoutExitCode = code;\n if (timedOut) return;\n if (code !== 0) {\n const message = stderr.trim() || `Advisory hook exited with code ${code ?? 'unknown'}`;\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, message, {\n 'hook.reason': 'non_zero_exit',\n 'hook.exit_code': code ?? UNKNOWN_EXIT_CODE,\n });\n warn(`[pi-hook] advisory hook failed (${code}): ${hook.command}\\n${stderr}`);\n finish({ failure: { command: hook.command, message, reason: 'non_zero_exit' } });\n return;\n }\n const jsonLine = stdout\n .trim()\n .split(LINE_BREAK)\n .reverse()\n .find((line) => line.startsWith(JSON_LINE_START));\n if (!jsonLine) {\n finish();\n return;\n }\n try {\n finish({ decision: JSON.parse(jsonLine) as HookDecision });\n } catch (error) {\n // The hook ran and had an opinion, and it was dropped.\n void telemetry?.recordWarning(HOOK_TELEMETRY_EVENT.hookFailed, error, { 'hook.reason': 'invalid_json' });\n warn(`[pi-hook] advisory hook returned invalid JSON: ${hook.command}\\n`);\n finish({\n failure: {\n command: hook.command,\n message: error instanceof Error ? error.message : String(error),\n reason: 'invalid_json',\n },\n });\n }\n });\n child.stdin?.end(JSON.stringify(payload));\n });\n },\n };\n}\n","export const SERVICE_NAME = 'doom-pi-hook';\nexport const PACKAGE_NAME = '@agimon-ai/doompi-hook';\n","import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nimport { SERVICE_NAME, PACKAGE_NAME } from '../../constants/hookTelemetry';\nimport type { HookTelemetry } from '../../types/telemetry';\n\nexport interface HookTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport function createHookTelemetry(options: HookTelemetryOptions = {}): HookTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n };\n}\n","import type { PluginHookDocument, ResolvedHook } from '../../types/hooks';\nimport { matchesTool } from '../toolNames';\n\n/**\n * The plugin hooks one dispatch runs.\n *\n * Plugin configs are the Claude Code `hooks.json` shape verbatim: an event name\n * maps to groups, each with an optional matcher and a list of commands. Order\n * follows the declaration order of the plugins the harness resolved.\n */\nexport function selectPluginHooks(\n documents: ReadonlyArray<PluginHookDocument>,\n eventName: string,\n toolName?: string,\n): ResolvedHook[] {\n const matches: ResolvedHook[] = [];\n for (const document of documents) {\n for (const group of document.config.hooks?.[eventName] ?? []) {\n if (!matchesTool(group.matcher, toolName)) continue;\n for (const hook of group.hooks ?? []) matches.push({ hook, root: document.pluginRoot });\n }\n }\n return matches;\n}\n","export const HOOK_EVENT = {\n sessionStart: 'SessionStart',\n preToolUse: 'PreToolUse',\n postToolUse: 'PostToolUse',\n stop: 'Stop',\n sessionEnd: 'SessionEnd',\n} as const;\n"],"mappings":"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"}