@agimon-ai/doompi-hook 0.0.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/adapters/hookDocuments.cjs +2 -0
  5. package/dist/adapters/hookDocuments.cjs.map +1 -0
  6. package/dist/adapters/hookDocuments.d.cts +23 -0
  7. package/dist/adapters/hookDocuments.d.cts.map +1 -0
  8. package/dist/adapters/hookDocuments.d.mts +23 -0
  9. package/dist/adapters/hookDocuments.d.mts.map +1 -0
  10. package/dist/adapters/hookDocuments.mjs +2 -0
  11. package/dist/adapters/hookDocuments.mjs.map +1 -0
  12. package/dist/adapters/hookRunner.cjs +2 -0
  13. package/dist/adapters/hookRunner.cjs.map +1 -0
  14. package/dist/adapters/hookRunner.d.cts +22 -0
  15. package/dist/adapters/hookRunner.d.cts.map +1 -0
  16. package/dist/adapters/hookRunner.d.mts +22 -0
  17. package/dist/adapters/hookRunner.d.mts.map +1 -0
  18. package/dist/adapters/hookRunner.mjs +2 -0
  19. package/dist/adapters/hookRunner.mjs.map +1 -0
  20. package/dist/adapters/pi/extension.cjs +2 -0
  21. package/dist/adapters/pi/extension.cjs.map +1 -0
  22. package/dist/adapters/pi/extension.d.cts +14 -0
  23. package/dist/adapters/pi/extension.d.cts.map +1 -0
  24. package/dist/adapters/pi/extension.d.mts +14 -0
  25. package/dist/adapters/pi/extension.d.mts.map +1 -0
  26. package/dist/adapters/pi/extension.mjs +2 -0
  27. package/dist/adapters/pi/extension.mjs.map +1 -0
  28. package/dist/adapters/pi/hookHandlers.cjs +8 -0
  29. package/dist/adapters/pi/hookHandlers.cjs.map +1 -0
  30. package/dist/adapters/pi/hookHandlers.mjs +8 -0
  31. package/dist/adapters/pi/hookHandlers.mjs.map +1 -0
  32. package/dist/adapters/telemetry/logSinkTelemetry.cjs +2 -0
  33. package/dist/adapters/telemetry/logSinkTelemetry.cjs.map +1 -0
  34. package/dist/adapters/telemetry/logSinkTelemetry.d.cts +14 -0
  35. package/dist/adapters/telemetry/logSinkTelemetry.d.cts.map +1 -0
  36. package/dist/adapters/telemetry/logSinkTelemetry.d.mts +14 -0
  37. package/dist/adapters/telemetry/logSinkTelemetry.d.mts.map +1 -0
  38. package/dist/adapters/telemetry/logSinkTelemetry.mjs +2 -0
  39. package/dist/adapters/telemetry/logSinkTelemetry.mjs.map +1 -0
  40. package/dist/extensions/pi.cjs +1 -0
  41. package/dist/extensions/pi.d.cts +2 -0
  42. package/dist/extensions/pi.d.mts +2 -0
  43. package/dist/extensions/pi.mjs +1 -0
  44. package/dist/index.cjs +1 -0
  45. package/dist/index.d.cts +12 -0
  46. package/dist/index.d.mts +12 -0
  47. package/dist/index.mjs +1 -0
  48. package/dist/services/hookDecisions.cjs +3 -0
  49. package/dist/services/hookDecisions.cjs.map +1 -0
  50. package/dist/services/hookDecisions.d.cts +19 -0
  51. package/dist/services/hookDecisions.d.cts.map +1 -0
  52. package/dist/services/hookDecisions.d.mts +19 -0
  53. package/dist/services/hookDecisions.d.mts.map +1 -0
  54. package/dist/services/hookDecisions.mjs +3 -0
  55. package/dist/services/hookDecisions.mjs.map +1 -0
  56. package/dist/services/hookPayload.cjs +2 -0
  57. package/dist/services/hookPayload.cjs.map +1 -0
  58. package/dist/services/hookPayload.d.cts +9 -0
  59. package/dist/services/hookPayload.d.cts.map +1 -0
  60. package/dist/services/hookPayload.d.mts +9 -0
  61. package/dist/services/hookPayload.d.mts.map +1 -0
  62. package/dist/services/hookPayload.mjs +2 -0
  63. package/dist/services/hookPayload.mjs.map +1 -0
  64. package/dist/services/hookRegistry.cjs +2 -0
  65. package/dist/services/hookRegistry.cjs.map +1 -0
  66. package/dist/services/hookRegistry.d.cts +37 -0
  67. package/dist/services/hookRegistry.d.cts.map +1 -0
  68. package/dist/services/hookRegistry.d.mts +37 -0
  69. package/dist/services/hookRegistry.d.mts.map +1 -0
  70. package/dist/services/hookRegistry.mjs +2 -0
  71. package/dist/services/hookRegistry.mjs.map +1 -0
  72. package/dist/services/pluginHooks.cjs +2 -0
  73. package/dist/services/pluginHooks.cjs.map +1 -0
  74. package/dist/services/pluginHooks.d.cts +13 -0
  75. package/dist/services/pluginHooks.d.cts.map +1 -0
  76. package/dist/services/pluginHooks.d.mts +13 -0
  77. package/dist/services/pluginHooks.d.mts.map +1 -0
  78. package/dist/services/pluginHooks.mjs +2 -0
  79. package/dist/services/pluginHooks.mjs.map +1 -0
  80. package/dist/services/toolNames.cjs +2 -0
  81. package/dist/services/toolNames.cjs.map +1 -0
  82. package/dist/services/toolNames.d.cts +8 -0
  83. package/dist/services/toolNames.d.cts.map +1 -0
  84. package/dist/services/toolNames.d.mts +8 -0
  85. package/dist/services/toolNames.d.mts.map +1 -0
  86. package/dist/services/toolNames.mjs +2 -0
  87. package/dist/services/toolNames.mjs.map +1 -0
  88. package/dist/types/hooks.cjs +2 -0
  89. package/dist/types/hooks.cjs.map +1 -0
  90. package/dist/types/hooks.d.cts +145 -0
  91. package/dist/types/hooks.d.cts.map +1 -0
  92. package/dist/types/hooks.d.mts +145 -0
  93. package/dist/types/hooks.d.mts.map +1 -0
  94. package/dist/types/hooks.mjs +2 -0
  95. package/dist/types/hooks.mjs.map +1 -0
  96. package/dist/types/telemetry.cjs +2 -0
  97. package/dist/types/telemetry.cjs.map +1 -0
  98. package/dist/types/telemetry.d.cts +20 -0
  99. package/dist/types/telemetry.d.cts.map +1 -0
  100. package/dist/types/telemetry.d.mts +20 -0
  101. package/dist/types/telemetry.d.mts.map +1 -0
  102. package/dist/types/telemetry.mjs +2 -0
  103. package/dist/types/telemetry.mjs.map +1 -0
  104. package/llms.txt +11 -0
  105. package/package.json +88 -0
  106. package/src/prompts/doompi-author-hook/SKILL.md +51 -0
@@ -0,0 +1,145 @@
1
+ //#region src/types/hooks.d.ts
2
+ /**
3
+ * The hook document contract and the ports that run it.
4
+ *
5
+ * Every name here is written in Claude Code's vocabulary, because that is what
6
+ * `.doom/hooks.yaml` and plugin `hooks.json` files are authored against. The
7
+ * translation onto Pi's lifecycle happens at the adapter boundary.
8
+ */
9
+ /** Registry event names. Each is used twice per dispatch, so drift would be silent. */
10
+ declare const HOOK_EVENT: {
11
+ readonly sessionStart: 'SessionStart';
12
+ readonly preToolUse: 'PreToolUse';
13
+ readonly postToolUse: 'PostToolUse';
14
+ readonly stop: 'Stop';
15
+ readonly sessionEnd: 'SessionEnd';
16
+ };
17
+ type HookEventName = (typeof HOOK_EVENT)[keyof typeof HOOK_EVENT];
18
+ interface HookCommand {
19
+ command: string;
20
+ /** Seconds before the hook is terminated. Defaults to DEFAULT_HOOK_TIMEOUT_SECONDS. */
21
+ timeout?: number;
22
+ }
23
+ /** What one registry row declares for the `pi` frontend. */
24
+ interface RegistryBinding {
25
+ matcher?: string;
26
+ command: string;
27
+ timeout?: number;
28
+ skipInSubagent?: boolean;
29
+ order?: number;
30
+ }
31
+ /** A registry row plus the group membership used to include or drop it. */
32
+ interface RegistryEntry extends RegistryBinding {
33
+ event: string;
34
+ order: number;
35
+ /** Declaration order across every source, used as the sort tiebreaker. */
36
+ position: number;
37
+ groupId: string;
38
+ core: boolean;
39
+ /** Root of the config that declared the group, exported as CLAUDE_PLUGIN_ROOT. */
40
+ baseDirectory: string;
41
+ }
42
+ interface RegistryGroup {
43
+ core?: boolean;
44
+ hooks?: Array<{
45
+ event: string;
46
+ pi?: RegistryBinding;
47
+ }>;
48
+ }
49
+ interface RegistryDocument {
50
+ groups?: Record<string, RegistryGroup>;
51
+ }
52
+ /** One `.doom/hooks.yaml` as read from disk, before it is parsed. */
53
+ interface HookDocumentSource {
54
+ baseDirectory: string;
55
+ text: string;
56
+ }
57
+ /** One `.doom/hooks.yaml` after parsing, tagged with the root that declared it. */
58
+ interface ParsedRegistrySource {
59
+ baseDirectory: string;
60
+ document: RegistryDocument;
61
+ }
62
+ interface PluginHookGroup {
63
+ matcher?: string;
64
+ hooks?: HookCommand[];
65
+ }
66
+ interface PluginHookConfig {
67
+ hooks?: Record<string, PluginHookGroup[]>;
68
+ }
69
+ /** A parsed plugin config plus the root exported to its commands. */
70
+ interface PluginHookDocument {
71
+ pluginRoot: string;
72
+ config: PluginHookConfig;
73
+ }
74
+ /** A hook command paired with the config root it runs against. */
75
+ interface ResolvedHook {
76
+ hook: HookCommand;
77
+ root: string;
78
+ }
79
+ /** The JSON a hook may write on stdout to steer or block the call it observed. */
80
+ interface HookDecision {
81
+ decision?: string;
82
+ reason?: string;
83
+ hookSpecificOutput?: {
84
+ permissionDecision?: string;
85
+ reason?: string;
86
+ additionalContext?: string;
87
+ };
88
+ }
89
+ type HookFailureReason = 'spawn_failed' | 'non_zero_exit' | 'timeout' | 'invalid_json' | 'registry_read' | 'plugin_config';
90
+ interface HookFailure {
91
+ command: string;
92
+ message: string;
93
+ reason: HookFailureReason;
94
+ }
95
+ interface HookOutcome {
96
+ decision?: HookDecision;
97
+ failure?: HookFailure;
98
+ }
99
+ /**
100
+ * The tool fields a payload is built from, named without importing Pi.
101
+ *
102
+ * Pi's ToolCallEvent and ToolResultEvent both satisfy this structurally, so the
103
+ * adapter forwards them unchanged and the payload builder stays host-neutral.
104
+ */
105
+ interface HookToolEvent {
106
+ readonly type: string;
107
+ readonly toolName: string;
108
+ readonly input: unknown;
109
+ readonly content?: readonly unknown[];
110
+ readonly isError?: boolean;
111
+ }
112
+ /** The JSON document handed to a hook on stdin. */
113
+ type HookPayload = Record<string, unknown>;
114
+ interface HookRunOptions {
115
+ repoRoot: string;
116
+ /** Exported as CLAUDE_PLUGIN_ROOT so a hook can reach its own scripts. */
117
+ pluginRoot?: string;
118
+ }
119
+ /** Runs one hook command and reports what it decided or how it failed. */
120
+ interface HookRunner {
121
+ run(hook: HookCommand, payload: HookPayload, options: HookRunOptions): Promise<HookOutcome>;
122
+ }
123
+ /** Registry rows for this repository, or the failure that emptied them. */
124
+ interface RegistryRead {
125
+ entries: RegistryEntry[];
126
+ failure?: HookFailure;
127
+ }
128
+ /** Plugin configs that could be read, and one failure per config that could not. */
129
+ interface PluginDocumentRead {
130
+ documents: PluginHookDocument[];
131
+ failures: HookFailure[];
132
+ }
133
+ /** Where a plugin declares its hooks. Mirrors the harness state entry. */
134
+ interface PluginHookSourceRef {
135
+ readonly pluginRoot: string;
136
+ readonly configPath: string;
137
+ }
138
+ /** Reads the hook documents a session runs from. */
139
+ interface HookDocumentReader {
140
+ registry(repoRoot: string): Promise<RegistryRead>;
141
+ plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead>;
142
+ }
143
+ //#endregion
144
+ export { 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 };
145
+ //# sourceMappingURL=hooks.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.cts","names":[],"sources":["../../src/types/hooks.ts"],"mappings":";;;;;;;;;cASa;WACX;WACA;WACA;WACA;WACA;;KAGU,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"}
@@ -0,0 +1,145 @@
1
+ //#region src/types/hooks.d.ts
2
+ /**
3
+ * The hook document contract and the ports that run it.
4
+ *
5
+ * Every name here is written in Claude Code's vocabulary, because that is what
6
+ * `.doom/hooks.yaml` and plugin `hooks.json` files are authored against. The
7
+ * translation onto Pi's lifecycle happens at the adapter boundary.
8
+ */
9
+ /** Registry event names. Each is used twice per dispatch, so drift would be silent. */
10
+ declare const HOOK_EVENT: {
11
+ readonly sessionStart: 'SessionStart';
12
+ readonly preToolUse: 'PreToolUse';
13
+ readonly postToolUse: 'PostToolUse';
14
+ readonly stop: 'Stop';
15
+ readonly sessionEnd: 'SessionEnd';
16
+ };
17
+ type HookEventName = (typeof HOOK_EVENT)[keyof typeof HOOK_EVENT];
18
+ interface HookCommand {
19
+ command: string;
20
+ /** Seconds before the hook is terminated. Defaults to DEFAULT_HOOK_TIMEOUT_SECONDS. */
21
+ timeout?: number;
22
+ }
23
+ /** What one registry row declares for the `pi` frontend. */
24
+ interface RegistryBinding {
25
+ matcher?: string;
26
+ command: string;
27
+ timeout?: number;
28
+ skipInSubagent?: boolean;
29
+ order?: number;
30
+ }
31
+ /** A registry row plus the group membership used to include or drop it. */
32
+ interface RegistryEntry extends RegistryBinding {
33
+ event: string;
34
+ order: number;
35
+ /** Declaration order across every source, used as the sort tiebreaker. */
36
+ position: number;
37
+ groupId: string;
38
+ core: boolean;
39
+ /** Root of the config that declared the group, exported as CLAUDE_PLUGIN_ROOT. */
40
+ baseDirectory: string;
41
+ }
42
+ interface RegistryGroup {
43
+ core?: boolean;
44
+ hooks?: Array<{
45
+ event: string;
46
+ pi?: RegistryBinding;
47
+ }>;
48
+ }
49
+ interface RegistryDocument {
50
+ groups?: Record<string, RegistryGroup>;
51
+ }
52
+ /** One `.doom/hooks.yaml` as read from disk, before it is parsed. */
53
+ interface HookDocumentSource {
54
+ baseDirectory: string;
55
+ text: string;
56
+ }
57
+ /** One `.doom/hooks.yaml` after parsing, tagged with the root that declared it. */
58
+ interface ParsedRegistrySource {
59
+ baseDirectory: string;
60
+ document: RegistryDocument;
61
+ }
62
+ interface PluginHookGroup {
63
+ matcher?: string;
64
+ hooks?: HookCommand[];
65
+ }
66
+ interface PluginHookConfig {
67
+ hooks?: Record<string, PluginHookGroup[]>;
68
+ }
69
+ /** A parsed plugin config plus the root exported to its commands. */
70
+ interface PluginHookDocument {
71
+ pluginRoot: string;
72
+ config: PluginHookConfig;
73
+ }
74
+ /** A hook command paired with the config root it runs against. */
75
+ interface ResolvedHook {
76
+ hook: HookCommand;
77
+ root: string;
78
+ }
79
+ /** The JSON a hook may write on stdout to steer or block the call it observed. */
80
+ interface HookDecision {
81
+ decision?: string;
82
+ reason?: string;
83
+ hookSpecificOutput?: {
84
+ permissionDecision?: string;
85
+ reason?: string;
86
+ additionalContext?: string;
87
+ };
88
+ }
89
+ type HookFailureReason = 'spawn_failed' | 'non_zero_exit' | 'timeout' | 'invalid_json' | 'registry_read' | 'plugin_config';
90
+ interface HookFailure {
91
+ command: string;
92
+ message: string;
93
+ reason: HookFailureReason;
94
+ }
95
+ interface HookOutcome {
96
+ decision?: HookDecision;
97
+ failure?: HookFailure;
98
+ }
99
+ /**
100
+ * The tool fields a payload is built from, named without importing Pi.
101
+ *
102
+ * Pi's ToolCallEvent and ToolResultEvent both satisfy this structurally, so the
103
+ * adapter forwards them unchanged and the payload builder stays host-neutral.
104
+ */
105
+ interface HookToolEvent {
106
+ readonly type: string;
107
+ readonly toolName: string;
108
+ readonly input: unknown;
109
+ readonly content?: readonly unknown[];
110
+ readonly isError?: boolean;
111
+ }
112
+ /** The JSON document handed to a hook on stdin. */
113
+ type HookPayload = Record<string, unknown>;
114
+ interface HookRunOptions {
115
+ repoRoot: string;
116
+ /** Exported as CLAUDE_PLUGIN_ROOT so a hook can reach its own scripts. */
117
+ pluginRoot?: string;
118
+ }
119
+ /** Runs one hook command and reports what it decided or how it failed. */
120
+ interface HookRunner {
121
+ run(hook: HookCommand, payload: HookPayload, options: HookRunOptions): Promise<HookOutcome>;
122
+ }
123
+ /** Registry rows for this repository, or the failure that emptied them. */
124
+ interface RegistryRead {
125
+ entries: RegistryEntry[];
126
+ failure?: HookFailure;
127
+ }
128
+ /** Plugin configs that could be read, and one failure per config that could not. */
129
+ interface PluginDocumentRead {
130
+ documents: PluginHookDocument[];
131
+ failures: HookFailure[];
132
+ }
133
+ /** Where a plugin declares its hooks. Mirrors the harness state entry. */
134
+ interface PluginHookSourceRef {
135
+ readonly pluginRoot: string;
136
+ readonly configPath: string;
137
+ }
138
+ /** Reads the hook documents a session runs from. */
139
+ interface HookDocumentReader {
140
+ registry(repoRoot: string): Promise<RegistryRead>;
141
+ plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead>;
142
+ }
143
+ //#endregion
144
+ export { 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 };
145
+ //# sourceMappingURL=hooks.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.mts","names":[],"sources":["../../src/types/hooks.ts"],"mappings":";;;;;;;;;cASa;WACX;WACA;WACA;WACA;WACA;;KAGU,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"}
@@ -0,0 +1,2 @@
1
+ const e={sessionStart:`SessionStart`,preToolUse:`PreToolUse`,postToolUse:`PostToolUse`,stop:`Stop`,sessionEnd:`SessionEnd`};export{e as HOOK_EVENT};
2
+ //# sourceMappingURL=hooks.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.mjs","names":[],"sources":["../../src/types/hooks.ts"],"sourcesContent":["/**\n * The hook document contract and the ports that run it.\n *\n * Every name here is written in Claude Code's vocabulary, because that is what\n * `.doom/hooks.yaml` and plugin `hooks.json` files are authored against. The\n * translation onto Pi's lifecycle happens at the adapter boundary.\n */\n\n/** Registry event names. Each is used twice per dispatch, so drift would be silent. */\nexport const HOOK_EVENT = {\n sessionStart: 'SessionStart',\n preToolUse: 'PreToolUse',\n postToolUse: 'PostToolUse',\n stop: 'Stop',\n sessionEnd: 'SessionEnd',\n} as const;\n\nexport type HookEventName = (typeof HOOK_EVENT)[keyof typeof HOOK_EVENT];\n\nexport interface HookCommand {\n command: string;\n /** Seconds before the hook is terminated. Defaults to DEFAULT_HOOK_TIMEOUT_SECONDS. */\n timeout?: number;\n}\n\n/** What one registry row declares for the `pi` frontend. */\nexport interface RegistryBinding {\n matcher?: string;\n command: string;\n timeout?: number;\n skipInSubagent?: boolean;\n order?: number;\n}\n\n/** A registry row plus the group membership used to include or drop it. */\nexport interface RegistryEntry extends RegistryBinding {\n event: string;\n order: number;\n /** Declaration order across every source, used as the sort tiebreaker. */\n position: number;\n groupId: string;\n core: boolean;\n /** Root of the config that declared the group, exported as CLAUDE_PLUGIN_ROOT. */\n baseDirectory: string;\n}\n\nexport interface RegistryGroup {\n core?: boolean;\n hooks?: Array<{ event: string; pi?: RegistryBinding }>;\n}\n\nexport interface RegistryDocument {\n groups?: Record<string, RegistryGroup>;\n}\n\n/** One `.doom/hooks.yaml` as read from disk, before it is parsed. */\nexport interface HookDocumentSource {\n baseDirectory: string;\n text: string;\n}\n\n/** One `.doom/hooks.yaml` after parsing, tagged with the root that declared it. */\nexport interface ParsedRegistrySource {\n baseDirectory: string;\n document: RegistryDocument;\n}\n\nexport interface PluginHookGroup {\n matcher?: string;\n hooks?: HookCommand[];\n}\n\nexport interface PluginHookConfig {\n hooks?: Record<string, PluginHookGroup[]>;\n}\n\n/** A parsed plugin config plus the root exported to its commands. */\nexport interface PluginHookDocument {\n pluginRoot: string;\n config: PluginHookConfig;\n}\n\n/** A hook command paired with the config root it runs against. */\nexport interface ResolvedHook {\n hook: HookCommand;\n root: string;\n}\n\n/** The JSON a hook may write on stdout to steer or block the call it observed. */\nexport interface HookDecision {\n decision?: string;\n reason?: string;\n hookSpecificOutput?: {\n permissionDecision?: string;\n reason?: string;\n additionalContext?: string;\n };\n}\n\nexport type HookFailureReason =\n | 'spawn_failed'\n | 'non_zero_exit'\n | 'timeout'\n | 'invalid_json'\n | 'registry_read'\n | 'plugin_config';\n\nexport interface HookFailure {\n command: string;\n message: string;\n reason: HookFailureReason;\n}\n\nexport interface HookOutcome {\n decision?: HookDecision;\n failure?: HookFailure;\n}\n\n/**\n * The tool fields a payload is built from, named without importing Pi.\n *\n * Pi's ToolCallEvent and ToolResultEvent both satisfy this structurally, so the\n * adapter forwards them unchanged and the payload builder stays host-neutral.\n */\nexport interface HookToolEvent {\n readonly type: string;\n readonly toolName: string;\n readonly input: unknown;\n readonly content?: readonly unknown[];\n readonly isError?: boolean;\n}\n\n/** The JSON document handed to a hook on stdin. */\nexport type HookPayload = Record<string, unknown>;\n\nexport interface HookRunOptions {\n repoRoot: string;\n /** Exported as CLAUDE_PLUGIN_ROOT so a hook can reach its own scripts. */\n pluginRoot?: string;\n}\n\n/** Runs one hook command and reports what it decided or how it failed. */\nexport interface HookRunner {\n run(hook: HookCommand, payload: HookPayload, options: HookRunOptions): Promise<HookOutcome>;\n}\n\n/** Registry rows for this repository, or the failure that emptied them. */\nexport interface RegistryRead {\n entries: RegistryEntry[];\n failure?: HookFailure;\n}\n\n/** Plugin configs that could be read, and one failure per config that could not. */\nexport interface PluginDocumentRead {\n documents: PluginHookDocument[];\n failures: HookFailure[];\n}\n\n/** Where a plugin declares its hooks. Mirrors the harness state entry. */\nexport interface PluginHookSourceRef {\n readonly pluginRoot: string;\n readonly configPath: string;\n}\n\n/** Reads the hook documents a session runs from. */\nexport interface HookDocumentReader {\n registry(repoRoot: string): Promise<RegistryRead>;\n plugins(sources: readonly PluginHookSourceRef[]): Promise<PluginDocumentRead>;\n}\n"],"mappings":"AASA,MAAa,EAAa,CACxB,aAAc,eACd,WAAY,aACZ,YAAa,cACb,KAAM,OACN,WAAY,YACd"}
@@ -0,0 +1,2 @@
1
+ const e={hookFailed:`doom_pi_hook.failed`,hookRegistryReadFailed:`doom_pi_hook.registry_read_failed`};exports.HOOK_TELEMETRY_EVENT=e;
2
+ //# sourceMappingURL=telemetry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.cjs","names":[],"sources":["../../src/types/telemetry.ts"],"sourcesContent":["/** Telemetry events this package reports, and the port that records them. */\nexport const HOOK_TELEMETRY_EVENT = {\n hookFailed: 'doom_pi_hook.failed',\n hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed',\n} as const;\n\nexport type HookTelemetryEventName = (typeof HOOK_TELEMETRY_EVENT)[keyof typeof HOOK_TELEMETRY_EVENT];\nexport type HookTelemetryAttributes = Record<string, string | number | boolean>;\n\n/**\n * A hook that never ran is indistinguishable from one that passed, which is why\n * both a failed run and an unreadable registry are worth reporting rather than\n * swallowing.\n */\nexport interface HookTelemetry {\n recordError(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;\n recordWarning(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;\n}\n"],"mappings":"AACA,MAAa,EAAuB,CAClC,WAAY,sBACZ,uBAAwB,mCAC1B"}
@@ -0,0 +1,20 @@
1
+ //#region src/types/telemetry.d.ts
2
+ /** Telemetry events this package reports, and the port that records them. */
3
+ declare const HOOK_TELEMETRY_EVENT: {
4
+ readonly hookFailed: 'doom_pi_hook.failed';
5
+ readonly hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed';
6
+ };
7
+ type HookTelemetryEventName = (typeof HOOK_TELEMETRY_EVENT)[keyof typeof HOOK_TELEMETRY_EVENT];
8
+ type HookTelemetryAttributes = Record<string, string | number | boolean>;
9
+ /**
10
+ * A hook that never ran is indistinguishable from one that passed, which is why
11
+ * both a failed run and an unreadable registry are worth reporting rather than
12
+ * swallowing.
13
+ */
14
+ interface HookTelemetry {
15
+ recordError(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;
16
+ recordWarning(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;
17
+ }
18
+ //#endregion
19
+ export { HOOK_TELEMETRY_EVENT, HookTelemetry, HookTelemetryAttributes, HookTelemetryEventName };
20
+ //# sourceMappingURL=telemetry.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.cts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WACX;WACA;;KAGU,iCAAiC,mCAAmC;KACpE,0BAA0B;;;;;;UAOrB;EACf,YAAY,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B;EAClG,cAAc,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B"}
@@ -0,0 +1,20 @@
1
+ //#region src/types/telemetry.d.ts
2
+ /** Telemetry events this package reports, and the port that records them. */
3
+ declare const HOOK_TELEMETRY_EVENT: {
4
+ readonly hookFailed: 'doom_pi_hook.failed';
5
+ readonly hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed';
6
+ };
7
+ type HookTelemetryEventName = (typeof HOOK_TELEMETRY_EVENT)[keyof typeof HOOK_TELEMETRY_EVENT];
8
+ type HookTelemetryAttributes = Record<string, string | number | boolean>;
9
+ /**
10
+ * A hook that never ran is indistinguishable from one that passed, which is why
11
+ * both a failed run and an unreadable registry are worth reporting rather than
12
+ * swallowing.
13
+ */
14
+ interface HookTelemetry {
15
+ recordError(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;
16
+ recordWarning(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;
17
+ }
18
+ //#endregion
19
+ export { HOOK_TELEMETRY_EVENT, HookTelemetry, HookTelemetryAttributes, HookTelemetryEventName };
20
+ //# sourceMappingURL=telemetry.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.mts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WACX;WACA;;KAGU,iCAAiC,mCAAmC;KACpE,0BAA0B;;;;;;UAOrB;EACf,YAAY,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B;EAClG,cAAc,OAAO,wBAAwB,gBAAgB,aAAa,0BAA0B"}
@@ -0,0 +1,2 @@
1
+ const e={hookFailed:`doom_pi_hook.failed`,hookRegistryReadFailed:`doom_pi_hook.registry_read_failed`};export{e as HOOK_TELEMETRY_EVENT};
2
+ //# sourceMappingURL=telemetry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.mjs","names":[],"sources":["../../src/types/telemetry.ts"],"sourcesContent":["/** Telemetry events this package reports, and the port that records them. */\nexport const HOOK_TELEMETRY_EVENT = {\n hookFailed: 'doom_pi_hook.failed',\n hookRegistryReadFailed: 'doom_pi_hook.registry_read_failed',\n} as const;\n\nexport type HookTelemetryEventName = (typeof HOOK_TELEMETRY_EVENT)[keyof typeof HOOK_TELEMETRY_EVENT];\nexport type HookTelemetryAttributes = Record<string, string | number | boolean>;\n\n/**\n * A hook that never ran is indistinguishable from one that passed, which is why\n * both a failed run and an unreadable registry are worth reporting rather than\n * swallowing.\n */\nexport interface HookTelemetry {\n recordError(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;\n recordWarning(event: HookTelemetryEventName, error: unknown, attributes?: HookTelemetryAttributes): Promise<void>;\n}\n"],"mappings":"AACA,MAAa,EAAuB,CAClC,WAAY,sBACZ,uBAAwB,mCAC1B"}
package/llms.txt ADDED
@@ -0,0 +1,11 @@
1
+ # Doom Pi Hook
2
+
3
+ Claude-Code-compatible repository and plugin hooks for Doom Pi sessions.
4
+
5
+ ## How to author
6
+
7
+ - [Author DoomPi hooks](./src/prompts/doompi-author-hook/SKILL.md): Create `.doom/hooks.yaml` groups and Claude-compatible hook commands.
8
+
9
+ ## Package reference
10
+
11
+ - [Package README](./README.md): The hook registry, group selection, event mapping, decisions, and failure handling.
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@agimon-ai/doompi-hook",
3
+ "version": "0.0.1-alpha.10",
4
+ "description": "Claude-Code-compatible repository and plugin hook runner for DoomPi sessions.",
5
+ "keywords": [
6
+ "ai",
7
+ "claude-code-hooks",
8
+ "coding-agent",
9
+ "developer-tools",
10
+ "doompi",
11
+ "guardrails",
12
+ "hooks",
13
+ "pi-coding-agent",
14
+ "pi-extension",
15
+ "pi-package",
16
+ "repository-hooks"
17
+ ],
18
+ "homepage": "https://agimon.ai",
19
+ "license": "MIT",
20
+ "author": "Vuong Ngo",
21
+ "files": [
22
+ "dist",
23
+ "llms.txt",
24
+ "README.md",
25
+ "src/prompts",
26
+ "LICENSE",
27
+ "package.json"
28
+ ],
29
+ "type": "module",
30
+ "main": "./dist/index.cjs",
31
+ "types": "./dist/index.d.mts",
32
+ "jsnext:main": "./dist/index.mjs",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.mts",
36
+ "import": "./dist/index.mjs",
37
+ "require": "./dist/index.cjs"
38
+ },
39
+ "./extensions/pi": {
40
+ "types": "./dist/extensions/pi.d.mts",
41
+ "import": "./dist/extensions/pi.mjs",
42
+ "require": "./dist/extensions/pi.cjs"
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "dependencies": {
50
+ "@deepseek-ai/cordis": "4.0.1",
51
+ "yaml": "2.9.0",
52
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.32",
53
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.32",
54
+ "@agimon-ai/doompi-config": "0.0.1-alpha.32"
55
+ },
56
+ "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",
61
+ "typescript": "7.0.2",
62
+ "vitest": "4.1.11",
63
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.30"
64
+ },
65
+ "peerDependencies": {
66
+ "@earendil-works/pi-coding-agent": "0.84.2"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "@earendil-works/pi-coding-agent": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "engines": {
74
+ "node": ">=22.19.0"
75
+ },
76
+ "pi": {
77
+ "extensions": [
78
+ "./dist/extensions/pi.mjs"
79
+ ]
80
+ },
81
+ "scripts": {
82
+ "build": "tsdown",
83
+ "test": "vitest --run",
84
+ "typecheck": "tsc --noEmit",
85
+ "lint": "oxlint . && oxfmt . --check",
86
+ "fixcode": "oxlint . --fix && oxfmt ."
87
+ }
88
+ }
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: doompi-author-hook
3
+ description: Author DoomPi repository or plugin hooks. Use when creating or changing .doom/hooks.yaml, selecting hook groups from modes.yaml, writing hook commands, or adapting Claude Code hook payloads and decisions to DoomPi.
4
+ ---
5
+
6
+ # Author DoomPi hooks
7
+
8
+ Write the smallest hook that enforces the requested policy, and treat every hook command as executable repository code.
9
+
10
+ ## Repository registry
11
+
12
+ Define repository and personal hooks in `.doom/hooks.yaml`:
13
+
14
+ ```yaml
15
+ groups:
16
+ safety:
17
+ core: true
18
+ hooks:
19
+ - event: PreToolUse
20
+ pi:
21
+ matcher: Bash
22
+ command: .doom/hooks/guard-destructive-commands.sh
23
+ timeout: 10
24
+ skipInSubagent: true
25
+ order: 0
26
+ ```
27
+
28
+ - Personal configuration comes from `~/.pi/.doom/hooks.yaml`; repository configuration comes from `<repo>/.doom/hooks.yaml`.
29
+ - A repository group replaces a personal group with the same ID as one complete value.
30
+ - `core: true` runs regardless of the selected groups. Otherwise, add the group ID to a layer's `hookGroups` in `modes.yaml`.
31
+ - `matcher` is a regular expression over Claude tool names such as `Bash` and `Write`, even though Pi uses lowercase tool names internally.
32
+ - Use `skipInSubagent: true` when running the hook in a child could duplicate or close parent-owned work. Lower `order` values run first, with declaration order as the tiebreaker.
33
+
34
+ Supported repository events are `SessionStart`, `PreToolUse`, `PostToolUse`, and `Stop`. `SessionEnd` is resolved only from plugin `hooks.json` documents.
35
+
36
+ ## Command contract
37
+
38
+ DoomPi runs each command through `/bin/bash -c` from the repository root and writes the Claude-compatible event payload to stdin. The environment includes `CLAUDE_PROJECT_DIR`, `CODEX_REPO_ROOT`, `ORIGINAL_REPO_PATH`, and `CLAUDE_PLUGIN_ROOT`. For `.doom/hooks.yaml`, `CLAUDE_PLUGIN_ROOT` is the root that declared the personal or repository document.
39
+
40
+ The final stdout line beginning with `{` is parsed as the hook decision. Use Claude Code decision fields only when the event supports them. `PreToolUse` may deny a tool or add context, `PostToolUse` may append context or mark a result as denied, and `SessionStart` may add context. `Stop` and `SessionEnd` run for side effects.
41
+
42
+ Hook failures are advisory: nonzero exits, invalid JSON, spawn failures, and timeouts are reported to the agent instead of crashing the turn. Set `timeout` in seconds only when the 10-second default is unsuitable. A timeout terminates the command's process group.
43
+
44
+ ## Verification
45
+
46
+ Before enabling a new hook broadly:
47
+
48
+ 1. Run the command directly with a representative JSON payload on stdin.
49
+ 2. Start DoomPi with the intended major mode and confirm the group appears in `doompi --explain` output.
50
+ 3. Exercise the exact lifecycle event in a disposable session, including a child session when `skipInSubagent` matters.
51
+ 4. Confirm failure output reaches the agent and that repeated shutdown does not repeat external side effects unexpectedly.