@agimon-ai/doompi-hook 0.0.1-alpha.7 → 0.0.1-alpha.71

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.
Files changed (130) hide show
  1. package/README.md +11 -4
  2. package/dist/apiContracts.cjs +17 -0
  3. package/dist/apiContracts.cjs.map +1 -0
  4. package/dist/apiContracts.d.mts +53 -0
  5. package/dist/apiContracts.d.mts.map +1 -0
  6. package/dist/apiContracts.mjs +13 -0
  7. package/dist/apiContracts.mjs.map +1 -0
  8. package/dist/extensions/mcp.cjs +26 -0
  9. package/dist/extensions/mcp.cjs.map +1 -0
  10. package/dist/extensions/mcp.d.mts +5 -0
  11. package/dist/extensions/mcp.d.mts.map +1 -0
  12. package/dist/extensions/mcp.mjs +21 -0
  13. package/dist/extensions/mcp.mjs.map +1 -0
  14. package/dist/extensions/pi.cjs +337 -1
  15. package/dist/extensions/pi.cjs.map +1 -0
  16. package/dist/extensions/pi.d.mts +6 -2
  17. package/dist/extensions/pi.d.mts.map +1 -0
  18. package/dist/extensions/pi.mjs +332 -1
  19. package/dist/extensions/pi.mjs.map +1 -0
  20. package/dist/extensions/server.cjs +83 -0
  21. package/dist/extensions/server.cjs.map +1 -0
  22. package/dist/extensions/server.d.mts +5 -0
  23. package/dist/extensions/server.d.mts.map +1 -0
  24. package/dist/extensions/server.mjs +78 -0
  25. package/dist/extensions/server.mjs.map +1 -0
  26. package/dist/hooks-CReMXrU9.cjs +615 -0
  27. package/dist/hooks-CReMXrU9.cjs.map +1 -0
  28. package/dist/hooks-D2s_5hzZ.mjs +473 -0
  29. package/dist/hooks-D2s_5hzZ.mjs.map +1 -0
  30. package/dist/index-LpLV04fM.d.mts +297 -0
  31. package/dist/index-LpLV04fM.d.mts.map +1 -0
  32. package/dist/index.cjs +22 -1
  33. package/dist/index.d.mts +2 -12
  34. package/dist/index.mjs +2 -1
  35. package/package.json +53 -14
  36. package/dist/_virtual/_rolldown/runtime.cjs +0 -1
  37. package/dist/adapters/hookDocuments.cjs +0 -2
  38. package/dist/adapters/hookDocuments.cjs.map +0 -1
  39. package/dist/adapters/hookDocuments.d.cts +0 -23
  40. package/dist/adapters/hookDocuments.d.cts.map +0 -1
  41. package/dist/adapters/hookDocuments.d.mts +0 -23
  42. package/dist/adapters/hookDocuments.d.mts.map +0 -1
  43. package/dist/adapters/hookDocuments.mjs +0 -2
  44. package/dist/adapters/hookDocuments.mjs.map +0 -1
  45. package/dist/adapters/hookRunner.cjs +0 -2
  46. package/dist/adapters/hookRunner.cjs.map +0 -1
  47. package/dist/adapters/hookRunner.d.cts +0 -22
  48. package/dist/adapters/hookRunner.d.cts.map +0 -1
  49. package/dist/adapters/hookRunner.d.mts +0 -22
  50. package/dist/adapters/hookRunner.d.mts.map +0 -1
  51. package/dist/adapters/hookRunner.mjs +0 -2
  52. package/dist/adapters/hookRunner.mjs.map +0 -1
  53. package/dist/adapters/pi/extension.cjs +0 -2
  54. package/dist/adapters/pi/extension.cjs.map +0 -1
  55. package/dist/adapters/pi/extension.d.cts +0 -14
  56. package/dist/adapters/pi/extension.d.cts.map +0 -1
  57. package/dist/adapters/pi/extension.d.mts +0 -14
  58. package/dist/adapters/pi/extension.d.mts.map +0 -1
  59. package/dist/adapters/pi/extension.mjs +0 -2
  60. package/dist/adapters/pi/extension.mjs.map +0 -1
  61. package/dist/adapters/pi/hookHandlers.cjs +0 -8
  62. package/dist/adapters/pi/hookHandlers.cjs.map +0 -1
  63. package/dist/adapters/pi/hookHandlers.mjs +0 -8
  64. package/dist/adapters/pi/hookHandlers.mjs.map +0 -1
  65. package/dist/adapters/telemetry/logSinkTelemetry.cjs +0 -2
  66. package/dist/adapters/telemetry/logSinkTelemetry.cjs.map +0 -1
  67. package/dist/adapters/telemetry/logSinkTelemetry.d.cts +0 -14
  68. package/dist/adapters/telemetry/logSinkTelemetry.d.cts.map +0 -1
  69. package/dist/adapters/telemetry/logSinkTelemetry.d.mts +0 -14
  70. package/dist/adapters/telemetry/logSinkTelemetry.d.mts.map +0 -1
  71. package/dist/adapters/telemetry/logSinkTelemetry.mjs +0 -2
  72. package/dist/adapters/telemetry/logSinkTelemetry.mjs.map +0 -1
  73. package/dist/extensions/pi.d.cts +0 -2
  74. package/dist/index.d.cts +0 -12
  75. package/dist/services/hookDecisions.cjs +0 -3
  76. package/dist/services/hookDecisions.cjs.map +0 -1
  77. package/dist/services/hookDecisions.d.cts +0 -19
  78. package/dist/services/hookDecisions.d.cts.map +0 -1
  79. package/dist/services/hookDecisions.d.mts +0 -19
  80. package/dist/services/hookDecisions.d.mts.map +0 -1
  81. package/dist/services/hookDecisions.mjs +0 -3
  82. package/dist/services/hookDecisions.mjs.map +0 -1
  83. package/dist/services/hookPayload.cjs +0 -2
  84. package/dist/services/hookPayload.cjs.map +0 -1
  85. package/dist/services/hookPayload.d.cts +0 -9
  86. package/dist/services/hookPayload.d.cts.map +0 -1
  87. package/dist/services/hookPayload.d.mts +0 -9
  88. package/dist/services/hookPayload.d.mts.map +0 -1
  89. package/dist/services/hookPayload.mjs +0 -2
  90. package/dist/services/hookPayload.mjs.map +0 -1
  91. package/dist/services/hookRegistry.cjs +0 -2
  92. package/dist/services/hookRegistry.cjs.map +0 -1
  93. package/dist/services/hookRegistry.d.cts +0 -37
  94. package/dist/services/hookRegistry.d.cts.map +0 -1
  95. package/dist/services/hookRegistry.d.mts +0 -37
  96. package/dist/services/hookRegistry.d.mts.map +0 -1
  97. package/dist/services/hookRegistry.mjs +0 -2
  98. package/dist/services/hookRegistry.mjs.map +0 -1
  99. package/dist/services/pluginHooks.cjs +0 -2
  100. package/dist/services/pluginHooks.cjs.map +0 -1
  101. package/dist/services/pluginHooks.d.cts +0 -13
  102. package/dist/services/pluginHooks.d.cts.map +0 -1
  103. package/dist/services/pluginHooks.d.mts +0 -13
  104. package/dist/services/pluginHooks.d.mts.map +0 -1
  105. package/dist/services/pluginHooks.mjs +0 -2
  106. package/dist/services/pluginHooks.mjs.map +0 -1
  107. package/dist/services/toolNames.cjs +0 -2
  108. package/dist/services/toolNames.cjs.map +0 -1
  109. package/dist/services/toolNames.d.cts +0 -8
  110. package/dist/services/toolNames.d.cts.map +0 -1
  111. package/dist/services/toolNames.d.mts +0 -8
  112. package/dist/services/toolNames.d.mts.map +0 -1
  113. package/dist/services/toolNames.mjs +0 -2
  114. package/dist/services/toolNames.mjs.map +0 -1
  115. package/dist/types/hooks.cjs +0 -2
  116. package/dist/types/hooks.cjs.map +0 -1
  117. package/dist/types/hooks.d.cts +0 -145
  118. package/dist/types/hooks.d.cts.map +0 -1
  119. package/dist/types/hooks.d.mts +0 -145
  120. package/dist/types/hooks.d.mts.map +0 -1
  121. package/dist/types/hooks.mjs +0 -2
  122. package/dist/types/hooks.mjs.map +0 -1
  123. package/dist/types/telemetry.cjs +0 -2
  124. package/dist/types/telemetry.cjs.map +0 -1
  125. package/dist/types/telemetry.d.cts +0 -20
  126. package/dist/types/telemetry.d.cts.map +0 -1
  127. package/dist/types/telemetry.d.mts +0 -20
  128. package/dist/types/telemetry.d.mts.map +0 -1
  129. package/dist/types/telemetry.mjs +0 -2
  130. package/dist/types/telemetry.mjs.map +0 -1
@@ -0,0 +1,297 @@
1
+ import { spawn } from "node:child_process";
2
+ import { DoomTelemetryOptions } from "@agimon-ai/doompi-telemetry";
3
+ import "@agimon-ai/doompi-config/types";
4
+ import "@deepseek-ai/cordis";
5
+ import "@earendil-works/pi-coding-agent";
6
+ //#region src/constants/hooks.d.ts
7
+ declare const HOOK_EVENT: {
8
+ readonly sessionStart: 'SessionStart';
9
+ readonly preToolUse: 'PreToolUse';
10
+ readonly postToolUse: 'PostToolUse';
11
+ readonly stop: 'Stop';
12
+ readonly sessionEnd: 'SessionEnd';
13
+ };
14
+ //#endregion
15
+ //#region src/types/hooks.d.ts
16
+ /**
17
+ * The hook document contract and the ports that run it.
18
+ *
19
+ * Every name here is written in Claude Code's vocabulary, because that is what
20
+ * `.doom/hooks.yaml` and plugin `hooks.json` files are authored against. The
21
+ * translation onto Pi's lifecycle happens at the adapter boundary.
22
+ */
23
+ /** Registry event names. Each is used twice per dispatch, so drift would be silent. */
24
+ type HookEventName = (typeof HOOK_EVENT)[keyof typeof HOOK_EVENT];
25
+ interface HookCommand {
26
+ command: string;
27
+ /** Seconds before the hook is terminated. Defaults to DEFAULT_HOOK_TIMEOUT_SECONDS. */
28
+ timeout?: number;
29
+ }
30
+ /** What one registry row declares for the `pi` frontend. */
31
+ interface RegistryBinding {
32
+ matcher?: string;
33
+ command: string;
34
+ timeout?: number;
35
+ skipInSubagent?: boolean;
36
+ order?: number;
37
+ }
38
+ /** A registry row plus the group membership used to include or drop it. */
39
+ interface RegistryEntry extends RegistryBinding {
40
+ event: string;
41
+ order: number;
42
+ /** Declaration order across every source, used as the sort tiebreaker. */
43
+ position: number;
44
+ groupId: string;
45
+ core: boolean;
46
+ /** Root of the config that declared the group, exported as CLAUDE_PLUGIN_ROOT. */
47
+ baseDirectory: string;
48
+ }
49
+ interface RegistryGroup {
50
+ core?: boolean;
51
+ hooks?: Array<{
52
+ event: string;
53
+ pi?: RegistryBinding;
54
+ }>;
55
+ }
56
+ interface RegistryDocument {
57
+ groups?: Record<string, RegistryGroup>;
58
+ }
59
+ /** One `.doom/hooks.yaml` as read from disk, before it is parsed. */
60
+ interface HookDocumentSource {
61
+ baseDirectory: string;
62
+ text: string;
63
+ }
64
+ /** One `.doom/hooks.yaml` after parsing, tagged with the root that declared it. */
65
+ interface ParsedRegistrySource {
66
+ baseDirectory: string;
67
+ document: RegistryDocument;
68
+ }
69
+ interface PluginHookGroup {
70
+ matcher?: string;
71
+ hooks?: HookCommand[];
72
+ }
73
+ interface PluginHookConfig {
74
+ hooks?: Record<string, PluginHookGroup[]>;
75
+ }
76
+ /** A parsed plugin config plus the root exported to its commands. */
77
+ interface PluginHookDocument {
78
+ pluginRoot: string;
79
+ config: PluginHookConfig;
80
+ }
81
+ /** A hook command paired with the config root it runs against. */
82
+ interface ResolvedHook {
83
+ hook: HookCommand;
84
+ root: string;
85
+ }
86
+ /** The JSON a hook may write on stdout to steer or block the call it observed. */
87
+ interface HookDecision {
88
+ decision?: string;
89
+ reason?: string;
90
+ hookSpecificOutput?: {
91
+ permissionDecision?: string;
92
+ reason?: string;
93
+ additionalContext?: string;
94
+ };
95
+ }
96
+ type HookFailureReason = 'spawn_failed' | 'non_zero_exit' | 'timeout' | 'invalid_json' | 'registry_read' | 'plugin_config';
97
+ interface HookFailure {
98
+ command: string;
99
+ message: string;
100
+ reason: HookFailureReason;
101
+ }
102
+ interface HookOutcome {
103
+ decision?: HookDecision;
104
+ failure?: HookFailure;
105
+ }
106
+ /**
107
+ * The tool fields a payload is built from, named without importing Pi.
108
+ *
109
+ * Pi's ToolCallEvent and ToolResultEvent both satisfy this structurally, so the
110
+ * adapter forwards them unchanged and the payload builder stays host-neutral.
111
+ */
112
+ interface HookToolEvent {
113
+ readonly type: string;
114
+ readonly toolName: string;
115
+ readonly input: unknown;
116
+ readonly content?: readonly unknown[];
117
+ readonly isError?: boolean;
118
+ }
119
+ /** The JSON document handed to a hook on stdin. */
120
+ type HookPayload = Record<string, unknown>;
121
+ interface HookRunOptions {
122
+ repoRoot: string;
123
+ /** Exported as CLAUDE_PLUGIN_ROOT so a hook can reach its own scripts. */
124
+ pluginRoot?: string;
125
+ }
126
+ /** Runs one hook command and reports what it decided or how it failed. */
127
+ interface HookRunner {
128
+ run(hook: HookCommand, payload: HookPayload, options: HookRunOptions): Promise<HookOutcome>;
129
+ }
130
+ /** Registry rows for this repository, or the failure that emptied them. */
131
+ interface RegistryRead {
132
+ entries: RegistryEntry[];
133
+ failure?: HookFailure;
134
+ }
135
+ /** Plugin configs that could be read, and one failure per config that could not. */
136
+ interface PluginDocumentRead {
137
+ documents: PluginHookDocument[];
138
+ failures: HookFailure[];
139
+ }
140
+ /** Where a plugin declares its hooks. Mirrors the harness state entry. */
141
+ interface PluginHookSourceRef {
142
+ readonly pluginRoot: string;
143
+ readonly configPath: string;
144
+ }
145
+ /** Reads the hook documents a session runs from. */
146
+ interface HookDocumentReader {
147
+ registry(repoRoot: string): Promise<RegistryRead>;
148
+ plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead>;
149
+ }
150
+ //#endregion
151
+ //#region src/services/hookDecisions/index.d.ts
152
+ declare function decisionsFrom(outcomes: ReadonlyArray<HookOutcome>): HookDecision[];
153
+ declare function failuresFrom(outcomes: ReadonlyArray<HookOutcome>): HookFailure[];
154
+ /** Both spellings a hook may use to refuse the call it observed. */
155
+ declare function isDenied(decision: HookDecision | undefined): boolean;
156
+ declare function decisionReason(decision: HookDecision | undefined): string | undefined;
157
+ /** Context a hook wants added to the conversation rather than used to block. */
158
+ declare function additionalContextsFrom(decisions: ReadonlyArray<HookDecision>): string[];
159
+ /** What a post-tool hook has to say, whichever field it said it in. */
160
+ declare function toolResultMessages(decisions: ReadonlyArray<HookDecision>): string[];
161
+ /**
162
+ * A guardrail that never ran looks exactly like one that passed, so the agent is
163
+ * told which checks were missed and what its options are.
164
+ */
165
+ declare function hookFailureMessage(failures: ReadonlyArray<HookFailure>): string;
166
+ //#endregion
167
+ //#region src/constants/telemetry.d.ts
168
+ declare const HOOK_TELEMETRY_EVENT: {
169
+ readonly hookFailed: 'doom_pi_hook.failed';
170
+ readonly hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed';
171
+ };
172
+ //#endregion
173
+ //#region src/types/telemetry.d.ts
174
+ /** Telemetry events this package reports, and the port that records them. */
175
+ type HookTelemetryEventName = (typeof HOOK_TELEMETRY_EVENT)[keyof typeof HOOK_TELEMETRY_EVENT];
176
+ type HookTelemetryAttributes = Record<string, string | number | boolean>;
177
+ /**
178
+ * A hook that never ran is indistinguishable from one that passed, which is why
179
+ * both a failed run and an unreadable registry are worth reporting rather than
180
+ * swallowing.
181
+ */
182
+ interface HookTelemetry {
183
+ recordError(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;
184
+ recordWarning(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;
185
+ }
186
+ //#endregion
187
+ //#region src/services/hookDocuments/index.d.ts
188
+ interface HookDocumentReaderOptions {
189
+ telemetry?: HookTelemetry;
190
+ /** Where the global `.doom` directory lives. Defaults to the user's home. */
191
+ homeDirectory?: string;
192
+ readFile?: (filePath: string) => Promise<string>;
193
+ warn?: (message: string) => void;
194
+ }
195
+ /**
196
+ * Reads `.doom/hooks.yaml` from the global config directory and the repository,
197
+ * and the plugin hook configs the harness resolved.
198
+ *
199
+ * Every file is re-read on every dispatch: it costs almost nothing and keeps an
200
+ * in-place rewrite visible, which an mtime check would miss when two writes land
201
+ * inside the same filesystem timestamp granularity. Both caches are keyed on
202
+ * contents instead, so parsing — the expensive half — is what they skip.
203
+ */
204
+ declare function createHookDocumentReader(options?: HookDocumentReaderOptions): HookDocumentReader;
205
+ //#endregion
206
+ //#region src/services/hookPayload/index.d.ts
207
+ /** The payload shape a Claude Code session hook is written against. */
208
+ declare function toolHookPayload(event: HookToolEvent, hookEventName: string, repoRoot: string, sessionId: string): HookPayload;
209
+ /** Session lifecycle hooks observe no tool, so they only need where and who. */
210
+ declare function sessionHookPayload(sessionId: string, repoRoot: string): HookPayload;
211
+ //#endregion
212
+ //#region src/services/hookRegistry/index.d.ts
213
+ /** Which rows a dispatch keeps, once the session's selection is known. */
214
+ interface RegistrySelection {
215
+ event: string;
216
+ toolName?: string;
217
+ /** Selected group ids, or undefined for "every group", the standalone default. */
218
+ allowedGroups?: readonly string[];
219
+ inSubagent: boolean;
220
+ }
221
+ /**
222
+ * Identity of a set of registry sources, for callers that cache the parse.
223
+ *
224
+ * Keyed on contents rather than mtime, so it stays correct for an in-place
225
+ * rewrite and needs no invalidation hook. Reading both files is cheap; parsing
226
+ * is what a cache built on this skips.
227
+ */
228
+ declare function registryCacheKey(sources: ReadonlyArray<HookDocumentSource>): string;
229
+ /**
230
+ * Flattens parsed registry documents into sorted rows, keeping only bindings
231
+ * that declare a `pi` frontend.
232
+ *
233
+ * A later source replaces the group of the same id outright rather than merging
234
+ * into it, so a repository that redefines a group owns it completely.
235
+ */
236
+ declare function registryEntries(sources: ReadonlyArray<ParsedRegistrySource>): RegistryEntry[];
237
+ /**
238
+ * The rows one dispatch runs.
239
+ *
240
+ * Group selection changes when /mode switches mid-session, so inclusion is
241
+ * applied per call rather than being baked into a cached parse. Core groups
242
+ * always load; the rest are gated by the layers the harness resolved.
243
+ */
244
+ declare function selectRegistryHooks(entries: ReadonlyArray<RegistryEntry>, selection: RegistrySelection): ResolvedHook[];
245
+ //#endregion
246
+ //#region src/services/hookRunner/index.d.ts
247
+ interface BashHookRunnerOptions {
248
+ telemetry?: HookTelemetry;
249
+ env?: NodeJS.ProcessEnv;
250
+ platform?: NodeJS.Platform;
251
+ spawn?: typeof spawn;
252
+ warn?: (message: string) => void;
253
+ }
254
+ /**
255
+ * Runs advisory hook commands through bash and reports what each one decided.
256
+ *
257
+ * A hook is a command the repository owns, so it is spawned in its own process
258
+ * group and terminated as one: a hook that starts a server and stalls should
259
+ * not leave the server behind when the timeout fires.
260
+ */
261
+ declare function createBashHookRunner(options?: BashHookRunnerOptions): HookRunner;
262
+ //#endregion
263
+ //#region src/services/hookRuntime/type.d.ts
264
+ interface HookExtensionOptions {
265
+ telemetry?: HookTelemetry;
266
+ runner?: HookRunner;
267
+ documents?: HookDocumentReader;
268
+ }
269
+ //#endregion
270
+ //#region src/services/hookTelemetry/index.d.ts
271
+ interface HookTelemetryOptions {
272
+ cwd?: string;
273
+ workspaceRoot?: string;
274
+ env?: NodeJS.ProcessEnv;
275
+ telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;
276
+ warn?: (message: string) => void;
277
+ }
278
+ declare function createHookTelemetry(options?: HookTelemetryOptions): HookTelemetry;
279
+ //#endregion
280
+ //#region src/services/pluginHooks/index.d.ts
281
+ /**
282
+ * The plugin hooks one dispatch runs.
283
+ *
284
+ * Plugin configs are the Claude Code `hooks.json` shape verbatim: an event name
285
+ * maps to groups, each with an optional matcher and a list of commands. Order
286
+ * follows the declaration order of the plugins the harness resolved.
287
+ */
288
+ declare function selectPluginHooks(documents: ReadonlyArray<PluginHookDocument>, eventName: string, toolName?: string): ResolvedHook[];
289
+ //#endregion
290
+ //#region src/services/toolNames/index.d.ts
291
+ /** The Claude name a hook matcher expects for a tool Pi just ran. */
292
+ declare function toClaudeToolName(tool: string): string;
293
+ /** Whether a matcher, if the row declares one, accepts the tool that fired. */
294
+ declare function matchesTool(matcher: string | undefined, toolName: string | undefined): boolean;
295
+ //#endregion
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
@@ -0,0 +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"}
package/dist/index.cjs CHANGED
@@ -1 +1,22 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types/telemetry.cjs"),t=require("./services/toolNames.cjs"),n=require("./services/hookRegistry.cjs"),r=require("./adapters/hookDocuments.cjs"),i=require("./adapters/hookRunner.cjs"),a=require("./adapters/telemetry/logSinkTelemetry.cjs"),o=require("./services/hookDecisions.cjs"),s=require("./services/hookPayload.cjs"),c=require("./services/pluginHooks.cjs"),l=require("./types/hooks.cjs"),u=require("./adapters/pi/extension.cjs");exports.HOOK_EVENT=l.HOOK_EVENT,exports.HOOK_TELEMETRY_EVENT=e.HOOK_TELEMETRY_EVENT,exports.additionalContextsFrom=o.additionalContextsFrom,exports.createBashHookRunner=i.createBashHookRunner,exports.createHookDocumentReader=r.createHookDocumentReader,exports.createHookTelemetry=a.createHookTelemetry,exports.decisionReason=o.decisionReason,exports.decisionsFrom=o.decisionsFrom,exports.failuresFrom=o.failuresFrom,exports.hookExtension=u.default,exports.hookFailureMessage=o.hookFailureMessage,exports.isDenied=o.isDenied,exports.matchesTool=t.matchesTool,exports.registryCacheKey=n.registryCacheKey,exports.registryEntries=n.registryEntries,exports.selectPluginHooks=c.selectPluginHooks,exports.selectRegistryHooks=n.selectRegistryHooks,exports.sessionHookPayload=s.sessionHookPayload,exports.toClaudeToolName=t.toClaudeToolName,exports.toolHookPayload=s.toolHookPayload,exports.toolResultMessages=o.toolResultMessages;
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_hooks = require("./hooks-CReMXrU9.cjs");
3
+ exports.HOOK_EVENT = require_hooks.HOOK_EVENT;
4
+ exports.HOOK_TELEMETRY_EVENT = require_hooks.HOOK_TELEMETRY_EVENT;
5
+ exports.additionalContextsFrom = require_hooks.additionalContextsFrom;
6
+ exports.createBashHookRunner = require_hooks.createBashHookRunner;
7
+ exports.createHookDocumentReader = require_hooks.createHookDocumentReader;
8
+ exports.createHookTelemetry = require_hooks.createHookTelemetry;
9
+ exports.decisionReason = require_hooks.decisionReason;
10
+ exports.decisionsFrom = require_hooks.decisionsFrom;
11
+ exports.failuresFrom = require_hooks.failuresFrom;
12
+ exports.hookFailureMessage = require_hooks.hookFailureMessage;
13
+ exports.isDenied = require_hooks.isDenied;
14
+ exports.matchesTool = require_hooks.matchesTool;
15
+ exports.registryCacheKey = require_hooks.registryCacheKey;
16
+ exports.registryEntries = require_hooks.registryEntries;
17
+ exports.selectPluginHooks = require_hooks.selectPluginHooks;
18
+ exports.selectRegistryHooks = require_hooks.selectRegistryHooks;
19
+ exports.sessionHookPayload = require_hooks.sessionHookPayload;
20
+ exports.toClaudeToolName = require_hooks.toClaudeToolName;
21
+ exports.toolHookPayload = require_hooks.toolHookPayload;
22
+ exports.toolResultMessages = require_hooks.toolResultMessages;
package/dist/index.d.mts CHANGED
@@ -1,12 +1,2 @@
1
- import { HOOK_EVENT, HookCommand, HookDecision, HookDocumentReader, HookDocumentSource, HookEventName, HookFailure, HookFailureReason, HookOutcome, HookPayload, HookRunOptions, HookRunner, HookToolEvent, ParsedRegistrySource, PluginDocumentRead, PluginHookConfig, PluginHookDocument, PluginHookGroup, PluginHookSourceRef, RegistryBinding, RegistryDocument, RegistryEntry, RegistryGroup, RegistryRead, ResolvedHook } from "./types/hooks.mjs";
2
- import { HOOK_TELEMETRY_EVENT, HookTelemetry, HookTelemetryAttributes, HookTelemetryEventName } from "./types/telemetry.mjs";
3
- import hookExtension, { HookExtensionOptions } from "./adapters/pi/extension.mjs";
4
- import { HookDocumentReaderOptions, createHookDocumentReader } from "./adapters/hookDocuments.mjs";
5
- import { BashHookRunnerOptions, createBashHookRunner } from "./adapters/hookRunner.mjs";
6
- import { HookTelemetryOptions, createHookTelemetry } from "./adapters/telemetry/logSinkTelemetry.mjs";
7
- import { additionalContextsFrom, decisionReason, decisionsFrom, failuresFrom, hookFailureMessage, isDenied, toolResultMessages } from "./services/hookDecisions.mjs";
8
- import { sessionHookPayload, toolHookPayload } from "./services/hookPayload.mjs";
9
- import { RegistrySelection, registryCacheKey, registryEntries, selectRegistryHooks } from "./services/hookRegistry.mjs";
10
- import { selectPluginHooks } from "./services/pluginHooks.mjs";
11
- import { matchesTool, toClaudeToolName } from "./services/toolNames.mjs";
12
- 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, hookExtension, hookFailureMessage, isDenied, matchesTool, registryCacheKey, registryEntries, selectPluginHooks, selectRegistryHooks, sessionHookPayload, toClaudeToolName, toolHookPayload, toolResultMessages };
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";
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 +1,2 @@
1
- import{HOOK_TELEMETRY_EVENT as e}from"./types/telemetry.mjs";import{matchesTool as t,toClaudeToolName as n}from"./services/toolNames.mjs";import{registryCacheKey as r,registryEntries as i,selectRegistryHooks as a}from"./services/hookRegistry.mjs";import{createHookDocumentReader as o}from"./adapters/hookDocuments.mjs";import{createBashHookRunner as s}from"./adapters/hookRunner.mjs";import{createHookTelemetry as c}from"./adapters/telemetry/logSinkTelemetry.mjs";import{additionalContextsFrom as l,decisionReason as u,decisionsFrom as d,failuresFrom as f,hookFailureMessage as p,isDenied as m,toolResultMessages as h}from"./services/hookDecisions.mjs";import{sessionHookPayload as g,toolHookPayload as _}from"./services/hookPayload.mjs";import{selectPluginHooks as v}from"./services/pluginHooks.mjs";import{HOOK_EVENT as y}from"./types/hooks.mjs";import b from"./adapters/pi/extension.mjs";export{y as HOOK_EVENT,e as HOOK_TELEMETRY_EVENT,l as additionalContextsFrom,s as createBashHookRunner,o as createHookDocumentReader,c as createHookTelemetry,u as decisionReason,d as decisionsFrom,f as failuresFrom,b as hookExtension,p as hookFailureMessage,m as isDenied,t as matchesTool,r as registryCacheKey,i as registryEntries,v as selectPluginHooks,a as selectRegistryHooks,g as sessionHookPayload,n as toClaudeToolName,_ as toolHookPayload,h as toolResultMessages};
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";
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.7",
3
+ "version": "0.0.1-alpha.71",
4
4
  "description": "Claude-Code-compatible repository and plugin hook runner for DoomPi sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -16,8 +16,16 @@
16
16
  "repository-hooks"
17
17
  ],
18
18
  "homepage": "https://agimon.ai",
19
+ "bugs": {
20
+ "url": "https://github.com/AgiFlow/doompi/issues"
21
+ },
19
22
  "license": "MIT",
20
23
  "author": "Vuong Ngo",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/AgiFlow/doompi.git",
27
+ "directory": "packages/default/doompi-hook"
28
+ },
21
29
  "files": [
22
30
  "dist",
23
31
  "llms.txt",
@@ -28,6 +36,7 @@
28
36
  ],
29
37
  "type": "module",
30
38
  "main": "./dist/index.cjs",
39
+ "module": "./dist/index.mjs",
31
40
  "types": "./dist/index.d.mts",
32
41
  "jsnext:main": "./dist/index.mjs",
33
42
  "exports": {
@@ -36,34 +45,45 @@
36
45
  "import": "./dist/index.mjs",
37
46
  "require": "./dist/index.cjs"
38
47
  },
48
+ "./apiContracts": {
49
+ "types": "./dist/apiContracts.d.mts",
50
+ "import": "./dist/apiContracts.mjs",
51
+ "require": "./dist/apiContracts.cjs"
52
+ },
39
53
  "./extensions/pi": {
40
54
  "types": "./dist/extensions/pi.d.mts",
41
55
  "import": "./dist/extensions/pi.mjs",
42
56
  "require": "./dist/extensions/pi.cjs"
43
57
  },
58
+ "./extensions/server": {
59
+ "types": "./dist/extensions/server.d.mts",
60
+ "import": "./dist/extensions/server.mjs",
61
+ "require": "./dist/extensions/server.cjs"
62
+ },
44
63
  "./package.json": "./package.json"
45
64
  },
46
65
  "publishConfig": {
47
66
  "access": "public"
48
67
  },
49
68
  "dependencies": {
50
- "@deepseek-ai/cordis": "4.0.1",
51
- "yaml": "2.9.0",
52
- "@agimon-ai/doompi-config": "0.0.1-alpha.29",
53
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.29",
54
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.29"
69
+ "@agimon-ai/doompi-config": "0.0.1-alpha.93",
70
+ "@agimon-ai/doompi-core": "0.0.1-alpha.94",
71
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.85",
72
+ "@deepseek-ai/cordis": "4.0.2",
73
+ "yaml": "2.9.1"
55
74
  },
56
75
  "devDependencies": {
57
- "@earendil-works/pi-coding-agent": "0.84.2",
58
- "@types/node": "26.2.0",
59
- "@vitest/coverage-v8": "4.1.11",
60
- "tsdown": "0.22.14",
76
+ "@agimon-ai/doompi-build": "0.0.1-alpha.17",
77
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.83",
78
+ "@earendil-works/pi-coding-agent": "0.87.1",
79
+ "@types/node": "26.6.1",
80
+ "@vitest/coverage-v8": "5.0.1",
81
+ "tsdown": "0.23.0",
61
82
  "typescript": "7.0.2",
62
- "vitest": "4.1.11",
63
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.27"
83
+ "vitest": "5.0.1"
64
84
  },
65
85
  "peerDependencies": {
66
- "@earendil-works/pi-coding-agent": "0.84.2"
86
+ "@earendil-works/pi-coding-agent": "0.87.1"
67
87
  },
68
88
  "peerDependenciesMeta": {
69
89
  "@earendil-works/pi-coding-agent": {
@@ -73,13 +93,32 @@
73
93
  "engines": {
74
94
  "node": ">=22.19.0"
75
95
  },
96
+ "doompiMcp": {
97
+ "entry": "./generated/mcp.ts",
98
+ "dist": "./dist/extensions/mcp.mjs",
99
+ "scopes": [
100
+ "session"
101
+ ]
102
+ },
103
+ "doompiServer": {
104
+ "entry": "./generated/server.ts",
105
+ "dist": "./dist/extensions/server.mjs",
106
+ "scopes": [
107
+ "session"
108
+ ],
109
+ "contracts": {
110
+ "entry": "./src/exports/apiContracts.ts",
111
+ "dist": "./dist/apiContracts.mjs"
112
+ }
113
+ },
76
114
  "pi": {
77
115
  "extensions": [
78
116
  "./dist/extensions/pi.mjs"
79
117
  ]
80
118
  },
81
119
  "scripts": {
82
- "build": "tsdown",
120
+ "build": "tsdown && pnpm build:mcp",
121
+ "build:mcp": "tsdown --config tsdown.mcp.config.ts",
83
122
  "test": "vitest --run",
84
123
  "typecheck": "tsc --noEmit",
85
124
  "lint": "oxlint . && oxfmt . --check",
@@ -1 +0,0 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
@@ -1,2 +0,0 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/telemetry.cjs"),n=require("../services/hookRegistry.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("@agimon-ai/doompi-config/layeredConfig"),a=require("yaml");function o(e){return e instanceof Error?e.message:String(e)}function s(e){return e?.code===`ENOENT`}function c(e={}){let c=e.readFile??(e=>r.default.promises.readFile(e,`utf8`)),l=e.telemetry,u=e.warn??(e=>void process.stderr.write(e)),d,f=[],p=new Map,m=async e=>{try{return{baseDirectory:e.baseDirectory,text:await c(e.filePath)}}catch(e){if(s(e))return;throw e}},h=e=>{let t=n.registryCacheKey(e);return d===t?f:(f=n.registryEntries(e.map(e=>({baseDirectory:e.baseDirectory,document:(0,a.parse)(e.text)??{}}))),d=t,f)};return{async registry(n){let r=(0,i.doomConfigCandidates)(`hooks.yaml`,n,e.homeDirectory),a=r.map(e=>e.filePath).join(` and `);try{let e=(await Promise.all(r.map(m))).filter(e=>e!==void 0);return{entries:h(e)}}catch(e){return l?.recordError(t.HOOK_TELEMETRY_EVENT.hookRegistryReadFailed,e,{"hook.registry.source":`repository`}),u(`[pi-hook] could not read ${a}: ${o(e)}\n`),{entries:[],failure:{command:a,message:o(e),reason:`registry_read`}}}},async plugins(e){let t=[],n=[];for(let r of e)try{let e=await c(r.configPath),n=p.get(r.configPath),i=n?.source===e?n.config:JSON.parse(e);p.set(r.configPath,{source:e,config:i}),t.push({pluginRoot:r.pluginRoot,config:i})}catch(e){n.push({command:r.configPath,message:o(e),reason:`plugin_config`})}return{documents:t,failures:n}}}}exports.createHookDocumentReader=c;
2
- //# sourceMappingURL=hookDocuments.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hookDocuments.cjs","names":["fs","registryCacheKey","registryEntries","parseYaml","doomConfigCandidates","HOOK_TELEMETRY_EVENT"],"sources":["../../src/adapters/hookDocuments.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { doomConfigCandidates } from '@agimon-ai/doompi-config/layeredConfig';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n HookDocumentReader,\n HookDocumentSource,\n PluginDocumentRead,\n PluginHookConfig,\n PluginHookDocument,\n PluginHookSourceRef,\n RegistryDocument,\n RegistryEntry,\n RegistryRead,\n} from '../types/hooks.ts';\nimport { HOOK_TELEMETRY_EVENT, type HookTelemetry } from '../types/telemetry.ts';\nimport { registryCacheKey, registryEntries } from '../services/hookRegistry.ts';\n\nconst HOOKS_FILE = 'hooks.yaml';\nconst FILE_ENCODING = 'utf8';\nconst FILE_NOT_FOUND_ERROR = 'ENOENT';\nconst REGISTRY_SOURCE_ATTRIBUTE = 'repository';\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"],"mappings":"uPA8BA,SAAS,EAAO,EAAwB,CACtC,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAEA,SAAS,EAAc,EAAyB,CAC9C,OAAQ,GAA6C,OAAS,QAChE,CAWA,SAAgB,EAAyB,EAAqC,CAAC,EAAuB,CACpG,IAAM,EAAW,EAAQ,WAAc,GAAqBA,EAAAA,QAAG,SAAS,SAAS,EAAU,MAAa,GAClG,EAAY,EAAQ,UACpB,EAAO,EAAQ,OAAU,GAAoB,KAAK,QAAQ,OAAO,MAAM,CAAO,GAChF,EACA,EAAyC,CAAC,EACxC,EAAoB,IAAI,IAExB,EAAa,KAAO,IAGqB,CAC7C,GAAI,CACF,MAAO,CAAE,cAAe,EAAU,cAAe,KAAM,MAAM,EAAS,EAAU,QAAQ,CAAE,CAC5F,OAAS,EAAO,CACd,GAAI,EAAc,CAAK,EAAG,OAC1B,MAAM,CACR,CACF,EAEM,EAAgB,GAAgE,CACpF,IAAM,EAAMC,EAAAA,iBAAiB,CAAO,EASpC,OARI,IAAsB,EAAY,GACtC,EAAwBC,EAAAA,gBACtB,EAAQ,IAAK,IAAY,CACvB,cAAe,EAAO,cACtB,UAAA,EAAWC,EAAAA,MAAAA,CAAU,EAAO,IAAI,GAAK,CAAC,CACxC,EAAE,CACJ,EACA,EAAoB,EACb,EACT,EAEA,MAAO,CACL,MAAM,SAAS,EAAyC,CACtD,IAAM,GAAA,EAAaC,EAAAA,qBAAAA,CAAqB,aAAY,EAAU,EAAQ,aAAa,EAI7E,EAAe,EAAW,IAAK,GAAc,EAAU,QAAQ,CAAC,CAAC,KAAK,OAAO,EACnF,GAAI,CACF,IAAM,GAAW,MAAM,QAAQ,IAAI,EAAW,IAAI,CAAU,CAAC,EAAA,CAAG,OAC7D,GAAyC,IAAW,IAAA,EACvD,EACA,MAAO,CAAE,QAAS,EAAa,CAAO,CAAE,CAC1C,OAAS,EAAO,CAOd,OAJA,GAAgB,YAAYC,EAAAA,qBAAqB,uBAAwB,EAAO,CAC9E,uBAAwB,YAC1B,CAAC,EACD,EAAK,4BAA4B,EAAa,IAAI,EAAO,CAAK,EAAE,GAAG,EAC5D,CAAE,QAAS,CAAC,EAAG,QAAS,CAAE,QAAS,EAAc,QAAS,EAAO,CAAK,EAAG,OAAQ,eAAgB,CAAE,CAC5G,CACF,EAEA,MAAM,QAAQ,EAAsE,CAClF,IAAM,EAAkC,CAAC,EACnC,EAA2C,CAAC,EAClD,IAAK,IAAM,KAAU,EACnB,GAAI,CACF,IAAM,EAAO,MAAM,EAAS,EAAO,UAAU,EACvC,EAAS,EAAkB,IAAI,EAAO,UAAU,EAChD,EAAS,GAAQ,SAAW,EAAO,EAAO,OAAU,KAAK,MAAM,CAAI,EACzE,EAAkB,IAAI,EAAO,WAAY,CAAE,OAAQ,EAAM,QAAO,CAAC,EACjE,EAAU,KAAK,CAAE,WAAY,EAAO,WAAY,QAAO,CAAC,CAC1D,OAAS,EAAO,CACd,EAAS,KAAK,CAAE,QAAS,EAAO,WAAY,QAAS,EAAO,CAAK,EAAG,OAAQ,eAAgB,CAAC,CAC/F,CAEF,MAAO,CAAE,YAAW,UAAS,CAC/B,CACF,CACF"}
@@ -1,23 +0,0 @@
1
- import { HookDocumentReader } from "../types/hooks.cjs";
2
- import { HookTelemetry } from "../types/telemetry.cjs";
3
- //#region src/adapters/hookDocuments.d.ts
4
- interface HookDocumentReaderOptions {
5
- telemetry?: HookTelemetry;
6
- /** Where the global `.doom` directory lives. Defaults to the user's home. */
7
- homeDirectory?: string;
8
- readFile?: (filePath: string) => Promise<string>;
9
- warn?: (message: string) => void;
10
- }
11
- /**
12
- * Reads `.doom/hooks.yaml` from the global config directory and the repository,
13
- * and the plugin hook configs the harness resolved.
14
- *
15
- * Every file is re-read on every dispatch: it costs almost nothing and keeps an
16
- * in-place rewrite visible, which an mtime check would miss when two writes land
17
- * inside the same filesystem timestamp granularity. Both caches are keyed on
18
- * contents instead, so parsing — the expensive half — is what they skip.
19
- */
20
- declare function createHookDocumentReader(options?: HookDocumentReaderOptions): HookDocumentReader;
21
- //#endregion
22
- export { HookDocumentReaderOptions, createHookDocumentReader };
23
- //# sourceMappingURL=hookDocuments.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hookDocuments.d.cts","names":[],"sources":["../../src/adapters/hookDocuments.ts"],"mappings":";;;UAsBiB;EACf,YAAY;;EAEZ;EACA,YAAY,qBAAqB;EACjC,QAAQ;;;;;;;;;;;iBAoBM,yBAAyB,UAAS,4BAAiC"}
@@ -1,23 +0,0 @@
1
- import { HookDocumentReader } from "../types/hooks.mjs";
2
- import { HookTelemetry } from "../types/telemetry.mjs";
3
- //#region src/adapters/hookDocuments.d.ts
4
- interface HookDocumentReaderOptions {
5
- telemetry?: HookTelemetry;
6
- /** Where the global `.doom` directory lives. Defaults to the user's home. */
7
- homeDirectory?: string;
8
- readFile?: (filePath: string) => Promise<string>;
9
- warn?: (message: string) => void;
10
- }
11
- /**
12
- * Reads `.doom/hooks.yaml` from the global config directory and the repository,
13
- * and the plugin hook configs the harness resolved.
14
- *
15
- * Every file is re-read on every dispatch: it costs almost nothing and keeps an
16
- * in-place rewrite visible, which an mtime check would miss when two writes land
17
- * inside the same filesystem timestamp granularity. Both caches are keyed on
18
- * contents instead, so parsing — the expensive half — is what they skip.
19
- */
20
- declare function createHookDocumentReader(options?: HookDocumentReaderOptions): HookDocumentReader;
21
- //#endregion
22
- export { HookDocumentReaderOptions, createHookDocumentReader };
23
- //# sourceMappingURL=hookDocuments.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hookDocuments.d.mts","names":[],"sources":["../../src/adapters/hookDocuments.ts"],"mappings":";;;UAsBiB;EACf,YAAY;;EAEZ;EACA,YAAY,qBAAqB;EACjC,QAAQ;;;;;;;;;;;iBAoBM,yBAAyB,UAAS,4BAAiC"}
@@ -1,2 +0,0 @@
1
- import{HOOK_TELEMETRY_EVENT as e}from"../types/telemetry.mjs";import{registryCacheKey as t,registryEntries as n}from"../services/hookRegistry.mjs";import r from"node:fs";import{doomConfigCandidates as i}from"@agimon-ai/doompi-config/layeredConfig";import{parse as a}from"yaml";function o(e){return e instanceof Error?e.message:String(e)}function s(e){return e?.code===`ENOENT`}function c(c={}){let l=c.readFile??(e=>r.promises.readFile(e,`utf8`)),u=c.telemetry,d=c.warn??(e=>void process.stderr.write(e)),f,p=[],m=new Map,h=async e=>{try{return{baseDirectory:e.baseDirectory,text:await l(e.filePath)}}catch(e){if(s(e))return;throw e}},g=e=>{let r=t(e);return f===r?p:(p=n(e.map(e=>({baseDirectory:e.baseDirectory,document:a(e.text)??{}}))),f=r,p)};return{async registry(t){let n=i(`hooks.yaml`,t,c.homeDirectory),r=n.map(e=>e.filePath).join(` and `);try{let e=(await Promise.all(n.map(h))).filter(e=>e!==void 0);return{entries:g(e)}}catch(t){return u?.recordError(e.hookRegistryReadFailed,t,{"hook.registry.source":`repository`}),d(`[pi-hook] could not read ${r}: ${o(t)}\n`),{entries:[],failure:{command:r,message:o(t),reason:`registry_read`}}}},async plugins(e){let t=[],n=[];for(let r of e)try{let e=await l(r.configPath),n=m.get(r.configPath),i=n?.source===e?n.config:JSON.parse(e);m.set(r.configPath,{source:e,config:i}),t.push({pluginRoot:r.pluginRoot,config:i})}catch(e){n.push({command:r.configPath,message:o(e),reason:`plugin_config`})}return{documents:t,failures:n}}}}export{c as createHookDocumentReader};
2
- //# sourceMappingURL=hookDocuments.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hookDocuments.mjs","names":["parseYaml"],"sources":["../../src/adapters/hookDocuments.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { doomConfigCandidates } from '@agimon-ai/doompi-config/layeredConfig';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n HookDocumentReader,\n HookDocumentSource,\n PluginDocumentRead,\n PluginHookConfig,\n PluginHookDocument,\n PluginHookSourceRef,\n RegistryDocument,\n RegistryEntry,\n RegistryRead,\n} from '../types/hooks.ts';\nimport { HOOK_TELEMETRY_EVENT, type HookTelemetry } from '../types/telemetry.ts';\nimport { registryCacheKey, registryEntries } from '../services/hookRegistry.ts';\n\nconst HOOKS_FILE = 'hooks.yaml';\nconst FILE_ENCODING = 'utf8';\nconst FILE_NOT_FOUND_ERROR = 'ENOENT';\nconst REGISTRY_SOURCE_ATTRIBUTE = 'repository';\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"],"mappings":"qRA8BA,SAAS,EAAO,EAAwB,CACtC,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAEA,SAAS,EAAc,EAAyB,CAC9C,OAAQ,GAA6C,OAAS,QAChE,CAWA,SAAgB,EAAyB,EAAqC,CAAC,EAAuB,CACpG,IAAM,EAAW,EAAQ,WAAc,GAAqB,EAAG,SAAS,SAAS,EAAU,MAAa,GAClG,EAAY,EAAQ,UACpB,EAAO,EAAQ,OAAU,GAAoB,KAAK,QAAQ,OAAO,MAAM,CAAO,GAChF,EACA,EAAyC,CAAC,EACxC,EAAoB,IAAI,IAExB,EAAa,KAAO,IAGqB,CAC7C,GAAI,CACF,MAAO,CAAE,cAAe,EAAU,cAAe,KAAM,MAAM,EAAS,EAAU,QAAQ,CAAE,CAC5F,OAAS,EAAO,CACd,GAAI,EAAc,CAAK,EAAG,OAC1B,MAAM,CACR,CACF,EAEM,EAAgB,GAAgE,CACpF,IAAM,EAAM,EAAiB,CAAO,EASpC,OARI,IAAsB,EAAY,GACtC,EAAwB,EACtB,EAAQ,IAAK,IAAY,CACvB,cAAe,EAAO,cACtB,SAAWA,EAAU,EAAO,IAAI,GAAK,CAAC,CACxC,EAAE,CACJ,EACA,EAAoB,EACb,EACT,EAEA,MAAO,CACL,MAAM,SAAS,EAAyC,CACtD,IAAM,EAAa,EAAqB,aAAY,EAAU,EAAQ,aAAa,EAI7E,EAAe,EAAW,IAAK,GAAc,EAAU,QAAQ,CAAC,CAAC,KAAK,OAAO,EACnF,GAAI,CACF,IAAM,GAAW,MAAM,QAAQ,IAAI,EAAW,IAAI,CAAU,CAAC,EAAA,CAAG,OAC7D,GAAyC,IAAW,IAAA,EACvD,EACA,MAAO,CAAE,QAAS,EAAa,CAAO,CAAE,CAC1C,OAAS,EAAO,CAOd,OAJA,GAAgB,YAAY,EAAqB,uBAAwB,EAAO,CAC9E,uBAAwB,YAC1B,CAAC,EACD,EAAK,4BAA4B,EAAa,IAAI,EAAO,CAAK,EAAE,GAAG,EAC5D,CAAE,QAAS,CAAC,EAAG,QAAS,CAAE,QAAS,EAAc,QAAS,EAAO,CAAK,EAAG,OAAQ,eAAgB,CAAE,CAC5G,CACF,EAEA,MAAM,QAAQ,EAAsE,CAClF,IAAM,EAAkC,CAAC,EACnC,EAA2C,CAAC,EAClD,IAAK,IAAM,KAAU,EACnB,GAAI,CACF,IAAM,EAAO,MAAM,EAAS,EAAO,UAAU,EACvC,EAAS,EAAkB,IAAI,EAAO,UAAU,EAChD,EAAS,GAAQ,SAAW,EAAO,EAAO,OAAU,KAAK,MAAM,CAAI,EACzE,EAAkB,IAAI,EAAO,WAAY,CAAE,OAAQ,EAAM,QAAO,CAAC,EACjE,EAAU,KAAK,CAAE,WAAY,EAAO,WAAY,QAAO,CAAC,CAC1D,OAAS,EAAO,CACd,EAAS,KAAK,CAAE,QAAS,EAAO,WAAY,QAAS,EAAO,CAAK,EAAG,OAAQ,eAAgB,CAAC,CAC/F,CAEF,MAAO,CAAE,YAAW,UAAS,CAC/B,CACF,CACF"}
@@ -1,2 +0,0 @@
1
- const e=require("../types/telemetry.cjs");let t=require("node:child_process");const n=`ESRCH`,r=/\r?\n/;function i(e){return e instanceof Error&&`code`in e?String(e.code):void 0}function a(a={}){let o=a.spawn??t.spawn,s=a.telemetry,c=a.warn??(e=>void process.stderr.write(e)),l=(a.platform??process.platform)!==`win32`,u=(e,t)=>r=>{try{if(l&&e!==void 0){process.kill(-e,r);return}t(r)}catch(e){i(e)!==n&&process.emitWarning(`Could not signal repository hook process: ${String(e)}`)}};return{run(t,d,f){let p=t.timeout??10,m=a.env??process.env;return new Promise(a=>{let h=o(`/bin/bash`,[`-c`,t.command],{cwd:f.repoRoot,env:{...m,CLAUDE_PROJECT_DIR:f.repoRoot,CODEX_REPO_ROOT:f.repoRoot,ORIGINAL_REPO_PATH:m.ORIGINAL_REPO_PATH??f.repoRoot,...f.pluginRoot?{CLAUDE_PLUGIN_ROOT:f.pluginRoot}:{}},stdio:[`pipe`,`pipe`,`pipe`],detached:l}),g=``,_=``,v=!1,y=!1,b=!1,x=null,S,C=u(h.pid,e=>h.kill(e)),w=()=>{if(!l||h.pid===void 0)return!b;try{return process.kill(-h.pid,0),!0}catch(e){return i(e)!==n}},T=(e={})=>{v||(v=!0,clearTimeout(D),S&&clearTimeout(S),a(e))},E=()=>{let n=`Hook timed out after ${p} seconds.`;s?.recordWarning(e.HOOK_TELEMETRY_EVENT.hookFailed,n,{"hook.reason":`timeout`,"hook.exit_code":x??-1}),c(`[pi-hook] advisory hook timed out: ${t.command}\n${_}`),T({failure:{command:t.command,message:n,reason:`timeout`}})},D=setTimeout(()=>{y=!0,C(`SIGTERM`),S=setTimeout(()=>{w()&&C(`SIGKILL`),E()},2e3)},p*1e3);h.stdout?.on(`data`,e=>{g+=e.toString()}),h.stderr?.on(`data`,e=>{_+=e.toString()}),h.once(`error`,n=>{y||(s?.recordError(e.HOOK_TELEMETRY_EVENT.hookFailed,n,{"hook.reason":`spawn_failed`}),c(`[pi-hook] advisory hook failed: ${n.message}\n`),T({failure:{command:t.command,message:n.message,reason:`spawn_failed`}}))}),h.once(`exit`,n=>{if(b=!0,x=n,y)return;if(n!==0){let r=_.trim()||`Advisory hook exited with code ${n??`unknown`}`;s?.recordWarning(e.HOOK_TELEMETRY_EVENT.hookFailed,r,{"hook.reason":`non_zero_exit`,"hook.exit_code":n??-1}),c(`[pi-hook] advisory hook failed (${n}): ${t.command}\n${_}`),T({failure:{command:t.command,message:r,reason:`non_zero_exit`}});return}let i=g.trim().split(r).reverse().find(e=>e.startsWith(`{`));if(!i){T();return}try{T({decision:JSON.parse(i)})}catch(n){s?.recordWarning(e.HOOK_TELEMETRY_EVENT.hookFailed,n,{"hook.reason":`invalid_json`}),c(`[pi-hook] advisory hook returned invalid JSON: ${t.command}\n`),T({failure:{command:t.command,message:n instanceof Error?n.message:String(n),reason:`invalid_json`}})}}),h.stdin?.end(JSON.stringify(d))})}}}exports.createBashHookRunner=a;
2
- //# sourceMappingURL=hookRunner.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hookRunner.cjs","names":["spawnProcess","HOOK_TELEMETRY_EVENT"],"sources":["../../src/adapters/hookRunner.ts"],"sourcesContent":["import { spawn as spawnProcess } from 'node:child_process';\nimport type {\n HookCommand,\n HookDecision,\n HookOutcome,\n HookPayload,\n HookRunOptions,\n HookRunner,\n} from '../types/hooks.ts';\nimport { HOOK_TELEMETRY_EVENT, type HookTelemetry } from '../types/telemetry.ts';\n\nconst HOOK_SHELL = '/bin/bash';\nconst HOOK_SHELL_COMMAND_FLAG = '-c';\nconst HOOK_TERMINATION_GRACE_MS = 2_000;\nconst DEFAULT_HOOK_TIMEOUT_SECONDS = 10;\nconst MILLISECONDS_PER_SECOND = 1_000;\nconst PROCESS_NOT_FOUND_ERROR = 'ESRCH';\nconst WINDOWS_PLATFORM = 'win32';\nconst UNKNOWN_EXIT_CODE = -1;\nconst JSON_LINE_START = '{';\nconst LINE_BREAK = /\\r?\\n/;\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"],"mappings":"8EAWA,MAKM,EAA0B,QAI1B,EAAa,QAUnB,SAAS,EAAU,EAAoC,CACrD,OAAO,aAAiB,OAAS,SAAU,EAAQ,OAAO,EAAM,IAAI,EAAI,IAAA,EAC1E,CASA,SAAgB,EAAqB,EAAiC,CAAC,EAAe,CACpF,IAAM,EAAQ,EAAQ,OAASA,EAAAA,MACzB,EAAY,EAAQ,UACpB,EAAO,EAAQ,OAAU,GAAoB,KAAK,QAAQ,OAAO,MAAM,CAAO,GAC9E,GAAmB,EAAQ,UAAY,QAAQ,YAAc,QAE7D,GACJ,EACA,IAEQ,GAAW,CACjB,GAAI,CACF,GAAI,GAAmB,IAAQ,IAAA,GAAW,CACxC,QAAQ,KAAK,CAAC,EAAK,CAAM,EACzB,MACF,CACA,EAAK,CAAM,CACb,OAAS,EAAO,CACV,EAAU,CAAK,IAAM,GACvB,QAAQ,YAAY,6CAA6C,OAAO,CAAK,GAAG,CAEpF,CACF,EAGF,MAAO,CACL,IAAI,EAAmB,EAAsB,EAAkD,CAC7F,IAAM,EAAiB,EAAK,SAAW,GACjC,EAAc,EAAQ,KAAO,QAAQ,IAC3C,OAAO,IAAI,QAAS,GAAY,CAK9B,IAAM,EAAQ,EAAM,YAAY,CAAC,KAAyB,EAAK,OAAO,EAAG,CACvE,IAAK,EAAW,SAChB,IAAK,CACH,GAAG,EACH,mBAAoB,EAAW,SAC/B,gBAAiB,EAAW,SAC5B,mBAAoB,EAAY,oBAAsB,EAAW,SACjE,GAAI,EAAW,WAAa,CAAE,mBAAoB,EAAW,UAAW,EAAI,CAAC,CAC/E,EACA,MAAO,CAAC,OAAQ,OAAQ,MAAM,EAC9B,SAAU,CACZ,CAAC,EACG,EAAS,GACT,EAAS,GACT,EAAU,GACV,EAAW,GACX,EAAc,GACd,EAAiC,KACjC,EAEE,EAAS,EAAmB,EAAM,IAAM,GAAU,EAAM,KAAK,CAAK,CAAC,EAEnE,MAAqC,CACzC,GAAI,CAAC,GAAmB,EAAM,MAAQ,IAAA,GAAW,MAAO,CAAC,EACzD,GAAI,CAEF,OADA,QAAQ,KAAK,CAAC,EAAM,IAAK,CAAC,EACnB,EACT,OAAS,EAAO,CACd,OAAO,EAAU,CAAK,IAAM,CAC9B,CACF,EAEM,GAAU,EAAuB,CAAC,IAAY,CAC9C,IACJ,EAAU,GACV,aAAa,CAAK,EACd,GAAiB,aAAa,CAAe,EACjD,EAAQ,CAAO,EACjB,EAEM,MAA4B,CAChC,IAAM,EAAU,wBAAwB,EAAe,WACvD,GAAgB,cAAcC,EAAAA,qBAAqB,WAAY,EAAS,CACtE,cAAe,UACf,iBAAkB,GAAmB,EACvC,CAAC,EACD,EAAK,sCAAsC,EAAK,QAAQ,IAAI,GAAQ,EACpE,EAAO,CAAE,QAAS,CAAE,QAAS,EAAK,QAAS,UAAS,OAAQ,SAAU,CAAE,CAAC,CAC3E,EAEM,EAAQ,eAAiB,CAC7B,EAAW,GACX,EAAO,SAAS,EAChB,EAAkB,eAAiB,CAC7B,EAAoB,GAAG,EAAO,SAAS,EAC3C,EAAc,CAChB,EAAG,GAAyB,CAC9B,EAAG,EAAiB,GAAuB,EAE3C,EAAM,QAAQ,GAAG,OAAS,GAAkB,CAC1C,GAAU,EAAM,SAAS,CAC3B,CAAC,EACD,EAAM,QAAQ,GAAG,OAAS,GAAkB,CAC1C,GAAU,EAAM,SAAS,CAC3B,CAAC,EACD,EAAM,KAAK,QAAU,GAAiB,CAChC,IAGJ,GAAgB,YAAYA,EAAAA,qBAAqB,WAAY,EAAO,CAAE,cAAe,cAAe,CAAC,EACrG,EAAK,mCAAmC,EAAM,QAAQ,GAAG,EACzD,EAAO,CAAE,QAAS,CAAE,QAAS,EAAK,QAAS,QAAS,EAAM,QAAS,OAAQ,cAAe,CAAE,CAAC,EAC/F,CAAC,EACD,EAAM,KAAK,OAAS,GAAwB,CAG1C,GAFA,EAAc,GACd,EAAkB,EACd,EAAU,OACd,GAAI,IAAS,EAAG,CACd,IAAM,EAAU,EAAO,KAAK,GAAK,kCAAkC,GAAQ,YAC3E,GAAgB,cAAcA,EAAAA,qBAAqB,WAAY,EAAS,CACtE,cAAe,gBACf,iBAAkB,GAAQ,EAC5B,CAAC,EACD,EAAK,mCAAmC,EAAK,KAAK,EAAK,QAAQ,IAAI,GAAQ,EAC3E,EAAO,CAAE,QAAS,CAAE,QAAS,EAAK,QAAS,UAAS,OAAQ,eAAgB,CAAE,CAAC,EAC/E,MACF,CACA,IAAM,EAAW,EACd,KAAK,CAAC,CACN,MAAM,CAAU,CAAC,CACjB,QAAQ,CAAC,CACT,KAAM,GAAS,EAAK,WAAW,GAAe,CAAC,EAClD,GAAI,CAAC,EAAU,CACb,EAAO,EACP,MACF,CACA,GAAI,CACF,EAAO,CAAE,SAAU,KAAK,MAAM,CAAQ,CAAkB,CAAC,CAC3D,OAAS,EAAO,CAEd,GAAgB,cAAcA,EAAAA,qBAAqB,WAAY,EAAO,CAAE,cAAe,cAAe,CAAC,EACvG,EAAK,kDAAkD,EAAK,QAAQ,GAAG,EACvE,EAAO,CACL,QAAS,CACP,QAAS,EAAK,QACd,QAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAC9D,OAAQ,cACV,CACF,CAAC,CACH,CACF,CAAC,EACD,EAAM,OAAO,IAAI,KAAK,UAAU,CAAO,CAAC,CAC1C,CAAC,CACH,CACF,CACF"}