@hailer/mcp 1.1.11 → 1.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/dist/app.js +18 -5
  2. package/dist/bot/bot-config.d.ts +12 -1
  3. package/dist/bot/bot-config.js +98 -14
  4. package/dist/bot/bot-manager.d.ts +13 -3
  5. package/dist/bot/bot-manager.js +80 -25
  6. package/dist/bot/bot.d.ts +46 -0
  7. package/dist/bot/bot.js +542 -166
  8. package/dist/bot/services/message-classifier.js +17 -0
  9. package/dist/bot/services/permission-guard.d.ts +52 -0
  10. package/dist/bot/services/permission-guard.js +149 -0
  11. package/dist/bot/services/types.d.ts +5 -0
  12. package/dist/bot/services/typing-indicator.d.ts +6 -1
  13. package/dist/bot/services/typing-indicator.js +19 -3
  14. package/dist/config.d.ts +6 -1
  15. package/dist/config.js +43 -0
  16. package/dist/core.js +3 -6
  17. package/dist/mcp/UserContextCache.d.ts +5 -0
  18. package/dist/mcp/UserContextCache.js +51 -19
  19. package/dist/mcp/hailer-clients.d.ts +19 -1
  20. package/dist/mcp/hailer-clients.js +157 -20
  21. package/dist/mcp/session-store.d.ts +68 -0
  22. package/dist/mcp/session-store.js +169 -0
  23. package/dist/mcp/signal-handler.js +12 -12
  24. package/dist/mcp/tool-registry.d.ts +17 -4
  25. package/dist/mcp/tool-registry.js +37 -7
  26. package/dist/mcp/tools/activity.js +99 -7
  27. package/dist/mcp/tools/app-scaffold.js +304 -336
  28. package/dist/mcp/tools/company.d.ts +9 -0
  29. package/dist/mcp/tools/company.js +88 -0
  30. package/dist/mcp/tools/discussion.js +68 -0
  31. package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
  32. package/dist/mcp/tools/workflow-permissions.js +204 -0
  33. package/dist/mcp/tools/workflow.js +57 -18
  34. package/dist/mcp/utils/index.d.ts +2 -0
  35. package/dist/mcp/utils/index.js +12 -1
  36. package/dist/mcp/utils/role-utils.d.ts +74 -0
  37. package/dist/mcp/utils/role-utils.js +151 -0
  38. package/dist/mcp/utils/types.d.ts +43 -1
  39. package/dist/mcp/utils/types.js +14 -0
  40. package/dist/mcp/webhook-handler.d.ts +6 -0
  41. package/dist/mcp/webhook-handler.js +11 -0
  42. package/dist/mcp-server.d.ts +23 -2
  43. package/dist/mcp-server.js +639 -111
  44. package/dist/plugins/vipunen/client.d.ts +150 -0
  45. package/dist/plugins/vipunen/client.js +535 -0
  46. package/dist/plugins/vipunen/config/schema-config.json +19 -0
  47. package/dist/plugins/vipunen/config/schema-doc.json +22 -0
  48. package/dist/plugins/vipunen/index.d.ts +41 -0
  49. package/dist/plugins/vipunen/index.js +88 -0
  50. package/dist/plugins/vipunen/tools.d.ts +26 -0
  51. package/dist/plugins/vipunen/tools.js +501 -0
  52. package/package.json +2 -1
  53. package/.claude/.context-watchdog.json +0 -1
  54. package/.claude/.session-checked +0 -1
  55. package/.claude/CLAUDE.md +0 -370
  56. package/.claude/agents/agent-ada-skill-builder.md +0 -94
  57. package/.claude/agents/agent-alejandro-function-fields.md +0 -342
  58. package/.claude/agents/agent-bjorn-config-audit.md +0 -103
  59. package/.claude/agents/agent-builder-agent-creator.md +0 -130
  60. package/.claude/agents/agent-code-simplifier.md +0 -53
  61. package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
  62. package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
  63. package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
  64. package/.claude/agents/agent-helga-workflow-config.md +0 -204
  65. package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
  66. package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
  67. package/.claude/agents/agent-ivan-monolith.md +0 -154
  68. package/.claude/agents/agent-kenji-data-reader.md +0 -86
  69. package/.claude/agents/agent-lars-code-inspector.md +0 -102
  70. package/.claude/agents/agent-marco-mockup-builder.md +0 -110
  71. package/.claude/agents/agent-marcus-api-documenter.md +0 -323
  72. package/.claude/agents/agent-marketplace-publisher.md +0 -280
  73. package/.claude/agents/agent-marketplace-reviewer.md +0 -309
  74. package/.claude/agents/agent-permissions-handler.md +0 -208
  75. package/.claude/agents/agent-simple-writer.md +0 -48
  76. package/.claude/agents/agent-svetlana-code-review.md +0 -171
  77. package/.claude/agents/agent-tanya-test-runner.md +0 -333
  78. package/.claude/agents/agent-ui-designer.md +0 -100
  79. package/.claude/agents/agent-viktor-sql-insights.md +0 -212
  80. package/.claude/agents/agent-web-search.md +0 -55
  81. package/.claude/agents/agent-yevgeni-discussions.md +0 -45
  82. package/.claude/agents/agent-zara-zapier.md +0 -159
  83. package/.claude/commands/app-squad.md +0 -135
  84. package/.claude/commands/audit-squad.md +0 -158
  85. package/.claude/commands/autoplan.md +0 -563
  86. package/.claude/commands/cleanup-squad.md +0 -98
  87. package/.claude/commands/config-squad.md +0 -106
  88. package/.claude/commands/crud-squad.md +0 -87
  89. package/.claude/commands/data-squad.md +0 -97
  90. package/.claude/commands/debug-squad.md +0 -303
  91. package/.claude/commands/doc-squad.md +0 -65
  92. package/.claude/commands/handoff.md +0 -137
  93. package/.claude/commands/health.md +0 -49
  94. package/.claude/commands/help.md +0 -29
  95. package/.claude/commands/help:agents.md +0 -151
  96. package/.claude/commands/help:commands.md +0 -78
  97. package/.claude/commands/help:faq.md +0 -79
  98. package/.claude/commands/help:plugins.md +0 -50
  99. package/.claude/commands/help:skills.md +0 -93
  100. package/.claude/commands/help:tools.md +0 -75
  101. package/.claude/commands/hotfix-squad.md +0 -112
  102. package/.claude/commands/integration-squad.md +0 -82
  103. package/.claude/commands/janitor-squad.md +0 -167
  104. package/.claude/commands/learn-auto.md +0 -120
  105. package/.claude/commands/learn.md +0 -120
  106. package/.claude/commands/mcp-list.md +0 -27
  107. package/.claude/commands/onboard-squad.md +0 -140
  108. package/.claude/commands/plan-workspace.md +0 -732
  109. package/.claude/commands/prd.md +0 -130
  110. package/.claude/commands/project-status.md +0 -82
  111. package/.claude/commands/publish.md +0 -138
  112. package/.claude/commands/recap.md +0 -69
  113. package/.claude/commands/restore.md +0 -64
  114. package/.claude/commands/review-squad.md +0 -152
  115. package/.claude/commands/save.md +0 -24
  116. package/.claude/commands/stats.md +0 -19
  117. package/.claude/commands/swarm.md +0 -210
  118. package/.claude/commands/tool-builder.md +0 -39
  119. package/.claude/commands/ws-pull.md +0 -44
  120. package/.claude/hooks/_shared-memory.cjs +0 -305
  121. package/.claude/hooks/_utils.cjs +0 -108
  122. package/.claude/hooks/agent-failure-detector.cjs +0 -383
  123. package/.claude/hooks/agent-usage-logger.cjs +0 -204
  124. package/.claude/hooks/app-edit-guard.cjs +0 -494
  125. package/.claude/hooks/auto-learn.cjs +0 -304
  126. package/.claude/hooks/bash-guard.cjs +0 -272
  127. package/.claude/hooks/builder-mode-manager.cjs +0 -354
  128. package/.claude/hooks/bulk-activity-guard.cjs +0 -271
  129. package/.claude/hooks/context-watchdog.cjs +0 -230
  130. package/.claude/hooks/delegation-reminder.cjs +0 -465
  131. package/.claude/hooks/design-system-lint.cjs +0 -271
  132. package/.claude/hooks/post-scaffold-hook.cjs +0 -181
  133. package/.claude/hooks/prompt-guard.cjs +0 -354
  134. package/.claude/hooks/publish-template-guard.cjs +0 -147
  135. package/.claude/hooks/session-start.cjs +0 -35
  136. package/.claude/hooks/shared-memory-writer.cjs +0 -147
  137. package/.claude/hooks/skill-injector.cjs +0 -140
  138. package/.claude/hooks/skill-usage-logger.cjs +0 -258
  139. package/.claude/hooks/src-edit-guard.cjs +0 -240
  140. package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
  141. package/.claude/settings.json +0 -257
  142. package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
  143. package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
  144. package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
  145. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
  146. package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
  147. package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
  148. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
  149. package/.claude/skills/agent-structure/SKILL.md +0 -98
  150. package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
  151. package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
  152. package/.claude/skills/delegation-routing/SKILL.md +0 -202
  153. package/.claude/skills/frontend-design/SKILL.md +0 -254
  154. package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
  155. package/.claude/skills/hailer-api-client/SKILL.md +0 -518
  156. package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
  157. package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
  158. package/.claude/skills/hailer-design-system/SKILL.md +0 -235
  159. package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
  160. package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
  161. package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
  162. package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
  163. package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
  164. package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
  165. package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
  166. package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
  167. package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
  168. package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
  169. package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
  170. package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
  171. package/.claude/skills/integration-patterns/SKILL.md +0 -421
  172. package/.claude/skills/json-only-output/SKILL.md +0 -72
  173. package/.claude/skills/lsp-setup/SKILL.md +0 -160
  174. package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
  175. package/.claude/skills/optional-parameters/SKILL.md +0 -72
  176. package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
  177. package/.claude/skills/testing-patterns/SKILL.md +0 -630
  178. package/.claude/skills/tool-builder/SKILL.md +0 -250
  179. package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
  180. package/.claude/skills/tool-response-verification/SKILL.md +0 -92
  181. package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
  182. package/.hailer-mcp-port +0 -1
  183. package/.mcp.json +0 -13
  184. package/.opencode/agent/agent-ada-skill-builder.md +0 -35
  185. package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
  186. package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
  187. package/.opencode/agent/agent-builder-agent-creator.md +0 -39
  188. package/.opencode/agent/agent-code-simplifier.md +0 -31
  189. package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
  190. package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
  191. package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
  192. package/.opencode/agent/agent-helga-workflow-config.md +0 -204
  193. package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
  194. package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
  195. package/.opencode/agent/agent-ivan-monolith.md +0 -46
  196. package/.opencode/agent/agent-kenji-data-reader.md +0 -53
  197. package/.opencode/agent/agent-lars-code-inspector.md +0 -28
  198. package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
  199. package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
  200. package/.opencode/agent/agent-marketplace-publisher.md +0 -44
  201. package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
  202. package/.opencode/agent/agent-permissions-handler.md +0 -50
  203. package/.opencode/agent/agent-simple-writer.md +0 -45
  204. package/.opencode/agent/agent-svetlana-code-review.md +0 -39
  205. package/.opencode/agent/agent-tanya-test-runner.md +0 -57
  206. package/.opencode/agent/agent-ui-designer.md +0 -56
  207. package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
  208. package/.opencode/agent/agent-web-search.md +0 -42
  209. package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
  210. package/.opencode/agent/agent-zara-zapier.md +0 -53
  211. package/.opencode/commands/app-squad.md +0 -135
  212. package/.opencode/commands/audit-squad.md +0 -158
  213. package/.opencode/commands/autoplan.md +0 -563
  214. package/.opencode/commands/cleanup-squad.md +0 -98
  215. package/.opencode/commands/config-squad.md +0 -106
  216. package/.opencode/commands/crud-squad.md +0 -87
  217. package/.opencode/commands/data-squad.md +0 -97
  218. package/.opencode/commands/debug-squad.md +0 -303
  219. package/.opencode/commands/doc-squad.md +0 -65
  220. package/.opencode/commands/handoff.md +0 -137
  221. package/.opencode/commands/health.md +0 -49
  222. package/.opencode/commands/help-agents.md +0 -151
  223. package/.opencode/commands/help-commands.md +0 -32
  224. package/.opencode/commands/help-faq.md +0 -29
  225. package/.opencode/commands/help-plugins.md +0 -28
  226. package/.opencode/commands/help-skills.md +0 -7
  227. package/.opencode/commands/help-tools.md +0 -40
  228. package/.opencode/commands/help.md +0 -28
  229. package/.opencode/commands/hotfix-squad.md +0 -112
  230. package/.opencode/commands/integration-squad.md +0 -82
  231. package/.opencode/commands/janitor-squad.md +0 -167
  232. package/.opencode/commands/learn-auto.md +0 -120
  233. package/.opencode/commands/learn.md +0 -120
  234. package/.opencode/commands/mcp-list.md +0 -27
  235. package/.opencode/commands/onboard-squad.md +0 -140
  236. package/.opencode/commands/plan-workspace.md +0 -732
  237. package/.opencode/commands/prd.md +0 -131
  238. package/.opencode/commands/project-status.md +0 -82
  239. package/.opencode/commands/publish.md +0 -138
  240. package/.opencode/commands/recap.md +0 -69
  241. package/.opencode/commands/restore.md +0 -64
  242. package/.opencode/commands/review-squad.md +0 -152
  243. package/.opencode/commands/save.md +0 -24
  244. package/.opencode/commands/stats.md +0 -19
  245. package/.opencode/commands/swarm.md +0 -210
  246. package/.opencode/commands/tool-builder.md +0 -39
  247. package/.opencode/commands/ws-pull.md +0 -44
  248. package/.opencode/opencode.json +0 -21
  249. package/inbox/failures.log +0 -1
  250. package/inbox/usage.jsonl +0 -4
  251. package/scripts/postinstall.cjs +0 -64
  252. package/scripts/test-hal-tools.ts +0 -154
@@ -1,354 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * <hook-name>prompt-guard</hook-name>
4
- *
5
- * <purpose>
6
- * Consolidated UserPromptSubmit hook. Replaces 4 separate hooks:
7
- * - session-structure-check (project structure + auto-update hooks)
8
- * - interactive-mode (suggest clarifying questions)
9
- * - sync-marketplace-agents (update CLAUDE.md with marketplace agents)
10
- * - git-hooks-check (notify about missing pre-commit hook)
11
- *
12
- * Single Node process instead of 4 = faster, no timeouts.
13
- * </purpose>
14
- *
15
- * <triggers>UserPromptSubmit</triggers>
16
- * @version 1.0.0
17
- */
18
-
19
- const fs = require('fs');
20
- const path = require('path');
21
- const os = require('os');
22
-
23
- const ALLOW = JSON.stringify({ decision: 'allow' });
24
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
25
-
26
- // Skip if stdin is TTY (no piped input)
27
- if (process.stdin.isTTY) {
28
- console.log(ALLOW);
29
- process.exit(0);
30
- }
31
-
32
- // --- Read stdin ---
33
- let input = '';
34
- process.stdin.setEncoding('utf8');
35
- process.stdin.on('data', chunk => input += chunk);
36
- process.stdin.on('end', () => {
37
- try {
38
- const data = JSON.parse(input || '{}');
39
- processHook(data);
40
- } catch (e) {
41
- console.error('[prompt-guard] CRASH:', e.message, e.stack);
42
- console.log(ALLOW);
43
- process.exit(0);
44
- }
45
- });
46
-
47
- function processHook(data) {
48
- const messages = [];
49
-
50
- // 1. Session structure check (once per session)
51
- try {
52
- const structureMsg = checkSessionStructure();
53
- if (structureMsg) messages.push(structureMsg);
54
- } catch (e) {
55
- console.error('[prompt-guard] checkSessionStructure crashed:', e.message);
56
- }
57
-
58
- // 2. Interactive mode (every message)
59
- try {
60
- const interactiveMsg = checkInteractiveMode(data.prompt);
61
- if (interactiveMsg) messages.push(interactiveMsg);
62
- } catch (e) {
63
- console.error('[prompt-guard] checkInteractiveMode crashed:', e.message);
64
- }
65
-
66
- // 3. Sync marketplace agents (only when plugins changed)
67
- try { syncMarketplaceAgents(); } catch (e) {
68
- console.error('[prompt-guard] Marketplace sync crashed:', e.message);
69
- }
70
-
71
- // 4. Git hooks check (once per session)
72
- try {
73
- const gitMsg = checkGitHooks();
74
- if (gitMsg) console.error(gitMsg);
75
- } catch (e) {
76
- console.error('[prompt-guard] checkGitHooks crashed:', e.message);
77
- }
78
-
79
- // Output
80
- if (messages.length > 0) {
81
- console.log(JSON.stringify({
82
- decision: 'allow',
83
- message: messages.join('\n')
84
- }));
85
- } else {
86
- console.log(ALLOW);
87
- }
88
- process.exit(0);
89
- }
90
-
91
- // ============================================================
92
- // 1. Session Structure Check (from session-structure-check.cjs)
93
- // ============================================================
94
- function checkSessionStructure() {
95
- // Find project root
96
- let projectRoot = projectDir;
97
- while (projectRoot !== '/' && !fs.existsSync(path.join(projectRoot, '.claude'))) {
98
- projectRoot = path.dirname(projectRoot);
99
- }
100
-
101
- // Session marker - only run once
102
- const markerPath = path.join(projectRoot, '.claude', '.session-checked');
103
- if (fs.existsSync(markerPath)) return null;
104
-
105
- try { fs.writeFileSync(markerPath, new Date().toISOString()); } catch {}
106
-
107
- const reminders = [];
108
-
109
- const handoffPath = path.join(projectRoot, 'SESSION-HANDOFF.md');
110
- const developmentMdPath = path.join(projectRoot, 'DEVELOPMENT.md');
111
- const workspacePath = path.join(projectRoot, 'workspace');
112
-
113
- if (fs.existsSync(handoffPath)) {
114
- reminders.push('SESSION-HANDOFF.md found - read it to resume previous work, then update it.');
115
- }
116
- if (!fs.existsSync(developmentMdPath) && fs.existsSync(workspacePath)) {
117
- reminders.push('No DEVELOPMENT.md found. Consider creating one to track project status.');
118
- }
119
-
120
- // Hooks are now project-local via $CLAUDE_PROJECT_DIR in settings.json
121
- // No auto-update needed
122
-
123
- if (reminders.length > 0) {
124
- return '<session-start>\n' + reminders.join('\n') + '\n</session-start>';
125
- }
126
- return null;
127
- }
128
-
129
- // ============================================================
130
- // 2. Interactive Mode (from interactive-mode.cjs)
131
- // ============================================================
132
- function checkInteractiveMode(prompt) {
133
- if (!prompt) return null;
134
-
135
- const taskPatterns = [
136
- { pattern: /(add|create).*(field|workflow|phase)/i, type: 'schema', questions: ['Field type?', 'Required or optional?', 'Default values?'] },
137
- { pattern: /(build|create|make).*(app|dashboard|portal|ui)/i, type: 'app', questions: ['What data to display?', 'What layout/components?', 'What user actions needed?'] },
138
- { pattern: /(create|add).*(insight|report)/i, type: 'insight', questions: ['What metrics/aggregations?', 'Which workflows to query?', 'Any filters needed?'] },
139
- { pattern: /(import|create).*(activit|record)|bulk/i, type: 'data', questions: ['Which workflow?', 'What field values?', 'How many records?'] },
140
- { pattern: /(update|change|modify).*(activit|record|field|data)/i, type: 'update', questions: ['Which records affected?', 'What new values?', 'Confirm before applying?'] },
141
- ];
142
-
143
- // Feature implementation detection
144
- const isFeatureRequest = /implement|build.*feature|add.*feature|new feature/i.test(prompt);
145
- const planMarkers = (prompt.match(/step\s*\d|phase\s*\d|##|requirement|trigger|action/gi) || []).length;
146
- const hasDetailedPlan = prompt.length > 500 || planMarkers >= 2;
147
-
148
- if (isFeatureRequest && hasDetailedPlan) {
149
- return `<interactive-mode>
150
- FEATURE IMPLEMENTATION DETECTED with detailed plan.
151
-
152
- Before implementing, ask: "Want me to create a PRD first?"
153
- - PRDs track requirements in docs/prd-*.md
154
- - Links to DEVELOPMENT.md roadmap
155
- - Skip only if user explicitly declines
156
-
157
- Use AskUserQuestion to offer PRD creation.
158
- </interactive-mode>`;
159
- }
160
-
161
- const matched = taskPatterns.find(p => p.pattern.test(prompt));
162
- if (matched) {
163
- return `<interactive-mode>
164
- BEFORE STARTING: Consider asking clarifying questions.
165
-
166
- Task type detected: ${matched.type}
167
- Suggested questions to ask user:
168
- ${matched.questions.map(q => `- ${q}`).join('\n')}
169
-
170
- Use AskUserQuestion tool if requirements are unclear.
171
- Gather specifics before spawning agents or making changes.
172
- </interactive-mode>`;
173
- }
174
-
175
- return null;
176
- }
177
-
178
- // ============================================================
179
- // 3. Sync Marketplace Agents (from sync-marketplace-agents.cjs)
180
- // ============================================================
181
- function syncMarketplaceAgents() {
182
- const crypto = require('crypto');
183
-
184
- const PLUGINS_DIR = path.join(os.homedir(), '.claude', 'plugins', 'marketplaces');
185
- const INSTALLED_PLUGINS = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
186
- const CLAUDE_MD = path.join(projectDir, 'CLAUDE.md');
187
- const SYNC_STATE_DIR = path.join(os.homedir(), '.claude', 'sync-state');
188
- const PROJECT_HASH = crypto.createHash('md5').update(projectDir).digest('hex').slice(0, 12);
189
- const SYNC_STATE = path.join(SYNC_STATE_DIR, `${PROJECT_HASH}.state`);
190
-
191
- function getInstalledPluginsHash() {
192
- if (!fs.existsSync(INSTALLED_PLUGINS)) return 'empty';
193
- return crypto.createHash('md5').update(fs.readFileSync(INSTALLED_PLUGINS, 'utf-8')).digest('hex');
194
- }
195
-
196
- // Check if plugins changed
197
- const currentHash = getInstalledPluginsHash();
198
- try {
199
- if (fs.existsSync(SYNC_STATE) && fs.readFileSync(SYNC_STATE, 'utf-8').trim() === currentHash) {
200
- return; // No change
201
- }
202
- } catch {}
203
-
204
- try {
205
- const installedPlugins = getInstalledPlugins(INSTALLED_PLUGINS);
206
- const agents = scanMarketplaceAgents(PLUGINS_DIR, installedPlugins);
207
- updateClaudeMd(CLAUDE_MD, agents);
208
-
209
- // Save sync state
210
- fs.mkdirSync(SYNC_STATE_DIR, { recursive: true });
211
- fs.writeFileSync(SYNC_STATE, currentHash);
212
- console.error(`[prompt-guard] Detected plugin changes, found ${agents.length} marketplace agents`);
213
- } catch (e) {
214
- console.error(`[prompt-guard] Sync agents warning: ${e.message}`);
215
- }
216
- }
217
-
218
- function getInstalledPlugins(installedPluginsPath) {
219
- const plugins = new Set();
220
- if (!fs.existsSync(installedPluginsPath)) return plugins;
221
- try {
222
- const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
223
- if (data.plugins) for (const key of Object.keys(data.plugins)) plugins.add(key);
224
- } catch {}
225
- return plugins;
226
- }
227
-
228
- function scanMarketplaceAgents(pluginsDir, enabledPlugins) {
229
- const agents = [];
230
- if (!fs.existsSync(pluginsDir)) return agents;
231
-
232
- for (const marketplace of fs.readdirSync(pluginsDir)) {
233
- const marketplacePath = path.join(pluginsDir, marketplace);
234
- if (!fs.statSync(marketplacePath).isDirectory()) continue;
235
-
236
- // Flat structure: {marketplace}/agents/
237
- const flatAgentsPath = path.join(marketplacePath, 'agents');
238
- if (fs.existsSync(flatAgentsPath)) {
239
- const pluginKey = `${marketplace}@${marketplace}`;
240
- if (enabledPlugins.has(pluginKey)) {
241
- for (const f of fs.readdirSync(flatAgentsPath).filter(f => f.endsWith('.md'))) {
242
- const content = fs.readFileSync(path.join(flatAgentsPath, f), 'utf-8');
243
- const fm = parseFrontmatter(content);
244
- agents.push({ marketplace, plugin: marketplace, name: path.basename(f, '.md'), fullName: `${marketplace}:${path.basename(f, '.md')}`, description: fm.description || '', model: fm.model || 'sonnet' });
245
- }
246
- }
247
- }
248
-
249
- // Root-level plugins: {marketplace}/{plugin}/agents/
250
- for (const item of fs.readdirSync(marketplacePath)) {
251
- if (item === '.claude-plugin' || item === '.git' || item === 'plugins') continue;
252
- const itemPath = path.join(marketplacePath, item);
253
- if (!fs.statSync(itemPath).isDirectory()) continue;
254
- if (!fs.existsSync(path.join(itemPath, '.claude-plugin', 'plugin.json'))) continue;
255
- if (!enabledPlugins.has(`${item}@${marketplace}`)) continue;
256
- const agentsPath = path.join(itemPath, 'agents');
257
- if (!fs.existsSync(agentsPath)) continue;
258
- for (const f of fs.readdirSync(agentsPath).filter(f => f.endsWith('.md'))) {
259
- const content = fs.readFileSync(path.join(agentsPath, f), 'utf-8');
260
- const fm = parseFrontmatter(content);
261
- agents.push({ marketplace, plugin: item, name: path.basename(f, '.md'), fullName: `${item}:${path.basename(f, '.md')}`, description: fm.description || '', model: fm.model || 'sonnet' });
262
- }
263
- }
264
-
265
- // Nested: {marketplace}/plugins/{plugin}/agents/
266
- const nestedPath = path.join(marketplacePath, 'plugins');
267
- if (fs.existsSync(nestedPath)) {
268
- for (const plugin of fs.readdirSync(nestedPath)) {
269
- if (!enabledPlugins.has(`${plugin}@${marketplace}`)) continue;
270
- const pluginPath = path.join(nestedPath, plugin);
271
- if (!fs.statSync(pluginPath).isDirectory()) continue;
272
- const agentsPath = path.join(pluginPath, 'agents');
273
- if (!fs.existsSync(agentsPath)) continue;
274
- for (const f of fs.readdirSync(agentsPath).filter(f => f.endsWith('.md'))) {
275
- const content = fs.readFileSync(path.join(agentsPath, f), 'utf-8');
276
- const fm = parseFrontmatter(content);
277
- agents.push({ marketplace, plugin, name: path.basename(f, '.md'), fullName: `${plugin}:${path.basename(f, '.md')}`, description: fm.description || '', model: fm.model || 'sonnet' });
278
- }
279
- }
280
- }
281
- }
282
- return agents;
283
- }
284
-
285
- function parseFrontmatter(content) {
286
- const match = content.match(/^---\n([\s\S]*?)\n---/);
287
- if (!match) return {};
288
- const fm = {};
289
- for (const line of match[1].split('\n')) {
290
- const i = line.indexOf(':');
291
- if (i <= 0 || line.startsWith(' ') || line.startsWith('\t')) continue;
292
- const key = line.slice(0, i).trim();
293
- let value = line.slice(i + 1).trim();
294
- if (key === 'description') value = value.replace(/\\n.*/g, '').slice(0, 100);
295
- fm[key] = value;
296
- }
297
- return fm;
298
- }
299
-
300
- function updateClaudeMd(claudeMdPath, agents) {
301
- if (!fs.existsSync(claudeMdPath)) return;
302
- let content = fs.readFileSync(claudeMdPath, 'utf-8');
303
-
304
- let table = agents.length === 0 ? 'No marketplace agents installed.' :
305
- '| Agent | Plugin | Model | Description |\n|-------|--------|-------|-------------|\n' +
306
- agents.map(a => `| \`${a.fullName}\` | ${a.plugin} | ${a.model} | ${a.description.slice(0, 50)}${a.description.length > 50 ? '...' : ''} |`).join('\n');
307
-
308
- const sectionStart = content.indexOf('<config-source>');
309
- const sectionEnd = content.indexOf('</config-source>');
310
- if (sectionStart === -1 || sectionEnd === -1) return;
311
-
312
- const tableStart = content.indexOf('| Agent |', sectionStart);
313
- const noAgents = content.indexOf('No marketplace agents installed.', sectionStart);
314
-
315
- let replaceStart, replaceEnd;
316
- if (tableStart !== -1 && tableStart < sectionEnd) { replaceStart = tableStart; replaceEnd = sectionEnd; }
317
- else if (noAgents !== -1 && noAgents < sectionEnd) { replaceStart = noAgents; replaceEnd = noAgents + 'No marketplace agents installed.'.length; }
318
- else { replaceStart = content.indexOf('\n', sectionStart) + 1; replaceEnd = replaceStart; }
319
-
320
- content = content.substring(0, replaceStart) + table + '\n\n' + content.substring(replaceEnd);
321
- fs.writeFileSync(claudeMdPath, content);
322
- }
323
-
324
- // ============================================================
325
- // 4. Git Hooks Check (from git-hooks-check.cjs)
326
- // ============================================================
327
- function checkGitHooks() {
328
- const SESSION_FILE = path.join(os.tmpdir(), '.claude-git-hooks-checked');
329
- const SESSION_TTL = 3600000;
330
-
331
- try {
332
- if (fs.existsSync(SESSION_FILE)) {
333
- const data = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8'));
334
- if (data.projectDir === projectDir && (Date.now() - data.checkedAt) < SESSION_TTL) return null;
335
- }
336
- } catch {}
337
-
338
- fs.writeFileSync(SESSION_FILE, JSON.stringify({ projectDir, checkedAt: Date.now() }));
339
-
340
- const gitDir = path.join(projectDir, '.git');
341
- if (!fs.existsSync(gitDir)) return null;
342
-
343
- const hookSource = path.join(projectDir, '.claude', 'hooks', 'pre-commit-test.sh');
344
- if (!fs.existsSync(hookSource)) return null;
345
-
346
- const preCommitHook = path.join(gitDir, 'hooks', 'pre-commit');
347
- if (fs.existsSync(preCommitHook)) return null;
348
-
349
- return `
350
- Git pre-commit hook not installed.
351
- Install: ln -sf ../../.claude/hooks/pre-commit-test.sh .git/hooks/pre-commit
352
- `;
353
- }
354
-
@@ -1,147 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * <hook-name>publish-template-guard</hook-name>
4
- *
5
- * <purpose>
6
- * Ensures all required fields are provided before publish_template is called.
7
- * Blocks incomplete calls and instructs Claude to gather information first.
8
- * </purpose>
9
- *
10
- * <triggers>
11
- * - mcp__hailer__publish_template tool calls
12
- * </triggers>
13
- *
14
- * <required-fields>
15
- * - title: Template name (max 64 chars)
16
- * - description: What it includes (max 4096 chars)
17
- * - version: Semantic version (e.g. "1.0.0")
18
- * - versionDescription: Release notes
19
- * - publisher: Company or person name
20
- * - iconFileId: Uploaded icon ID (24 chars)
21
- * </required-fields>
22
- *
23
- * <behavior>
24
- * 1. Checks tool_input for missing required fields
25
- * 2. Blocks with decision: "block" if incomplete
26
- * 3. Provides AskUserQuestion template for Claude to gather info
27
- * </behavior>
28
- *
29
- * <workflow>
30
- * 1. Claude asks user for template details
31
- * 2. Claude uploads icon with upload_files
32
- * 3. Claude calls publish_template with ALL fields
33
- * </workflow>
34
- */
35
-
36
- // Skip in subagent context - subagents can't use AskUserQuestion to recover
37
- if (process.env.CLAUDE_AGENT_ID || process.env.CLAUDE_SUBAGENT) {
38
- console.log(JSON.stringify({ decision: 'allow' }));
39
- process.exit(0);
40
- }
41
-
42
- // Read hook input from stdin
43
- let input = '';
44
- process.stdin.setEncoding('utf8');
45
- process.stdin.on('data', chunk => input += chunk);
46
- process.stdin.on('end', () => {
47
- try {
48
- const data = JSON.parse(input);
49
- processHook(data);
50
- } catch {
51
- // Invalid JSON, allow the call
52
- console.log(JSON.stringify({ decision: 'allow' }));
53
- process.exit(0);
54
- }
55
- });
56
-
57
- function processHook(data) {
58
- const { tool_name, tool_input } = data;
59
-
60
- // Only intercept publish_template (MCP tool name format)
61
- if (!tool_name || !tool_name.includes('publish_template')) {
62
- console.log(JSON.stringify({ decision: 'allow' }));
63
- process.exit(0);
64
- }
65
-
66
- // Parse tool input
67
- let args = {};
68
- try {
69
- if (typeof tool_input === 'string') {
70
- args = JSON.parse(tool_input);
71
- } else if (typeof tool_input === 'object') {
72
- args = tool_input;
73
- }
74
- } catch {
75
- // Can't parse, let it through
76
- console.log(JSON.stringify({ decision: 'allow' }));
77
- process.exit(0);
78
- }
79
-
80
- // Check for missing required fields
81
- const missing = [];
82
- if (!args.title) missing.push('title (Name, max 64 chars)');
83
- if (!args.description) missing.push('description (max 4096 chars)');
84
- if (!args.version) missing.push('version (e.g. "1.0.0")');
85
- if (!args.versionDescription) missing.push('versionDescription (release notes)');
86
- if (!args.publisher) missing.push('publisher (company name)');
87
- if (!args.iconFileId) missing.push('iconFileId (upload icon first)');
88
-
89
- if (missing.length > 0) {
90
- console.log(JSON.stringify({
91
- decision: 'block',
92
- message: `BLOCKED: publish_template is missing required fields.
93
-
94
- Missing: ${missing.join(', ')}
95
-
96
- Before calling publish_template, you MUST gather ALL information from the user.
97
-
98
- Use AskUserQuestion with these questions:
99
-
100
- \`\`\`json
101
- {
102
- "questions": [
103
- {
104
- "question": "What should be the template name? (max 64 characters)",
105
- "header": "Name",
106
- "options": [
107
- { "label": "Enter name", "description": "Type a descriptive template name" },
108
- { "label": "Use workspace name", "description": "Use current workspace name as template name" }
109
- ],
110
- "multiSelect": false
111
- }
112
- ]
113
- }
114
- \`\`\`
115
-
116
- Then ask for:
117
- 1. **Name** (title) - Template display name (max 64 chars)
118
- 2. **Description** - What this template includes (max 4096 chars)
119
- 3. **Version** - Semantic version like "1.0.0"
120
- 4. **Version description** - Release notes for this version
121
- 5. **Publisher** - Company or person publishing this template
122
- 6. **Icon** - Ask how to provide icon (URL, file path, or existing fileId)
123
-
124
- After gathering info:
125
- 1. Upload icon with isPublic: true (CRITICAL!):
126
- upload_files({ files: [{ path: "...", isPublic: true }] })
127
- 2. Then call publish_template with ALL fields:
128
-
129
- publish_template({
130
- title: "Template Name",
131
- description: "What it does...",
132
- version: "1.0.0",
133
- versionDescription: "Initial release with...",
134
- publisher: "Company Name",
135
- iconFileId: "<24-char-id>",
136
- imageFileIds: ["<24-char-id>"] // Optional: preview images
137
- })
138
-
139
- DO NOT call publish_template until you have ALL required information.`
140
- }));
141
- process.exit(0);
142
- }
143
-
144
- // All required fields provided, allow the call
145
- console.log(JSON.stringify({ decision: 'allow' }));
146
- process.exit(0);
147
- }
@@ -1,35 +0,0 @@
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
- }
@@ -1,147 +0,0 @@
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
- }