@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,304 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * <hook-name>auto-learn</hook-name>
4
- *
5
- * <purpose>
6
- * Detects potential learnings from agent responses and prompts user to confirm.
7
- * Only appends to LEARNINGS.md after user approval.
8
- * Complements manual /learn command.
9
- * </purpose>
10
- *
11
- * <triggers>
12
- * - PostToolUse on Task (agent completions)
13
- * </triggers>
14
- *
15
- * <detection-patterns>
16
- * - "learned that", "discovered that", "found that"
17
- * - "note:", "important:", "gotcha:", "tip:"
18
- * - "always", "never", "must", "should" (in context of rules)
19
- * - "the trick is", "the key is", "turns out"
20
- * </detection-patterns>
21
- *
22
- * <behavior>
23
- * 1. Scan tool response for learning patterns
24
- * 2. If found, output prompt for Claude to use AskUserQuestion
25
- * 3. User confirms which learnings to save
26
- * 4. Claude runs --save command to append to LEARNINGS.md
27
- * </behavior>
28
- */
29
-
30
- const fs = require('fs');
31
- const path = require('path');
32
- const os = require('os');
33
-
34
- const LEARNINGS_FILE = path.join(
35
- process.env.HAILER_INBOX_DIR || path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), 'inbox'),
36
- 'learnings.md'
37
- );
38
- const PENDING_FILE = path.join(os.tmpdir(), '.claude-pending-learnings.json');
39
-
40
- // Patterns that indicate a learning/insight
41
- const LEARNING_PATTERNS = [
42
- /(?:i |we |claude )(?:learned|discovered|found|realized) that (.{20,150})/i,
43
- /(?:note|important|gotcha|tip|caveat|warning):\s*(.{20,150})/i,
44
- /(?:the (?:trick|key|solution|fix) (?:is|was)) (.{20,150})/i,
45
- /(?:turns out|it turns out) (.{20,150})/i,
46
- /(?:remember to|don't forget to|make sure to) (.{20,100})/i,
47
- ];
48
-
49
- // Category detection based on content
50
- const CATEGORY_PATTERNS = [
51
- { pattern: /agent|kenji|dmitri|giuseppe|helga|alejandro|viktor/i, category: 'agent' },
52
- { pattern: /skill|pattern|template/i, category: 'skill' },
53
- { pattern: /workflow|field|phase|activity/i, category: 'workflow' },
54
- { pattern: /bug|error|fix|broken/i, category: 'bug' },
55
- { pattern: /hook|guard|check/i, category: 'hook' },
56
- ];
57
-
58
- // Read hook input from stdin
59
- let input = '';
60
- process.stdin.setEncoding('utf8');
61
- process.stdin.on('data', chunk => input += chunk);
62
- process.stdin.on('end', () => {
63
- try {
64
- const data = JSON.parse(input);
65
- processHook(data);
66
- } catch (e) {
67
- console.error(`[auto-learn] Warning: ${e.message}`);
68
- process.exit(0);
69
- }
70
- });
71
-
72
- function detectCategory(text) {
73
- for (const { pattern, category } of CATEGORY_PATTERNS) {
74
- if (pattern.test(text)) {
75
- return category;
76
- }
77
- }
78
- return 'pattern';
79
- }
80
-
81
- function extractLearnings(text) {
82
- const learnings = [];
83
-
84
- // Limit input size to prevent ReDoS attacks (100KB max)
85
- const MAX_INPUT_SIZE = 100000;
86
- if (text.length > MAX_INPUT_SIZE) {
87
- text = text.substring(0, MAX_INPUT_SIZE);
88
- }
89
-
90
- // Normalize whitespace for multi-line matching
91
- const normalizedText = text.replace(/\s+/g, ' ');
92
-
93
- // NOTE: Uses global match then per-result extraction. Acceptable for typical agent output sizes (<100KB).
94
- // For larger inputs, consider exec() loop instead.
95
- const matchStartTime = Date.now();
96
- for (const pattern of LEARNING_PATTERNS) {
97
- if (Date.now() - matchStartTime > 1000) break; // ReDoS timeout protection
98
- // Use 'gis' flags for case-insensitive, dotall (multi-line) matching
99
- const matches = normalizedText.match(new RegExp(pattern.source, 'gis'));
100
- if (matches) {
101
- for (const match of matches) {
102
- const extracted = match.match(new RegExp(pattern.source, 'is'));
103
- if (extracted && extracted[1]) {
104
- let learning = extracted[1].trim();
105
- learning = learning.replace(/\s+/g, ' ').replace(/[.!,;]$/, '').trim();
106
- if (learning.length >= 20 && !learnings.some(l => l.text === learning)) {
107
- learnings.push({
108
- text: learning,
109
- category: detectCategory(learning)
110
- });
111
- }
112
- }
113
- }
114
- }
115
- }
116
-
117
- return learnings;
118
- }
119
-
120
- function processHook(data) {
121
- const { tool_name, tool_input, tool_response } = data;
122
-
123
- // Only process Task completions
124
- if (tool_name !== 'Task') {
125
- process.exit(0);
126
- }
127
-
128
- if (tool_response === undefined) {
129
- process.exit(0);
130
- }
131
-
132
- const agentName = tool_input?.subagent_type;
133
- const responseText = typeof tool_response === 'string'
134
- ? tool_response
135
- : JSON.stringify(tool_response || '');
136
-
137
- const learnings = extractLearnings(responseText);
138
-
139
- if (learnings.length > 0) {
140
- // Save pending learnings for later confirmation
141
- fs.writeFileSync(PENDING_FILE, JSON.stringify({
142
- learnings,
143
- agentName,
144
- timestamp: new Date().toISOString()
145
- }));
146
-
147
- // Build options for AskUserQuestion (max 4)
148
- const options = learnings.slice(0, 3).map((l, i) => ({
149
- label: `Save #${i + 1}`,
150
- description: l.text.substring(0, 50) + (l.text.length > 50 ? '...' : '')
151
- }));
152
- options.push({ label: 'Skip all', description: 'Don\'t save any of these' });
153
-
154
- const output = `
155
- <user-prompt-submit-hook>
156
- 📚 POTENTIAL LEARNING${learnings.length > 1 ? 'S' : ''} DETECTED
157
-
158
- ${learnings.map((l, i) => `${i + 1}. [${l.category}] ${l.text}`).join('\n')}
159
-
160
- 👉 Ask user: "Save to inbox/learnings.md?" Options: ${options.map(o => o.label).join(' / ')}
161
-
162
- If yes, run: node .claude/hooks/auto-learn.cjs --save <indices>
163
- </user-prompt-submit-hook>
164
- `;
165
-
166
- console.log(output);
167
- }
168
-
169
- process.exit(0);
170
- }
171
-
172
- // CLI: Save specific learnings by index
173
- if (process.argv[2] === '--save') {
174
- const indices = process.argv.slice(3)
175
- .map(n => parseInt(n, 10))
176
- .filter(n => !isNaN(n))
177
- .map(n => n - 1);
178
-
179
- if (!fs.existsSync(PENDING_FILE)) {
180
- console.error('No pending learnings found');
181
- process.exit(1);
182
- }
183
-
184
- const pending = JSON.parse(fs.readFileSync(PENDING_FILE, 'utf8'));
185
- const toSave = indices
186
- .filter(i => i >= 0 && i < pending.learnings.length)
187
- .map(i => pending.learnings[i]);
188
-
189
- if (toSave.length === 0) {
190
- console.log('No valid learnings selected');
191
- process.exit(0);
192
- }
193
-
194
- // Ensure LEARNINGS.md exists (recursive: true is idempotent, no TOCTOU race)
195
- if (!fs.existsSync(LEARNINGS_FILE)) {
196
- const dir = path.dirname(LEARNINGS_FILE);
197
- fs.mkdirSync(dir, { recursive: true });
198
- fs.writeFileSync(LEARNINGS_FILE, `# Inbox
199
-
200
- Learnings captured from all projects. Review and integrate into marketplace agents/skills.
201
-
202
- ## Pending
203
- <!-- /learn and auto-learn add items here -->
204
-
205
- ## Applied
206
- <!-- Move items here after integrating into agents/skills -->
207
- `);
208
- }
209
-
210
- let content = fs.readFileSync(LEARNINGS_FILE, 'utf8');
211
- const timestamp = new Date().toISOString().split('T')[0];
212
-
213
- const entries = toSave.map(l => {
214
- const source = pending.agentName ? `from ${pending.agentName}` : '';
215
- const autoTag = '🤖 auto-learn';
216
- return `- [${l.category}] ${l.text} _(${autoTag}${source ? `, ${source}` : ''}, ${timestamp})_`;
217
- }).join('\n');
218
-
219
- // Add under Pending section
220
- const pendingMatch = content.match(/^(## Pending\s*\n(?:<!--[^>]*-->\s*\n)?)/m);
221
- if (pendingMatch) {
222
- const insertPoint = content.indexOf(pendingMatch[0]) + pendingMatch[0].length;
223
- content = content.slice(0, insertPoint) + entries + '\n' + content.slice(insertPoint);
224
- } else {
225
- // Fallback: append to end if no Pending section found
226
- content += `\n## Pending\n${entries}\n`;
227
- }
228
-
229
- // Atomic write: temp file + rename to prevent corruption
230
- const tmpFile = LEARNINGS_FILE + '.tmp-' + process.pid;
231
- fs.writeFileSync(tmpFile, content);
232
- fs.renameSync(tmpFile, LEARNINGS_FILE);
233
-
234
- // Safe unlink: ignore ENOENT if file already deleted
235
- try {
236
- fs.unlinkSync(PENDING_FILE);
237
- } catch (err) {
238
- if (err.code !== 'ENOENT') throw err;
239
- }
240
-
241
- console.log(`✅ Saved ${toSave.length} learning(s) to inbox/learnings.md`);
242
- process.exit(0);
243
- }
244
-
245
- // CLI: Clear pending
246
- if (process.argv[2] === '--clear') {
247
- if (fs.existsSync(PENDING_FILE)) {
248
- fs.unlinkSync(PENDING_FILE);
249
- console.log('✅ Cleared pending learnings');
250
- } else {
251
- console.log('No pending learnings');
252
- }
253
- process.exit(0);
254
- }
255
-
256
- // CLI: Show pending
257
- if (process.argv[2] === '--pending') {
258
- if (!fs.existsSync(PENDING_FILE)) {
259
- console.log('No pending learnings');
260
- process.exit(0);
261
- }
262
- const pending = JSON.parse(fs.readFileSync(PENDING_FILE, 'utf8'));
263
- console.log('Pending learnings:');
264
- pending.learnings.forEach((l, i) => {
265
- console.log(` ${i + 1}. [${l.category}] ${l.text}`);
266
- });
267
- process.exit(0);
268
- }
269
-
270
- // CLI: Test
271
- if (process.argv[2] === '--test' && process.argv[3]) {
272
- const text = process.argv.slice(3).join(' ');
273
- const learnings = extractLearnings(text);
274
- if (learnings.length === 0) {
275
- console.log('No learnings detected');
276
- } else {
277
- console.log('Detected:');
278
- learnings.forEach(l => console.log(` [${l.category}] ${l.text}`));
279
- }
280
- process.exit(0);
281
- }
282
-
283
- // CLI: Help
284
- if (process.argv[2] === '--help' || process.argv[2] === '-h') {
285
- console.log(`
286
- Auto-Learn Hook - Detects learnings and prompts for confirmation
287
-
288
- Usage:
289
- node auto-learn.cjs --save 1 2 3 Save learnings by index
290
- node auto-learn.cjs --pending Show pending learnings
291
- node auto-learn.cjs --clear Clear pending learnings
292
- node auto-learn.cjs --test <text> Test detection on sample text
293
- node auto-learn.cjs --help Show this help
294
-
295
- Workflow:
296
- 1. Hook detects learning in agent response
297
- 2. Prompts Claude to ask user for confirmation
298
- 3. User selects which to save
299
- 4. Claude runs --save with indices
300
-
301
- Complements manual /learn command.
302
- `);
303
- process.exit(0);
304
- }
@@ -1,272 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * <hook-name>bash-guard</hook-name>
4
- *
5
- * <purpose>
6
- * Consolidated Bash PreToolUse guard. Replaces 4 separate hooks:
7
- * - destructive-command-guard (blocks dangerous commands)
8
- * - mcp-server-guard (blocks server start commands)
9
- * - workspace-pull-guard (warns before pull with dirty workspace)
10
- * - workspace-auto-save (auto-commits before push)
11
- *
12
- * Single Node process instead of 4 = faster, no timeouts.
13
- * </purpose>
14
- *
15
- * <triggers>PreToolUse on Bash</triggers>
16
- * @version 1.0.0
17
- */
18
-
19
- const { spawnSync } = require('child_process');
20
- const fs = require('fs');
21
- const path = require('path');
22
- const os = require('os');
23
-
24
- const ALLOW = JSON.stringify({ decision: 'allow' });
25
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
26
-
27
- // --- CLI modes ---
28
- if (process.argv[2] === '--bypass' && process.argv[3]) {
29
- const command = process.argv[3];
30
- const bypass = { command, createdAt: Date.now(), expiresAt: Date.now() + 120000 };
31
- const BYPASS_FILE = path.join(os.tmpdir(), '.claude-destructive-bypass.json');
32
- const tmpFile = BYPASS_FILE + '.tmp-' + process.pid;
33
- fs.writeFileSync(tmpFile, JSON.stringify(bypass, null, 2));
34
- fs.renameSync(tmpFile, BYPASS_FILE);
35
- console.log(`Bypass created for: ${command}`);
36
- console.log('Expires in 2 minutes. Retry the command now.');
37
- process.exit(0);
38
- }
39
- if (process.argv[2] === '--history') {
40
- try {
41
- const result = spawnSync('git', ['log', '--oneline', '--grep=Auto-save workspace', '-20'], {
42
- cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
43
- });
44
- console.log((result.stdout || '').trim() || 'No auto-saves found');
45
- } catch { console.log('Could not read git log'); }
46
- process.exit(0);
47
- }
48
- if (process.argv[2] === '--help' || process.argv[2] === '-h') {
49
- console.log(`
50
- Bash Guard - Consolidated Bash PreToolUse hook
51
-
52
- Combines: destructive-command-guard, mcp-server-guard, workspace-pull-guard, workspace-auto-save
53
-
54
- Usage:
55
- node bash-guard.cjs --bypass '<cmd>' Create one-time bypass for blocked command
56
- node bash-guard.cjs --history Show recent workspace auto-saves
57
- node bash-guard.cjs --help Show this help
58
- `);
59
- process.exit(0);
60
- }
61
-
62
- // --- Read stdin ---
63
- let input = '';
64
- process.stdin.setEncoding('utf8');
65
- process.stdin.on('data', chunk => input += chunk);
66
- process.stdin.on('end', () => {
67
- try {
68
- const data = JSON.parse(input || '{}');
69
- processHook(data);
70
- } catch {
71
- allow();
72
- }
73
- });
74
-
75
- function allow(msg) {
76
- if (msg) console.error('[bash-guard] ' + msg);
77
- console.log(ALLOW);
78
- process.exit(0);
79
- }
80
-
81
- function block(reason) {
82
- console.log(JSON.stringify({ decision: 'block', reason }));
83
- process.exit(0);
84
- }
85
-
86
- function processHook(data) {
87
- const { tool_name, tool_input } = data;
88
- if (tool_name !== 'Bash') { allow(); return; }
89
-
90
- const command = tool_input?.command || '';
91
- if (!command) { allow(); return; }
92
-
93
- // Run checks in order of specificity
94
- checkDestructive(command);
95
- checkMcpServer(command);
96
- checkWorkspacePull(command);
97
- checkWorkspaceAutoSave(command);
98
- allow();
99
- }
100
-
101
- // ============================================================
102
- // 1. Destructive Command Guard
103
- // ============================================================
104
- const DANGEROUS_PATTERNS = [
105
- { pattern: /git\s+reset\s+--hard/i, name: 'git reset --hard', risk: 'Permanently discards all uncommitted changes', alternative: 'git stash or git reset --soft' },
106
- { pattern: /git\s+clean\s+-[fd]/i, name: 'git clean -f/-fd', risk: 'Permanently deletes untracked files', alternative: 'git clean -n (dry run)' },
107
- { pattern: /\brm\s+-[a-zA-Z]{0,10}(rf|fr)[a-zA-Z]{0,10}\b/i, name: 'rm -rf', risk: 'Recursively deletes files without confirmation', alternative: 'rm -ri (interactive) or move to trash' },
108
- { pattern: /git\s+push\s+[^|]*--force(?!-with-lease)[^|]*(main|master)/i, name: 'git push --force to main/master', risk: 'Overwrites remote history', alternative: 'git push --force-with-lease' },
109
- { pattern: /git\s+push\s+[^|]*(main|master)[^|]*--force(?!-with-lease)/i, name: 'git push --force to main/master', risk: 'Overwrites remote history', alternative: 'git push --force-with-lease' },
110
- { pattern: /git\s+checkout\s+\.\s*$/i, name: 'git checkout .', risk: 'Discards all uncommitted changes', alternative: 'git stash or git diff' },
111
- { pattern: /git\s+restore\s+\.\s*$/i, name: 'git restore .', risk: 'Discards all uncommitted changes', alternative: 'git stash or git diff' },
112
- { pattern: /git\s+branch\s+-D/, name: 'git branch -D', risk: 'Force-deletes branch even if not merged', alternative: 'git branch -d (safe delete)' }
113
- ];
114
-
115
- const BYPASS_FILE = path.join(os.tmpdir(), '.claude-destructive-bypass.json');
116
-
117
- function isSubagentActive() {
118
- try {
119
- const stackFile = path.join(os.tmpdir(), '.claude-agent-stack.json');
120
- if (fs.existsSync(stackFile)) {
121
- const stack = JSON.parse(fs.readFileSync(stackFile, 'utf8'));
122
- return Array.isArray(stack.agents) && stack.agents.length > 0;
123
- }
124
- } catch {}
125
- return false;
126
- }
127
-
128
- function checkBypass(command) {
129
- try {
130
- const bypass = JSON.parse(fs.readFileSync(BYPASS_FILE, 'utf8'));
131
- if (typeof bypass.expiresAt !== 'number' || typeof bypass.command !== 'string') {
132
- try { fs.unlinkSync(BYPASS_FILE); } catch {}
133
- return false;
134
- }
135
- if (bypass.expiresAt <= Date.now()) {
136
- try { fs.unlinkSync(BYPASS_FILE); } catch {}
137
- return false;
138
- }
139
- if (bypass.command === command) {
140
- try { fs.unlinkSync(BYPASS_FILE); } catch {}
141
- return true;
142
- }
143
- } catch {}
144
- return false;
145
- }
146
-
147
- function checkDestructive(command) {
148
- if (isSubagentActive()) return;
149
- if (command.includes('bash-guard') && command.includes('--bypass')) return;
150
- if (checkBypass(command)) return;
151
-
152
- for (const { pattern, name, risk, alternative } of DANGEROUS_PATTERNS) {
153
- if (pattern.test(command)) {
154
- block(`BLOCKED: Destructive Command Detected
155
-
156
- **Command:** \`${name}\`
157
- **Risk:** ${risk}
158
-
159
- **USE THESE INSTEAD (no bypass needed):**
160
- - Delete files individually: \`rm file1 file2\` (no -rf flag)
161
- - Delete directory contents: \`find /path -mindepth 1 -delete\`
162
- - Move to trash: \`mv /path ~/.Trash/\`
163
- - Safe alternative: ${alternative}
164
-
165
- **ONLY if user explicitly requests the destructive command:**
166
- 1. Ask user with AskUserQuestion
167
- 2. Run: Bash: node "${process.argv[1].replace(/'/g, "'\\''")}" --bypass '${command.replace(/'/g, "'\\''")}'
168
- 3. Retry the original command`);
169
- }
170
- }
171
- }
172
-
173
- // ============================================================
174
- // 2. MCP Server Guard
175
- // ============================================================
176
- const SERVER_START_PATTERNS = [
177
- /npm run dev\b/, /npm run start\b/, /npm start\b/,
178
- /tsx\s+.*src\/app\.ts/, /tsx\s+watch\s+.*src\/app\.ts/,
179
- /node\s+.*src\/app\.ts/, /node\s+.*dist\/app\.js/,
180
- /npx\s+tsx\s+.*src\/app/
181
- ];
182
-
183
- function checkMcpServer(command) {
184
- if (!SERVER_START_PATTERNS.some(p => p.test(command))) return;
185
-
186
- block(`MCP server commands are blocked. Tell the user to run manually:
187
- cd ${projectDir} && npm run dev`);
188
- }
189
-
190
- // ============================================================
191
- // 3. Workspace Pull Guard
192
- // ============================================================
193
- function checkWorkspacePull(command) {
194
- if (!command.match(/npm\s+run\s+pull/)) return;
195
- if (process.env.CLAUDE_AGENT_ID || process.env.CLAUDE_SUBAGENT) return;
196
-
197
- const workspaceDir = path.join(projectDir, 'workspace');
198
- if (!fs.existsSync(workspaceDir)) { invalidateSharedMemory(); return; }
199
-
200
- try {
201
- const result = spawnSync('git', ['status', '--porcelain', 'workspace/'], {
202
- cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
203
- });
204
- const status = (result.stdout || '').trim();
205
-
206
- if (status) {
207
- const changedFiles = status.split('\n').map(l => l.trim()).filter(Boolean);
208
- block(`BLOCKED: Uncommitted changes in workspace/
209
-
210
- \`npm run pull\` will OVERWRITE your local TypeScript files!
211
-
212
- **Changed files:**
213
- ${changedFiles.map(f => ' ' + f).join('\n')}
214
-
215
- Options:
216
- 1. Push first: npm run push → npm run pull
217
- 2. Save first: /save "WIP before pull" → npm run pull
218
- 3. Discard: git checkout -- workspace/ → npm run pull`);
219
- }
220
- invalidateSharedMemory();
221
- } catch {
222
- invalidateSharedMemory();
223
- }
224
- }
225
-
226
- // ============================================================
227
- // 4. Workspace Auto-Save
228
- // ============================================================
229
- const PUSH_PATTERNS = [
230
- /npm\s+run\s+.*push/, /npm\s+run\s+.*sync/,
231
- /workflows-sync/, /fields-push/, /phases-push/,
232
- /insights-push/, /templates-sync/
233
- ];
234
-
235
- function checkWorkspaceAutoSave(command) {
236
- if (!PUSH_PATTERNS.some(p => p.test(command))) return;
237
-
238
- const workspaceDir = path.join(projectDir, 'workspace');
239
- if (!fs.existsSync(workspaceDir)) return;
240
-
241
- try {
242
- const result = spawnSync('git', ['status', '--porcelain', 'workspace/'], {
243
- cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
244
- });
245
- const status = (result.stdout || '').trim();
246
- if (!status) { console.error('[bash-guard] workspace/ clean, no auto-save needed'); return; }
247
-
248
- const changes = status.split('\n').filter(Boolean).length;
249
- const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ').replace(/[:.]/g, '-');
250
- spawnSync('git', ['add', 'workspace/'], { cwd: projectDir, encoding: 'utf8' });
251
- const commitResult = spawnSync('git', ['commit', '-m', `Auto-save workspace before push (${timestamp})`], {
252
- cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
253
- });
254
- if (commitResult.status !== 0 && !commitResult.stderr?.includes('nothing to commit')) {
255
- throw new Error(commitResult.stderr || 'git commit failed');
256
- }
257
- console.error('[bash-guard] Auto-saved ' + changes + ' workspace change(s) before push');
258
- } catch (err) {
259
- console.error('[bash-guard] Could not auto-save: ' + err.message);
260
- }
261
- }
262
-
263
- // ============================================================
264
- // Shared memory invalidation (after pull)
265
- // ============================================================
266
- function invalidateSharedMemory() {
267
- try {
268
- const mem = require('./_shared-memory.cjs');
269
- mem.invalidate();
270
- console.error('[bash-guard] Shared memory invalidated (workspace pull).');
271
- } catch {}
272
- }