@triflux/core 10.49.0 → 10.50.0
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/hooks/agy-session-hook.mjs +1 -1
- package/hooks/claude-cwd-projection-refresh.mjs +7 -79
- package/hooks/codex-session-hook.mjs +3 -8
- package/hooks/hook-orchestrator.mjs +8 -648
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hub/bridge.mjs +290 -51
- package/hub/team/claude-daemon-control.mjs +54 -6
- package/hub/team/claude-transcript.mjs +240 -0
- package/hub/team/session-context.mjs +203 -0
- package/hud/constants.mjs +10 -0
- package/hud/hud-qos-status.mjs +23 -4
- package/hud/providers/gemini.mjs +4 -1
- package/hud/renderers.mjs +10 -6
- package/package.json +1 -1
- package/scripts/lib/doctor-env-checks.mjs +0 -85
- package/scripts/lib/legacy-hook-cleanup.mjs +230 -0
- package/scripts/lib/legacy-mcp-cleanup.mjs +772 -0
- package/scripts/lib/mcp-guard-engine.mjs +44 -271
- package/scripts/lib/mcp-manifest.mjs +2 -2
- package/scripts/lib/session-presence.mjs +249 -0
- package/hooks/agent-route-guard.mjs +0 -113
- package/hooks/cross-review-tracker.mjs +0 -135
- package/hooks/cto-north-star-brief.mjs +0 -133
- package/hooks/error-context.mjs +0 -185
- package/hooks/hook-adaptive-collector.mjs +0 -88
- package/hooks/hook-manager.mjs +0 -426
- package/hooks/hook-registry.json +0 -367
- package/hooks/hooks.json +0 -149
- package/hooks/keyword-rules.json +0 -676
- package/hooks/mcp-config-watcher.mjs +0 -84
- package/hooks/permission-safe-allow.mjs +0 -267
- package/hooks/pipeline-stop.mjs +0 -435
- package/hooks/post-tool-tips.mjs +0 -306
- package/hooks/pre-compact-snapshot.mjs +0 -181
- package/hooks/safety-guard.mjs +0 -466
- package/hooks/session-end-cleanup.mjs +0 -360
- package/hooks/session-start-fast.mjs +0 -579
- package/hooks/session-start-lake.mjs +0 -81
- package/hooks/subagent-tracker.mjs +0 -281
- package/hooks/subagent-verifier.mjs +0 -85
- package/scripts/lib/cross-review-utils.mjs +0 -59
- package/scripts/lib/hook-utils.mjs +0 -16
- package/scripts/lib/keyword-rules.mjs +0 -296
- package/scripts/lib/mcp-gateway-health-check.mjs +0 -224
- package/scripts/lib/mcp-gateway-servers.mjs +0 -70
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +0 -35
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// hooks/mcp-config-watcher.mjs — PostToolUse:Edit|Write 훅
|
|
3
|
-
//
|
|
4
|
-
// 감시 대상 MCP 설정 파일 변경을 감지해 stdio 서버를 즉시 차단/치환한다.
|
|
5
|
-
// 경로가 watched_paths와 매칭되지 않으면 바로 종료해 일반 편집 성능에 영향이 없도록 한다.
|
|
6
|
-
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
8
|
-
import {
|
|
9
|
-
isWatchedPath,
|
|
10
|
-
loadRegistryOrDefault,
|
|
11
|
-
remediate,
|
|
12
|
-
scanForStdioServers,
|
|
13
|
-
} from "../scripts/lib/mcp-guard-engine.mjs";
|
|
14
|
-
|
|
15
|
-
function readStdin() {
|
|
16
|
-
try {
|
|
17
|
-
return readFileSync(0, "utf8");
|
|
18
|
-
} catch {
|
|
19
|
-
return "";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function buildSystemMessage(filePath, stdioServers, result) {
|
|
24
|
-
const lines = [`[mcp-guard] 감시 대상 MCP 설정 변경 감지: ${filePath}`];
|
|
25
|
-
|
|
26
|
-
if (result.modified) {
|
|
27
|
-
const actionLabel = result.replacement ? "자동 치환" : "자동 제거";
|
|
28
|
-
lines.push(
|
|
29
|
-
`[mcp-guard] stdio MCP ${actionLabel}: ${stdioServers.map((server) => server.name).join(", ")}`,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
if (result.replacement?.name && result.replacement?.url) {
|
|
33
|
-
lines.push(
|
|
34
|
-
`[mcp-guard] 대체 서버: ${result.replacement.name} -> ${result.replacement.url}`,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (result.backupPath) {
|
|
39
|
-
lines.push(`[mcp-guard] 백업: ${result.backupPath}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for (const warning of result.warnings || []) {
|
|
44
|
-
lines.push(warning);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return lines.length > 0 ? lines.join("\n") : "";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function main() {
|
|
51
|
-
const raw = readStdin();
|
|
52
|
-
if (!raw.trim()) process.exit(0);
|
|
53
|
-
|
|
54
|
-
let input;
|
|
55
|
-
try {
|
|
56
|
-
input = JSON.parse(raw);
|
|
57
|
-
} catch {
|
|
58
|
-
process.exit(0);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const toolName = input.tool_name || "";
|
|
62
|
-
if (toolName !== "Edit" && toolName !== "Write") process.exit(0);
|
|
63
|
-
|
|
64
|
-
const filePath = input.tool_input?.file_path || "";
|
|
65
|
-
if (!filePath || !isWatchedPath(filePath)) process.exit(0);
|
|
66
|
-
|
|
67
|
-
const registry = loadRegistryOrDefault();
|
|
68
|
-
|
|
69
|
-
const stdioServers = scanForStdioServers(filePath);
|
|
70
|
-
if (stdioServers.length === 0) process.exit(0);
|
|
71
|
-
|
|
72
|
-
const result = remediate(filePath, stdioServers, registry.policies);
|
|
73
|
-
const systemMessage = buildSystemMessage(filePath, stdioServers, result);
|
|
74
|
-
|
|
75
|
-
if (systemMessage) {
|
|
76
|
-
process.stdout.write(JSON.stringify({ systemMessage }));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
main();
|
|
82
|
-
} catch {
|
|
83
|
-
process.exit(0);
|
|
84
|
-
}
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { stdin, stdout } from "node:process";
|
|
4
|
-
|
|
5
|
-
const META_PATTERNS = [/&&/, /\|\|/, /[;|<>`]/, /\$\(/];
|
|
6
|
-
const SHELL_EXPANSION_PATTERN =
|
|
7
|
-
/(?:^|[^\\])\$(?:[A-Za-z_][A-Za-z0-9_]*|\{[^}]*\}|[?*#@$!0-9-])|(?:^|\s)~[A-Za-z0-9_-]*(?=\/|$)/;
|
|
8
|
-
const RISKY_PATTERNS = [
|
|
9
|
-
/(^|\s)rm(\s|$)/,
|
|
10
|
-
/(^|\s)mv(\s|$)/,
|
|
11
|
-
/(^|\s)cp(\s|$)/,
|
|
12
|
-
/(^|\s)curl(\s|$)/,
|
|
13
|
-
/(^|\s)wget(\s|$)/,
|
|
14
|
-
/(^|\s)sudo(\s|$)/,
|
|
15
|
-
/(^|\s)chmod(\s|$)/,
|
|
16
|
-
/(^|\s)chown(\s|$)/,
|
|
17
|
-
/(^|\s)git\s+push(\s|$)/,
|
|
18
|
-
/(^|\s)git\s+reset(\s|$)/,
|
|
19
|
-
/(^|\s)git\s+clean(\s|$)/,
|
|
20
|
-
/(^|\s)npm\s+publish(\s|$)/,
|
|
21
|
-
/(^|\s)pnpm\s+publish(\s|$)/,
|
|
22
|
-
/(^|\s)yarn\s+publish(\s|$)/,
|
|
23
|
-
];
|
|
24
|
-
const SEARCH_EXTERNAL_INPUT_FLAGS = new Set([
|
|
25
|
-
"--exclude-from",
|
|
26
|
-
"--file",
|
|
27
|
-
"--files-from",
|
|
28
|
-
"--ignore-file",
|
|
29
|
-
"--pre",
|
|
30
|
-
"--pre-glob",
|
|
31
|
-
]);
|
|
32
|
-
const FIND_SIDE_EFFECT_OPERATORS = new Set([
|
|
33
|
-
"-delete",
|
|
34
|
-
"-exec",
|
|
35
|
-
"-execdir",
|
|
36
|
-
"-fprint",
|
|
37
|
-
"-fprint0",
|
|
38
|
-
"-fprintf",
|
|
39
|
-
"-fls",
|
|
40
|
-
"-ok",
|
|
41
|
-
"-okdir",
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
function silentlyExit() {
|
|
45
|
-
process.exit(0);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function readStdin() {
|
|
49
|
-
return new Promise((resolve) => {
|
|
50
|
-
let input = "";
|
|
51
|
-
stdin.setEncoding("utf8");
|
|
52
|
-
stdin.on("data", (chunk) => {
|
|
53
|
-
input += chunk;
|
|
54
|
-
});
|
|
55
|
-
stdin.on("end", () => resolve(input));
|
|
56
|
-
stdin.resume();
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function shellWords(command) {
|
|
61
|
-
const words = [];
|
|
62
|
-
const pattern = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
|
|
63
|
-
let match;
|
|
64
|
-
while ((match = pattern.exec(command)) !== null) {
|
|
65
|
-
words.push(match[1] ?? match[2] ?? match[3]);
|
|
66
|
-
}
|
|
67
|
-
return words;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function isRepoRelativePath(value) {
|
|
71
|
-
if (!value || value.startsWith("/") || value.startsWith("~")) return false;
|
|
72
|
-
if (value === ".") return true;
|
|
73
|
-
if (value === ".." || value.startsWith("../") || value.includes("/../"))
|
|
74
|
-
return false;
|
|
75
|
-
return /^[A-Za-z0-9._/*?@{}+,:=-][A-Za-z0-9._/*?'@{}+,:=-]*$/.test(value);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function hasUnsafeSurface(command) {
|
|
79
|
-
return (
|
|
80
|
-
META_PATTERNS.some((pattern) => pattern.test(command)) ||
|
|
81
|
-
SHELL_EXPANSION_PATTERN.test(command) ||
|
|
82
|
-
RISKY_PATTERNS.some((pattern) => pattern.test(command))
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function isExternalSearchInputFlag(word) {
|
|
87
|
-
return (
|
|
88
|
-
SEARCH_EXTERNAL_INPUT_FLAGS.has(word) ||
|
|
89
|
-
[...SEARCH_EXTERNAL_INPUT_FLAGS].some((flag) =>
|
|
90
|
-
word.startsWith(`${flag}=`),
|
|
91
|
-
) ||
|
|
92
|
-
/^-[^-]*f/.test(word)
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function allowGit(words, command) {
|
|
97
|
-
if (command === "git status" || command === "git status --short")
|
|
98
|
-
return "git status";
|
|
99
|
-
if (command === "git diff --stat" || command === "git diff --cached --stat")
|
|
100
|
-
return "git diff stat";
|
|
101
|
-
if (command === "git branch --show-current") return "git branch";
|
|
102
|
-
if (words[0] === "git" && words[1] === "log" && words[2] === "--oneline") {
|
|
103
|
-
const rest = words.slice(3);
|
|
104
|
-
if (
|
|
105
|
-
rest.every(
|
|
106
|
-
(word) =>
|
|
107
|
-
/^[A-Za-z0-9._/@{}~^:+,=-]+$/.test(word) && !word.startsWith("-"),
|
|
108
|
-
)
|
|
109
|
-
) {
|
|
110
|
-
return "git log";
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function allowListing(words) {
|
|
117
|
-
if (words[0] === "pwd" && words.length === 1) return "pwd";
|
|
118
|
-
|
|
119
|
-
if (words[0] === "ls") {
|
|
120
|
-
const args = words.slice(1);
|
|
121
|
-
if (
|
|
122
|
-
args.every(
|
|
123
|
-
(word) => /^-[A-Za-z0-9]+$/.test(word) || isRepoRelativePath(word),
|
|
124
|
-
)
|
|
125
|
-
) {
|
|
126
|
-
return "ls";
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (words[0] === "find") {
|
|
131
|
-
const args = words.slice(1);
|
|
132
|
-
if (args.some((word) => FIND_SIDE_EFFECT_OPERATORS.has(word))) return null;
|
|
133
|
-
const firstOption = args.findIndex((word) => word.startsWith("-"));
|
|
134
|
-
const pathArgs = (
|
|
135
|
-
firstOption === -1 ? args : args.slice(0, firstOption)
|
|
136
|
-
).filter(Boolean);
|
|
137
|
-
if (pathArgs.length === 0 || pathArgs.every(isRepoRelativePath)) {
|
|
138
|
-
return "find";
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function allowSearch(words) {
|
|
146
|
-
if (words[0] !== "rg" && words[0] !== "grep") return null;
|
|
147
|
-
const args = words.slice(1);
|
|
148
|
-
if (args.length === 0) return null;
|
|
149
|
-
if (
|
|
150
|
-
args.some(
|
|
151
|
-
(word) =>
|
|
152
|
-
isExternalSearchInputFlag(word) ||
|
|
153
|
-
word.startsWith("/") ||
|
|
154
|
-
word.startsWith("~") ||
|
|
155
|
-
word.startsWith("$") ||
|
|
156
|
-
word === ".." ||
|
|
157
|
-
word.startsWith("../") ||
|
|
158
|
-
word.includes("/../"),
|
|
159
|
-
)
|
|
160
|
-
) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
return `${words[0]} search`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function allowTargetedTest(words) {
|
|
167
|
-
if (words[0] !== "node" || words[1] !== "scripts/test-lock.mjs") return null;
|
|
168
|
-
if (!words.includes("--test")) return null;
|
|
169
|
-
const args = words.slice(2);
|
|
170
|
-
const allowedFlags = new Set(["--test", "--test-force-exit"]);
|
|
171
|
-
const pathArgs = [];
|
|
172
|
-
for (const word of args) {
|
|
173
|
-
if (allowedFlags.has(word)) continue;
|
|
174
|
-
if (/^--test-concurrency=\d+$/.test(word)) continue;
|
|
175
|
-
if (word.startsWith("-")) return null;
|
|
176
|
-
pathArgs.push(word);
|
|
177
|
-
}
|
|
178
|
-
if (pathArgs.length === 0) return null;
|
|
179
|
-
if (
|
|
180
|
-
pathArgs.every(
|
|
181
|
-
(word) =>
|
|
182
|
-
word.startsWith("tests/") || word.startsWith("scripts/__tests__/"),
|
|
183
|
-
) &&
|
|
184
|
-
pathArgs.every(isRepoRelativePath)
|
|
185
|
-
) {
|
|
186
|
-
return "targeted test";
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function allowNpm(command) {
|
|
192
|
-
// npm scripts are intentionally not auto-allowed: package.json can redefine
|
|
193
|
-
// them to perform arbitrary side effects. Prefer explicit node test commands.
|
|
194
|
-
void command;
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function allowanceReason(command) {
|
|
199
|
-
const trimmed = command.trim().replace(/\s+/g, " ");
|
|
200
|
-
if (!trimmed || hasUnsafeSurface(trimmed)) return null;
|
|
201
|
-
|
|
202
|
-
const words = shellWords(trimmed);
|
|
203
|
-
if (words.length === 0) return null;
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
allowGit(words, trimmed) ??
|
|
207
|
-
allowListing(words) ??
|
|
208
|
-
allowSearch(words) ??
|
|
209
|
-
allowTargetedTest(words) ??
|
|
210
|
-
allowNpm(trimmed)
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function hasAskSuggestion(value) {
|
|
215
|
-
if (!value || typeof value !== "object") return false;
|
|
216
|
-
if (Array.isArray(value)) return value.some(hasAskSuggestion);
|
|
217
|
-
if (value.behavior === "ask") return true;
|
|
218
|
-
if (value.decision?.behavior === "ask") return true;
|
|
219
|
-
return Object.values(value).some(hasAskSuggestion);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function emitAllow(reason) {
|
|
223
|
-
stdout.write(
|
|
224
|
-
JSON.stringify({
|
|
225
|
-
hookSpecificOutput: {
|
|
226
|
-
hookEventName: "PermissionRequest",
|
|
227
|
-
decision: { behavior: "allow" },
|
|
228
|
-
additionalContext: `[permission-safe-allow] allowed: ${reason}`,
|
|
229
|
-
},
|
|
230
|
-
}),
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const input = await readStdin();
|
|
235
|
-
let payload;
|
|
236
|
-
try {
|
|
237
|
-
payload = JSON.parse(input || "{}");
|
|
238
|
-
} catch {
|
|
239
|
-
silentlyExit();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
payload?.hook_event_name !== "PermissionRequest" ||
|
|
244
|
-
payload?.tool_name !== "Bash"
|
|
245
|
-
) {
|
|
246
|
-
silentlyExit();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
hasAskSuggestion(
|
|
251
|
-
payload.permission_suggestions ?? payload.permissionSuggestions,
|
|
252
|
-
)
|
|
253
|
-
) {
|
|
254
|
-
silentlyExit();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const command = payload?.tool_input?.command ?? payload?.command;
|
|
258
|
-
if (typeof command !== "string") {
|
|
259
|
-
silentlyExit();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const reason = allowanceReason(command);
|
|
263
|
-
if (!reason) {
|
|
264
|
-
silentlyExit();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
emitAllow(reason);
|