@hailer/mcp 1.1.12 → 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.
- package/CHANGELOG.md +0 -7
- package/{.claude → dist}/CLAUDE.md +2 -2
- package/dist/app.js +18 -5
- package/dist/bot/bot-config.d.ts +10 -1
- package/dist/bot/bot-config.js +64 -3
- package/dist/bot/bot-manager.d.ts +2 -0
- package/dist/bot/bot-manager.js +9 -2
- package/dist/bot/bot.d.ts +33 -0
- package/dist/bot/bot.js +461 -160
- package/dist/bot/services/message-classifier.js +17 -0
- package/dist/bot/services/permission-guard.d.ts +52 -0
- package/dist/bot/services/permission-guard.js +149 -0
- package/dist/bot/services/types.d.ts +5 -0
- package/dist/bot/services/typing-indicator.d.ts +6 -1
- package/dist/bot/services/typing-indicator.js +19 -3
- package/dist/cli.js +0 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.js +43 -0
- package/dist/core.js +3 -6
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/mcp/UserContextCache.d.ts +5 -0
- package/dist/mcp/UserContextCache.js +51 -19
- package/dist/mcp/hailer-clients.d.ts +19 -1
- package/dist/mcp/hailer-clients.js +158 -24
- package/dist/mcp/session-store.d.ts +68 -0
- package/dist/mcp/session-store.js +169 -0
- package/dist/mcp/signal-handler.js +2 -0
- package/dist/mcp/tool-registry.d.ts +17 -4
- package/dist/mcp/tool-registry.js +37 -7
- package/dist/mcp/tools/activity.js +99 -7
- package/dist/mcp/tools/app-scaffold.js +304 -336
- 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/bug-fixer-tools.d.ts +45 -0
- package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
- package/dist/mcp/tools/company.d.ts +9 -0
- package/dist/mcp/tools/company.js +88 -0
- package/dist/mcp/tools/discussion.js +68 -0
- package/dist/mcp/tools/document.d.ts +11 -0
- package/dist/mcp/tools/document.js +741 -0
- package/dist/mcp/tools/investigate.d.ts +9 -0
- package/dist/mcp/tools/investigate.js +254 -0
- package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
- package/dist/mcp/tools/workflow-permissions.js +204 -0
- package/dist/mcp/tools/workflow.js +57 -18
- package/dist/mcp/utils/index.d.ts +2 -0
- package/dist/mcp/utils/index.js +12 -1
- package/dist/mcp/utils/role-utils.d.ts +74 -0
- package/dist/mcp/utils/role-utils.js +151 -0
- package/dist/mcp/utils/types.d.ts +43 -1
- package/dist/mcp/utils/types.js +14 -0
- package/dist/mcp/webhook-handler.d.ts +4 -0
- package/dist/mcp/webhook-handler.js +8 -0
- package/dist/mcp-server.d.ts +23 -2
- package/dist/mcp-server.js +639 -127
- package/dist/plugins/vipunen/client.d.ts +150 -0
- package/dist/plugins/vipunen/client.js +535 -0
- package/dist/plugins/vipunen/config/schema-config.json +19 -0
- package/dist/plugins/vipunen/config/schema-doc.json +22 -0
- package/dist/plugins/vipunen/index.d.ts +41 -0
- package/dist/plugins/vipunen/index.js +88 -0
- package/dist/plugins/vipunen/tools.d.ts +26 -0
- package/dist/plugins/vipunen/tools.js +501 -0
- package/dist/stdio-server.d.ts +14 -0
- package/dist/stdio-server.js +101 -0
- package/package.json +2 -1
- package/.claude/agents/agent-ada-skill-builder.md +0 -94
- package/.claude/agents/agent-alejandro-function-fields.md +0 -342
- package/.claude/agents/agent-bjorn-config-audit.md +0 -103
- package/.claude/agents/agent-builder-agent-creator.md +0 -130
- package/.claude/agents/agent-code-simplifier.md +0 -53
- package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
- package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
- package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
- package/.claude/agents/agent-helga-workflow-config.md +0 -204
- package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
- package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
- package/.claude/agents/agent-ivan-monolith.md +0 -154
- package/.claude/agents/agent-kenji-data-reader.md +0 -86
- package/.claude/agents/agent-lars-code-inspector.md +0 -102
- package/.claude/agents/agent-marco-mockup-builder.md +0 -110
- package/.claude/agents/agent-marcus-api-documenter.md +0 -323
- package/.claude/agents/agent-marketplace-publisher.md +0 -280
- package/.claude/agents/agent-marketplace-reviewer.md +0 -309
- package/.claude/agents/agent-permissions-handler.md +0 -208
- package/.claude/agents/agent-simple-writer.md +0 -48
- package/.claude/agents/agent-svetlana-code-review.md +0 -171
- package/.claude/agents/agent-tanya-test-runner.md +0 -333
- package/.claude/agents/agent-ui-designer.md +0 -100
- package/.claude/agents/agent-viktor-sql-insights.md +0 -212
- package/.claude/agents/agent-web-search.md +0 -55
- package/.claude/agents/agent-yevgeni-discussions.md +0 -45
- package/.claude/agents/agent-zara-zapier.md +0 -159
- package/.claude/commands/app-squad.md +0 -135
- package/.claude/commands/audit-squad.md +0 -158
- package/.claude/commands/autoplan.md +0 -563
- package/.claude/commands/cleanup-squad.md +0 -98
- package/.claude/commands/config-squad.md +0 -106
- package/.claude/commands/crud-squad.md +0 -87
- package/.claude/commands/data-squad.md +0 -97
- package/.claude/commands/debug-squad.md +0 -303
- package/.claude/commands/doc-squad.md +0 -65
- package/.claude/commands/handoff.md +0 -137
- package/.claude/commands/health.md +0 -49
- package/.claude/commands/help.md +0 -29
- package/.claude/commands/help:agents.md +0 -151
- package/.claude/commands/help:commands.md +0 -78
- package/.claude/commands/help:faq.md +0 -79
- package/.claude/commands/help:plugins.md +0 -50
- package/.claude/commands/help:skills.md +0 -93
- package/.claude/commands/help:tools.md +0 -75
- package/.claude/commands/hotfix-squad.md +0 -112
- package/.claude/commands/integration-squad.md +0 -82
- package/.claude/commands/janitor-squad.md +0 -167
- package/.claude/commands/learn-auto.md +0 -120
- package/.claude/commands/learn.md +0 -120
- package/.claude/commands/mcp-list.md +0 -27
- package/.claude/commands/onboard-squad.md +0 -140
- package/.claude/commands/plan-workspace.md +0 -732
- package/.claude/commands/prd.md +0 -130
- package/.claude/commands/project-status.md +0 -82
- package/.claude/commands/publish.md +0 -138
- package/.claude/commands/recap.md +0 -69
- package/.claude/commands/restore.md +0 -64
- package/.claude/commands/review-squad.md +0 -152
- package/.claude/commands/save.md +0 -24
- package/.claude/commands/stats.md +0 -19
- package/.claude/commands/swarm.md +0 -210
- package/.claude/commands/tool-builder.md +0 -39
- package/.claude/commands/ws-pull.md +0 -44
- package/.claude/hooks/_shared-memory.cjs +0 -305
- package/.claude/hooks/_utils.cjs +0 -108
- package/.claude/hooks/agent-failure-detector.cjs +0 -383
- package/.claude/hooks/agent-usage-logger.cjs +0 -204
- package/.claude/hooks/app-edit-guard.cjs +0 -494
- package/.claude/hooks/auto-learn.cjs +0 -304
- package/.claude/hooks/bash-guard.cjs +0 -272
- package/.claude/hooks/builder-mode-manager.cjs +0 -354
- package/.claude/hooks/bulk-activity-guard.cjs +0 -271
- package/.claude/hooks/context-watchdog.cjs +0 -230
- package/.claude/hooks/delegation-reminder.cjs +0 -465
- package/.claude/hooks/design-system-lint.cjs +0 -271
- package/.claude/hooks/post-scaffold-hook.cjs +0 -181
- package/.claude/hooks/prompt-guard.cjs +0 -354
- package/.claude/hooks/publish-template-guard.cjs +0 -147
- package/.claude/hooks/session-start.cjs +0 -35
- package/.claude/hooks/shared-memory-writer.cjs +0 -147
- package/.claude/hooks/skill-injector.cjs +0 -140
- package/.claude/hooks/skill-usage-logger.cjs +0 -258
- package/.claude/hooks/src-edit-guard.cjs +0 -240
- package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
- package/.claude/settings.json +0 -257
- package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
- package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
- package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
- package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
- package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
- package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
- package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
- package/.claude/skills/agent-structure/SKILL.md +0 -98
- package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
- package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
- package/.claude/skills/delegation-routing/SKILL.md +0 -202
- package/.claude/skills/frontend-design/SKILL.md +0 -254
- package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
- package/.claude/skills/hailer-api-client/SKILL.md +0 -518
- package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
- package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
- package/.claude/skills/hailer-design-system/SKILL.md +0 -235
- package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
- package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
- package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
- package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
- package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
- package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
- package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
- package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
- package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
- package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
- package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
- package/.claude/skills/integration-patterns/SKILL.md +0 -421
- package/.claude/skills/json-only-output/SKILL.md +0 -72
- package/.claude/skills/lsp-setup/SKILL.md +0 -160
- package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
- package/.claude/skills/optional-parameters/SKILL.md +0 -72
- package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
- package/.claude/skills/testing-patterns/SKILL.md +0 -630
- package/.claude/skills/tool-builder/SKILL.md +0 -250
- package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
- package/.claude/skills/tool-response-verification/SKILL.md +0 -92
- package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
- package/.mcp.json +0 -13
- package/.opencode/agent/agent-ada-skill-builder.md +0 -35
- package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
- package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
- package/.opencode/agent/agent-builder-agent-creator.md +0 -39
- package/.opencode/agent/agent-code-simplifier.md +0 -31
- package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
- package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
- package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
- package/.opencode/agent/agent-helga-workflow-config.md +0 -203
- package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
- package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
- package/.opencode/agent/agent-ivan-monolith.md +0 -46
- package/.opencode/agent/agent-kenji-data-reader.md +0 -53
- package/.opencode/agent/agent-lars-code-inspector.md +0 -28
- package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
- package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
- package/.opencode/agent/agent-marketplace-publisher.md +0 -44
- package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
- package/.opencode/agent/agent-permissions-handler.md +0 -50
- package/.opencode/agent/agent-simple-writer.md +0 -45
- package/.opencode/agent/agent-svetlana-code-review.md +0 -39
- package/.opencode/agent/agent-tanya-test-runner.md +0 -57
- package/.opencode/agent/agent-ui-designer.md +0 -56
- package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
- package/.opencode/agent/agent-web-search.md +0 -42
- package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
- package/.opencode/agent/agent-zara-zapier.md +0 -53
- package/.opencode/commands/app-squad.md +0 -135
- package/.opencode/commands/audit-squad.md +0 -158
- package/.opencode/commands/autoplan.md +0 -563
- package/.opencode/commands/cleanup-squad.md +0 -98
- package/.opencode/commands/config-squad.md +0 -106
- package/.opencode/commands/crud-squad.md +0 -87
- package/.opencode/commands/data-squad.md +0 -97
- package/.opencode/commands/debug-squad.md +0 -303
- package/.opencode/commands/doc-squad.md +0 -65
- package/.opencode/commands/handoff.md +0 -137
- package/.opencode/commands/health.md +0 -49
- package/.opencode/commands/help-agents.md +0 -151
- package/.opencode/commands/help-commands.md +0 -32
- package/.opencode/commands/help-faq.md +0 -29
- package/.opencode/commands/help-plugins.md +0 -28
- package/.opencode/commands/help-skills.md +0 -7
- package/.opencode/commands/help-tools.md +0 -40
- package/.opencode/commands/help.md +0 -28
- package/.opencode/commands/hotfix-squad.md +0 -112
- package/.opencode/commands/integration-squad.md +0 -82
- package/.opencode/commands/janitor-squad.md +0 -167
- package/.opencode/commands/learn-auto.md +0 -120
- package/.opencode/commands/learn.md +0 -120
- package/.opencode/commands/mcp-list.md +0 -27
- package/.opencode/commands/onboard-squad.md +0 -140
- package/.opencode/commands/plan-workspace.md +0 -732
- package/.opencode/commands/prd.md +0 -131
- package/.opencode/commands/project-status.md +0 -82
- package/.opencode/commands/publish.md +0 -138
- package/.opencode/commands/recap.md +0 -69
- package/.opencode/commands/restore.md +0 -64
- package/.opencode/commands/review-squad.md +0 -152
- package/.opencode/commands/save.md +0 -24
- package/.opencode/commands/stats.md +0 -19
- package/.opencode/commands/swarm.md +0 -210
- package/.opencode/commands/tool-builder.md +0 -39
- package/.opencode/commands/ws-pull.md +0 -44
- package/.opencode/opencode.json +0 -28
- package/SESSION-HANDOFF.md +0 -68
- package/inbox/2026-03-04-bot-config-patterns.md +0 -24
- package/scripts/postinstall.cjs +0 -64
- 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
|
-
}
|