@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.
- package/.claude/.session-checked +1 -0
- package/.claude/agents/agent-ada-skill-builder.md +10 -2
- package/.claude/agents/agent-alejandro-function-fields.md +104 -37
- package/.claude/agents/agent-bjorn-config-audit.md +41 -21
- package/.claude/agents/agent-builder-agent-creator.md +13 -3
- package/.claude/agents/agent-code-simplifier.md +53 -0
- package/.claude/agents/agent-dmitri-activity-crud.md +126 -11
- package/.claude/agents/agent-giuseppe-app-builder.md +212 -22
- package/.claude/agents/agent-gunther-mcp-tools.md +7 -36
- package/.claude/agents/agent-helga-workflow-config.md +75 -10
- package/.claude/agents/agent-igor-activity-mover-automation.md +125 -0
- package/.claude/agents/agent-ingrid-doc-templates.md +164 -36
- package/.claude/agents/agent-ivan-monolith.md +154 -0
- package/.claude/agents/agent-kenji-data-reader.md +15 -8
- package/.claude/agents/agent-lars-code-inspector.md +56 -8
- package/.claude/agents/agent-marco-mockup-builder.md +110 -0
- package/.claude/agents/agent-marcus-api-documenter.md +323 -0
- package/.claude/agents/agent-marketplace-publisher.md +232 -72
- package/.claude/agents/agent-marketplace-reviewer.md +255 -79
- package/.claude/agents/agent-permissions-handler.md +208 -0
- package/.claude/agents/agent-simple-writer.md +48 -0
- package/.claude/agents/agent-svetlana-code-review.md +127 -14
- package/.claude/agents/agent-tanya-test-runner.md +333 -0
- package/.claude/agents/agent-ui-designer.md +100 -0
- package/.claude/agents/agent-viktor-sql-insights.md +19 -6
- package/.claude/agents/agent-web-search.md +55 -0
- package/.claude/agents/agent-yevgeni-discussions.md +7 -1
- package/.claude/agents/agent-zara-zapier.md +159 -0
- package/.claude/commands/app-squad.md +135 -0
- package/.claude/commands/audit-squad.md +158 -0
- package/.claude/commands/autoplan.md +563 -0
- package/.claude/commands/cleanup-squad.md +98 -0
- package/.claude/commands/config-squad.md +106 -0
- package/.claude/commands/crud-squad.md +87 -0
- package/.claude/commands/data-squad.md +97 -0
- package/.claude/commands/debug-squad.md +303 -0
- package/.claude/commands/doc-squad.md +65 -0
- package/.claude/commands/handoff.md +137 -0
- package/.claude/commands/health.md +49 -0
- package/.claude/commands/help.md +2 -1
- package/.claude/commands/help:agents.md +96 -16
- package/.claude/commands/help:commands.md +55 -11
- package/.claude/commands/help:faq.md +16 -1
- package/.claude/commands/help:skills.md +93 -0
- package/.claude/commands/hotfix-squad.md +112 -0
- package/.claude/commands/integration-squad.md +82 -0
- package/.claude/commands/janitor-squad.md +167 -0
- package/.claude/commands/learn-auto.md +120 -0
- package/.claude/commands/learn.md +120 -0
- package/.claude/commands/mcp-list.md +27 -0
- package/.claude/commands/onboard-squad.md +140 -0
- package/.claude/commands/plan-workspace.md +732 -0
- package/.claude/commands/prd.md +131 -0
- package/.claude/commands/project-status.md +82 -0
- package/.claude/commands/publish.md +138 -0
- package/.claude/commands/recap.md +69 -0
- package/.claude/commands/restore.md +64 -0
- package/.claude/commands/review-squad.md +152 -0
- package/.claude/commands/save.md +24 -0
- package/.claude/commands/stats.md +19 -0
- package/.claude/commands/swarm.md +210 -0
- package/.claude/commands/tool-builder.md +3 -1
- package/.claude/commands/ws-pull.md +1 -1
- package/.claude/commands/yolo-off.md +17 -0
- package/.claude/commands/yolo.md +82 -0
- package/.claude/hooks/_shared-memory.cjs +305 -0
- package/.claude/hooks/_utils.cjs +134 -0
- package/.claude/hooks/agent-failure-detector.cjs +164 -79
- package/.claude/hooks/agent-usage-logger.cjs +204 -0
- package/.claude/hooks/app-edit-guard.cjs +20 -4
- package/.claude/hooks/auto-learn.cjs +316 -0
- package/.claude/hooks/bash-guard.cjs +282 -0
- package/.claude/hooks/builder-mode-manager.cjs +183 -54
- package/.claude/hooks/bulk-activity-guard.cjs +283 -0
- package/.claude/hooks/context-watchdog.cjs +292 -0
- package/.claude/hooks/delegation-reminder.cjs +478 -0
- package/.claude/hooks/design-system-lint.cjs +283 -0
- package/.claude/hooks/post-scaffold-hook.cjs +16 -3
- package/.claude/hooks/prompt-guard.cjs +366 -0
- package/.claude/hooks/publish-template-guard.cjs +16 -0
- package/.claude/hooks/session-start.cjs +35 -0
- package/.claude/hooks/shared-memory-writer.cjs +147 -0
- package/.claude/hooks/skill-injector.cjs +140 -0
- package/.claude/hooks/skill-usage-logger.cjs +258 -0
- package/.claude/hooks/src-edit-guard.cjs +16 -1
- package/.claude/hooks/sync-marketplace-agents.cjs +53 -8
- package/.claude/scripts/yolo-toggle.cjs +142 -0
- package/.claude/settings.json +141 -14
- package/.claude/skills/SDK-activity-patterns/SKILL.md +428 -0
- package/.claude/skills/SDK-document-templates/SKILL.md +1033 -0
- package/.claude/skills/SDK-function-fields/SKILL.md +542 -0
- package/.claude/skills/SDK-generate-skill/SKILL.md +92 -0
- package/.claude/skills/SDK-init-skill/SKILL.md +127 -0
- package/.claude/skills/SDK-insight-queries/SKILL.md +787 -0
- package/.claude/skills/SDK-ws-config-skill/SKILL.md +1139 -0
- package/.claude/skills/agent-structure/SKILL.md +98 -0
- package/.claude/skills/api-documentation-patterns/SKILL.md +474 -0
- package/.claude/skills/chrome-mcp-reference/SKILL.md +370 -0
- package/.claude/skills/delegation-routing/SKILL.md +202 -0
- package/.claude/skills/frontend-design/SKILL.md +254 -0
- package/.claude/skills/hailer-activity-mover/SKILL.md +213 -0
- package/.claude/skills/hailer-api-client/SKILL.md +518 -0
- package/.claude/skills/hailer-app-builder/SKILL.md +939 -11
- package/.claude/skills/hailer-apps-pictures/SKILL.md +269 -0
- package/.claude/skills/hailer-design-system/SKILL.md +235 -0
- package/.claude/skills/hailer-monolith-automations/SKILL.md +686 -0
- package/.claude/skills/hailer-permissions-system/SKILL.md +121 -0
- package/.claude/skills/hailer-project-protocol/SKILL.md +488 -0
- package/.claude/skills/hailer-rest-api/SKILL.md +61 -0
- package/.claude/skills/hailer-rest-api/hailer-activities.md +184 -0
- package/.claude/skills/hailer-rest-api/hailer-admin.md +473 -0
- package/.claude/skills/hailer-rest-api/hailer-calendar.md +256 -0
- package/.claude/skills/hailer-rest-api/hailer-feed.md +249 -0
- package/.claude/skills/hailer-rest-api/hailer-insights.md +195 -0
- package/.claude/skills/hailer-rest-api/hailer-messaging.md +276 -0
- package/.claude/skills/hailer-rest-api/hailer-workflows.md +283 -0
- package/.claude/skills/insight-join-patterns/SKILL.md +3 -0
- package/.claude/skills/integration-patterns/SKILL.md +421 -0
- package/.claude/skills/json-only-output/SKILL.md +52 -12
- package/.claude/skills/lsp-setup/SKILL.md +160 -0
- package/.claude/skills/mcp-direct-tools/SKILL.md +153 -0
- package/.claude/skills/optional-parameters/SKILL.md +32 -23
- package/.claude/skills/publish-hailer-app/SKILL.md +76 -12
- package/.claude/skills/testing-patterns/SKILL.md +630 -0
- package/.claude/skills/tool-builder/SKILL.md +250 -0
- package/.claude/skills/tool-parameter-usage/SKILL.md +59 -45
- package/.claude/skills/tool-response-verification/SKILL.md +82 -48
- package/.claude/skills/zapier-hailer-patterns/SKILL.md +581 -0
- package/.env.example +26 -7
- package/CLAUDE.md +290 -224
- package/dist/CLAUDE.md +370 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.js +101 -101
- package/dist/bot/bot-config.d.ts +26 -0
- package/dist/bot/bot-config.js +135 -0
- package/dist/bot/bot-manager.d.ts +40 -0
- package/dist/bot/bot-manager.js +137 -0
- package/dist/bot/bot.d.ts +127 -0
- package/dist/bot/bot.js +1328 -0
- package/dist/bot/operation-logger.d.ts +28 -0
- package/dist/bot/operation-logger.js +132 -0
- package/dist/bot/services/conversation-manager.d.ts +60 -0
- package/dist/bot/services/conversation-manager.js +246 -0
- package/dist/bot/services/index.d.ts +9 -0
- package/dist/bot/services/index.js +18 -0
- package/dist/bot/services/message-classifier.d.ts +42 -0
- package/dist/bot/services/message-classifier.js +228 -0
- package/dist/bot/services/message-formatter.d.ts +88 -0
- package/dist/bot/services/message-formatter.js +411 -0
- package/dist/bot/services/session-logger.d.ts +162 -0
- package/dist/bot/services/session-logger.js +724 -0
- package/dist/bot/services/token-billing.d.ts +78 -0
- package/dist/bot/services/token-billing.js +233 -0
- package/dist/bot/services/types.d.ts +169 -0
- package/dist/bot/services/types.js +12 -0
- package/dist/bot/services/typing-indicator.d.ts +23 -0
- package/dist/bot/services/typing-indicator.js +60 -0
- package/dist/bot/services/workspace-schema-cache.d.ts +122 -0
- package/dist/bot/services/workspace-schema-cache.js +506 -0
- package/dist/bot/tool-executor.d.ts +28 -0
- package/dist/bot/tool-executor.js +48 -0
- package/dist/bot/workspace-overview.d.ts +12 -0
- package/dist/bot/workspace-overview.js +94 -0
- package/dist/cli.d.ts +1 -8
- package/dist/cli.js +1 -253
- package/dist/config.d.ts +96 -3
- package/dist/config.js +148 -37
- package/dist/core.d.ts +5 -0
- package/dist/core.js +61 -8
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/lib/logger.d.ts +0 -1
- package/dist/lib/logger.js +39 -23
- package/dist/lib/request-logger.d.ts +77 -0
- package/dist/lib/request-logger.js +147 -0
- package/dist/mcp/UserContextCache.js +16 -13
- package/dist/mcp/hailer-clients.js +18 -17
- package/dist/mcp/signal-handler.js +43 -13
- package/dist/mcp/tool-registry.d.ts +4 -15
- package/dist/mcp/tool-registry.js +94 -32
- package/dist/mcp/tools/activity.js +28 -69
- package/dist/mcp/tools/app-core.js +9 -4
- package/dist/mcp/tools/app-marketplace.js +22 -12
- package/dist/mcp/tools/app-member.js +5 -2
- package/dist/mcp/tools/app-scaffold.js +32 -18
- package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
- package/dist/mcp/tools/bot-config/constants.js +94 -0
- package/dist/mcp/tools/bot-config/core.d.ts +253 -0
- package/dist/mcp/tools/bot-config/core.js +2456 -0
- package/dist/mcp/tools/bot-config/index.d.ts +10 -0
- package/dist/mcp/tools/bot-config/index.js +59 -0
- package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
- package/dist/mcp/tools/bot-config/tools.js +15 -0
- package/dist/mcp/tools/bot-config/types.d.ts +50 -0
- package/dist/mcp/tools/bot-config/types.js +6 -0
- package/dist/mcp/tools/discussion.js +107 -77
- package/dist/mcp/tools/document.d.ts +11 -0
- package/dist/mcp/tools/document.js +741 -0
- package/dist/mcp/tools/file.js +5 -2
- package/dist/mcp/tools/insight.js +36 -12
- package/dist/mcp/tools/investigate.d.ts +9 -0
- package/dist/mcp/tools/investigate.js +254 -0
- package/dist/mcp/tools/user.d.ts +2 -4
- package/dist/mcp/tools/user.js +9 -50
- package/dist/mcp/tools/workflow.d.ts +1 -0
- package/dist/mcp/tools/workflow.js +164 -52
- package/dist/mcp/utils/hailer-api-client.js +26 -17
- package/dist/mcp/webhook-handler.d.ts +64 -3
- package/dist/mcp/webhook-handler.js +227 -9
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +237 -25
- package/dist/plugins/bug-fixer/index.d.ts +2 -0
- package/dist/plugins/bug-fixer/index.js +18 -0
- package/dist/plugins/bug-fixer/tools.d.ts +45 -0
- package/dist/plugins/bug-fixer/tools.js +1096 -0
- package/package.json +10 -10
- package/scripts/test-hal-tools.ts +154 -0
- package/.claude/agents/agent-nora-name-functions.md +0 -123
- package/.claude/assistant-knowledge.md +0 -23
- package/.claude/commands/install-plugin.md +0 -261
- package/.claude/commands/list-plugins.md +0 -42
- package/.claude/commands/marketplace-setup.md +0 -33
- package/.claude/commands/publish-plugin.md +0 -55
- package/.claude/commands/uninstall-plugin.md +0 -87
- package/.claude/hooks/interactive-mode.cjs +0 -87
- package/.claude/hooks/mcp-server-guard.cjs +0 -108
- package/.claude/skills/marketplace-publishing.md +0 -155
- package/dist/bot/chat-bot.d.ts +0 -31
- package/dist/bot/chat-bot.js +0 -357
- package/dist/mcp/tools/metrics.d.ts +0 -13
- package/dist/mcp/tools/metrics.js +0 -546
- package/dist/stdio-server.d.ts +0 -14
- 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
|
|
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 = '';
|