@hailer/mcp 1.0.28 → 1.1.2

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 -249
  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 +29 -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 +219 -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
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * <hook-name>design-system-lint</hook-name>
4
+ *
5
+ * <purpose>
6
+ * Warns if app code doesn't follow Hailer Design System patterns.
7
+ * Runs after Giuseppe writes/edits files in app directories.
8
+ * </purpose>
9
+ *
10
+ * <triggers>
11
+ * - PostToolUse on Write and Edit
12
+ * - Only for files in apps/ or containing Chakra imports
13
+ * </triggers>
14
+ *
15
+ * <checks>
16
+ * - Hardcoded colors (hex, rgb, hsl)
17
+ * - External icon libraries (react-icons, heroicons, etc.)
18
+ * - Plain HTML table elements instead of Chakra Table
19
+ * - Missing theme import when using ChakraProvider
20
+ * </checks>
21
+ *
22
+ * <behavior>
23
+ * 1. Check if file is in app directory or has Chakra imports
24
+ * 2. Scan content for violations
25
+ * 3. Output warnings to stderr (non-blocking)
26
+ * </behavior>
27
+ */
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+
32
+ // Skip in yolo mode
33
+ try {
34
+ const statePath = path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), '.claude', 'yolo-state.json');
35
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
36
+ if (state.mode === 'yolo') process.exit(0);
37
+ } catch (e) {
38
+ // ENOENT is expected when not in yolo mode - only warn on unexpected errors
39
+ if (e.code !== 'ENOENT' && !e.message.includes('Unexpected')) {
40
+ console.error(`[design-system-lint] Warning: ${e.message}`);
41
+ }
42
+ }
43
+
44
+ // Read hook input from stdin
45
+ let input = '';
46
+ process.stdin.setEncoding('utf8');
47
+ process.stdin.on('data', chunk => input += chunk);
48
+ process.stdin.on('end', () => {
49
+ try {
50
+ const data = JSON.parse(input);
51
+ processHook(data);
52
+ } catch (e) {
53
+ console.error(`[design-system-lint] Warning: ${e.message}`);
54
+ process.exit(0);
55
+ }
56
+ });
57
+
58
+ // Design system violation patterns
59
+ const VIOLATIONS = [
60
+ {
61
+ name: 'hardcoded-hex-color',
62
+ pattern: /(?:color|bg|background|border|fill|stroke)[:=]\s*(?:["'`]#[0-9a-fA-F]{3,8}["'`]|\$\{[^}]*#[0-9a-fA-F]{3,8}[^}]*\})/g,
63
+ message: 'Hardcoded hex color found',
64
+ suggestion: 'Use Chakra color tokens (e.g., "gray.500", "green.400") or semantic tokens ("subtleText", "bodyText")'
65
+ },
66
+ {
67
+ name: 'hardcoded-rgb-color',
68
+ pattern: /(?:color|bg|background|border)[:=]\s*(?:["'`]rgba?\([^)]+\)["'`]|\$\{[^}]*rgba?\([^)]+\)[^}]*\})/g,
69
+ message: 'Hardcoded RGB/RGBA color found',
70
+ suggestion: 'Use Chakra color tokens instead'
71
+ },
72
+ {
73
+ name: 'external-icons',
74
+ pattern: /import\s+.*\s+from\s+["'](react-icons|@heroicons|@fortawesome|lucide-react|@tabler\/icons)/g,
75
+ message: 'External icon library imported',
76
+ suggestion: 'Use Hailer icons from design-system/theme/icons (e.g., HailerPlus, HailerSettings)'
77
+ },
78
+ {
79
+ name: 'plain-html-table',
80
+ pattern: /<(table|thead|tbody|tr|th|td)[\s>]/gi,
81
+ message: 'Plain HTML table elements found',
82
+ suggestion: 'Use Chakra UI Table components: <Table>, <Thead>, <Tbody>, <Tr>, <Th>, <Td>'
83
+ },
84
+ {
85
+ name: 'missing-theme-import',
86
+ pattern: /ChakraProvider/,
87
+ message: 'ChakraProvider without theme prop',
88
+ suggestion: 'Import and pass the Hailer theme: <ChakraProvider theme={theme}>'
89
+ },
90
+ {
91
+ name: 'inline-style',
92
+ pattern: /style\s*=\s*\{\s*\{[^}]*(?:color|background|border)[^}]*\}\s*\}/g,
93
+ message: 'Inline style with color/background',
94
+ suggestion: 'Use Chakra style props instead of inline styles'
95
+ },
96
+ {
97
+ name: 'wrong-stat-pattern',
98
+ pattern: /<Box[^>]*>[\s\S]*?<Text[^>]*fontSize=["']sm["'][^>]*>[\s\S]*?<\/Text>[\s\S]*?<(?:Heading|Text)[^>]*fontSize=["'](?:2xl|3xl|xl)["']/g,
99
+ message: 'Manual stat card pattern detected',
100
+ suggestion: 'Use Chakra Stat components: <Stat>, <StatLabel>, <StatNumber>, <StatHelpText>'
101
+ }
102
+ ];
103
+
104
+ // Files/patterns to skip
105
+ const SKIP_PATTERNS = [
106
+ /node_modules/,
107
+ /\.d\.ts$/,
108
+ /\.json$/,
109
+ /\.md$/,
110
+ /\.css$/,
111
+ /theme\.ts$/, // Don't lint the theme file itself
112
+ /customColors\.ts$/
113
+ ];
114
+
115
+ function shouldLintFile(filePath) {
116
+ // Skip certain files
117
+ for (const pattern of SKIP_PATTERNS) {
118
+ if (pattern.test(filePath)) {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ // Check if it's in apps/ directory
124
+ if (filePath.includes('/apps/') || filePath.includes('\\apps\\')) {
125
+ return true;
126
+ }
127
+
128
+ // Check if it's a TypeScript/JavaScript file that might be an app
129
+ if (/\.(tsx?|jsx?)$/.test(filePath)) {
130
+ return true;
131
+ }
132
+
133
+ return false;
134
+ }
135
+
136
+ function lintContent(content, filePath) {
137
+ const warnings = [];
138
+
139
+ // Only lint if file has Chakra imports (indicates it's a UI file)
140
+ const hasChakraImport = content.includes('@chakra-ui/react') || content.includes('chakra-ui');
141
+ const isInApps = filePath.includes('/apps/') || filePath.includes('\\apps\\');
142
+
143
+ if (!hasChakraImport && !isInApps) {
144
+ return warnings;
145
+ }
146
+
147
+ // Single-pass scan: loop through violations once, check content for each pattern
148
+ // Performance: O(n*m) where n=violations, m=pattern matches. Acceptable for typical files (<50KB)
149
+ for (const violation of VIOLATIONS) {
150
+ const matches = content.match(violation.pattern);
151
+ if (matches) {
152
+ // Special handling for missing-theme-import
153
+ if (violation.name === 'missing-theme-import') {
154
+ // Only warn if ChakraProvider exists but no theme= prop nearby
155
+ if (content.includes('ChakraProvider') && !content.includes('theme={') && !content.includes('theme=')) {
156
+ warnings.push({
157
+ name: violation.name,
158
+ message: violation.message,
159
+ suggestion: violation.suggestion,
160
+ count: 1
161
+ });
162
+ }
163
+ continue;
164
+ }
165
+
166
+ warnings.push({
167
+ name: violation.name,
168
+ message: violation.message,
169
+ suggestion: violation.suggestion,
170
+ count: matches.length,
171
+ examples: matches.slice(0, 2).map(m => m.substring(0, 60))
172
+ });
173
+ }
174
+ }
175
+
176
+ return warnings;
177
+ }
178
+
179
+ function processHook(data) {
180
+ const { tool_name, tool_input, tool_response } = data;
181
+
182
+ // Only check Write and Edit tools on PostToolUse
183
+ if (tool_name !== 'Write' && tool_name !== 'Edit') {
184
+ process.exit(0);
185
+ }
186
+
187
+ // Need tool_response to confirm it's PostToolUse
188
+ if (tool_response === undefined) {
189
+ process.exit(0);
190
+ }
191
+
192
+ const filePath = tool_input?.file_path;
193
+ if (!filePath) {
194
+ process.exit(0);
195
+ }
196
+
197
+ // Check if we should lint this file
198
+ if (!shouldLintFile(filePath)) {
199
+ process.exit(0);
200
+ }
201
+
202
+ // Read the file content
203
+ let content;
204
+ try {
205
+ content = fs.readFileSync(filePath, 'utf8');
206
+ } catch (e) {
207
+ console.error(`[design-system-lint] Warning: ${e.message}`);
208
+ process.exit(0);
209
+ }
210
+
211
+ // Lint the content
212
+ const warnings = lintContent(content, filePath);
213
+
214
+ if (warnings.length > 0) {
215
+ const output = `
216
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
217
+ 🎨 DESIGN SYSTEM LINT WARNINGS
218
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
219
+
220
+ File: ${path.basename(filePath)}
221
+
222
+ ${warnings.map(w => `
223
+ ⚠️ ${w.message}${w.count > 1 ? ` (${w.count} occurrences)` : ''}
224
+ → ${w.suggestion}
225
+ ${w.examples ? w.examples.map(e => ` Example: ${e}...`).join('\n') : ''}`).join('\n')}
226
+
227
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
228
+ 📚 Reference: Load \`hailer-design-system\` skill for full guide
229
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
230
+ `;
231
+ console.error(output);
232
+ }
233
+
234
+ process.exit(0);
235
+ }
236
+
237
+ // CLI: Lint a specific file
238
+ if (process.argv[2] && !process.argv[2].startsWith('-')) {
239
+ const filePath = path.resolve(process.argv[2]);
240
+
241
+ if (!fs.existsSync(filePath)) {
242
+ console.error(`File not found: ${filePath}`);
243
+ process.exit(1);
244
+ }
245
+
246
+ const content = fs.readFileSync(filePath, 'utf8');
247
+ const warnings = lintContent(content, filePath);
248
+
249
+ if (warnings.length === 0) {
250
+ console.log(`✅ No design system issues found in ${path.basename(filePath)}`);
251
+ } else {
252
+ console.log(`Found ${warnings.length} issue(s) in ${path.basename(filePath)}:\n`);
253
+ for (const w of warnings) {
254
+ console.log(`⚠️ ${w.message}${w.count > 1 ? ` (${w.count}x)` : ''}`);
255
+ console.log(` → ${w.suggestion}\n`);
256
+ }
257
+ }
258
+ process.exit(0);
259
+ }
260
+
261
+ // CLI: Help
262
+ if (process.argv[2] === '--help' || process.argv[2] === '-h') {
263
+ console.log(`
264
+ Design System Lint - Checks app code for Hailer Design System compliance
265
+
266
+ Usage:
267
+ node design-system-lint.cjs <file> Lint a specific file
268
+ node design-system-lint.cjs --help Show this help
269
+
270
+ Checks for:
271
+ - Hardcoded colors (hex, rgb, rgba)
272
+ - External icon libraries
273
+ - Plain HTML table elements
274
+ - Missing theme in ChakraProvider
275
+ - Inline styles with colors
276
+ - Manual stat card patterns
277
+
278
+ As a hook:
279
+ PostToolUse on Write/Edit
280
+ Outputs warnings to stderr (non-blocking)
281
+ `);
282
+ process.exit(0);
283
+ }
@@ -29,9 +29,17 @@
29
29
 
30
30
  const path = require('path');
31
31
  const fs = require('fs');
32
- const { execSync } = require('child_process');
32
+ const os = require('os');
33
33
 
34
- const TRACKER_DIR = '/tmp/.claude-scaffolded-apps';
34
+ // Skip in yolo mode
35
+ try {
36
+ const statePath = path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), '.claude', 'yolo-state.json');
37
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
38
+ if (state.mode === 'yolo') process.exit(0);
39
+ } catch {}
40
+
41
+ const TEMP_DIR = os.tmpdir();
42
+ const TRACKER_DIR = path.join(TEMP_DIR, '.claude-scaffolded-apps');
35
43
 
36
44
  // Ensure tracker directory exists
37
45
  if (!fs.existsSync(TRACKER_DIR)) {
@@ -122,6 +130,11 @@ You MUST use AskUserQuestion with EXACTLY this format:
122
130
  IF USER SAYS YES: Follow these steps
123
131
  ----------------------------------------
124
132
 
133
+ 0. Enable builder mode (required for Giuseppe to edit app files):
134
+ \`\`\`bash
135
+ node .claude/hooks/app-edit-guard.cjs --agent-on
136
+ \`\`\`
137
+
125
138
  1. Load the spawn-app-builder skill:
126
139
  \`\`\`javascript
127
140
  Skill("spawn-app-builder")
@@ -153,7 +166,7 @@ ASK THE USER NOW - Do not skip this question!
153
166
  ============================================================
154
167
  `;
155
168
 
156
- console.error(output);
169
+ console.log(JSON.stringify({ decision: 'allow', message: output }));
157
170
  process.exit(0);
158
171
  }
159
172
 
@@ -0,0 +1,366 @@
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
+ // --- Yolo mode check ---
27
+ let isYolo = false;
28
+ try {
29
+ const statePath = path.join(projectDir, '.claude', 'yolo-state.json');
30
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
31
+ isYolo = state.mode === 'yolo';
32
+ } catch {}
33
+
34
+ // Skip if stdin is TTY (no piped input)
35
+ if (process.stdin.isTTY) {
36
+ console.log(ALLOW);
37
+ process.exit(0);
38
+ }
39
+
40
+ // --- Read stdin ---
41
+ let input = '';
42
+ process.stdin.setEncoding('utf8');
43
+ process.stdin.on('data', chunk => input += chunk);
44
+ process.stdin.on('end', () => {
45
+ try {
46
+ const data = JSON.parse(input || '{}');
47
+ processHook(data);
48
+ } catch (e) {
49
+ console.error('[prompt-guard] CRASH:', e.message, e.stack);
50
+ console.log(ALLOW);
51
+ process.exit(0);
52
+ }
53
+ });
54
+
55
+ function processHook(data) {
56
+ const messages = [];
57
+
58
+ // 1. Session structure check (once per session)
59
+ try {
60
+ const structureMsg = checkSessionStructure();
61
+ if (structureMsg) messages.push(structureMsg);
62
+ } catch (e) {
63
+ console.error('[prompt-guard] checkSessionStructure crashed:', e.message);
64
+ }
65
+
66
+ // 2. Interactive mode (every message, skipped in yolo)
67
+ try {
68
+ if (!isYolo) {
69
+ const interactiveMsg = checkInteractiveMode(data.prompt);
70
+ if (interactiveMsg) messages.push(interactiveMsg);
71
+ }
72
+ } catch (e) {
73
+ console.error('[prompt-guard] checkInteractiveMode crashed:', e.message);
74
+ }
75
+
76
+ // 3. Sync marketplace agents (only when plugins changed)
77
+ try { syncMarketplaceAgents(); } catch (e) {
78
+ console.error('[prompt-guard] Marketplace sync crashed:', e.message);
79
+ }
80
+
81
+ // 4. Git hooks check (once per session, skipped in yolo)
82
+ try {
83
+ if (!isYolo) {
84
+ const gitMsg = checkGitHooks();
85
+ if (gitMsg) console.error(gitMsg);
86
+ }
87
+ } catch (e) {
88
+ console.error('[prompt-guard] checkGitHooks crashed:', e.message);
89
+ }
90
+
91
+ // Output
92
+ if (messages.length > 0) {
93
+ console.log(JSON.stringify({
94
+ decision: 'allow',
95
+ message: messages.join('\n')
96
+ }));
97
+ } else {
98
+ console.log(ALLOW);
99
+ }
100
+ process.exit(0);
101
+ }
102
+
103
+ // ============================================================
104
+ // 1. Session Structure Check (from session-structure-check.cjs)
105
+ // ============================================================
106
+ function checkSessionStructure() {
107
+ // Find project root
108
+ let projectRoot = projectDir;
109
+ while (projectRoot !== '/' && !fs.existsSync(path.join(projectRoot, '.claude'))) {
110
+ projectRoot = path.dirname(projectRoot);
111
+ }
112
+
113
+ // Session marker - only run once
114
+ const markerPath = path.join(projectRoot, '.claude', '.session-checked');
115
+ if (fs.existsSync(markerPath)) return null;
116
+
117
+ try { fs.writeFileSync(markerPath, new Date().toISOString()); } catch {}
118
+
119
+ const reminders = [];
120
+
121
+ const handoffPath = path.join(projectRoot, 'SESSION-HANDOFF.md');
122
+ const developmentMdPath = path.join(projectRoot, 'DEVELOPMENT.md');
123
+ const workspacePath = path.join(projectRoot, 'workspace');
124
+
125
+ if (fs.existsSync(handoffPath)) {
126
+ reminders.push('SESSION-HANDOFF.md found - read it to resume previous work, then update it.');
127
+ }
128
+ if (!fs.existsSync(developmentMdPath) && fs.existsSync(workspacePath)) {
129
+ reminders.push('No DEVELOPMENT.md found. Consider creating one to track project status.');
130
+ }
131
+
132
+ // Hooks are now project-local via $CLAUDE_PROJECT_DIR in settings.json
133
+ // No auto-update needed
134
+
135
+ if (reminders.length > 0) {
136
+ return '<session-start>\n' + reminders.join('\n') + '\n</session-start>';
137
+ }
138
+ return null;
139
+ }
140
+
141
+ // ============================================================
142
+ // 2. Interactive Mode (from interactive-mode.cjs)
143
+ // ============================================================
144
+ function checkInteractiveMode(prompt) {
145
+ if (!prompt) return null;
146
+
147
+ const taskPatterns = [
148
+ { pattern: /(add|create).*(field|workflow|phase)/i, type: 'schema', questions: ['Field type?', 'Required or optional?', 'Default values?'] },
149
+ { pattern: /(build|create|make).*(app|dashboard|portal|ui)/i, type: 'app', questions: ['What data to display?', 'What layout/components?', 'What user actions needed?'] },
150
+ { pattern: /(create|add).*(insight|report)/i, type: 'insight', questions: ['What metrics/aggregations?', 'Which workflows to query?', 'Any filters needed?'] },
151
+ { pattern: /(import|create).*(activit|record)|bulk/i, type: 'data', questions: ['Which workflow?', 'What field values?', 'How many records?'] },
152
+ { pattern: /(update|change|modify).*(activit|record|field|data)/i, type: 'update', questions: ['Which records affected?', 'What new values?', 'Confirm before applying?'] },
153
+ ];
154
+
155
+ // Feature implementation detection
156
+ const isFeatureRequest = /implement|build.*feature|add.*feature|new feature/i.test(prompt);
157
+ const planMarkers = (prompt.match(/step\s*\d|phase\s*\d|##|requirement|trigger|action/gi) || []).length;
158
+ const hasDetailedPlan = prompt.length > 500 || planMarkers >= 2;
159
+
160
+ if (isFeatureRequest && hasDetailedPlan) {
161
+ return `<interactive-mode>
162
+ FEATURE IMPLEMENTATION DETECTED with detailed plan.
163
+
164
+ Before implementing, ask: "Want me to create a PRD first?"
165
+ - PRDs track requirements in docs/prd-*.md
166
+ - Links to DEVELOPMENT.md roadmap
167
+ - Skip only if user explicitly declines
168
+
169
+ Use AskUserQuestion to offer PRD creation.
170
+ </interactive-mode>`;
171
+ }
172
+
173
+ const matched = taskPatterns.find(p => p.pattern.test(prompt));
174
+ if (matched) {
175
+ return `<interactive-mode>
176
+ BEFORE STARTING: Consider asking clarifying questions.
177
+
178
+ Task type detected: ${matched.type}
179
+ Suggested questions to ask user:
180
+ ${matched.questions.map(q => `- ${q}`).join('\n')}
181
+
182
+ Use AskUserQuestion tool if requirements are unclear.
183
+ Gather specifics before spawning agents or making changes.
184
+ </interactive-mode>`;
185
+ }
186
+
187
+ return null;
188
+ }
189
+
190
+ // ============================================================
191
+ // 3. Sync Marketplace Agents (from sync-marketplace-agents.cjs)
192
+ // ============================================================
193
+ function syncMarketplaceAgents() {
194
+ const crypto = require('crypto');
195
+
196
+ const PLUGINS_DIR = path.join(os.homedir(), '.claude', 'plugins', 'marketplaces');
197
+ const INSTALLED_PLUGINS = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
198
+ const CLAUDE_MD = path.join(projectDir, 'CLAUDE.md');
199
+ const SYNC_STATE_DIR = path.join(os.homedir(), '.claude', 'sync-state');
200
+ const PROJECT_HASH = crypto.createHash('md5').update(projectDir).digest('hex').slice(0, 12);
201
+ const SYNC_STATE = path.join(SYNC_STATE_DIR, `${PROJECT_HASH}.state`);
202
+
203
+ function getInstalledPluginsHash() {
204
+ if (!fs.existsSync(INSTALLED_PLUGINS)) return 'empty';
205
+ return crypto.createHash('md5').update(fs.readFileSync(INSTALLED_PLUGINS, 'utf-8')).digest('hex');
206
+ }
207
+
208
+ // Check if plugins changed
209
+ const currentHash = getInstalledPluginsHash();
210
+ try {
211
+ if (fs.existsSync(SYNC_STATE) && fs.readFileSync(SYNC_STATE, 'utf-8').trim() === currentHash) {
212
+ return; // No change
213
+ }
214
+ } catch {}
215
+
216
+ try {
217
+ const installedPlugins = getInstalledPlugins(INSTALLED_PLUGINS);
218
+ const agents = scanMarketplaceAgents(PLUGINS_DIR, installedPlugins);
219
+ updateClaudeMd(CLAUDE_MD, agents);
220
+
221
+ // Save sync state
222
+ fs.mkdirSync(SYNC_STATE_DIR, { recursive: true });
223
+ fs.writeFileSync(SYNC_STATE, currentHash);
224
+ console.error(`[prompt-guard] Detected plugin changes, found ${agents.length} marketplace agents`);
225
+ } catch (e) {
226
+ console.error(`[prompt-guard] Sync agents warning: ${e.message}`);
227
+ }
228
+ }
229
+
230
+ function getInstalledPlugins(installedPluginsPath) {
231
+ const plugins = new Set();
232
+ if (!fs.existsSync(installedPluginsPath)) return plugins;
233
+ try {
234
+ const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
235
+ if (data.plugins) for (const key of Object.keys(data.plugins)) plugins.add(key);
236
+ } catch {}
237
+ return plugins;
238
+ }
239
+
240
+ function scanMarketplaceAgents(pluginsDir, enabledPlugins) {
241
+ const agents = [];
242
+ if (!fs.existsSync(pluginsDir)) return agents;
243
+
244
+ for (const marketplace of fs.readdirSync(pluginsDir)) {
245
+ const marketplacePath = path.join(pluginsDir, marketplace);
246
+ if (!fs.statSync(marketplacePath).isDirectory()) continue;
247
+
248
+ // Flat structure: {marketplace}/agents/
249
+ const flatAgentsPath = path.join(marketplacePath, 'agents');
250
+ if (fs.existsSync(flatAgentsPath)) {
251
+ const pluginKey = `${marketplace}@${marketplace}`;
252
+ if (enabledPlugins.has(pluginKey)) {
253
+ for (const f of fs.readdirSync(flatAgentsPath).filter(f => f.endsWith('.md'))) {
254
+ const content = fs.readFileSync(path.join(flatAgentsPath, f), 'utf-8');
255
+ const fm = parseFrontmatter(content);
256
+ agents.push({ marketplace, plugin: marketplace, name: path.basename(f, '.md'), fullName: `${marketplace}:${path.basename(f, '.md')}`, description: fm.description || '', model: fm.model || 'sonnet' });
257
+ }
258
+ }
259
+ }
260
+
261
+ // Root-level plugins: {marketplace}/{plugin}/agents/
262
+ for (const item of fs.readdirSync(marketplacePath)) {
263
+ if (item === '.claude-plugin' || item === '.git' || item === 'plugins') continue;
264
+ const itemPath = path.join(marketplacePath, item);
265
+ if (!fs.statSync(itemPath).isDirectory()) continue;
266
+ if (!fs.existsSync(path.join(itemPath, '.claude-plugin', 'plugin.json'))) continue;
267
+ if (!enabledPlugins.has(`${item}@${marketplace}`)) continue;
268
+ const agentsPath = path.join(itemPath, 'agents');
269
+ if (!fs.existsSync(agentsPath)) continue;
270
+ for (const f of fs.readdirSync(agentsPath).filter(f => f.endsWith('.md'))) {
271
+ const content = fs.readFileSync(path.join(agentsPath, f), 'utf-8');
272
+ const fm = parseFrontmatter(content);
273
+ agents.push({ marketplace, plugin: item, name: path.basename(f, '.md'), fullName: `${item}:${path.basename(f, '.md')}`, description: fm.description || '', model: fm.model || 'sonnet' });
274
+ }
275
+ }
276
+
277
+ // Nested: {marketplace}/plugins/{plugin}/agents/
278
+ const nestedPath = path.join(marketplacePath, 'plugins');
279
+ if (fs.existsSync(nestedPath)) {
280
+ for (const plugin of fs.readdirSync(nestedPath)) {
281
+ if (!enabledPlugins.has(`${plugin}@${marketplace}`)) continue;
282
+ const pluginPath = path.join(nestedPath, plugin);
283
+ if (!fs.statSync(pluginPath).isDirectory()) continue;
284
+ const agentsPath = path.join(pluginPath, 'agents');
285
+ if (!fs.existsSync(agentsPath)) continue;
286
+ for (const f of fs.readdirSync(agentsPath).filter(f => f.endsWith('.md'))) {
287
+ const content = fs.readFileSync(path.join(agentsPath, f), 'utf-8');
288
+ const fm = parseFrontmatter(content);
289
+ agents.push({ marketplace, plugin, name: path.basename(f, '.md'), fullName: `${plugin}:${path.basename(f, '.md')}`, description: fm.description || '', model: fm.model || 'sonnet' });
290
+ }
291
+ }
292
+ }
293
+ }
294
+ return agents;
295
+ }
296
+
297
+ function parseFrontmatter(content) {
298
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
299
+ if (!match) return {};
300
+ const fm = {};
301
+ for (const line of match[1].split('\n')) {
302
+ const i = line.indexOf(':');
303
+ if (i <= 0 || line.startsWith(' ') || line.startsWith('\t')) continue;
304
+ const key = line.slice(0, i).trim();
305
+ let value = line.slice(i + 1).trim();
306
+ if (key === 'description') value = value.replace(/\\n.*/g, '').slice(0, 100);
307
+ fm[key] = value;
308
+ }
309
+ return fm;
310
+ }
311
+
312
+ function updateClaudeMd(claudeMdPath, agents) {
313
+ if (!fs.existsSync(claudeMdPath)) return;
314
+ let content = fs.readFileSync(claudeMdPath, 'utf-8');
315
+
316
+ let table = agents.length === 0 ? 'No marketplace agents installed.' :
317
+ '| Agent | Plugin | Model | Description |\n|-------|--------|-------|-------------|\n' +
318
+ agents.map(a => `| \`${a.fullName}\` | ${a.plugin} | ${a.model} | ${a.description.slice(0, 50)}${a.description.length > 50 ? '...' : ''} |`).join('\n');
319
+
320
+ const sectionStart = content.indexOf('<config-source>');
321
+ const sectionEnd = content.indexOf('</config-source>');
322
+ if (sectionStart === -1 || sectionEnd === -1) return;
323
+
324
+ const tableStart = content.indexOf('| Agent |', sectionStart);
325
+ const noAgents = content.indexOf('No marketplace agents installed.', sectionStart);
326
+
327
+ let replaceStart, replaceEnd;
328
+ if (tableStart !== -1 && tableStart < sectionEnd) { replaceStart = tableStart; replaceEnd = sectionEnd; }
329
+ else if (noAgents !== -1 && noAgents < sectionEnd) { replaceStart = noAgents; replaceEnd = noAgents + 'No marketplace agents installed.'.length; }
330
+ else { replaceStart = content.indexOf('\n', sectionStart) + 1; replaceEnd = replaceStart; }
331
+
332
+ content = content.substring(0, replaceStart) + table + '\n\n' + content.substring(replaceEnd);
333
+ fs.writeFileSync(claudeMdPath, content);
334
+ }
335
+
336
+ // ============================================================
337
+ // 4. Git Hooks Check (from git-hooks-check.cjs)
338
+ // ============================================================
339
+ function checkGitHooks() {
340
+ const SESSION_FILE = path.join(os.tmpdir(), '.claude-git-hooks-checked');
341
+ const SESSION_TTL = 3600000;
342
+
343
+ try {
344
+ if (fs.existsSync(SESSION_FILE)) {
345
+ const data = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8'));
346
+ if (data.projectDir === projectDir && (Date.now() - data.checkedAt) < SESSION_TTL) return null;
347
+ }
348
+ } catch {}
349
+
350
+ fs.writeFileSync(SESSION_FILE, JSON.stringify({ projectDir, checkedAt: Date.now() }));
351
+
352
+ const gitDir = path.join(projectDir, '.git');
353
+ if (!fs.existsSync(gitDir)) return null;
354
+
355
+ const hookSource = path.join(projectDir, '.claude', 'hooks', 'pre-commit-test.sh');
356
+ if (!fs.existsSync(hookSource)) return null;
357
+
358
+ const preCommitHook = path.join(gitDir, 'hooks', 'pre-commit');
359
+ if (fs.existsSync(preCommitHook)) return null;
360
+
361
+ return `
362
+ Git pre-commit hook not installed.
363
+ Install: ln -sf ../../.claude/hooks/pre-commit-test.sh .git/hooks/pre-commit
364
+ `;
365
+ }
366
+