@hailer/mcp 1.1.12 → 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 (271) hide show
  1. package/CHANGELOG.md +0 -7
  2. package/{.claude → dist}/CLAUDE.md +2 -2
  3. package/dist/app.js +18 -5
  4. package/dist/bot/bot-config.d.ts +10 -1
  5. package/dist/bot/bot-config.js +64 -3
  6. package/dist/bot/bot-manager.d.ts +2 -0
  7. package/dist/bot/bot-manager.js +9 -2
  8. package/dist/bot/bot.d.ts +33 -0
  9. package/dist/bot/bot.js +461 -160
  10. package/dist/bot/services/message-classifier.js +17 -0
  11. package/dist/bot/services/permission-guard.d.ts +52 -0
  12. package/dist/bot/services/permission-guard.js +149 -0
  13. package/dist/bot/services/types.d.ts +5 -0
  14. package/dist/bot/services/typing-indicator.d.ts +6 -1
  15. package/dist/bot/services/typing-indicator.js +19 -3
  16. package/dist/cli.js +0 -0
  17. package/dist/config.d.ts +6 -1
  18. package/dist/config.js +43 -0
  19. package/dist/core.js +3 -6
  20. package/dist/lib/discussion-lock.d.ts +42 -0
  21. package/dist/lib/discussion-lock.js +110 -0
  22. package/dist/mcp/UserContextCache.d.ts +5 -0
  23. package/dist/mcp/UserContextCache.js +51 -19
  24. package/dist/mcp/hailer-clients.d.ts +19 -1
  25. package/dist/mcp/hailer-clients.js +158 -24
  26. package/dist/mcp/session-store.d.ts +68 -0
  27. package/dist/mcp/session-store.js +169 -0
  28. package/dist/mcp/signal-handler.js +2 -0
  29. package/dist/mcp/tool-registry.d.ts +17 -4
  30. package/dist/mcp/tool-registry.js +37 -7
  31. package/dist/mcp/tools/activity.js +99 -7
  32. package/dist/mcp/tools/app-scaffold.js +304 -336
  33. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  34. package/dist/mcp/tools/bot-config/constants.js +94 -0
  35. package/dist/mcp/tools/bot-config/core.d.ts +253 -0
  36. package/dist/mcp/tools/bot-config/core.js +2456 -0
  37. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  38. package/dist/mcp/tools/bot-config/index.js +59 -0
  39. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  40. package/dist/mcp/tools/bot-config/tools.js +15 -0
  41. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  42. package/dist/mcp/tools/bot-config/types.js +6 -0
  43. package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
  44. package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
  45. package/dist/mcp/tools/company.d.ts +9 -0
  46. package/dist/mcp/tools/company.js +88 -0
  47. package/dist/mcp/tools/discussion.js +68 -0
  48. package/dist/mcp/tools/document.d.ts +11 -0
  49. package/dist/mcp/tools/document.js +741 -0
  50. package/dist/mcp/tools/investigate.d.ts +9 -0
  51. package/dist/mcp/tools/investigate.js +254 -0
  52. package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
  53. package/dist/mcp/tools/workflow-permissions.js +204 -0
  54. package/dist/mcp/tools/workflow.js +57 -18
  55. package/dist/mcp/utils/index.d.ts +2 -0
  56. package/dist/mcp/utils/index.js +12 -1
  57. package/dist/mcp/utils/role-utils.d.ts +74 -0
  58. package/dist/mcp/utils/role-utils.js +151 -0
  59. package/dist/mcp/utils/types.d.ts +43 -1
  60. package/dist/mcp/utils/types.js +14 -0
  61. package/dist/mcp/webhook-handler.d.ts +4 -0
  62. package/dist/mcp/webhook-handler.js +8 -0
  63. package/dist/mcp-server.d.ts +23 -2
  64. package/dist/mcp-server.js +639 -127
  65. package/dist/plugins/vipunen/client.d.ts +150 -0
  66. package/dist/plugins/vipunen/client.js +535 -0
  67. package/dist/plugins/vipunen/config/schema-config.json +19 -0
  68. package/dist/plugins/vipunen/config/schema-doc.json +22 -0
  69. package/dist/plugins/vipunen/index.d.ts +41 -0
  70. package/dist/plugins/vipunen/index.js +88 -0
  71. package/dist/plugins/vipunen/tools.d.ts +26 -0
  72. package/dist/plugins/vipunen/tools.js +501 -0
  73. package/dist/stdio-server.d.ts +14 -0
  74. package/dist/stdio-server.js +101 -0
  75. package/package.json +2 -1
  76. package/.claude/agents/agent-ada-skill-builder.md +0 -94
  77. package/.claude/agents/agent-alejandro-function-fields.md +0 -342
  78. package/.claude/agents/agent-bjorn-config-audit.md +0 -103
  79. package/.claude/agents/agent-builder-agent-creator.md +0 -130
  80. package/.claude/agents/agent-code-simplifier.md +0 -53
  81. package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
  82. package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
  83. package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
  84. package/.claude/agents/agent-helga-workflow-config.md +0 -204
  85. package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
  86. package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
  87. package/.claude/agents/agent-ivan-monolith.md +0 -154
  88. package/.claude/agents/agent-kenji-data-reader.md +0 -86
  89. package/.claude/agents/agent-lars-code-inspector.md +0 -102
  90. package/.claude/agents/agent-marco-mockup-builder.md +0 -110
  91. package/.claude/agents/agent-marcus-api-documenter.md +0 -323
  92. package/.claude/agents/agent-marketplace-publisher.md +0 -280
  93. package/.claude/agents/agent-marketplace-reviewer.md +0 -309
  94. package/.claude/agents/agent-permissions-handler.md +0 -208
  95. package/.claude/agents/agent-simple-writer.md +0 -48
  96. package/.claude/agents/agent-svetlana-code-review.md +0 -171
  97. package/.claude/agents/agent-tanya-test-runner.md +0 -333
  98. package/.claude/agents/agent-ui-designer.md +0 -100
  99. package/.claude/agents/agent-viktor-sql-insights.md +0 -212
  100. package/.claude/agents/agent-web-search.md +0 -55
  101. package/.claude/agents/agent-yevgeni-discussions.md +0 -45
  102. package/.claude/agents/agent-zara-zapier.md +0 -159
  103. package/.claude/commands/app-squad.md +0 -135
  104. package/.claude/commands/audit-squad.md +0 -158
  105. package/.claude/commands/autoplan.md +0 -563
  106. package/.claude/commands/cleanup-squad.md +0 -98
  107. package/.claude/commands/config-squad.md +0 -106
  108. package/.claude/commands/crud-squad.md +0 -87
  109. package/.claude/commands/data-squad.md +0 -97
  110. package/.claude/commands/debug-squad.md +0 -303
  111. package/.claude/commands/doc-squad.md +0 -65
  112. package/.claude/commands/handoff.md +0 -137
  113. package/.claude/commands/health.md +0 -49
  114. package/.claude/commands/help.md +0 -29
  115. package/.claude/commands/help:agents.md +0 -151
  116. package/.claude/commands/help:commands.md +0 -78
  117. package/.claude/commands/help:faq.md +0 -79
  118. package/.claude/commands/help:plugins.md +0 -50
  119. package/.claude/commands/help:skills.md +0 -93
  120. package/.claude/commands/help:tools.md +0 -75
  121. package/.claude/commands/hotfix-squad.md +0 -112
  122. package/.claude/commands/integration-squad.md +0 -82
  123. package/.claude/commands/janitor-squad.md +0 -167
  124. package/.claude/commands/learn-auto.md +0 -120
  125. package/.claude/commands/learn.md +0 -120
  126. package/.claude/commands/mcp-list.md +0 -27
  127. package/.claude/commands/onboard-squad.md +0 -140
  128. package/.claude/commands/plan-workspace.md +0 -732
  129. package/.claude/commands/prd.md +0 -130
  130. package/.claude/commands/project-status.md +0 -82
  131. package/.claude/commands/publish.md +0 -138
  132. package/.claude/commands/recap.md +0 -69
  133. package/.claude/commands/restore.md +0 -64
  134. package/.claude/commands/review-squad.md +0 -152
  135. package/.claude/commands/save.md +0 -24
  136. package/.claude/commands/stats.md +0 -19
  137. package/.claude/commands/swarm.md +0 -210
  138. package/.claude/commands/tool-builder.md +0 -39
  139. package/.claude/commands/ws-pull.md +0 -44
  140. package/.claude/hooks/_shared-memory.cjs +0 -305
  141. package/.claude/hooks/_utils.cjs +0 -108
  142. package/.claude/hooks/agent-failure-detector.cjs +0 -383
  143. package/.claude/hooks/agent-usage-logger.cjs +0 -204
  144. package/.claude/hooks/app-edit-guard.cjs +0 -494
  145. package/.claude/hooks/auto-learn.cjs +0 -304
  146. package/.claude/hooks/bash-guard.cjs +0 -272
  147. package/.claude/hooks/builder-mode-manager.cjs +0 -354
  148. package/.claude/hooks/bulk-activity-guard.cjs +0 -271
  149. package/.claude/hooks/context-watchdog.cjs +0 -230
  150. package/.claude/hooks/delegation-reminder.cjs +0 -465
  151. package/.claude/hooks/design-system-lint.cjs +0 -271
  152. package/.claude/hooks/post-scaffold-hook.cjs +0 -181
  153. package/.claude/hooks/prompt-guard.cjs +0 -354
  154. package/.claude/hooks/publish-template-guard.cjs +0 -147
  155. package/.claude/hooks/session-start.cjs +0 -35
  156. package/.claude/hooks/shared-memory-writer.cjs +0 -147
  157. package/.claude/hooks/skill-injector.cjs +0 -140
  158. package/.claude/hooks/skill-usage-logger.cjs +0 -258
  159. package/.claude/hooks/src-edit-guard.cjs +0 -240
  160. package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
  161. package/.claude/settings.json +0 -257
  162. package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
  163. package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
  164. package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
  165. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
  166. package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
  167. package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
  168. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
  169. package/.claude/skills/agent-structure/SKILL.md +0 -98
  170. package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
  171. package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
  172. package/.claude/skills/delegation-routing/SKILL.md +0 -202
  173. package/.claude/skills/frontend-design/SKILL.md +0 -254
  174. package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
  175. package/.claude/skills/hailer-api-client/SKILL.md +0 -518
  176. package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
  177. package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
  178. package/.claude/skills/hailer-design-system/SKILL.md +0 -235
  179. package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
  180. package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
  181. package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
  182. package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
  183. package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
  184. package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
  185. package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
  186. package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
  187. package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
  188. package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
  189. package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
  190. package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
  191. package/.claude/skills/integration-patterns/SKILL.md +0 -421
  192. package/.claude/skills/json-only-output/SKILL.md +0 -72
  193. package/.claude/skills/lsp-setup/SKILL.md +0 -160
  194. package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
  195. package/.claude/skills/optional-parameters/SKILL.md +0 -72
  196. package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
  197. package/.claude/skills/testing-patterns/SKILL.md +0 -630
  198. package/.claude/skills/tool-builder/SKILL.md +0 -250
  199. package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
  200. package/.claude/skills/tool-response-verification/SKILL.md +0 -92
  201. package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
  202. package/.mcp.json +0 -13
  203. package/.opencode/agent/agent-ada-skill-builder.md +0 -35
  204. package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
  205. package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
  206. package/.opencode/agent/agent-builder-agent-creator.md +0 -39
  207. package/.opencode/agent/agent-code-simplifier.md +0 -31
  208. package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
  209. package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
  210. package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
  211. package/.opencode/agent/agent-helga-workflow-config.md +0 -203
  212. package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
  213. package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
  214. package/.opencode/agent/agent-ivan-monolith.md +0 -46
  215. package/.opencode/agent/agent-kenji-data-reader.md +0 -53
  216. package/.opencode/agent/agent-lars-code-inspector.md +0 -28
  217. package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
  218. package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
  219. package/.opencode/agent/agent-marketplace-publisher.md +0 -44
  220. package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
  221. package/.opencode/agent/agent-permissions-handler.md +0 -50
  222. package/.opencode/agent/agent-simple-writer.md +0 -45
  223. package/.opencode/agent/agent-svetlana-code-review.md +0 -39
  224. package/.opencode/agent/agent-tanya-test-runner.md +0 -57
  225. package/.opencode/agent/agent-ui-designer.md +0 -56
  226. package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
  227. package/.opencode/agent/agent-web-search.md +0 -42
  228. package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
  229. package/.opencode/agent/agent-zara-zapier.md +0 -53
  230. package/.opencode/commands/app-squad.md +0 -135
  231. package/.opencode/commands/audit-squad.md +0 -158
  232. package/.opencode/commands/autoplan.md +0 -563
  233. package/.opencode/commands/cleanup-squad.md +0 -98
  234. package/.opencode/commands/config-squad.md +0 -106
  235. package/.opencode/commands/crud-squad.md +0 -87
  236. package/.opencode/commands/data-squad.md +0 -97
  237. package/.opencode/commands/debug-squad.md +0 -303
  238. package/.opencode/commands/doc-squad.md +0 -65
  239. package/.opencode/commands/handoff.md +0 -137
  240. package/.opencode/commands/health.md +0 -49
  241. package/.opencode/commands/help-agents.md +0 -151
  242. package/.opencode/commands/help-commands.md +0 -32
  243. package/.opencode/commands/help-faq.md +0 -29
  244. package/.opencode/commands/help-plugins.md +0 -28
  245. package/.opencode/commands/help-skills.md +0 -7
  246. package/.opencode/commands/help-tools.md +0 -40
  247. package/.opencode/commands/help.md +0 -28
  248. package/.opencode/commands/hotfix-squad.md +0 -112
  249. package/.opencode/commands/integration-squad.md +0 -82
  250. package/.opencode/commands/janitor-squad.md +0 -167
  251. package/.opencode/commands/learn-auto.md +0 -120
  252. package/.opencode/commands/learn.md +0 -120
  253. package/.opencode/commands/mcp-list.md +0 -27
  254. package/.opencode/commands/onboard-squad.md +0 -140
  255. package/.opencode/commands/plan-workspace.md +0 -732
  256. package/.opencode/commands/prd.md +0 -131
  257. package/.opencode/commands/project-status.md +0 -82
  258. package/.opencode/commands/publish.md +0 -138
  259. package/.opencode/commands/recap.md +0 -69
  260. package/.opencode/commands/restore.md +0 -64
  261. package/.opencode/commands/review-squad.md +0 -152
  262. package/.opencode/commands/save.md +0 -24
  263. package/.opencode/commands/stats.md +0 -19
  264. package/.opencode/commands/swarm.md +0 -210
  265. package/.opencode/commands/tool-builder.md +0 -39
  266. package/.opencode/commands/ws-pull.md +0 -44
  267. package/.opencode/opencode.json +0 -28
  268. package/SESSION-HANDOFF.md +0 -68
  269. package/inbox/2026-03-04-bot-config-patterns.md +0 -24
  270. package/scripts/postinstall.cjs +0 -64
  271. 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
- }