@hailer/mcp 1.1.11 → 1.1.13

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 (252) hide show
  1. package/dist/app.js +18 -5
  2. package/dist/bot/bot-config.d.ts +12 -1
  3. package/dist/bot/bot-config.js +98 -14
  4. package/dist/bot/bot-manager.d.ts +13 -3
  5. package/dist/bot/bot-manager.js +80 -25
  6. package/dist/bot/bot.d.ts +46 -0
  7. package/dist/bot/bot.js +542 -166
  8. package/dist/bot/services/message-classifier.js +17 -0
  9. package/dist/bot/services/permission-guard.d.ts +52 -0
  10. package/dist/bot/services/permission-guard.js +149 -0
  11. package/dist/bot/services/types.d.ts +5 -0
  12. package/dist/bot/services/typing-indicator.d.ts +6 -1
  13. package/dist/bot/services/typing-indicator.js +19 -3
  14. package/dist/config.d.ts +6 -1
  15. package/dist/config.js +43 -0
  16. package/dist/core.js +3 -6
  17. package/dist/mcp/UserContextCache.d.ts +5 -0
  18. package/dist/mcp/UserContextCache.js +51 -19
  19. package/dist/mcp/hailer-clients.d.ts +19 -1
  20. package/dist/mcp/hailer-clients.js +157 -20
  21. package/dist/mcp/session-store.d.ts +68 -0
  22. package/dist/mcp/session-store.js +169 -0
  23. package/dist/mcp/signal-handler.js +12 -12
  24. package/dist/mcp/tool-registry.d.ts +17 -4
  25. package/dist/mcp/tool-registry.js +37 -7
  26. package/dist/mcp/tools/activity.js +99 -7
  27. package/dist/mcp/tools/app-scaffold.js +304 -336
  28. package/dist/mcp/tools/company.d.ts +9 -0
  29. package/dist/mcp/tools/company.js +88 -0
  30. package/dist/mcp/tools/discussion.js +68 -0
  31. package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
  32. package/dist/mcp/tools/workflow-permissions.js +204 -0
  33. package/dist/mcp/tools/workflow.js +57 -18
  34. package/dist/mcp/utils/index.d.ts +2 -0
  35. package/dist/mcp/utils/index.js +12 -1
  36. package/dist/mcp/utils/role-utils.d.ts +74 -0
  37. package/dist/mcp/utils/role-utils.js +151 -0
  38. package/dist/mcp/utils/types.d.ts +43 -1
  39. package/dist/mcp/utils/types.js +14 -0
  40. package/dist/mcp/webhook-handler.d.ts +6 -0
  41. package/dist/mcp/webhook-handler.js +11 -0
  42. package/dist/mcp-server.d.ts +23 -2
  43. package/dist/mcp-server.js +639 -111
  44. package/dist/plugins/vipunen/client.d.ts +150 -0
  45. package/dist/plugins/vipunen/client.js +535 -0
  46. package/dist/plugins/vipunen/config/schema-config.json +19 -0
  47. package/dist/plugins/vipunen/config/schema-doc.json +22 -0
  48. package/dist/plugins/vipunen/index.d.ts +41 -0
  49. package/dist/plugins/vipunen/index.js +88 -0
  50. package/dist/plugins/vipunen/tools.d.ts +26 -0
  51. package/dist/plugins/vipunen/tools.js +501 -0
  52. package/package.json +2 -1
  53. package/.claude/.context-watchdog.json +0 -1
  54. package/.claude/.session-checked +0 -1
  55. package/.claude/CLAUDE.md +0 -370
  56. package/.claude/agents/agent-ada-skill-builder.md +0 -94
  57. package/.claude/agents/agent-alejandro-function-fields.md +0 -342
  58. package/.claude/agents/agent-bjorn-config-audit.md +0 -103
  59. package/.claude/agents/agent-builder-agent-creator.md +0 -130
  60. package/.claude/agents/agent-code-simplifier.md +0 -53
  61. package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
  62. package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
  63. package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
  64. package/.claude/agents/agent-helga-workflow-config.md +0 -204
  65. package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
  66. package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
  67. package/.claude/agents/agent-ivan-monolith.md +0 -154
  68. package/.claude/agents/agent-kenji-data-reader.md +0 -86
  69. package/.claude/agents/agent-lars-code-inspector.md +0 -102
  70. package/.claude/agents/agent-marco-mockup-builder.md +0 -110
  71. package/.claude/agents/agent-marcus-api-documenter.md +0 -323
  72. package/.claude/agents/agent-marketplace-publisher.md +0 -280
  73. package/.claude/agents/agent-marketplace-reviewer.md +0 -309
  74. package/.claude/agents/agent-permissions-handler.md +0 -208
  75. package/.claude/agents/agent-simple-writer.md +0 -48
  76. package/.claude/agents/agent-svetlana-code-review.md +0 -171
  77. package/.claude/agents/agent-tanya-test-runner.md +0 -333
  78. package/.claude/agents/agent-ui-designer.md +0 -100
  79. package/.claude/agents/agent-viktor-sql-insights.md +0 -212
  80. package/.claude/agents/agent-web-search.md +0 -55
  81. package/.claude/agents/agent-yevgeni-discussions.md +0 -45
  82. package/.claude/agents/agent-zara-zapier.md +0 -159
  83. package/.claude/commands/app-squad.md +0 -135
  84. package/.claude/commands/audit-squad.md +0 -158
  85. package/.claude/commands/autoplan.md +0 -563
  86. package/.claude/commands/cleanup-squad.md +0 -98
  87. package/.claude/commands/config-squad.md +0 -106
  88. package/.claude/commands/crud-squad.md +0 -87
  89. package/.claude/commands/data-squad.md +0 -97
  90. package/.claude/commands/debug-squad.md +0 -303
  91. package/.claude/commands/doc-squad.md +0 -65
  92. package/.claude/commands/handoff.md +0 -137
  93. package/.claude/commands/health.md +0 -49
  94. package/.claude/commands/help.md +0 -29
  95. package/.claude/commands/help:agents.md +0 -151
  96. package/.claude/commands/help:commands.md +0 -78
  97. package/.claude/commands/help:faq.md +0 -79
  98. package/.claude/commands/help:plugins.md +0 -50
  99. package/.claude/commands/help:skills.md +0 -93
  100. package/.claude/commands/help:tools.md +0 -75
  101. package/.claude/commands/hotfix-squad.md +0 -112
  102. package/.claude/commands/integration-squad.md +0 -82
  103. package/.claude/commands/janitor-squad.md +0 -167
  104. package/.claude/commands/learn-auto.md +0 -120
  105. package/.claude/commands/learn.md +0 -120
  106. package/.claude/commands/mcp-list.md +0 -27
  107. package/.claude/commands/onboard-squad.md +0 -140
  108. package/.claude/commands/plan-workspace.md +0 -732
  109. package/.claude/commands/prd.md +0 -130
  110. package/.claude/commands/project-status.md +0 -82
  111. package/.claude/commands/publish.md +0 -138
  112. package/.claude/commands/recap.md +0 -69
  113. package/.claude/commands/restore.md +0 -64
  114. package/.claude/commands/review-squad.md +0 -152
  115. package/.claude/commands/save.md +0 -24
  116. package/.claude/commands/stats.md +0 -19
  117. package/.claude/commands/swarm.md +0 -210
  118. package/.claude/commands/tool-builder.md +0 -39
  119. package/.claude/commands/ws-pull.md +0 -44
  120. package/.claude/hooks/_shared-memory.cjs +0 -305
  121. package/.claude/hooks/_utils.cjs +0 -108
  122. package/.claude/hooks/agent-failure-detector.cjs +0 -383
  123. package/.claude/hooks/agent-usage-logger.cjs +0 -204
  124. package/.claude/hooks/app-edit-guard.cjs +0 -494
  125. package/.claude/hooks/auto-learn.cjs +0 -304
  126. package/.claude/hooks/bash-guard.cjs +0 -272
  127. package/.claude/hooks/builder-mode-manager.cjs +0 -354
  128. package/.claude/hooks/bulk-activity-guard.cjs +0 -271
  129. package/.claude/hooks/context-watchdog.cjs +0 -230
  130. package/.claude/hooks/delegation-reminder.cjs +0 -465
  131. package/.claude/hooks/design-system-lint.cjs +0 -271
  132. package/.claude/hooks/post-scaffold-hook.cjs +0 -181
  133. package/.claude/hooks/prompt-guard.cjs +0 -354
  134. package/.claude/hooks/publish-template-guard.cjs +0 -147
  135. package/.claude/hooks/session-start.cjs +0 -35
  136. package/.claude/hooks/shared-memory-writer.cjs +0 -147
  137. package/.claude/hooks/skill-injector.cjs +0 -140
  138. package/.claude/hooks/skill-usage-logger.cjs +0 -258
  139. package/.claude/hooks/src-edit-guard.cjs +0 -240
  140. package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
  141. package/.claude/settings.json +0 -257
  142. package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
  143. package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
  144. package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
  145. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
  146. package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
  147. package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
  148. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
  149. package/.claude/skills/agent-structure/SKILL.md +0 -98
  150. package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
  151. package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
  152. package/.claude/skills/delegation-routing/SKILL.md +0 -202
  153. package/.claude/skills/frontend-design/SKILL.md +0 -254
  154. package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
  155. package/.claude/skills/hailer-api-client/SKILL.md +0 -518
  156. package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
  157. package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
  158. package/.claude/skills/hailer-design-system/SKILL.md +0 -235
  159. package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
  160. package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
  161. package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
  162. package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
  163. package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
  164. package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
  165. package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
  166. package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
  167. package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
  168. package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
  169. package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
  170. package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
  171. package/.claude/skills/integration-patterns/SKILL.md +0 -421
  172. package/.claude/skills/json-only-output/SKILL.md +0 -72
  173. package/.claude/skills/lsp-setup/SKILL.md +0 -160
  174. package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
  175. package/.claude/skills/optional-parameters/SKILL.md +0 -72
  176. package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
  177. package/.claude/skills/testing-patterns/SKILL.md +0 -630
  178. package/.claude/skills/tool-builder/SKILL.md +0 -250
  179. package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
  180. package/.claude/skills/tool-response-verification/SKILL.md +0 -92
  181. package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
  182. package/.hailer-mcp-port +0 -1
  183. package/.mcp.json +0 -13
  184. package/.opencode/agent/agent-ada-skill-builder.md +0 -35
  185. package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
  186. package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
  187. package/.opencode/agent/agent-builder-agent-creator.md +0 -39
  188. package/.opencode/agent/agent-code-simplifier.md +0 -31
  189. package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
  190. package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
  191. package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
  192. package/.opencode/agent/agent-helga-workflow-config.md +0 -204
  193. package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
  194. package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
  195. package/.opencode/agent/agent-ivan-monolith.md +0 -46
  196. package/.opencode/agent/agent-kenji-data-reader.md +0 -53
  197. package/.opencode/agent/agent-lars-code-inspector.md +0 -28
  198. package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
  199. package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
  200. package/.opencode/agent/agent-marketplace-publisher.md +0 -44
  201. package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
  202. package/.opencode/agent/agent-permissions-handler.md +0 -50
  203. package/.opencode/agent/agent-simple-writer.md +0 -45
  204. package/.opencode/agent/agent-svetlana-code-review.md +0 -39
  205. package/.opencode/agent/agent-tanya-test-runner.md +0 -57
  206. package/.opencode/agent/agent-ui-designer.md +0 -56
  207. package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
  208. package/.opencode/agent/agent-web-search.md +0 -42
  209. package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
  210. package/.opencode/agent/agent-zara-zapier.md +0 -53
  211. package/.opencode/commands/app-squad.md +0 -135
  212. package/.opencode/commands/audit-squad.md +0 -158
  213. package/.opencode/commands/autoplan.md +0 -563
  214. package/.opencode/commands/cleanup-squad.md +0 -98
  215. package/.opencode/commands/config-squad.md +0 -106
  216. package/.opencode/commands/crud-squad.md +0 -87
  217. package/.opencode/commands/data-squad.md +0 -97
  218. package/.opencode/commands/debug-squad.md +0 -303
  219. package/.opencode/commands/doc-squad.md +0 -65
  220. package/.opencode/commands/handoff.md +0 -137
  221. package/.opencode/commands/health.md +0 -49
  222. package/.opencode/commands/help-agents.md +0 -151
  223. package/.opencode/commands/help-commands.md +0 -32
  224. package/.opencode/commands/help-faq.md +0 -29
  225. package/.opencode/commands/help-plugins.md +0 -28
  226. package/.opencode/commands/help-skills.md +0 -7
  227. package/.opencode/commands/help-tools.md +0 -40
  228. package/.opencode/commands/help.md +0 -28
  229. package/.opencode/commands/hotfix-squad.md +0 -112
  230. package/.opencode/commands/integration-squad.md +0 -82
  231. package/.opencode/commands/janitor-squad.md +0 -167
  232. package/.opencode/commands/learn-auto.md +0 -120
  233. package/.opencode/commands/learn.md +0 -120
  234. package/.opencode/commands/mcp-list.md +0 -27
  235. package/.opencode/commands/onboard-squad.md +0 -140
  236. package/.opencode/commands/plan-workspace.md +0 -732
  237. package/.opencode/commands/prd.md +0 -131
  238. package/.opencode/commands/project-status.md +0 -82
  239. package/.opencode/commands/publish.md +0 -138
  240. package/.opencode/commands/recap.md +0 -69
  241. package/.opencode/commands/restore.md +0 -64
  242. package/.opencode/commands/review-squad.md +0 -152
  243. package/.opencode/commands/save.md +0 -24
  244. package/.opencode/commands/stats.md +0 -19
  245. package/.opencode/commands/swarm.md +0 -210
  246. package/.opencode/commands/tool-builder.md +0 -39
  247. package/.opencode/commands/ws-pull.md +0 -44
  248. package/.opencode/opencode.json +0 -21
  249. package/inbox/failures.log +0 -1
  250. package/inbox/usage.jsonl +0 -4
  251. package/scripts/postinstall.cjs +0 -64
  252. package/scripts/test-hal-tools.ts +0 -154
@@ -1,354 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * <hook-name>builder-mode-manager</hook-name>
4
- *
5
- * <purpose>
6
- * Automatically manages builder mode for code-editing agents.
7
- * Enables before spawn, disables after completion.
8
- * Handles nested agent stacks correctly.
9
- * </purpose>
10
- *
11
- * <triggers>
12
- * - PreToolUse on Task tool (before agent spawns)
13
- * - PostToolUse on Task tool (after agent completes)
14
- * </triggers>
15
- *
16
- * <code-editing-agents>
17
- * giuseppe, gunther, general-purpose, agent-builder, helga, ingrid, ada
18
- * </code-editing-agents>
19
- *
20
- * <behavior>
21
- * PreToolUse:
22
- * 1. Push agent onto stack
23
- * 2. If code-editing agent and builder mode off → enable
24
- *
25
- * PostToolUse:
26
- * 1. Pop agent from stack
27
- * 2. If no remaining code-editing agents → disable builder mode
28
- * </behavior>
29
- *
30
- * <shared-files>
31
- * /tmp/.claude-builder-agent-active (builder mode flag)
32
- * /tmp/.claude-agent-stack.json (nested agent tracking)
33
- * </shared-files>
34
- *
35
- * <cli-commands>
36
- * --status: Show builder mode and active agents
37
- * --reset: Clear stack and disable builder mode
38
- * </cli-commands>
39
- */
40
-
41
- const fs = require('fs');
42
- const path = require('path');
43
- const os = require('os');
44
-
45
- const TEMP_DIR = os.tmpdir();
46
- const BUILDER_MODE_FILE = path.join(TEMP_DIR, '.claude-builder-agent-active');
47
- const AGENT_STACK_FILE = path.join(TEMP_DIR, '.claude-agent-stack.json');
48
- const LOCK_FILE = AGENT_STACK_FILE + '.lock';
49
- const LOCK_TIMEOUT = 5000; // 5 seconds max wait
50
-
51
- // Agents that need builder mode (can edit code)
52
- // Uses partial matching - any subagent_type containing these strings triggers builder mode
53
- const CODE_EDITING_AGENTS = [
54
- 'giuseppe', // agent-giuseppe-app-builder
55
- 'gunther', // agent-gunther-mcp-tools
56
- 'general-purpose', // general-purpose agent
57
- 'agent-builder', // agent-builder-agent-creator
58
- 'helga', // agent-helga-workflow-config
59
- 'ingrid', // agent-ingrid-doc-templates
60
- 'ada', // agent-ada-skill-builder
61
- 'simple-writer', // agent-simple-writer
62
- 'code-simplifier', // agent-code-simplifier
63
- 'marco', // agent-marco-mockup-builder
64
- ];
65
-
66
- // NOTE: No in-memory cache - multiple hooks may run concurrently
67
- // Each hook reads fresh from file to avoid stale reads
68
-
69
- // Staleness threshold - remove agents older than this (4 hours - long-running agents need time)
70
- const AGENT_STALENESS_MS = 4 * 60 * 60 * 1000;
71
-
72
- // Read hook input from stdin
73
- let input = '';
74
- process.stdin.setEncoding('utf8');
75
- process.stdin.on('data', chunk => input += chunk);
76
- process.stdin.on('end', () => {
77
- try {
78
- const data = JSON.parse(input);
79
- processHook(data);
80
- } catch (e) {
81
- // Invalid JSON - allow to avoid blocking
82
- console.error(`[builder-mode-manager] Warning: ${e.message}`);
83
- console.log(JSON.stringify({ decision: 'allow' }));
84
- process.exit(0);
85
- }
86
- });
87
-
88
- function acquireLock() {
89
- const start = Date.now();
90
- while (Date.now() - start < LOCK_TIMEOUT) {
91
- try {
92
- fs.mkdirSync(LOCK_FILE);
93
- return true;
94
- } catch (e) {
95
- try {
96
- const stat = fs.statSync(LOCK_FILE);
97
- if (Date.now() - stat.mtimeMs > 10000) {
98
- fs.rmdirSync(LOCK_FILE);
99
- continue;
100
- }
101
- } catch (e2) { /* lock was released between check */ }
102
- const waitUntil = Date.now() + 50;
103
- while (Date.now() < waitUntil) { /* busy wait */ }
104
- }
105
- }
106
- return false;
107
- }
108
-
109
- function releaseLock() {
110
- try { fs.rmdirSync(LOCK_FILE); } catch (e) { /* already released */ }
111
- }
112
-
113
- /**
114
- * Remove stale agents from the stack (older than AGENT_STALENESS_MS)
115
- * Also clears builderModeEnabledBy if that agent was removed
116
- * Returns true if any agents were removed
117
- */
118
- function cleanupStaleAgents(stack) {
119
- const now = Date.now();
120
- const originalCount = stack.agents.length;
121
-
122
- stack.agents = stack.agents.filter(agent => {
123
- // Keep agents without timestamp (backward compatibility - assume current session)
124
- if (!agent.startedAt) return true;
125
-
126
- const agentTime = new Date(agent.startedAt).getTime();
127
-
128
- // Remove agents with invalid timestamps (NaN from malformed dates)
129
- if (isNaN(agentTime)) {
130
- console.error(`[builder-mode-manager] Warning: Invalid timestamp for agent ${agent.type}, removing`);
131
- return false;
132
- }
133
-
134
- return (now - agentTime) < AGENT_STALENESS_MS;
135
- });
136
-
137
- const removed = originalCount - stack.agents.length;
138
- if (removed > 0) {
139
- console.error(`[builder-mode-manager] Cleaned up ${removed} stale agent(s) from stack`);
140
- }
141
-
142
- // Clear builderModeEnabledBy if that agent is no longer in stack OR stack is empty
143
- if (stack.builderModeEnabledBy) {
144
- const enablerStillActive = stack.agents.some(a => a.type === stack.builderModeEnabledBy);
145
- if (!enablerStillActive || stack.agents.length === 0) {
146
- stack.builderModeEnabledBy = null;
147
- }
148
- }
149
-
150
- return removed > 0;
151
- }
152
-
153
- /**
154
- * Load the agent stack (tracks nested agent calls)
155
- * Always reads fresh from file - no caching to avoid race conditions
156
- * Automatically cleans up stale agents from previous sessions
157
- */
158
- function loadAgentStack() {
159
- try {
160
- if (fs.existsSync(AGENT_STACK_FILE)) {
161
- const stack = JSON.parse(fs.readFileSync(AGENT_STACK_FILE, 'utf8'));
162
-
163
- // Clean up stale agents from previous sessions
164
- const hadStaleAgents = cleanupStaleAgents(stack);
165
-
166
- // If we cleaned up agents, save the updated stack
167
- if (hadStaleAgents) {
168
- // Also reset builder mode if no agents remain
169
- if (stack.agents.length === 0) {
170
- stack.builderModeEnabledBy = null;
171
- disableBuilderMode();
172
- }
173
- saveAgentStack(stack);
174
- }
175
-
176
- return stack;
177
- }
178
- } catch (e) {
179
- console.error(`[builder-mode-manager] Warning: ${e.message}`);
180
- }
181
-
182
- return { agents: [], builderModeEnabledBy: null };
183
- }
184
-
185
- /**
186
- * Save the agent stack atomically (write to temp, then rename)
187
- */
188
- function saveAgentStack(stack) {
189
- const tmpFile = AGENT_STACK_FILE + '.tmp-' + process.pid;
190
- fs.writeFileSync(tmpFile, JSON.stringify(stack, null, 2), { mode: 0o600 });
191
- fs.renameSync(tmpFile, AGENT_STACK_FILE); // Atomic on POSIX
192
- }
193
-
194
- /**
195
- * Enable builder mode (atomic write: temp file + rename)
196
- */
197
- function enableBuilderMode(agentName) {
198
- const tmpFile = BUILDER_MODE_FILE + '.tmp-' + process.pid;
199
- fs.writeFileSync(tmpFile, JSON.stringify({
200
- enabledAt: new Date().toISOString(),
201
- enabledBy: agentName
202
- }), { mode: 0o600 });
203
- fs.renameSync(tmpFile, BUILDER_MODE_FILE); // Atomic on POSIX
204
- }
205
-
206
- /**
207
- * Disable builder mode
208
- */
209
- function disableBuilderMode() {
210
- if (fs.existsSync(BUILDER_MODE_FILE)) {
211
- fs.unlinkSync(BUILDER_MODE_FILE);
212
- return true;
213
- }
214
- return false;
215
- }
216
-
217
- /**
218
- * Check if builder mode is active
219
- */
220
- function isBuilderModeActive() {
221
- return fs.existsSync(BUILDER_MODE_FILE);
222
- }
223
-
224
- function processHook(data) {
225
- const { tool_name, tool_input, tool_response } = data;
226
-
227
- // Only handle Task tool
228
- if (tool_name !== 'Task') {
229
- console.log(JSON.stringify({ decision: 'allow' }));
230
- process.exit(0);
231
- }
232
-
233
- const agentType = tool_input?.subagent_type;
234
-
235
- if (!agentType) {
236
- console.log(JSON.stringify({ decision: 'allow' }));
237
- process.exit(0);
238
- }
239
-
240
- const needsBuilderMode = CODE_EDITING_AGENTS.some(name => agentType.includes(name));
241
-
242
- // Determine if this is PreToolUse or PostToolUse based on presence of tool_response
243
- const isPreToolUse = tool_response === undefined;
244
-
245
- const locked = acquireLock();
246
- try {
247
- const stack = loadAgentStack();
248
-
249
- if (isPreToolUse) {
250
- // === PreToolUse: Agent is about to be spawned ===
251
-
252
- // Push agent onto stack
253
- stack.agents.push({
254
- type: agentType,
255
- startedAt: new Date().toISOString(),
256
- needsBuilderMode
257
- });
258
-
259
- // Enable builder mode if needed and not already active
260
- if (needsBuilderMode && !isBuilderModeActive()) {
261
- enableBuilderMode(agentType);
262
- stack.builderModeEnabledBy = agentType;
263
- console.error(`🔧 Builder mode AUTO-ENABLED for agent: ${agentType}`);
264
- }
265
-
266
- saveAgentStack(stack);
267
-
268
- } else {
269
- // === PostToolUse: Agent has completed ===
270
-
271
- // Remove the matching agent from stack (find last one with this type)
272
- // Using findLastIndex instead of pop() to handle parallel agents completing out of order
273
- const removeIdx = stack.agents.findLastIndex(a => a.type === agentType);
274
- if (removeIdx >= 0) {
275
- stack.agents.splice(removeIdx, 1);
276
- } else {
277
- // Agent not found - could be stale or already cleaned up
278
- console.error(`[builder-mode-manager] Warning: agent ${agentType} not found in stack, skipping removal`);
279
- }
280
-
281
- // Check if any remaining agents need builder mode
282
- const remainingNeedBuilderMode = stack.agents.some(a => a.needsBuilderMode);
283
-
284
- // Disable builder mode if no more agents need it
285
- if (isBuilderModeActive() && !remainingNeedBuilderMode) {
286
- disableBuilderMode();
287
- stack.builderModeEnabledBy = null;
288
- console.error(`🔒 Builder mode AUTO-DISABLED (${agentType} completed)`);
289
- }
290
-
291
- saveAgentStack(stack);
292
- }
293
- } finally {
294
- if (locked) releaseLock();
295
- }
296
-
297
- // Always allow Task tool
298
- console.log(JSON.stringify({ decision: 'allow' }));
299
- process.exit(0);
300
- }
301
-
302
- // CLI: Show status
303
- if (process.argv[2] === '--status') {
304
- const stack = loadAgentStack();
305
- console.log('Builder Mode Manager Status');
306
- console.log('===========================');
307
- console.log(`Builder mode active: ${isBuilderModeActive()}`);
308
- console.log(`Active agents: ${stack.agents.length}`);
309
- if (stack.agents.length > 0) {
310
- stack.agents.forEach((a, i) => {
311
- console.log(` ${i + 1}. ${a.type} (needs builder: ${a.needsBuilderMode})`);
312
- });
313
- }
314
- if (stack.builderModeEnabledBy) {
315
- console.log(`Builder mode enabled by: ${stack.builderModeEnabledBy}`);
316
- }
317
- process.exit(0);
318
- }
319
-
320
- // CLI: Reset (clear stack and disable builder mode)
321
- if (process.argv[2] === '--reset') {
322
- disableBuilderMode();
323
- if (fs.existsSync(AGENT_STACK_FILE)) {
324
- fs.unlinkSync(AGENT_STACK_FILE);
325
- }
326
- console.log('✅ Builder mode manager reset');
327
- console.log(' - Builder mode disabled');
328
- console.log(' - Agent stack cleared');
329
- process.exit(0);
330
- }
331
-
332
- // CLI: Help
333
- if (process.argv[2] === '--help' || process.argv[2] === '-h') {
334
- console.log(`
335
- Builder Mode Manager - Auto-enables builder mode for code-editing agents
336
-
337
- Usage:
338
- node builder-mode-manager.cjs --status Show current status
339
- node builder-mode-manager.cjs --reset Reset builder mode and agent stack
340
- node builder-mode-manager.cjs --help Show this help
341
-
342
- Code-editing agents (auto-enable builder mode):
343
- ${CODE_EDITING_AGENTS.join(', ')}
344
-
345
- As a hook:
346
- PreToolUse (Task): Enables builder mode before spawning code-editing agents
347
- PostToolUse (Task): Disables builder mode when no more code-editing agents active
348
-
349
- Files:
350
- ${BUILDER_MODE_FILE} - Builder mode flag (shared with app-edit-guard, src-edit-guard)
351
- ${AGENT_STACK_FILE} - Tracks active agent stack for cleanup
352
- `);
353
- process.exit(0);
354
- }
@@ -1,271 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * <hook-name>bulk-activity-guard</hook-name>
4
- *
5
- * <purpose>
6
- * Confirms before creating many activities at once.
7
- * Prevents accidental bulk data creation via Dmitri agent.
8
- * </purpose>
9
- *
10
- * <triggers>
11
- * - PreToolUse on mcp__hailer__create_activity
12
- * - PreToolUse on mcp__hailer__bulk_create_activities (if exists)
13
- * </triggers>
14
- *
15
- * <thresholds>
16
- * - Single call with array > 5 items: Warn
17
- * - Tracked calls in short window > 10: Warn
18
- * </thresholds>
19
- *
20
- * <behavior>
21
- * 1. Check if bulk creation (array input or rapid sequential calls)
22
- * 2. If threshold exceeded, block and require confirmation
23
- * 3. Track recent calls in temp file for sequential detection
24
- * </behavior>
25
- */
26
-
27
- const fs = require('fs');
28
- const path = require('path');
29
- const os = require('os');
30
-
31
- // Skip in subagent context - orchestrator handles confirmation via delegation-bulk-guard
32
- // Subagents can't use AskUserQuestion or Bash, so blocking them is unrecoverable
33
- if (process.env.CLAUDE_AGENT_ID || process.env.CLAUDE_SUBAGENT) {
34
- // Output allow and exit - orchestrator already confirmed
35
- console.log(JSON.stringify({ decision: 'allow' }));
36
- process.exit(0);
37
- }
38
-
39
- const TRACKER_FILE = path.join(os.tmpdir(), '.claude-activity-creation-tracker.json');
40
- const LOCK_FILE = TRACKER_FILE + '.lock';
41
- const LOCK_TIMEOUT = 5000; // 5 seconds max wait
42
- const BULK_THRESHOLD = 5;
43
- const SEQUENTIAL_THRESHOLD = 10;
44
- const TIME_WINDOW_MS = 60000; // 1 minute window for sequential detection
45
-
46
- // Read hook input from stdin
47
- let input = '';
48
- process.stdin.setEncoding('utf8');
49
- process.stdin.on('data', chunk => input += chunk);
50
- process.stdin.on('end', () => {
51
- try {
52
- const data = JSON.parse(input);
53
- processHook(data);
54
- } catch (e) {
55
- console.error(`[bulk-activity-guard] Warning: ${e.message}`);
56
- console.log(JSON.stringify({ decision: 'allow' }));
57
- process.exit(0);
58
- }
59
- });
60
-
61
- function outputAllow() {
62
- console.log(JSON.stringify({ decision: 'allow' }));
63
- process.exit(0);
64
- }
65
-
66
- function outputBlock(message) {
67
- console.log(JSON.stringify({
68
- decision: 'block',
69
- reason: message
70
- }));
71
- process.exit(0);
72
- }
73
-
74
- function acquireLock() {
75
- const start = Date.now();
76
- while (Date.now() - start < LOCK_TIMEOUT) {
77
- try {
78
- fs.mkdirSync(LOCK_FILE);
79
- return true;
80
- } catch (e) {
81
- // Lock exists - check if stale (older than 10 seconds)
82
- try {
83
- const stat = fs.statSync(LOCK_FILE);
84
- if (Date.now() - stat.mtimeMs > 10000) {
85
- fs.rmdirSync(LOCK_FILE);
86
- continue;
87
- }
88
- } catch (e2) { /* lock was released between check */ }
89
- // Wait 50ms and retry
90
- const waitUntil = Date.now() + 50;
91
- while (Date.now() < waitUntil) { /* busy wait - hooks are sync */ }
92
- }
93
- }
94
- return false; // Timeout - proceed without lock (fallback)
95
- }
96
-
97
- function releaseLock() {
98
- try { fs.rmdirSync(LOCK_FILE); } catch (e) { /* already released */ }
99
- }
100
-
101
- function loadTracker() {
102
- try {
103
- if (fs.existsSync(TRACKER_FILE)) {
104
- return JSON.parse(fs.readFileSync(TRACKER_FILE, 'utf8'));
105
- }
106
- } catch (e) {
107
- console.error(`[bulk-activity-guard] Warning: ${e.message}`);
108
- }
109
- return { calls: [], confirmed: false, confirmedAt: null };
110
- }
111
-
112
- function saveTracker(tracker) {
113
- // Atomic write: write to temp file, then rename
114
- const tmpFile = TRACKER_FILE + '.tmp-' + process.pid;
115
- fs.writeFileSync(tmpFile, JSON.stringify(tracker, null, 2), { mode: 0o600 });
116
- fs.renameSync(tmpFile, TRACKER_FILE); // Atomic on POSIX
117
- }
118
-
119
- function processHook(data) {
120
- const { tool_name, tool_input } = data;
121
-
122
- // Only check activity creation tools - exact match to avoid false positives
123
- if (!tool_name || (tool_name !== 'mcp__hailer__create_activity' && tool_name !== 'mcp__hailer__bulk_create_activities')) {
124
- outputAllow();
125
- return;
126
- }
127
-
128
- const locked = acquireLock();
129
- try {
130
- const now = Date.now();
131
- const tracker = loadTracker();
132
-
133
- // Clean old calls outside time window
134
- tracker.calls = tracker.calls.filter(t => now - t < TIME_WINDOW_MS);
135
-
136
- // Check if recently confirmed (within last 5 minutes)
137
- if (tracker.confirmed && tracker.confirmedAt && (now - tracker.confirmedAt) < 300000) {
138
- // User recently confirmed bulk creation, allow
139
- tracker.calls.push(now);
140
- saveTracker(tracker);
141
- outputAllow();
142
- return;
143
- }
144
-
145
- // Reset confirmation if window expired
146
- tracker.confirmed = false;
147
- tracker.confirmedAt = null;
148
-
149
- // Check for bulk array in input
150
- let itemCount = 1;
151
- try {
152
- if (tool_input?.activities && Array.isArray(tool_input.activities)) {
153
- itemCount = tool_input.activities.length;
154
- }
155
- } catch {}
156
-
157
- // Add current call
158
- tracker.calls.push(now);
159
- const recentCallCount = tracker.calls.length;
160
- saveTracker(tracker);
161
-
162
- // Check thresholds
163
- const isBulkArray = itemCount > BULK_THRESHOLD;
164
- const isSequentialBulk = recentCallCount > SEQUENTIAL_THRESHOLD;
165
-
166
- if (isBulkArray || isSequentialBulk) {
167
- const reason = isBulkArray
168
- ? `Creating ${itemCount} activities in one call`
169
- : `${recentCallCount} activity creations in the last minute`;
170
-
171
- outputBlock(`
172
- 🚫 BLOCKED: Bulk Activity Creation Detected
173
-
174
- **Reason:** ${reason}
175
-
176
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
177
- CONFIRM WITH USER FIRST
178
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
179
-
180
- Use AskUserQuestion to confirm:
181
-
182
- \`\`\`json
183
- {
184
- "questions": [{
185
- "question": "About to create ${isBulkArray ? itemCount : recentCallCount}+ activities. Continue?",
186
- "header": "Bulk Create",
187
- "options": [
188
- { "label": "Yes, create all", "description": "Proceed with bulk creation" },
189
- { "label": "No, stop", "description": "Cancel the operation" }
190
- ],
191
- "multiSelect": false
192
- }]
193
- }
194
- \`\`\`
195
-
196
- If user confirms, run this to unlock for 5 minutes:
197
- Bash: node "${process.argv[1].replace(/'/g, "'\\''")}" --confirm
198
-
199
- Then retry the operation.
200
-
201
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
202
- `);
203
- }
204
-
205
- outputAllow();
206
- } finally {
207
- if (locked) releaseLock();
208
- }
209
- }
210
-
211
- // CLI: Confirm bulk creation
212
- if (process.argv[2] === '--confirm') {
213
- const locked = acquireLock();
214
- try {
215
- const tracker = loadTracker();
216
- tracker.confirmed = true;
217
- tracker.confirmedAt = Date.now();
218
- saveTracker(tracker);
219
- console.log('✅ Bulk activity creation confirmed for 5 minutes');
220
- } finally {
221
- if (locked) releaseLock();
222
- }
223
- process.exit(0);
224
- }
225
-
226
- // CLI: Reset tracker
227
- if (process.argv[2] === '--reset') {
228
- if (fs.existsSync(TRACKER_FILE)) {
229
- fs.unlinkSync(TRACKER_FILE);
230
- }
231
- console.log('✅ Activity creation tracker reset');
232
- process.exit(0);
233
- }
234
-
235
- // CLI: Status
236
- if (process.argv[2] === '--status') {
237
- const tracker = loadTracker();
238
- const now = Date.now();
239
- const recentCalls = tracker.calls.filter(t => now - t < TIME_WINDOW_MS).length;
240
- console.log('Activity Creation Tracker');
241
- console.log('=========================');
242
- console.log(`Recent calls (1 min): ${recentCalls}`);
243
- console.log(`Confirmed: ${tracker.confirmed}`);
244
- if (tracker.confirmedAt) {
245
- const remaining = Math.max(0, 300000 - (now - tracker.confirmedAt));
246
- console.log(`Confirmation expires in: ${Math.round(remaining / 1000)}s`);
247
- }
248
- process.exit(0);
249
- }
250
-
251
- // CLI: Help
252
- if (process.argv[2] === '--help' || process.argv[2] === '-h') {
253
- console.log(`
254
- Bulk Activity Guard - Confirms before bulk activity creation
255
-
256
- Usage:
257
- node bulk-activity-guard.cjs --confirm Confirm bulk creation for 5 minutes
258
- node bulk-activity-guard.cjs --reset Reset the tracker
259
- node bulk-activity-guard.cjs --status Show current status
260
- node bulk-activity-guard.cjs --help Show this help
261
-
262
- Thresholds:
263
- - Single call with >${BULK_THRESHOLD} items: Blocked
264
- - >${SEQUENTIAL_THRESHOLD} calls in 1 minute: Blocked
265
-
266
- As a hook:
267
- Reads JSON from stdin with tool_name and tool_input
268
- Outputs JSON with decision: "allow" or "block"
269
- `);
270
- process.exit(0);
271
- }