@stackmemoryai/stackmemory 0.3.21 → 0.3.22
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/cli/commands/linear-unified.js +2 -3
- package/dist/cli/commands/linear-unified.js.map +2 -2
- package/dist/cli/commands/tasks.js +1 -1
- package/dist/cli/commands/tasks.js.map +2 -2
- package/dist/integrations/mcp/handlers/code-execution-handlers.js +262 -0
- package/dist/integrations/mcp/handlers/code-execution-handlers.js.map +7 -0
- package/dist/integrations/mcp/tool-definitions-code.js +121 -0
- package/dist/integrations/mcp/tool-definitions-code.js.map +7 -0
- package/dist/servers/railway/index.js +98 -92
- package/dist/servers/railway/index.js.map +3 -3
- package/package.json +1 -2
- package/scripts/claude-sm-autostart.js +1 -1
- package/scripts/clean-linear-backlog.js +2 -2
- package/scripts/debug-linear-update.js +1 -1
- package/scripts/debug-railway-build.js +87 -0
- package/scripts/delete-linear-tasks.js +2 -2
- package/scripts/install-code-execution-hooks.sh +96 -0
- package/scripts/linear-task-review.js +1 -1
- package/scripts/sync-and-clean-tasks.js +1 -1
- package/scripts/sync-linear-graphql.js +3 -3
- package/scripts/sync-linear-tasks.js +1 -1
- package/scripts/test-code-execution.js +143 -0
- package/scripts/update-linear-tasks-fixed.js +1 -1
- package/scripts/validate-railway-deployment.js +137 -0
- package/templates/claude-hooks/hook-config.json +59 -0
- package/templates/claude-hooks/pre-tool-use +189 -0
- package/dist/servers/railway/minimal.js +0 -91
- package/dist/servers/railway/minimal.js.map +0 -7
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-Tool-Use Hook for StackMemory
|
|
5
|
+
* Controls and filters tool usage based on configured policies
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
// Configuration for tool restrictions
|
|
16
|
+
const CONFIG = {
|
|
17
|
+
// Mode: 'restrictive' | 'permissive' | 'code_only'
|
|
18
|
+
mode: process.env.STACKMEMORY_TOOL_MODE || 'permissive',
|
|
19
|
+
|
|
20
|
+
// Tools that are always allowed
|
|
21
|
+
alwaysAllowed: [
|
|
22
|
+
'mcp__stackmemory__save_context',
|
|
23
|
+
'mcp__stackmemory__load_context',
|
|
24
|
+
'TodoWrite',
|
|
25
|
+
'TodoRead',
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
// Tools that are blocked in restrictive mode
|
|
29
|
+
restrictedTools: [
|
|
30
|
+
'Bash',
|
|
31
|
+
'Write',
|
|
32
|
+
'Edit',
|
|
33
|
+
'Delete',
|
|
34
|
+
'WebFetch',
|
|
35
|
+
],
|
|
36
|
+
|
|
37
|
+
// Code execution tools
|
|
38
|
+
codeExecutionTools: [
|
|
39
|
+
'mcp__code-executor__execute_code',
|
|
40
|
+
'mcp__stackmemory__code.execute',
|
|
41
|
+
],
|
|
42
|
+
|
|
43
|
+
// Log file for audit
|
|
44
|
+
logFile: path.join(process.env.HOME || '/tmp', '.stackmemory', 'tool-use.log'),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Read input from stdin
|
|
48
|
+
async function readInput() {
|
|
49
|
+
let input = '';
|
|
50
|
+
for await (const chunk of process.stdin) {
|
|
51
|
+
input += chunk;
|
|
52
|
+
}
|
|
53
|
+
return JSON.parse(input);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Log tool usage
|
|
57
|
+
function logToolUse(toolName, allowed, reason) {
|
|
58
|
+
try {
|
|
59
|
+
const logDir = path.dirname(CONFIG.logFile);
|
|
60
|
+
if (!fs.existsSync(logDir)) {
|
|
61
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const logEntry = {
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
tool: toolName,
|
|
67
|
+
allowed,
|
|
68
|
+
reason,
|
|
69
|
+
mode: CONFIG.mode,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
fs.appendFileSync(CONFIG.logFile, JSON.stringify(logEntry) + '\n');
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Silently fail logging
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if tool should be allowed
|
|
79
|
+
function shouldAllowTool(toolName, params) {
|
|
80
|
+
// Always allowed tools
|
|
81
|
+
if (CONFIG.alwaysAllowed.includes(toolName)) {
|
|
82
|
+
return { allowed: true, reason: 'Always allowed tool' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Mode-specific logic
|
|
86
|
+
switch (CONFIG.mode) {
|
|
87
|
+
case 'code_only':
|
|
88
|
+
// Only allow code execution tools
|
|
89
|
+
if (CONFIG.codeExecutionTools.includes(toolName)) {
|
|
90
|
+
return { allowed: true, reason: 'Code execution tool in code_only mode' };
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
allowed: false,
|
|
94
|
+
reason: 'Only code execution is allowed in code_only mode',
|
|
95
|
+
suggestion: 'Use mcp__stackmemory__code.execute to run Python/JavaScript code',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
case 'restrictive':
|
|
99
|
+
// Block restricted tools
|
|
100
|
+
if (CONFIG.restrictedTools.includes(toolName)) {
|
|
101
|
+
return {
|
|
102
|
+
allowed: false,
|
|
103
|
+
reason: `Tool '${toolName}' is restricted in current mode`,
|
|
104
|
+
suggestion: 'This tool is blocked for safety. Consider using code execution instead.',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { allowed: true, reason: 'Not in restricted list' };
|
|
108
|
+
|
|
109
|
+
case 'permissive':
|
|
110
|
+
default:
|
|
111
|
+
// Allow everything but log dangerous operations
|
|
112
|
+
if (CONFIG.restrictedTools.includes(toolName)) {
|
|
113
|
+
logToolUse(toolName, true, 'Potentially dangerous tool in permissive mode');
|
|
114
|
+
}
|
|
115
|
+
return { allowed: true, reason: 'Permissive mode' };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Main hook logic
|
|
120
|
+
async function main() {
|
|
121
|
+
try {
|
|
122
|
+
const input = await readInput();
|
|
123
|
+
const { tool_name, parameters } = input;
|
|
124
|
+
|
|
125
|
+
// Check if tool should be allowed
|
|
126
|
+
const decision = shouldAllowTool(tool_name, parameters);
|
|
127
|
+
|
|
128
|
+
// Log the decision
|
|
129
|
+
logToolUse(tool_name, decision.allowed, decision.reason);
|
|
130
|
+
|
|
131
|
+
if (!decision.allowed) {
|
|
132
|
+
// Block the tool
|
|
133
|
+
console.error(JSON.stringify({
|
|
134
|
+
status: 'blocked',
|
|
135
|
+
tool: tool_name,
|
|
136
|
+
reason: decision.reason,
|
|
137
|
+
suggestion: decision.suggestion,
|
|
138
|
+
message: `Tool '${tool_name}' is blocked. ${decision.suggestion || ''}`,
|
|
139
|
+
}));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Special handling for code execution
|
|
144
|
+
if (CONFIG.codeExecutionTools.includes(tool_name)) {
|
|
145
|
+
// Add safety warnings
|
|
146
|
+
if (parameters.code && parameters.code.includes('import os')) {
|
|
147
|
+
console.error(JSON.stringify({
|
|
148
|
+
status: 'warning',
|
|
149
|
+
message: 'Code contains potentially dangerous imports. Reviewing...',
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Allow the tool
|
|
155
|
+
console.log(JSON.stringify({
|
|
156
|
+
status: 'allowed',
|
|
157
|
+
tool: tool_name,
|
|
158
|
+
mode: CONFIG.mode,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
// On error, allow by default (fail open)
|
|
163
|
+
console.error(JSON.stringify({
|
|
164
|
+
status: 'error',
|
|
165
|
+
message: error.message,
|
|
166
|
+
fallback: 'allowing tool due to hook error',
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Handle environment info request
|
|
172
|
+
if (process.argv.includes('--info')) {
|
|
173
|
+
console.log(JSON.stringify({
|
|
174
|
+
hook: 'pre-tool-use',
|
|
175
|
+
version: '1.0.0',
|
|
176
|
+
mode: CONFIG.mode,
|
|
177
|
+
modes: ['permissive', 'restrictive', 'code_only'],
|
|
178
|
+
description: 'Controls tool usage based on configured policies',
|
|
179
|
+
}));
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
main().catch(error => {
|
|
184
|
+
console.error(JSON.stringify({
|
|
185
|
+
status: 'error',
|
|
186
|
+
message: error.message,
|
|
187
|
+
}));
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import http from "http";
|
|
3
|
-
const PORT = process.env.PORT || 3e3;
|
|
4
|
-
const server = http.createServer(async (req, res) => {
|
|
5
|
-
console.log(`${(/* @__PURE__ */ new Date()).toISOString()} ${req.method} ${req.url}`);
|
|
6
|
-
if (req.url === "/health" || req.url === "/api/health") {
|
|
7
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
8
|
-
res.end(
|
|
9
|
-
JSON.stringify({
|
|
10
|
-
status: "healthy",
|
|
11
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12
|
-
port: PORT,
|
|
13
|
-
env: process.env.NODE_ENV || "development"
|
|
14
|
-
})
|
|
15
|
-
);
|
|
16
|
-
} else if (req.url === "/test-db") {
|
|
17
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
18
|
-
const testResults = { postgresql: {}, redis: {} };
|
|
19
|
-
if (process.env.DATABASE_URL) {
|
|
20
|
-
try {
|
|
21
|
-
const { Client } = (await import("pg")).default;
|
|
22
|
-
const pgClient = new Client({ connectionString: process.env.DATABASE_URL });
|
|
23
|
-
await pgClient.connect();
|
|
24
|
-
const result = await pgClient.query("SELECT NOW() as time, version() as version");
|
|
25
|
-
testResults.postgresql = {
|
|
26
|
-
status: "connected",
|
|
27
|
-
time: result.rows[0].time,
|
|
28
|
-
version: result.rows[0].version.split(" ")[0]
|
|
29
|
-
};
|
|
30
|
-
await pgClient.end();
|
|
31
|
-
} catch (error) {
|
|
32
|
-
testResults.postgresql = { status: "error", message: error.message };
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
testResults.postgresql = { status: "not_configured" };
|
|
36
|
-
}
|
|
37
|
-
const redisUrl = process.env.REDIS_URL || (process.env.REDISHOST ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT || 6379}` : null);
|
|
38
|
-
if (redisUrl) {
|
|
39
|
-
try {
|
|
40
|
-
const { createClient } = await import("redis");
|
|
41
|
-
const redisClient = createClient({ url: redisUrl });
|
|
42
|
-
await redisClient.connect();
|
|
43
|
-
await redisClient.ping();
|
|
44
|
-
const info = await redisClient.info("server");
|
|
45
|
-
const version = info.match(/redis_version:(.+)/)?.[1];
|
|
46
|
-
testResults.redis = {
|
|
47
|
-
status: "connected",
|
|
48
|
-
version,
|
|
49
|
-
url: redisUrl.replace(/:\/\/[^@]+@/, "://***:***@")
|
|
50
|
-
// Hide credentials
|
|
51
|
-
};
|
|
52
|
-
await redisClient.disconnect();
|
|
53
|
-
} catch (error) {
|
|
54
|
-
testResults.redis = { status: "error", message: error.message };
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
testResults.redis = { status: "not_configured" };
|
|
58
|
-
}
|
|
59
|
-
res.end(JSON.stringify(testResults, null, 2));
|
|
60
|
-
} else if (req.url === "/") {
|
|
61
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
62
|
-
res.end(
|
|
63
|
-
JSON.stringify({
|
|
64
|
-
message: "StackMemory Minimal Server Running",
|
|
65
|
-
version: "1.0.0",
|
|
66
|
-
endpoints: ["/health", "/api/health", "/test-db"]
|
|
67
|
-
})
|
|
68
|
-
);
|
|
69
|
-
} else {
|
|
70
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
71
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
server.listen(PORT, "0.0.0.0", () => {
|
|
75
|
-
console.log(`
|
|
76
|
-
=================================
|
|
77
|
-
Minimal Server Started
|
|
78
|
-
Port: ${PORT}
|
|
79
|
-
Time: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
80
|
-
=================================
|
|
81
|
-
`);
|
|
82
|
-
});
|
|
83
|
-
process.on("SIGTERM", () => {
|
|
84
|
-
console.log("SIGTERM received");
|
|
85
|
-
server.close(() => process.exit(0));
|
|
86
|
-
});
|
|
87
|
-
process.on("SIGINT", () => {
|
|
88
|
-
console.log("SIGINT received");
|
|
89
|
-
server.close(() => process.exit(0));
|
|
90
|
-
});
|
|
91
|
-
//# sourceMappingURL=minimal.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/servers/railway/minimal.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n/**\n * Minimal Railway Server - Absolute minimum for testing\n */\n\nimport http from 'http';\n\nconst PORT = process.env.PORT || 3000;\n\nconst server = http.createServer(async (req, res) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);\n\n if (req.url === '/health' || req.url === '/api/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n status: 'healthy',\n timestamp: new Date().toISOString(),\n port: PORT,\n env: process.env.NODE_ENV || 'development',\n })\n );\n } else if (req.url === '/test-db') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n \n // Test database connections\n const testResults = { postgresql: {}, redis: {} };\n \n // Test PostgreSQL\n if (process.env.DATABASE_URL) {\n try {\n const { Client } = (await import('pg')).default;\n const pgClient = new Client({ connectionString: process.env.DATABASE_URL });\n await pgClient.connect();\n \n const result = await pgClient.query('SELECT NOW() as time, version() as version');\n testResults.postgresql = {\n status: 'connected',\n time: result.rows[0].time,\n version: result.rows[0].version.split(' ')[0]\n };\n \n await pgClient.end();\n } catch (error) {\n testResults.postgresql = { status: 'error', message: error.message };\n }\n } else {\n testResults.postgresql = { status: 'not_configured' };\n }\n \n // Test Redis\n const redisUrl = process.env.REDIS_URL || \n (process.env.REDISHOST ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT || 6379}` : null);\n \n if (redisUrl) {\n try {\n const { createClient } = await import('redis');\n const redisClient = createClient({ url: redisUrl });\n await redisClient.connect();\n \n await redisClient.ping();\n const info = await redisClient.info('server');\n const version = info.match(/redis_version:(.+)/)?.[1];\n \n testResults.redis = {\n status: 'connected',\n version: version,\n url: redisUrl.replace(/:\\/\\/[^@]+@/, '://***:***@') // Hide credentials\n };\n \n await redisClient.disconnect();\n } catch (error) {\n testResults.redis = { status: 'error', message: error.message };\n }\n } else {\n testResults.redis = { status: 'not_configured' };\n }\n \n res.end(JSON.stringify(testResults, null, 2));\n } else if (req.url === '/') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n message: 'StackMemory Minimal Server Running',\n version: '1.0.0',\n endpoints: ['/health', '/api/health', '/test-db']\n })\n );\n } else {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n }\n});\n\nserver.listen(PORT, '0.0.0.0', () => {\n console.log(`\n=================================\nMinimal Server Started\nPort: ${PORT}\nTime: ${new Date().toISOString()}\n=================================\n `);\n});\n\n// Keep alive\nprocess.on('SIGTERM', () => {\n console.log('SIGTERM received');\n server.close(() => process.exit(0));\n});\n\nprocess.on('SIGINT', () => {\n console.log('SIGINT received');\n server.close(() => process.exit(0));\n});\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|