@hailer/mcp 1.0.29 → 1.1.3

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 (233) hide show
  1. package/.claude/.session-checked +1 -0
  2. package/.claude/agents/agent-ada-skill-builder.md +10 -2
  3. package/.claude/agents/agent-alejandro-function-fields.md +104 -37
  4. package/.claude/agents/agent-bjorn-config-audit.md +41 -21
  5. package/.claude/agents/agent-builder-agent-creator.md +13 -3
  6. package/.claude/agents/agent-code-simplifier.md +53 -0
  7. package/.claude/agents/agent-dmitri-activity-crud.md +126 -11
  8. package/.claude/agents/agent-giuseppe-app-builder.md +212 -22
  9. package/.claude/agents/agent-gunther-mcp-tools.md +7 -36
  10. package/.claude/agents/agent-helga-workflow-config.md +75 -10
  11. package/.claude/agents/agent-igor-activity-mover-automation.md +125 -0
  12. package/.claude/agents/agent-ingrid-doc-templates.md +164 -36
  13. package/.claude/agents/agent-ivan-monolith.md +154 -0
  14. package/.claude/agents/agent-kenji-data-reader.md +15 -8
  15. package/.claude/agents/agent-lars-code-inspector.md +56 -8
  16. package/.claude/agents/agent-marco-mockup-builder.md +110 -0
  17. package/.claude/agents/agent-marcus-api-documenter.md +323 -0
  18. package/.claude/agents/agent-marketplace-publisher.md +232 -72
  19. package/.claude/agents/agent-marketplace-reviewer.md +255 -79
  20. package/.claude/agents/agent-permissions-handler.md +208 -0
  21. package/.claude/agents/agent-simple-writer.md +48 -0
  22. package/.claude/agents/agent-svetlana-code-review.md +127 -14
  23. package/.claude/agents/agent-tanya-test-runner.md +333 -0
  24. package/.claude/agents/agent-ui-designer.md +100 -0
  25. package/.claude/agents/agent-viktor-sql-insights.md +19 -6
  26. package/.claude/agents/agent-web-search.md +55 -0
  27. package/.claude/agents/agent-yevgeni-discussions.md +7 -1
  28. package/.claude/agents/agent-zara-zapier.md +159 -0
  29. package/.claude/commands/app-squad.md +135 -0
  30. package/.claude/commands/audit-squad.md +158 -0
  31. package/.claude/commands/autoplan.md +563 -0
  32. package/.claude/commands/cleanup-squad.md +98 -0
  33. package/.claude/commands/config-squad.md +106 -0
  34. package/.claude/commands/crud-squad.md +87 -0
  35. package/.claude/commands/data-squad.md +97 -0
  36. package/.claude/commands/debug-squad.md +303 -0
  37. package/.claude/commands/doc-squad.md +65 -0
  38. package/.claude/commands/handoff.md +137 -0
  39. package/.claude/commands/health.md +49 -0
  40. package/.claude/commands/help.md +2 -1
  41. package/.claude/commands/help:agents.md +96 -16
  42. package/.claude/commands/help:commands.md +55 -11
  43. package/.claude/commands/help:faq.md +16 -1
  44. package/.claude/commands/help:skills.md +93 -0
  45. package/.claude/commands/hotfix-squad.md +112 -0
  46. package/.claude/commands/integration-squad.md +82 -0
  47. package/.claude/commands/janitor-squad.md +167 -0
  48. package/.claude/commands/learn-auto.md +120 -0
  49. package/.claude/commands/learn.md +120 -0
  50. package/.claude/commands/mcp-list.md +27 -0
  51. package/.claude/commands/onboard-squad.md +140 -0
  52. package/.claude/commands/plan-workspace.md +732 -0
  53. package/.claude/commands/prd.md +131 -0
  54. package/.claude/commands/project-status.md +82 -0
  55. package/.claude/commands/publish.md +138 -0
  56. package/.claude/commands/recap.md +69 -0
  57. package/.claude/commands/restore.md +64 -0
  58. package/.claude/commands/review-squad.md +152 -0
  59. package/.claude/commands/save.md +24 -0
  60. package/.claude/commands/stats.md +19 -0
  61. package/.claude/commands/swarm.md +210 -0
  62. package/.claude/commands/tool-builder.md +3 -1
  63. package/.claude/commands/ws-pull.md +1 -1
  64. package/.claude/commands/yolo-off.md +17 -0
  65. package/.claude/commands/yolo.md +82 -0
  66. package/.claude/hooks/_shared-memory.cjs +305 -0
  67. package/.claude/hooks/_utils.cjs +134 -0
  68. package/.claude/hooks/agent-failure-detector.cjs +164 -79
  69. package/.claude/hooks/agent-usage-logger.cjs +204 -0
  70. package/.claude/hooks/app-edit-guard.cjs +20 -4
  71. package/.claude/hooks/auto-learn.cjs +316 -0
  72. package/.claude/hooks/bash-guard.cjs +282 -0
  73. package/.claude/hooks/builder-mode-manager.cjs +183 -54
  74. package/.claude/hooks/bulk-activity-guard.cjs +283 -0
  75. package/.claude/hooks/context-watchdog.cjs +292 -0
  76. package/.claude/hooks/delegation-reminder.cjs +478 -0
  77. package/.claude/hooks/design-system-lint.cjs +283 -0
  78. package/.claude/hooks/post-scaffold-hook.cjs +16 -3
  79. package/.claude/hooks/prompt-guard.cjs +366 -0
  80. package/.claude/hooks/publish-template-guard.cjs +16 -0
  81. package/.claude/hooks/session-start.cjs +35 -0
  82. package/.claude/hooks/shared-memory-writer.cjs +147 -0
  83. package/.claude/hooks/skill-injector.cjs +140 -0
  84. package/.claude/hooks/skill-usage-logger.cjs +258 -0
  85. package/.claude/hooks/src-edit-guard.cjs +16 -1
  86. package/.claude/hooks/sync-marketplace-agents.cjs +53 -8
  87. package/.claude/scripts/yolo-toggle.cjs +142 -0
  88. package/.claude/settings.json +141 -14
  89. package/.claude/skills/SDK-activity-patterns/SKILL.md +428 -0
  90. package/.claude/skills/SDK-document-templates/SKILL.md +1033 -0
  91. package/.claude/skills/SDK-function-fields/SKILL.md +542 -0
  92. package/.claude/skills/SDK-generate-skill/SKILL.md +92 -0
  93. package/.claude/skills/SDK-init-skill/SKILL.md +127 -0
  94. package/.claude/skills/SDK-insight-queries/SKILL.md +787 -0
  95. package/.claude/skills/SDK-ws-config-skill/SKILL.md +1139 -0
  96. package/.claude/skills/agent-structure/SKILL.md +98 -0
  97. package/.claude/skills/api-documentation-patterns/SKILL.md +474 -0
  98. package/.claude/skills/chrome-mcp-reference/SKILL.md +370 -0
  99. package/.claude/skills/delegation-routing/SKILL.md +202 -0
  100. package/.claude/skills/frontend-design/SKILL.md +254 -0
  101. package/.claude/skills/hailer-activity-mover/SKILL.md +213 -0
  102. package/.claude/skills/hailer-api-client/SKILL.md +518 -0
  103. package/.claude/skills/hailer-app-builder/SKILL.md +939 -11
  104. package/.claude/skills/hailer-apps-pictures/SKILL.md +269 -0
  105. package/.claude/skills/hailer-design-system/SKILL.md +235 -0
  106. package/.claude/skills/hailer-monolith-automations/SKILL.md +686 -0
  107. package/.claude/skills/hailer-permissions-system/SKILL.md +121 -0
  108. package/.claude/skills/hailer-project-protocol/SKILL.md +488 -0
  109. package/.claude/skills/hailer-rest-api/SKILL.md +61 -0
  110. package/.claude/skills/hailer-rest-api/hailer-activities.md +184 -0
  111. package/.claude/skills/hailer-rest-api/hailer-admin.md +473 -0
  112. package/.claude/skills/hailer-rest-api/hailer-calendar.md +256 -0
  113. package/.claude/skills/hailer-rest-api/hailer-feed.md +249 -0
  114. package/.claude/skills/hailer-rest-api/hailer-insights.md +195 -0
  115. package/.claude/skills/hailer-rest-api/hailer-messaging.md +276 -0
  116. package/.claude/skills/hailer-rest-api/hailer-workflows.md +283 -0
  117. package/.claude/skills/insight-join-patterns/SKILL.md +3 -0
  118. package/.claude/skills/integration-patterns/SKILL.md +421 -0
  119. package/.claude/skills/json-only-output/SKILL.md +52 -12
  120. package/.claude/skills/lsp-setup/SKILL.md +160 -0
  121. package/.claude/skills/mcp-direct-tools/SKILL.md +153 -0
  122. package/.claude/skills/optional-parameters/SKILL.md +32 -23
  123. package/.claude/skills/publish-hailer-app/SKILL.md +76 -12
  124. package/.claude/skills/testing-patterns/SKILL.md +630 -0
  125. package/.claude/skills/tool-builder/SKILL.md +250 -0
  126. package/.claude/skills/tool-parameter-usage/SKILL.md +59 -45
  127. package/.claude/skills/tool-response-verification/SKILL.md +82 -48
  128. package/.claude/skills/zapier-hailer-patterns/SKILL.md +581 -0
  129. package/.env.example +26 -7
  130. package/CLAUDE.md +290 -224
  131. package/dist/CLAUDE.md +370 -0
  132. package/dist/app.d.ts +1 -1
  133. package/dist/app.js +101 -101
  134. package/dist/bot/bot-config.d.ts +26 -0
  135. package/dist/bot/bot-config.js +135 -0
  136. package/dist/bot/bot-manager.d.ts +40 -0
  137. package/dist/bot/bot-manager.js +137 -0
  138. package/dist/bot/bot.d.ts +127 -0
  139. package/dist/bot/bot.js +1328 -0
  140. package/dist/bot/operation-logger.d.ts +28 -0
  141. package/dist/bot/operation-logger.js +132 -0
  142. package/dist/bot/services/conversation-manager.d.ts +60 -0
  143. package/dist/bot/services/conversation-manager.js +246 -0
  144. package/dist/bot/services/index.d.ts +9 -0
  145. package/dist/bot/services/index.js +18 -0
  146. package/dist/bot/services/message-classifier.d.ts +42 -0
  147. package/dist/bot/services/message-classifier.js +228 -0
  148. package/dist/bot/services/message-formatter.d.ts +88 -0
  149. package/dist/bot/services/message-formatter.js +411 -0
  150. package/dist/bot/services/session-logger.d.ts +162 -0
  151. package/dist/bot/services/session-logger.js +724 -0
  152. package/dist/bot/services/token-billing.d.ts +78 -0
  153. package/dist/bot/services/token-billing.js +233 -0
  154. package/dist/bot/services/types.d.ts +169 -0
  155. package/dist/bot/services/types.js +12 -0
  156. package/dist/bot/services/typing-indicator.d.ts +23 -0
  157. package/dist/bot/services/typing-indicator.js +60 -0
  158. package/dist/bot/services/workspace-schema-cache.d.ts +122 -0
  159. package/dist/bot/services/workspace-schema-cache.js +506 -0
  160. package/dist/bot/tool-executor.d.ts +28 -0
  161. package/dist/bot/tool-executor.js +48 -0
  162. package/dist/bot/workspace-overview.d.ts +12 -0
  163. package/dist/bot/workspace-overview.js +94 -0
  164. package/dist/cli.d.ts +1 -8
  165. package/dist/cli.js +1 -253
  166. package/dist/config.d.ts +96 -3
  167. package/dist/config.js +148 -37
  168. package/dist/core.d.ts +5 -0
  169. package/dist/core.js +61 -8
  170. package/dist/lib/discussion-lock.d.ts +42 -0
  171. package/dist/lib/discussion-lock.js +110 -0
  172. package/dist/lib/logger.d.ts +0 -1
  173. package/dist/lib/logger.js +39 -23
  174. package/dist/lib/request-logger.d.ts +77 -0
  175. package/dist/lib/request-logger.js +147 -0
  176. package/dist/mcp/UserContextCache.js +16 -13
  177. package/dist/mcp/hailer-clients.js +18 -17
  178. package/dist/mcp/signal-handler.js +43 -13
  179. package/dist/mcp/tool-registry.d.ts +4 -15
  180. package/dist/mcp/tool-registry.js +94 -32
  181. package/dist/mcp/tools/activity.js +28 -69
  182. package/dist/mcp/tools/app-core.js +9 -4
  183. package/dist/mcp/tools/app-marketplace.js +22 -12
  184. package/dist/mcp/tools/app-member.js +5 -2
  185. package/dist/mcp/tools/app-scaffold.js +32 -18
  186. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  187. package/dist/mcp/tools/bot-config/constants.js +94 -0
  188. package/dist/mcp/tools/bot-config/core.d.ts +253 -0
  189. package/dist/mcp/tools/bot-config/core.js +2456 -0
  190. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  191. package/dist/mcp/tools/bot-config/index.js +59 -0
  192. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  193. package/dist/mcp/tools/bot-config/tools.js +15 -0
  194. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  195. package/dist/mcp/tools/bot-config/types.js +6 -0
  196. package/dist/mcp/tools/discussion.js +107 -77
  197. package/dist/mcp/tools/document.d.ts +11 -0
  198. package/dist/mcp/tools/document.js +741 -0
  199. package/dist/mcp/tools/file.js +5 -2
  200. package/dist/mcp/tools/insight.js +36 -12
  201. package/dist/mcp/tools/investigate.d.ts +9 -0
  202. package/dist/mcp/tools/investigate.js +254 -0
  203. package/dist/mcp/tools/user.d.ts +2 -4
  204. package/dist/mcp/tools/user.js +9 -50
  205. package/dist/mcp/tools/workflow.d.ts +1 -0
  206. package/dist/mcp/tools/workflow.js +164 -52
  207. package/dist/mcp/utils/hailer-api-client.js +26 -17
  208. package/dist/mcp/webhook-handler.d.ts +64 -3
  209. package/dist/mcp/webhook-handler.js +227 -9
  210. package/dist/mcp-server.d.ts +4 -0
  211. package/dist/mcp-server.js +237 -25
  212. package/dist/plugins/bug-fixer/index.d.ts +2 -0
  213. package/dist/plugins/bug-fixer/index.js +18 -0
  214. package/dist/plugins/bug-fixer/tools.d.ts +45 -0
  215. package/dist/plugins/bug-fixer/tools.js +1096 -0
  216. package/package.json +10 -10
  217. package/scripts/test-hal-tools.ts +154 -0
  218. package/.claude/agents/agent-nora-name-functions.md +0 -123
  219. package/.claude/assistant-knowledge.md +0 -23
  220. package/.claude/commands/install-plugin.md +0 -261
  221. package/.claude/commands/list-plugins.md +0 -42
  222. package/.claude/commands/marketplace-setup.md +0 -33
  223. package/.claude/commands/publish-plugin.md +0 -55
  224. package/.claude/commands/uninstall-plugin.md +0 -87
  225. package/.claude/hooks/interactive-mode.cjs +0 -87
  226. package/.claude/hooks/mcp-server-guard.cjs +0 -108
  227. package/.claude/skills/marketplace-publishing.md +0 -155
  228. package/dist/bot/chat-bot.d.ts +0 -31
  229. package/dist/bot/chat-bot.js +0 -357
  230. package/dist/mcp/tools/metrics.d.ts +0 -13
  231. package/dist/mcp/tools/metrics.js +0 -546
  232. package/dist/stdio-server.d.ts +0 -14
  233. package/dist/stdio-server.js +0 -114
@@ -33,6 +33,22 @@
33
33
  * </workflow>
34
34
  */
35
35
 
36
+ const fs = require('fs');
37
+ const path = require('path');
38
+
39
+ // Skip in yolo mode
40
+ try {
41
+ const statePath = path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), '.claude', 'yolo-state.json');
42
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
43
+ if (state.mode === 'yolo') process.exit(0);
44
+ } catch {}
45
+
46
+ // Skip in subagent context - subagents can't use AskUserQuestion to recover
47
+ if (process.env.CLAUDE_AGENT_ID || process.env.CLAUDE_SUBAGENT) {
48
+ console.log(JSON.stringify({ decision: 'allow' }));
49
+ process.exit(0);
50
+ }
51
+
36
52
  // Read hook input from stdin
37
53
  let input = '';
38
54
  process.stdin.setEncoding('utf8');
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart hook - auto-loads SESSION-HANDOFF.md into context.
4
+ * Eliminates the need to manually run /recap at session start.
5
+ */
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ // Consume stdin (required by hook protocol)
10
+ try { fs.readFileSync('/dev/stdin', 'utf8'); } catch (e) { /* stdin may be empty or closed */ }
11
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
12
+
13
+ const files = [
14
+ { name: 'SESSION-HANDOFF.md', label: 'Session Handoff' },
15
+ { name: 'DEVELOPMENT.md', label: 'Project Status' }
16
+ ];
17
+
18
+ const sections = [];
19
+
20
+ for (const file of files) {
21
+ const filePath = path.join(projectDir, file.name);
22
+ if (fs.existsSync(filePath)) {
23
+ const content = fs.readFileSync(filePath, 'utf8').trim();
24
+ if (content) {
25
+ sections.push(`## ${file.label}\n\n${content}`);
26
+ }
27
+ }
28
+ }
29
+
30
+ if (sections.length > 0) {
31
+ const context = `# Auto-loaded Project Context\n\n${sections.join('\n\n---\n\n')}`;
32
+ console.log(JSON.stringify({ additionalContext: context }));
33
+ } else {
34
+ console.log(JSON.stringify({}));
35
+ }
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * <hook-name>shared-memory-writer</hook-name>
4
+ *
5
+ * <purpose>
6
+ * Auto-caches Kenji agent results into shared memory for squad reuse.
7
+ * Watches PostToolUse:Task for Kenji completions, parses schema data,
8
+ * and writes to .shared-memory.json so subsequent squads skip redundant lookups.
9
+ * </purpose>
10
+ *
11
+ * <triggers>
12
+ * - PostToolUse on Task
13
+ * - Only when subagent_type is "agent-kenji-data-reader"
14
+ * - Only when response contains workflow schema data
15
+ * </triggers>
16
+ *
17
+ * <behavior>
18
+ * 1. Detects Kenji agent completions
19
+ * 2. Parses response for workflow schema data (workflow IDs, field lists, phases)
20
+ * 3. Writes to shared memory under "workflow-schemas" key
21
+ * 4. Non-blocking: outputs to stderr, never blocks the tool call
22
+ * </behavior>
23
+ */
24
+
25
+ // Skip if stdin is TTY (no piped input)
26
+ if (process.stdin.isTTY) {
27
+ process.exit(0);
28
+ }
29
+
30
+ let input = '';
31
+ process.stdin.setEncoding('utf8');
32
+ process.stdin.on('data', chunk => input += chunk);
33
+ process.stdin.on('end', () => {
34
+ try {
35
+ const data = JSON.parse(input);
36
+ processHook(data);
37
+ } catch (e) {
38
+ console.error(`[shared-memory-writer] Failed to parse input: ${e.message}`);
39
+ process.exit(0);
40
+ }
41
+ });
42
+
43
+ function processHook(data) {
44
+ const { tool_name, tool_input, tool_response } = data;
45
+
46
+ // Only process Task tool calls
47
+ if (tool_name !== 'Task') {
48
+ process.exit(0);
49
+ }
50
+
51
+ // Only process Kenji agent completions
52
+ const agentName = tool_input?.subagent_type;
53
+ if (agentName !== 'agent-kenji-data-reader') {
54
+ process.exit(0);
55
+ }
56
+
57
+ const responseText = typeof tool_response === 'string'
58
+ ? tool_response
59
+ : JSON.stringify(tool_response || '');
60
+
61
+ // Check if response contains schema-like data
62
+ if (!containsSchemaData(responseText)) {
63
+ console.error('[shared-memory-writer] Kenji response has no schema data, skipping cache.');
64
+ process.exit(0);
65
+ }
66
+
67
+ // Write to shared memory
68
+ try {
69
+ const mem = require('./_shared-memory.cjs');
70
+ const existing = mem.get('workflow-schemas');
71
+
72
+ // Merge with existing data if present, or write fresh
73
+ const schemaData = extractSchemaData(responseText);
74
+
75
+ if (existing && existing.data) {
76
+ // Merge: keep existing entries, add/overwrite with new ones
77
+ const merged = { ...existing.data, ...schemaData, lastUpdated: new Date().toISOString() };
78
+ mem.set('workflow-schemas', merged, 'agent-kenji-data-reader');
79
+ console.error(`[shared-memory-writer] Merged Kenji schema data into shared memory (${Object.keys(schemaData).length} keys).`);
80
+ } else {
81
+ schemaData.lastUpdated = new Date().toISOString();
82
+ mem.set('workflow-schemas', schemaData, 'agent-kenji-data-reader');
83
+ console.error(`[shared-memory-writer] Cached Kenji schema data to shared memory (${Object.keys(schemaData).length} keys).`);
84
+ }
85
+ } catch (e) {
86
+ console.error(`[shared-memory-writer] Failed to cache: ${e.message}`);
87
+ }
88
+
89
+ process.exit(0);
90
+ }
91
+
92
+ /**
93
+ * Check if response contains workflow schema data indicators.
94
+ */
95
+ function containsSchemaData(text) {
96
+ const lower = text.toLowerCase();
97
+ const indicators = [
98
+ 'workflow',
99
+ 'field',
100
+ 'phase',
101
+ 'processid',
102
+ 'fieldid',
103
+ 'phaseid',
104
+ 'fields.ts',
105
+ 'phases.ts',
106
+ 'workflows.ts'
107
+ ];
108
+
109
+ let matches = 0;
110
+ for (const indicator of indicators) {
111
+ if (lower.includes(indicator)) matches++;
112
+ }
113
+
114
+ // Need at least 2 schema indicators to be considered schema data
115
+ return matches >= 2;
116
+ }
117
+
118
+ /**
119
+ * Extract structured schema data from Kenji's response.
120
+ * Stores the raw response keyed by identifiable workflow references.
121
+ */
122
+ function extractSchemaData(text) {
123
+ const data = { _rawResponse: text };
124
+
125
+ // Try to parse as JSON first (Kenji often returns JSON)
126
+ try {
127
+ const parsed = JSON.parse(text);
128
+ if (parsed.result) {
129
+ data._parsed = parsed.result;
130
+ } else if (parsed.data) {
131
+ data._parsed = parsed.data;
132
+ } else {
133
+ data._parsed = parsed;
134
+ }
135
+ } catch {
136
+ // Not JSON - that's fine, the raw text is still useful context
137
+ }
138
+
139
+ // Extract workflow IDs mentioned (patterns like 24-char hex strings)
140
+ const workflowIdPattern = /[0-9a-f]{24}/g;
141
+ const ids = text.match(workflowIdPattern);
142
+ if (ids) {
143
+ data._workflowIds = [...new Set(ids)];
144
+ }
145
+
146
+ return data;
147
+ }
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * <hook-name>skill-injector</hook-name>
4
+ * <purpose>Auto-injects skill content into subagent context via SubagentStart</purpose>
5
+ * <triggers>SubagentStart</triggers>
6
+ *
7
+ * Problem: Agents have skills listed in frontmatter but never actually call
8
+ * the Skill tool. This hook reads the agent's frontmatter `skills:` list
9
+ * and injects skill content via additionalContext when the subagent starts.
10
+ *
11
+ * Uses SubagentStart hook event with additionalContext (not PreToolUse updatedInput).
12
+ *
13
+ * Hook type: SubagentStart
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
20
+ const AGENTS_DIR = path.join(PROJECT_DIR, '.claude', 'agents');
21
+ const SKILLS_DIR = path.join(PROJECT_DIR, '.claude', 'skills');
22
+
23
+ // Skip if stdin is TTY
24
+ if (process.stdin.isTTY) {
25
+ console.log(JSON.stringify({}));
26
+ process.exit(0);
27
+ }
28
+
29
+ let input = '';
30
+ process.stdin.setEncoding('utf8');
31
+ process.stdin.on('data', chunk => input += chunk);
32
+ process.stdin.on('end', () => {
33
+ try {
34
+ const data = JSON.parse(input);
35
+ processHook(data);
36
+ } catch (e) {
37
+ console.error(`[skill-injector] Parse error: ${e.message}`);
38
+ console.log(JSON.stringify({}));
39
+ process.exit(0);
40
+ }
41
+ });
42
+
43
+ /**
44
+ * Parse skills: list from agent frontmatter
45
+ */
46
+ function getAgentSkills(agentType) {
47
+ const agentFile = path.join(AGENTS_DIR, `${agentType}.md`);
48
+ if (!fs.existsSync(agentFile)) {
49
+ return [];
50
+ }
51
+
52
+ try {
53
+ const content = fs.readFileSync(agentFile, 'utf8');
54
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
55
+ if (!fmMatch) return [];
56
+
57
+ const frontmatter = fmMatch[1];
58
+ const skills = [];
59
+ let inSkills = false;
60
+
61
+ for (const line of frontmatter.split('\n')) {
62
+ if (line.match(/^skills:\s*$/)) {
63
+ inSkills = true;
64
+ continue;
65
+ }
66
+ if (inSkills) {
67
+ const skillMatch = line.match(/^\s+-\s+(.+)$/);
68
+ if (skillMatch) {
69
+ skills.push(skillMatch[1].trim());
70
+ } else {
71
+ break;
72
+ }
73
+ }
74
+ }
75
+
76
+ return skills;
77
+ } catch (e) {
78
+ console.error(`[skill-injector] Error reading ${agentFile}: ${e.message}`);
79
+ return [];
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Read skill content from SKILL.md
85
+ */
86
+ function readSkillContent(skillName) {
87
+ const skillFile = path.join(SKILLS_DIR, skillName, 'SKILL.md');
88
+ if (!fs.existsSync(skillFile)) {
89
+ return null;
90
+ }
91
+
92
+ try {
93
+ const content = fs.readFileSync(skillFile, 'utf8');
94
+ // Limit to 20KB per skill to avoid bloating context
95
+ if (content.length > 20000) {
96
+ return content.substring(0, 20000) + '\n\n[... truncated at 20KB ...]';
97
+ }
98
+ return content;
99
+ } catch (e) {
100
+ console.error(`[skill-injector] Error reading skill ${skillName}: ${e.message}`);
101
+ return null;
102
+ }
103
+ }
104
+
105
+ function processHook(data) {
106
+ const agentType = data.agent_type || '';
107
+
108
+ // Get skills for this agent type
109
+ const skills = getAgentSkills(agentType);
110
+ if (skills.length === 0) {
111
+ console.log(JSON.stringify({}));
112
+ process.exit(0);
113
+ }
114
+
115
+ // Load skill contents
116
+ const skillBlocks = [];
117
+ for (const skillName of skills) {
118
+ const content = readSkillContent(skillName);
119
+ if (content) {
120
+ skillBlocks.push(`<skill name="${skillName}">\n${content}\n</skill>`);
121
+ }
122
+ }
123
+
124
+ if (skillBlocks.length === 0) {
125
+ console.log(JSON.stringify({}));
126
+ process.exit(0);
127
+ }
128
+
129
+ const skillSection = `<injected-skills>\nThe following skills have been auto-loaded based on your agent configuration. Use this knowledge for your task.\n\n${skillBlocks.join('\n\n')}\n</injected-skills>`;
130
+
131
+ console.error(`[skill-injector] Injected ${skillBlocks.length} skill(s) into ${agentType}: ${skills.join(', ')}`);
132
+
133
+ console.log(JSON.stringify({
134
+ hookSpecificOutput: {
135
+ hookEventName: 'SubagentStart',
136
+ additionalContext: skillSection
137
+ }
138
+ }));
139
+ process.exit(0);
140
+ }
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * <hook-name>skill-usage-logger</hook-name>
4
+ *
5
+ * <purpose>
6
+ * Logs every skill load for analytics - which skills are used, by which agents, in which projects.
7
+ * </purpose>
8
+ *
9
+ * <triggers>
10
+ * - PostToolUse on Skill
11
+ * </triggers>
12
+ *
13
+ * <log-file>$CLAUDE_PROJECT_DIR/inbox/skill-usage.jsonl</log-file>
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+
20
+ const LOG_FILE = path.join(
21
+ process.env.HAILER_INBOX_DIR || path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), 'inbox'),
22
+ 'skill-usage.jsonl'
23
+ );
24
+
25
+ // Read hook input from stdin
26
+ let input = '';
27
+ process.stdin.setEncoding('utf8');
28
+ process.stdin.on('data', chunk => input += chunk);
29
+ process.stdin.on('end', () => {
30
+ try {
31
+ const data = JSON.parse(input);
32
+ processHook(data);
33
+ } catch (e) {
34
+ console.error(`[skill-usage-logger] Failed to parse input: ${e.message}`);
35
+ process.exit(0);
36
+ }
37
+ });
38
+
39
+ /**
40
+ * Detect if running in subagent context from transcript_path
41
+ * Subagent paths contain /subagents/
42
+ * Returns: { isSubagent: boolean, agentName: string | null }
43
+ */
44
+ function detectSubagentContext(transcriptPath) {
45
+ if (!transcriptPath) {
46
+ return { isSubagent: false, agentName: null };
47
+ }
48
+
49
+ // Subagent paths look like: .../{session_id}/subagents/agent-{agent_id}.jsonl
50
+ if (transcriptPath.includes('/subagents/')) {
51
+ // Extract agent name from path
52
+ const match = transcriptPath.match(/subagents\/([^/]+)\.jsonl$/);
53
+ if (match) {
54
+ // Clean up agent ID - might be like "agent-a7004fa" or full name
55
+ return { isSubagent: true, agentName: match[1] };
56
+ }
57
+ return { isSubagent: true, agentName: 'unknown' };
58
+ }
59
+
60
+ return { isSubagent: false, agentName: null };
61
+ }
62
+
63
+ function processHook(data) {
64
+ const { tool_name, tool_input, tool_response, transcript_path } = data;
65
+
66
+ // Only process Skill tool
67
+ if (tool_name !== 'Skill') {
68
+ process.exit(0);
69
+ }
70
+
71
+ // Extract skill name from tool_input
72
+ const skillName = tool_input?.skill || tool_input?.command || 'unknown';
73
+ const skillArgs = tool_input?.args || null;
74
+
75
+ // Detect subagent context
76
+ const { isSubagent, agentName } = detectSubagentContext(transcript_path);
77
+
78
+ // Get project from env or cwd
79
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
80
+ const project = path.basename(projectDir);
81
+
82
+ const entry = {
83
+ ts: new Date().toISOString(),
84
+ skill: skillName,
85
+ agent: isSubagent ? agentName : 'orchestrator',
86
+ isSubagent,
87
+ project,
88
+ args: skillArgs
89
+ };
90
+
91
+ try {
92
+ // Ensure directory exists
93
+ const logDir = path.dirname(LOG_FILE);
94
+ if (!fs.existsSync(logDir)) {
95
+ fs.mkdirSync(logDir, { recursive: true });
96
+ }
97
+
98
+ fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n');
99
+ } catch (err) {
100
+ console.error(`[skill-usage-logger] Could not write: ${err.message}`);
101
+ }
102
+
103
+ process.exit(0);
104
+ }
105
+
106
+ // CLI: Show recent skill loads
107
+ if (process.argv[2] === '--recent' || process.argv[2] === '-r') {
108
+ const limit = parseInt(process.argv[3]) || 20;
109
+
110
+ if (!fs.existsSync(LOG_FILE)) {
111
+ console.log('No skill usage data yet');
112
+ process.exit(0);
113
+ }
114
+
115
+ const lines = fs.readFileSync(LOG_FILE, 'utf8').trim().split('\n');
116
+ const recent = lines.slice(-limit);
117
+
118
+ console.log(`Last ${recent.length} skill loads:\n`);
119
+ console.log('Time'.padEnd(10) + 'Skill'.padEnd(30) + 'Agent'.padEnd(20) + 'Project');
120
+ console.log('-'.repeat(75));
121
+
122
+ for (const line of recent) {
123
+ try {
124
+ const entry = JSON.parse(line);
125
+ const time = entry.ts.split('T')[1].split('.')[0];
126
+ const agent = entry.isSubagent ? entry.agent : 'orchestrator';
127
+ console.log(`${time.padEnd(10)}${entry.skill.padEnd(30)}${agent.padEnd(20)}${entry.project}`);
128
+ } catch {}
129
+ }
130
+ process.exit(0);
131
+ }
132
+
133
+ // CLI: Show stats by skill
134
+ if (process.argv[2] === '--stats' || process.argv[2] === '-s') {
135
+ if (!fs.existsSync(LOG_FILE)) {
136
+ console.log('No skill usage data yet');
137
+ process.exit(0);
138
+ }
139
+
140
+ const lines = fs.readFileSync(LOG_FILE, 'utf8').trim().split('\n');
141
+ const skillStats = {};
142
+ const agentSkills = {};
143
+
144
+ for (const line of lines) {
145
+ try {
146
+ const entry = JSON.parse(line);
147
+
148
+ // Count by skill
149
+ if (!skillStats[entry.skill]) {
150
+ skillStats[entry.skill] = { total: 0, byOrchestrator: 0, bySubagent: 0 };
151
+ }
152
+ skillStats[entry.skill].total++;
153
+ if (entry.isSubagent) {
154
+ skillStats[entry.skill].bySubagent++;
155
+ } else {
156
+ skillStats[entry.skill].byOrchestrator++;
157
+ }
158
+
159
+ // Track which agents use which skills
160
+ if (entry.isSubagent && entry.agent) {
161
+ if (!agentSkills[entry.agent]) {
162
+ agentSkills[entry.agent] = new Set();
163
+ }
164
+ agentSkills[entry.agent].add(entry.skill);
165
+ }
166
+ } catch {}
167
+ }
168
+
169
+ console.log('Skill Usage Stats:\n');
170
+ console.log('Skill'.padEnd(35) + 'Total'.padEnd(8) + 'Orch'.padEnd(8) + 'Subagent');
171
+ console.log('-'.repeat(60));
172
+
173
+ const sorted = Object.entries(skillStats).sort((a, b) => b[1].total - a[1].total);
174
+ for (const [skill, s] of sorted) {
175
+ console.log(`${skill.padEnd(35)}${String(s.total).padEnd(8)}${String(s.byOrchestrator).padEnd(8)}${s.bySubagent}`);
176
+ }
177
+
178
+ console.log('-'.repeat(60));
179
+ console.log(`Total: ${lines.length} skill loads\n`);
180
+
181
+ // Show agent -> skill mapping
182
+ if (Object.keys(agentSkills).length > 0) {
183
+ console.log('Skills by Agent:\n');
184
+ for (const [agent, skills] of Object.entries(agentSkills)) {
185
+ console.log(` ${agent}: ${[...skills].join(', ')}`);
186
+ }
187
+ }
188
+
189
+ process.exit(0);
190
+ }
191
+
192
+ // CLI: Show unused skills (skills in .claude/skills/ not in log)
193
+ if (process.argv[2] === '--unused' || process.argv[2] === '-u') {
194
+ const skillsDir = path.join(process.cwd(), '.claude', 'skills');
195
+
196
+ if (!fs.existsSync(skillsDir)) {
197
+ console.log('No .claude/skills/ directory found');
198
+ process.exit(0);
199
+ }
200
+
201
+ // Get all skill directories
202
+ const allSkills = fs.readdirSync(skillsDir, { withFileTypes: true })
203
+ .filter(d => d.isDirectory())
204
+ .map(d => d.name);
205
+
206
+ // Get used skills from log
207
+ const usedSkills = new Set();
208
+ if (fs.existsSync(LOG_FILE)) {
209
+ const lines = fs.readFileSync(LOG_FILE, 'utf8').trim().split('\n');
210
+ for (const line of lines) {
211
+ try {
212
+ const entry = JSON.parse(line);
213
+ usedSkills.add(entry.skill);
214
+ } catch {}
215
+ }
216
+ }
217
+
218
+ const unused = allSkills.filter(s => !usedSkills.has(s));
219
+
220
+ if (unused.length === 0) {
221
+ console.log('All skills have been used at least once!');
222
+ } else {
223
+ console.log(`Unused skills (${unused.length}):\n`);
224
+ for (const skill of unused.sort()) {
225
+ console.log(` - ${skill}`);
226
+ }
227
+ }
228
+
229
+ process.exit(0);
230
+ }
231
+
232
+ // CLI: Clear log
233
+ if (process.argv[2] === '--clear') {
234
+ if (fs.existsSync(LOG_FILE)) {
235
+ fs.unlinkSync(LOG_FILE);
236
+ console.log('✓ Skill usage log cleared');
237
+ } else {
238
+ console.log('No log file to clear');
239
+ }
240
+ process.exit(0);
241
+ }
242
+
243
+ // CLI: Help
244
+ if (process.argv[2] === '--help' || process.argv[2] === '-h') {
245
+ console.log(`
246
+ Skill Usage Logger - Track skill loads by agents
247
+
248
+ Usage:
249
+ node skill-usage-logger.cjs --recent [N] Show last N skill loads (default 20)
250
+ node skill-usage-logger.cjs --stats Show usage statistics
251
+ node skill-usage-logger.cjs --unused Show skills never loaded
252
+ node skill-usage-logger.cjs --clear Clear the log
253
+ node skill-usage-logger.cjs --help Show this help
254
+
255
+ Log file: ${LOG_FILE}
256
+ `);
257
+ process.exit(0);
258
+ }
@@ -37,9 +37,24 @@
37
37
 
38
38
  const fs = require('fs');
39
39
  const path = require('path');
40
+ const os = require('os');
41
+
42
+ // Skip in yolo mode
43
+ try {
44
+ const statePath = path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), '.claude', 'yolo-state.json');
45
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
46
+ if (state.mode === 'yolo') process.exit(0);
47
+ } catch {}
48
+
49
+ // Skip in subagent context - subagents can't use AskUserQuestion or Bash to recover
50
+ if (process.env.CLAUDE_AGENT_ID || process.env.CLAUDE_SUBAGENT) {
51
+ console.log(JSON.stringify({ decision: 'allow' }));
52
+ process.exit(0);
53
+ }
40
54
 
41
55
  // UNIFIED: Use same builder mode file as app-edit-guard.cjs
42
- const BUILDER_MODE_FILE = '/tmp/.claude-builder-agent-active';
56
+ const TEMP_DIR = os.tmpdir();
57
+ const BUILDER_MODE_FILE = path.join(TEMP_DIR, '.claude-builder-agent-active');
43
58
 
44
59
  // Read hook input from stdin
45
60
  let input = '';