@hailer/mcp 1.1.11 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +18 -5
- package/dist/bot/bot-config.d.ts +12 -1
- package/dist/bot/bot-config.js +98 -14
- package/dist/bot/bot-manager.d.ts +13 -3
- package/dist/bot/bot-manager.js +80 -25
- package/dist/bot/bot.d.ts +46 -0
- package/dist/bot/bot.js +542 -166
- 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/config.d.ts +6 -1
- package/dist/config.js +43 -0
- package/dist/core.js +3 -6
- 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 +157 -20
- package/dist/mcp/session-store.d.ts +68 -0
- package/dist/mcp/session-store.js +169 -0
- package/dist/mcp/signal-handler.js +12 -12
- 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/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/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 +6 -0
- package/dist/mcp/webhook-handler.js +11 -0
- package/dist/mcp-server.d.ts +23 -2
- package/dist/mcp-server.js +639 -111
- 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/package.json +2 -1
- package/.claude/.context-watchdog.json +0 -1
- package/.claude/.session-checked +0 -1
- package/.claude/CLAUDE.md +0 -370
- 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/.hailer-mcp-port +0 -1
- 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 -204
- 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 -21
- package/inbox/failures.log +0 -1
- package/inbox/usage.jsonl +0 -4
- package/scripts/postinstall.cjs +0 -64
- package/scripts/test-hal-tools.ts +0 -154
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* <hook-name>auto-learn</hook-name>
|
|
4
|
-
*
|
|
5
|
-
* <purpose>
|
|
6
|
-
* Detects potential learnings from agent responses and prompts user to confirm.
|
|
7
|
-
* Only appends to LEARNINGS.md after user approval.
|
|
8
|
-
* Complements manual /learn command.
|
|
9
|
-
* </purpose>
|
|
10
|
-
*
|
|
11
|
-
* <triggers>
|
|
12
|
-
* - PostToolUse on Task (agent completions)
|
|
13
|
-
* </triggers>
|
|
14
|
-
*
|
|
15
|
-
* <detection-patterns>
|
|
16
|
-
* - "learned that", "discovered that", "found that"
|
|
17
|
-
* - "note:", "important:", "gotcha:", "tip:"
|
|
18
|
-
* - "always", "never", "must", "should" (in context of rules)
|
|
19
|
-
* - "the trick is", "the key is", "turns out"
|
|
20
|
-
* </detection-patterns>
|
|
21
|
-
*
|
|
22
|
-
* <behavior>
|
|
23
|
-
* 1. Scan tool response for learning patterns
|
|
24
|
-
* 2. If found, output prompt for Claude to use AskUserQuestion
|
|
25
|
-
* 3. User confirms which learnings to save
|
|
26
|
-
* 4. Claude runs --save command to append to LEARNINGS.md
|
|
27
|
-
* </behavior>
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const path = require('path');
|
|
32
|
-
const os = require('os');
|
|
33
|
-
|
|
34
|
-
const LEARNINGS_FILE = path.join(
|
|
35
|
-
process.env.HAILER_INBOX_DIR || path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), 'inbox'),
|
|
36
|
-
'learnings.md'
|
|
37
|
-
);
|
|
38
|
-
const PENDING_FILE = path.join(os.tmpdir(), '.claude-pending-learnings.json');
|
|
39
|
-
|
|
40
|
-
// Patterns that indicate a learning/insight
|
|
41
|
-
const LEARNING_PATTERNS = [
|
|
42
|
-
/(?:i |we |claude )(?:learned|discovered|found|realized) that (.{20,150})/i,
|
|
43
|
-
/(?:note|important|gotcha|tip|caveat|warning):\s*(.{20,150})/i,
|
|
44
|
-
/(?:the (?:trick|key|solution|fix) (?:is|was)) (.{20,150})/i,
|
|
45
|
-
/(?:turns out|it turns out) (.{20,150})/i,
|
|
46
|
-
/(?:remember to|don't forget to|make sure to) (.{20,100})/i,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
// Category detection based on content
|
|
50
|
-
const CATEGORY_PATTERNS = [
|
|
51
|
-
{ pattern: /agent|kenji|dmitri|giuseppe|helga|alejandro|viktor/i, category: 'agent' },
|
|
52
|
-
{ pattern: /skill|pattern|template/i, category: 'skill' },
|
|
53
|
-
{ pattern: /workflow|field|phase|activity/i, category: 'workflow' },
|
|
54
|
-
{ pattern: /bug|error|fix|broken/i, category: 'bug' },
|
|
55
|
-
{ pattern: /hook|guard|check/i, category: 'hook' },
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
// Read hook input from stdin
|
|
59
|
-
let input = '';
|
|
60
|
-
process.stdin.setEncoding('utf8');
|
|
61
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
62
|
-
process.stdin.on('end', () => {
|
|
63
|
-
try {
|
|
64
|
-
const data = JSON.parse(input);
|
|
65
|
-
processHook(data);
|
|
66
|
-
} catch (e) {
|
|
67
|
-
console.error(`[auto-learn] Warning: ${e.message}`);
|
|
68
|
-
process.exit(0);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
function detectCategory(text) {
|
|
73
|
-
for (const { pattern, category } of CATEGORY_PATTERNS) {
|
|
74
|
-
if (pattern.test(text)) {
|
|
75
|
-
return category;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return 'pattern';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function extractLearnings(text) {
|
|
82
|
-
const learnings = [];
|
|
83
|
-
|
|
84
|
-
// Limit input size to prevent ReDoS attacks (100KB max)
|
|
85
|
-
const MAX_INPUT_SIZE = 100000;
|
|
86
|
-
if (text.length > MAX_INPUT_SIZE) {
|
|
87
|
-
text = text.substring(0, MAX_INPUT_SIZE);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Normalize whitespace for multi-line matching
|
|
91
|
-
const normalizedText = text.replace(/\s+/g, ' ');
|
|
92
|
-
|
|
93
|
-
// NOTE: Uses global match then per-result extraction. Acceptable for typical agent output sizes (<100KB).
|
|
94
|
-
// For larger inputs, consider exec() loop instead.
|
|
95
|
-
const matchStartTime = Date.now();
|
|
96
|
-
for (const pattern of LEARNING_PATTERNS) {
|
|
97
|
-
if (Date.now() - matchStartTime > 1000) break; // ReDoS timeout protection
|
|
98
|
-
// Use 'gis' flags for case-insensitive, dotall (multi-line) matching
|
|
99
|
-
const matches = normalizedText.match(new RegExp(pattern.source, 'gis'));
|
|
100
|
-
if (matches) {
|
|
101
|
-
for (const match of matches) {
|
|
102
|
-
const extracted = match.match(new RegExp(pattern.source, 'is'));
|
|
103
|
-
if (extracted && extracted[1]) {
|
|
104
|
-
let learning = extracted[1].trim();
|
|
105
|
-
learning = learning.replace(/\s+/g, ' ').replace(/[.!,;]$/, '').trim();
|
|
106
|
-
if (learning.length >= 20 && !learnings.some(l => l.text === learning)) {
|
|
107
|
-
learnings.push({
|
|
108
|
-
text: learning,
|
|
109
|
-
category: detectCategory(learning)
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return learnings;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function processHook(data) {
|
|
121
|
-
const { tool_name, tool_input, tool_response } = data;
|
|
122
|
-
|
|
123
|
-
// Only process Task completions
|
|
124
|
-
if (tool_name !== 'Task') {
|
|
125
|
-
process.exit(0);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (tool_response === undefined) {
|
|
129
|
-
process.exit(0);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const agentName = tool_input?.subagent_type;
|
|
133
|
-
const responseText = typeof tool_response === 'string'
|
|
134
|
-
? tool_response
|
|
135
|
-
: JSON.stringify(tool_response || '');
|
|
136
|
-
|
|
137
|
-
const learnings = extractLearnings(responseText);
|
|
138
|
-
|
|
139
|
-
if (learnings.length > 0) {
|
|
140
|
-
// Save pending learnings for later confirmation
|
|
141
|
-
fs.writeFileSync(PENDING_FILE, JSON.stringify({
|
|
142
|
-
learnings,
|
|
143
|
-
agentName,
|
|
144
|
-
timestamp: new Date().toISOString()
|
|
145
|
-
}));
|
|
146
|
-
|
|
147
|
-
// Build options for AskUserQuestion (max 4)
|
|
148
|
-
const options = learnings.slice(0, 3).map((l, i) => ({
|
|
149
|
-
label: `Save #${i + 1}`,
|
|
150
|
-
description: l.text.substring(0, 50) + (l.text.length > 50 ? '...' : '')
|
|
151
|
-
}));
|
|
152
|
-
options.push({ label: 'Skip all', description: 'Don\'t save any of these' });
|
|
153
|
-
|
|
154
|
-
const output = `
|
|
155
|
-
<user-prompt-submit-hook>
|
|
156
|
-
📚 POTENTIAL LEARNING${learnings.length > 1 ? 'S' : ''} DETECTED
|
|
157
|
-
|
|
158
|
-
${learnings.map((l, i) => `${i + 1}. [${l.category}] ${l.text}`).join('\n')}
|
|
159
|
-
|
|
160
|
-
👉 Ask user: "Save to inbox/learnings.md?" Options: ${options.map(o => o.label).join(' / ')}
|
|
161
|
-
|
|
162
|
-
If yes, run: node .claude/hooks/auto-learn.cjs --save <indices>
|
|
163
|
-
</user-prompt-submit-hook>
|
|
164
|
-
`;
|
|
165
|
-
|
|
166
|
-
console.log(output);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
process.exit(0);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// CLI: Save specific learnings by index
|
|
173
|
-
if (process.argv[2] === '--save') {
|
|
174
|
-
const indices = process.argv.slice(3)
|
|
175
|
-
.map(n => parseInt(n, 10))
|
|
176
|
-
.filter(n => !isNaN(n))
|
|
177
|
-
.map(n => n - 1);
|
|
178
|
-
|
|
179
|
-
if (!fs.existsSync(PENDING_FILE)) {
|
|
180
|
-
console.error('No pending learnings found');
|
|
181
|
-
process.exit(1);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const pending = JSON.parse(fs.readFileSync(PENDING_FILE, 'utf8'));
|
|
185
|
-
const toSave = indices
|
|
186
|
-
.filter(i => i >= 0 && i < pending.learnings.length)
|
|
187
|
-
.map(i => pending.learnings[i]);
|
|
188
|
-
|
|
189
|
-
if (toSave.length === 0) {
|
|
190
|
-
console.log('No valid learnings selected');
|
|
191
|
-
process.exit(0);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Ensure LEARNINGS.md exists (recursive: true is idempotent, no TOCTOU race)
|
|
195
|
-
if (!fs.existsSync(LEARNINGS_FILE)) {
|
|
196
|
-
const dir = path.dirname(LEARNINGS_FILE);
|
|
197
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
198
|
-
fs.writeFileSync(LEARNINGS_FILE, `# Inbox
|
|
199
|
-
|
|
200
|
-
Learnings captured from all projects. Review and integrate into marketplace agents/skills.
|
|
201
|
-
|
|
202
|
-
## Pending
|
|
203
|
-
<!-- /learn and auto-learn add items here -->
|
|
204
|
-
|
|
205
|
-
## Applied
|
|
206
|
-
<!-- Move items here after integrating into agents/skills -->
|
|
207
|
-
`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
let content = fs.readFileSync(LEARNINGS_FILE, 'utf8');
|
|
211
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
212
|
-
|
|
213
|
-
const entries = toSave.map(l => {
|
|
214
|
-
const source = pending.agentName ? `from ${pending.agentName}` : '';
|
|
215
|
-
const autoTag = '🤖 auto-learn';
|
|
216
|
-
return `- [${l.category}] ${l.text} _(${autoTag}${source ? `, ${source}` : ''}, ${timestamp})_`;
|
|
217
|
-
}).join('\n');
|
|
218
|
-
|
|
219
|
-
// Add under Pending section
|
|
220
|
-
const pendingMatch = content.match(/^(## Pending\s*\n(?:<!--[^>]*-->\s*\n)?)/m);
|
|
221
|
-
if (pendingMatch) {
|
|
222
|
-
const insertPoint = content.indexOf(pendingMatch[0]) + pendingMatch[0].length;
|
|
223
|
-
content = content.slice(0, insertPoint) + entries + '\n' + content.slice(insertPoint);
|
|
224
|
-
} else {
|
|
225
|
-
// Fallback: append to end if no Pending section found
|
|
226
|
-
content += `\n## Pending\n${entries}\n`;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Atomic write: temp file + rename to prevent corruption
|
|
230
|
-
const tmpFile = LEARNINGS_FILE + '.tmp-' + process.pid;
|
|
231
|
-
fs.writeFileSync(tmpFile, content);
|
|
232
|
-
fs.renameSync(tmpFile, LEARNINGS_FILE);
|
|
233
|
-
|
|
234
|
-
// Safe unlink: ignore ENOENT if file already deleted
|
|
235
|
-
try {
|
|
236
|
-
fs.unlinkSync(PENDING_FILE);
|
|
237
|
-
} catch (err) {
|
|
238
|
-
if (err.code !== 'ENOENT') throw err;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
console.log(`✅ Saved ${toSave.length} learning(s) to inbox/learnings.md`);
|
|
242
|
-
process.exit(0);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// CLI: Clear pending
|
|
246
|
-
if (process.argv[2] === '--clear') {
|
|
247
|
-
if (fs.existsSync(PENDING_FILE)) {
|
|
248
|
-
fs.unlinkSync(PENDING_FILE);
|
|
249
|
-
console.log('✅ Cleared pending learnings');
|
|
250
|
-
} else {
|
|
251
|
-
console.log('No pending learnings');
|
|
252
|
-
}
|
|
253
|
-
process.exit(0);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// CLI: Show pending
|
|
257
|
-
if (process.argv[2] === '--pending') {
|
|
258
|
-
if (!fs.existsSync(PENDING_FILE)) {
|
|
259
|
-
console.log('No pending learnings');
|
|
260
|
-
process.exit(0);
|
|
261
|
-
}
|
|
262
|
-
const pending = JSON.parse(fs.readFileSync(PENDING_FILE, 'utf8'));
|
|
263
|
-
console.log('Pending learnings:');
|
|
264
|
-
pending.learnings.forEach((l, i) => {
|
|
265
|
-
console.log(` ${i + 1}. [${l.category}] ${l.text}`);
|
|
266
|
-
});
|
|
267
|
-
process.exit(0);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// CLI: Test
|
|
271
|
-
if (process.argv[2] === '--test' && process.argv[3]) {
|
|
272
|
-
const text = process.argv.slice(3).join(' ');
|
|
273
|
-
const learnings = extractLearnings(text);
|
|
274
|
-
if (learnings.length === 0) {
|
|
275
|
-
console.log('No learnings detected');
|
|
276
|
-
} else {
|
|
277
|
-
console.log('Detected:');
|
|
278
|
-
learnings.forEach(l => console.log(` [${l.category}] ${l.text}`));
|
|
279
|
-
}
|
|
280
|
-
process.exit(0);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// CLI: Help
|
|
284
|
-
if (process.argv[2] === '--help' || process.argv[2] === '-h') {
|
|
285
|
-
console.log(`
|
|
286
|
-
Auto-Learn Hook - Detects learnings and prompts for confirmation
|
|
287
|
-
|
|
288
|
-
Usage:
|
|
289
|
-
node auto-learn.cjs --save 1 2 3 Save learnings by index
|
|
290
|
-
node auto-learn.cjs --pending Show pending learnings
|
|
291
|
-
node auto-learn.cjs --clear Clear pending learnings
|
|
292
|
-
node auto-learn.cjs --test <text> Test detection on sample text
|
|
293
|
-
node auto-learn.cjs --help Show this help
|
|
294
|
-
|
|
295
|
-
Workflow:
|
|
296
|
-
1. Hook detects learning in agent response
|
|
297
|
-
2. Prompts Claude to ask user for confirmation
|
|
298
|
-
3. User selects which to save
|
|
299
|
-
4. Claude runs --save with indices
|
|
300
|
-
|
|
301
|
-
Complements manual /learn command.
|
|
302
|
-
`);
|
|
303
|
-
process.exit(0);
|
|
304
|
-
}
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* <hook-name>bash-guard</hook-name>
|
|
4
|
-
*
|
|
5
|
-
* <purpose>
|
|
6
|
-
* Consolidated Bash PreToolUse guard. Replaces 4 separate hooks:
|
|
7
|
-
* - destructive-command-guard (blocks dangerous commands)
|
|
8
|
-
* - mcp-server-guard (blocks server start commands)
|
|
9
|
-
* - workspace-pull-guard (warns before pull with dirty workspace)
|
|
10
|
-
* - workspace-auto-save (auto-commits before push)
|
|
11
|
-
*
|
|
12
|
-
* Single Node process instead of 4 = faster, no timeouts.
|
|
13
|
-
* </purpose>
|
|
14
|
-
*
|
|
15
|
-
* <triggers>PreToolUse on Bash</triggers>
|
|
16
|
-
* @version 1.0.0
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const { spawnSync } = require('child_process');
|
|
20
|
-
const fs = require('fs');
|
|
21
|
-
const path = require('path');
|
|
22
|
-
const os = require('os');
|
|
23
|
-
|
|
24
|
-
const ALLOW = JSON.stringify({ decision: 'allow' });
|
|
25
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
26
|
-
|
|
27
|
-
// --- CLI modes ---
|
|
28
|
-
if (process.argv[2] === '--bypass' && process.argv[3]) {
|
|
29
|
-
const command = process.argv[3];
|
|
30
|
-
const bypass = { command, createdAt: Date.now(), expiresAt: Date.now() + 120000 };
|
|
31
|
-
const BYPASS_FILE = path.join(os.tmpdir(), '.claude-destructive-bypass.json');
|
|
32
|
-
const tmpFile = BYPASS_FILE + '.tmp-' + process.pid;
|
|
33
|
-
fs.writeFileSync(tmpFile, JSON.stringify(bypass, null, 2));
|
|
34
|
-
fs.renameSync(tmpFile, BYPASS_FILE);
|
|
35
|
-
console.log(`Bypass created for: ${command}`);
|
|
36
|
-
console.log('Expires in 2 minutes. Retry the command now.');
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
if (process.argv[2] === '--history') {
|
|
40
|
-
try {
|
|
41
|
-
const result = spawnSync('git', ['log', '--oneline', '--grep=Auto-save workspace', '-20'], {
|
|
42
|
-
cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
43
|
-
});
|
|
44
|
-
console.log((result.stdout || '').trim() || 'No auto-saves found');
|
|
45
|
-
} catch { console.log('Could not read git log'); }
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
48
|
-
if (process.argv[2] === '--help' || process.argv[2] === '-h') {
|
|
49
|
-
console.log(`
|
|
50
|
-
Bash Guard - Consolidated Bash PreToolUse hook
|
|
51
|
-
|
|
52
|
-
Combines: destructive-command-guard, mcp-server-guard, workspace-pull-guard, workspace-auto-save
|
|
53
|
-
|
|
54
|
-
Usage:
|
|
55
|
-
node bash-guard.cjs --bypass '<cmd>' Create one-time bypass for blocked command
|
|
56
|
-
node bash-guard.cjs --history Show recent workspace auto-saves
|
|
57
|
-
node bash-guard.cjs --help Show this help
|
|
58
|
-
`);
|
|
59
|
-
process.exit(0);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// --- Read stdin ---
|
|
63
|
-
let input = '';
|
|
64
|
-
process.stdin.setEncoding('utf8');
|
|
65
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
66
|
-
process.stdin.on('end', () => {
|
|
67
|
-
try {
|
|
68
|
-
const data = JSON.parse(input || '{}');
|
|
69
|
-
processHook(data);
|
|
70
|
-
} catch {
|
|
71
|
-
allow();
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
function allow(msg) {
|
|
76
|
-
if (msg) console.error('[bash-guard] ' + msg);
|
|
77
|
-
console.log(ALLOW);
|
|
78
|
-
process.exit(0);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function block(reason) {
|
|
82
|
-
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
83
|
-
process.exit(0);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function processHook(data) {
|
|
87
|
-
const { tool_name, tool_input } = data;
|
|
88
|
-
if (tool_name !== 'Bash') { allow(); return; }
|
|
89
|
-
|
|
90
|
-
const command = tool_input?.command || '';
|
|
91
|
-
if (!command) { allow(); return; }
|
|
92
|
-
|
|
93
|
-
// Run checks in order of specificity
|
|
94
|
-
checkDestructive(command);
|
|
95
|
-
checkMcpServer(command);
|
|
96
|
-
checkWorkspacePull(command);
|
|
97
|
-
checkWorkspaceAutoSave(command);
|
|
98
|
-
allow();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ============================================================
|
|
102
|
-
// 1. Destructive Command Guard
|
|
103
|
-
// ============================================================
|
|
104
|
-
const DANGEROUS_PATTERNS = [
|
|
105
|
-
{ pattern: /git\s+reset\s+--hard/i, name: 'git reset --hard', risk: 'Permanently discards all uncommitted changes', alternative: 'git stash or git reset --soft' },
|
|
106
|
-
{ pattern: /git\s+clean\s+-[fd]/i, name: 'git clean -f/-fd', risk: 'Permanently deletes untracked files', alternative: 'git clean -n (dry run)' },
|
|
107
|
-
{ pattern: /\brm\s+-[a-zA-Z]{0,10}(rf|fr)[a-zA-Z]{0,10}\b/i, name: 'rm -rf', risk: 'Recursively deletes files without confirmation', alternative: 'rm -ri (interactive) or move to trash' },
|
|
108
|
-
{ pattern: /git\s+push\s+[^|]*--force(?!-with-lease)[^|]*(main|master)/i, name: 'git push --force to main/master', risk: 'Overwrites remote history', alternative: 'git push --force-with-lease' },
|
|
109
|
-
{ pattern: /git\s+push\s+[^|]*(main|master)[^|]*--force(?!-with-lease)/i, name: 'git push --force to main/master', risk: 'Overwrites remote history', alternative: 'git push --force-with-lease' },
|
|
110
|
-
{ pattern: /git\s+checkout\s+\.\s*$/i, name: 'git checkout .', risk: 'Discards all uncommitted changes', alternative: 'git stash or git diff' },
|
|
111
|
-
{ pattern: /git\s+restore\s+\.\s*$/i, name: 'git restore .', risk: 'Discards all uncommitted changes', alternative: 'git stash or git diff' },
|
|
112
|
-
{ pattern: /git\s+branch\s+-D/, name: 'git branch -D', risk: 'Force-deletes branch even if not merged', alternative: 'git branch -d (safe delete)' }
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
const BYPASS_FILE = path.join(os.tmpdir(), '.claude-destructive-bypass.json');
|
|
116
|
-
|
|
117
|
-
function isSubagentActive() {
|
|
118
|
-
try {
|
|
119
|
-
const stackFile = path.join(os.tmpdir(), '.claude-agent-stack.json');
|
|
120
|
-
if (fs.existsSync(stackFile)) {
|
|
121
|
-
const stack = JSON.parse(fs.readFileSync(stackFile, 'utf8'));
|
|
122
|
-
return Array.isArray(stack.agents) && stack.agents.length > 0;
|
|
123
|
-
}
|
|
124
|
-
} catch {}
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function checkBypass(command) {
|
|
129
|
-
try {
|
|
130
|
-
const bypass = JSON.parse(fs.readFileSync(BYPASS_FILE, 'utf8'));
|
|
131
|
-
if (typeof bypass.expiresAt !== 'number' || typeof bypass.command !== 'string') {
|
|
132
|
-
try { fs.unlinkSync(BYPASS_FILE); } catch {}
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
if (bypass.expiresAt <= Date.now()) {
|
|
136
|
-
try { fs.unlinkSync(BYPASS_FILE); } catch {}
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
if (bypass.command === command) {
|
|
140
|
-
try { fs.unlinkSync(BYPASS_FILE); } catch {}
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
} catch {}
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function checkDestructive(command) {
|
|
148
|
-
if (isSubagentActive()) return;
|
|
149
|
-
if (command.includes('bash-guard') && command.includes('--bypass')) return;
|
|
150
|
-
if (checkBypass(command)) return;
|
|
151
|
-
|
|
152
|
-
for (const { pattern, name, risk, alternative } of DANGEROUS_PATTERNS) {
|
|
153
|
-
if (pattern.test(command)) {
|
|
154
|
-
block(`BLOCKED: Destructive Command Detected
|
|
155
|
-
|
|
156
|
-
**Command:** \`${name}\`
|
|
157
|
-
**Risk:** ${risk}
|
|
158
|
-
|
|
159
|
-
**USE THESE INSTEAD (no bypass needed):**
|
|
160
|
-
- Delete files individually: \`rm file1 file2\` (no -rf flag)
|
|
161
|
-
- Delete directory contents: \`find /path -mindepth 1 -delete\`
|
|
162
|
-
- Move to trash: \`mv /path ~/.Trash/\`
|
|
163
|
-
- Safe alternative: ${alternative}
|
|
164
|
-
|
|
165
|
-
**ONLY if user explicitly requests the destructive command:**
|
|
166
|
-
1. Ask user with AskUserQuestion
|
|
167
|
-
2. Run: Bash: node "${process.argv[1].replace(/'/g, "'\\''")}" --bypass '${command.replace(/'/g, "'\\''")}'
|
|
168
|
-
3. Retry the original command`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ============================================================
|
|
174
|
-
// 2. MCP Server Guard
|
|
175
|
-
// ============================================================
|
|
176
|
-
const SERVER_START_PATTERNS = [
|
|
177
|
-
/npm run dev\b/, /npm run start\b/, /npm start\b/,
|
|
178
|
-
/tsx\s+.*src\/app\.ts/, /tsx\s+watch\s+.*src\/app\.ts/,
|
|
179
|
-
/node\s+.*src\/app\.ts/, /node\s+.*dist\/app\.js/,
|
|
180
|
-
/npx\s+tsx\s+.*src\/app/
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
function checkMcpServer(command) {
|
|
184
|
-
if (!SERVER_START_PATTERNS.some(p => p.test(command))) return;
|
|
185
|
-
|
|
186
|
-
block(`MCP server commands are blocked. Tell the user to run manually:
|
|
187
|
-
cd ${projectDir} && npm run dev`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ============================================================
|
|
191
|
-
// 3. Workspace Pull Guard
|
|
192
|
-
// ============================================================
|
|
193
|
-
function checkWorkspacePull(command) {
|
|
194
|
-
if (!command.match(/npm\s+run\s+pull/)) return;
|
|
195
|
-
if (process.env.CLAUDE_AGENT_ID || process.env.CLAUDE_SUBAGENT) return;
|
|
196
|
-
|
|
197
|
-
const workspaceDir = path.join(projectDir, 'workspace');
|
|
198
|
-
if (!fs.existsSync(workspaceDir)) { invalidateSharedMemory(); return; }
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
const result = spawnSync('git', ['status', '--porcelain', 'workspace/'], {
|
|
202
|
-
cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
203
|
-
});
|
|
204
|
-
const status = (result.stdout || '').trim();
|
|
205
|
-
|
|
206
|
-
if (status) {
|
|
207
|
-
const changedFiles = status.split('\n').map(l => l.trim()).filter(Boolean);
|
|
208
|
-
block(`BLOCKED: Uncommitted changes in workspace/
|
|
209
|
-
|
|
210
|
-
\`npm run pull\` will OVERWRITE your local TypeScript files!
|
|
211
|
-
|
|
212
|
-
**Changed files:**
|
|
213
|
-
${changedFiles.map(f => ' ' + f).join('\n')}
|
|
214
|
-
|
|
215
|
-
Options:
|
|
216
|
-
1. Push first: npm run push → npm run pull
|
|
217
|
-
2. Save first: /save "WIP before pull" → npm run pull
|
|
218
|
-
3. Discard: git checkout -- workspace/ → npm run pull`);
|
|
219
|
-
}
|
|
220
|
-
invalidateSharedMemory();
|
|
221
|
-
} catch {
|
|
222
|
-
invalidateSharedMemory();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ============================================================
|
|
227
|
-
// 4. Workspace Auto-Save
|
|
228
|
-
// ============================================================
|
|
229
|
-
const PUSH_PATTERNS = [
|
|
230
|
-
/npm\s+run\s+.*push/, /npm\s+run\s+.*sync/,
|
|
231
|
-
/workflows-sync/, /fields-push/, /phases-push/,
|
|
232
|
-
/insights-push/, /templates-sync/
|
|
233
|
-
];
|
|
234
|
-
|
|
235
|
-
function checkWorkspaceAutoSave(command) {
|
|
236
|
-
if (!PUSH_PATTERNS.some(p => p.test(command))) return;
|
|
237
|
-
|
|
238
|
-
const workspaceDir = path.join(projectDir, 'workspace');
|
|
239
|
-
if (!fs.existsSync(workspaceDir)) return;
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
const result = spawnSync('git', ['status', '--porcelain', 'workspace/'], {
|
|
243
|
-
cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
244
|
-
});
|
|
245
|
-
const status = (result.stdout || '').trim();
|
|
246
|
-
if (!status) { console.error('[bash-guard] workspace/ clean, no auto-save needed'); return; }
|
|
247
|
-
|
|
248
|
-
const changes = status.split('\n').filter(Boolean).length;
|
|
249
|
-
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ').replace(/[:.]/g, '-');
|
|
250
|
-
spawnSync('git', ['add', 'workspace/'], { cwd: projectDir, encoding: 'utf8' });
|
|
251
|
-
const commitResult = spawnSync('git', ['commit', '-m', `Auto-save workspace before push (${timestamp})`], {
|
|
252
|
-
cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
253
|
-
});
|
|
254
|
-
if (commitResult.status !== 0 && !commitResult.stderr?.includes('nothing to commit')) {
|
|
255
|
-
throw new Error(commitResult.stderr || 'git commit failed');
|
|
256
|
-
}
|
|
257
|
-
console.error('[bash-guard] Auto-saved ' + changes + ' workspace change(s) before push');
|
|
258
|
-
} catch (err) {
|
|
259
|
-
console.error('[bash-guard] Could not auto-save: ' + err.message);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// ============================================================
|
|
264
|
-
// Shared memory invalidation (after pull)
|
|
265
|
-
// ============================================================
|
|
266
|
-
function invalidateSharedMemory() {
|
|
267
|
-
try {
|
|
268
|
-
const mem = require('./_shared-memory.cjs');
|
|
269
|
-
mem.invalidate();
|
|
270
|
-
console.error('[bash-guard] Shared memory invalidated (workspace pull).');
|
|
271
|
-
} catch {}
|
|
272
|
-
}
|