@triflux/core 10.49.0 → 10.50.1

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.
Files changed (46) hide show
  1. package/hooks/agy-session-hook.mjs +1 -1
  2. package/hooks/claude-cwd-projection-refresh.mjs +7 -79
  3. package/hooks/codex-session-hook.mjs +3 -8
  4. package/hooks/hook-orchestrator.mjs +8 -648
  5. package/hooks/lib/resolve-root.mjs +1 -1
  6. package/hub/bridge.mjs +290 -51
  7. package/hub/team/claude-daemon-control.mjs +54 -6
  8. package/hub/team/claude-transcript.mjs +240 -0
  9. package/hub/team/session-context.mjs +203 -0
  10. package/hud/constants.mjs +10 -0
  11. package/hud/hud-qos-status.mjs +23 -4
  12. package/hud/providers/gemini.mjs +4 -1
  13. package/hud/renderers.mjs +10 -6
  14. package/package.json +1 -1
  15. package/scripts/lib/doctor-env-checks.mjs +0 -85
  16. package/scripts/lib/legacy-hook-cleanup.mjs +230 -0
  17. package/scripts/lib/legacy-mcp-cleanup.mjs +772 -0
  18. package/scripts/lib/mcp-guard-engine.mjs +44 -271
  19. package/scripts/lib/mcp-manifest.mjs +2 -2
  20. package/scripts/lib/session-presence.mjs +249 -0
  21. package/hooks/agent-route-guard.mjs +0 -113
  22. package/hooks/cross-review-tracker.mjs +0 -135
  23. package/hooks/cto-north-star-brief.mjs +0 -133
  24. package/hooks/error-context.mjs +0 -185
  25. package/hooks/hook-adaptive-collector.mjs +0 -88
  26. package/hooks/hook-manager.mjs +0 -426
  27. package/hooks/hook-registry.json +0 -367
  28. package/hooks/hooks.json +0 -149
  29. package/hooks/keyword-rules.json +0 -676
  30. package/hooks/mcp-config-watcher.mjs +0 -84
  31. package/hooks/permission-safe-allow.mjs +0 -267
  32. package/hooks/pipeline-stop.mjs +0 -435
  33. package/hooks/post-tool-tips.mjs +0 -306
  34. package/hooks/pre-compact-snapshot.mjs +0 -181
  35. package/hooks/safety-guard.mjs +0 -466
  36. package/hooks/session-end-cleanup.mjs +0 -360
  37. package/hooks/session-start-fast.mjs +0 -579
  38. package/hooks/session-start-lake.mjs +0 -81
  39. package/hooks/subagent-tracker.mjs +0 -281
  40. package/hooks/subagent-verifier.mjs +0 -85
  41. package/scripts/lib/cross-review-utils.mjs +0 -59
  42. package/scripts/lib/hook-utils.mjs +0 -16
  43. package/scripts/lib/keyword-rules.mjs +0 -296
  44. package/scripts/lib/mcp-gateway-health-check.mjs +0 -224
  45. package/scripts/lib/mcp-gateway-servers.mjs +0 -70
  46. package/scripts/lib/mcp-gateway-wrapper-check.mjs +0 -35
@@ -1,281 +0,0 @@
1
- #!/usr/bin/env node
2
- // hooks/subagent-tracker.mjs — SubagentStart/SubagentStop lifecycle tracker
3
- //
4
- // Maintains a small repo-local JSON store of running and recently completed
5
- // subagents so paired lifecycle hooks can add bounded context without blocking.
6
-
7
- import {
8
- mkdirSync,
9
- readFileSync,
10
- renameSync,
11
- rmSync,
12
- writeFileSync,
13
- } from "node:fs";
14
- import { dirname, join } from "node:path";
15
-
16
- const STATE_VERSION = 1;
17
- const DEFAULT_TTL_MS = 2 * 60 * 60 * 1000;
18
- const MAX_COMPLETED = 20;
19
- // Lifecycle hooks are best-effort, but under CI/Linux process contention a
20
- // 30-way SubagentStart burst can legitimately take around a second to drain.
21
- // Keep this bounded so hooks do not hang indefinitely, while avoiding silent
22
- // lifecycle drops during normal high-concurrency fan-out.
23
- const DEFAULT_LOCK_TIMEOUT_MS = 3000;
24
- const LOCK_RETRY_MS = 20;
25
-
26
- function readStdin() {
27
- try {
28
- return readFileSync(0, "utf8");
29
- } catch {
30
- return "";
31
- }
32
- }
33
-
34
- function isObject(value) {
35
- return value !== null && typeof value === "object" && !Array.isArray(value);
36
- }
37
-
38
- function stateDirFromEnv(env = process.env, cwd = process.cwd()) {
39
- return (
40
- env.TRIFLUX_SUBAGENT_STATE_DIR ||
41
- join(env.CLAUDE_CWD || cwd, ".triflux", "subagents")
42
- );
43
- }
44
-
45
- function emptyState(now = new Date()) {
46
- return {
47
- version: STATE_VERSION,
48
- updatedAt: now.toISOString(),
49
- agents: {},
50
- completed: [],
51
- };
52
- }
53
-
54
- function normalizeState(value, now) {
55
- const state = isObject(value) ? value : {};
56
- return {
57
- version: STATE_VERSION,
58
- updatedAt:
59
- typeof state.updatedAt === "string" ? state.updatedAt : now.toISOString(),
60
- agents: isObject(state.agents) ? state.agents : {},
61
- completed: Array.isArray(state.completed) ? state.completed : [],
62
- };
63
- }
64
-
65
- function readState(path, now) {
66
- try {
67
- return normalizeState(JSON.parse(readFileSync(path, "utf8")), now);
68
- } catch {
69
- return emptyState(now);
70
- }
71
- }
72
-
73
- function writeState(path, state) {
74
- mkdirSync(dirname(path), { recursive: true });
75
- const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
76
- writeFileSync(tmpPath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
77
- renameSync(tmpPath, path);
78
- }
79
-
80
- function sleepSync(ms) {
81
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
82
- }
83
-
84
- function withStateLock(statePath, fn, timeoutMs = DEFAULT_LOCK_TIMEOUT_MS) {
85
- const lockDir = `${statePath}.lock`;
86
- const started = Date.now();
87
- while (true) {
88
- try {
89
- mkdirSync(lockDir, { recursive: false });
90
- try {
91
- return fn();
92
- } finally {
93
- rmSync(lockDir, { recursive: true, force: true });
94
- }
95
- } catch (error) {
96
- if (error?.code !== "EEXIST") throw error;
97
- if (Date.now() - started >= timeoutMs) return undefined;
98
- sleepSync(LOCK_RETRY_MS);
99
- }
100
- }
101
- }
102
-
103
- function lockTimeoutMsFromOptions(opts = {}) {
104
- if (Number.isFinite(opts.lockTimeoutMs)) return opts.lockTimeoutMs;
105
- const raw = opts.env?.TRIFLUX_SUBAGENT_LOCK_TIMEOUT_MS;
106
- if (raw === undefined || raw === "") return DEFAULT_LOCK_TIMEOUT_MS;
107
- const parsed = Number(raw);
108
- return Number.isFinite(parsed) && parsed > 0
109
- ? Math.trunc(parsed)
110
- : DEFAULT_LOCK_TIMEOUT_MS;
111
- }
112
-
113
- function lifecycleKey(input) {
114
- if (typeof input.agent_id === "string" && input.agent_id.trim()) {
115
- return input.agent_id;
116
- }
117
- if (typeof input.subagent_id === "string" && input.subagent_id.trim()) {
118
- return input.subagent_id;
119
- }
120
- const sessionId =
121
- typeof input.session_id === "string" && input.session_id.trim()
122
- ? input.session_id
123
- : "unknown";
124
- const agentType =
125
- (typeof input.agent_type === "string" && input.agent_type.trim()) ||
126
- (typeof input.subagent_type === "string" && input.subagent_type.trim()) ||
127
- "unknown";
128
- return `${sessionId}:${agentType}`;
129
- }
130
-
131
- function agentType(input, existing) {
132
- return (
133
- (typeof input.agent_type === "string" && input.agent_type.trim()) ||
134
- (typeof input.subagent_type === "string" && input.subagent_type.trim()) ||
135
- existing?.agentType ||
136
- "unknown"
137
- );
138
- }
139
-
140
- function expectedDeliverables(input) {
141
- if (input.expectedDeliverables !== undefined)
142
- return input.expectedDeliverables;
143
- if (input.expected_deliverables !== undefined)
144
- return input.expected_deliverables;
145
- if (input.expected_deliverable !== undefined)
146
- return input.expected_deliverable;
147
- return undefined;
148
- }
149
-
150
- function cleanupStaleAgents(state, nowMs, ttlMs) {
151
- for (const [key, agent] of Object.entries(state.agents)) {
152
- const startedMs = Date.parse(agent?.startedAt || "");
153
- if (!Number.isFinite(startedMs) || nowMs - startedMs > ttlMs) {
154
- delete state.agents[key];
155
- }
156
- }
157
- }
158
-
159
- function boundedCompleted(completed) {
160
- return completed.slice(-MAX_COMPLETED);
161
- }
162
-
163
- function completionSummary({ key, input, running, now, durationMs }) {
164
- const summary = {
165
- key,
166
- stoppedAt: now.toISOString(),
167
- sessionId:
168
- (typeof input.session_id === "string" && input.session_id.trim()) ||
169
- running?.sessionId ||
170
- undefined,
171
- agentType: agentType(input, running),
172
- durationMs,
173
- };
174
- if (running?.startedAt) summary.startedAt = running.startedAt;
175
- if (running?.expectedDeliverables !== undefined) {
176
- summary.expectedDeliverables = running.expectedDeliverables;
177
- }
178
- return Object.fromEntries(
179
- Object.entries(summary).filter(([, value]) => value !== undefined),
180
- );
181
- }
182
-
183
- function stopOutput({ summary, runningCount, completedCount }) {
184
- const message =
185
- `[subagent-tracker] ${summary.agentType} ` +
186
- `duration=${summary.durationMs}ms ` +
187
- `running=${runningCount} completed=${completedCount}`;
188
- return { systemMessage: message };
189
- }
190
-
191
- export function recordLifecycle(input, opts = {}) {
192
- if (!isObject(input)) return undefined;
193
- const eventName = input.hook_event_name;
194
- if (eventName !== "SubagentStart" && eventName !== "SubagentStop") {
195
- return undefined;
196
- }
197
-
198
- const now = opts.now instanceof Date ? opts.now : new Date();
199
- const nowMs = now.getTime();
200
- const ttlMs = Number.isFinite(opts.ttlMs) ? opts.ttlMs : DEFAULT_TTL_MS;
201
- const stateDir = opts.stateDir || stateDirFromEnv(opts.env, opts.cwd);
202
- const statePath = join(stateDir, "subagents.json");
203
- mkdirSync(stateDir, { recursive: true });
204
- return withStateLock(
205
- statePath,
206
- () => {
207
- const state = readState(statePath, now);
208
-
209
- cleanupStaleAgents(state, nowMs, ttlMs);
210
-
211
- const key = lifecycleKey(input);
212
- let output;
213
-
214
- if (eventName === "SubagentStart") {
215
- const existing = state.agents[key];
216
- const deliverables = expectedDeliverables(input);
217
- state.agents[key] = {
218
- key,
219
- startedAt: existing?.startedAt || now.toISOString(),
220
- sessionId:
221
- (typeof input.session_id === "string" && input.session_id.trim()) ||
222
- existing?.sessionId ||
223
- undefined,
224
- agentType: agentType(input, existing),
225
- ...(deliverables !== undefined
226
- ? { expectedDeliverables: deliverables }
227
- : existing?.expectedDeliverables !== undefined
228
- ? { expectedDeliverables: existing.expectedDeliverables }
229
- : {}),
230
- };
231
- } else {
232
- const running = state.agents[key];
233
- if (running) delete state.agents[key];
234
- const startedMs = Date.parse(running?.startedAt || "");
235
- const durationMs = Number.isFinite(startedMs)
236
- ? Math.max(0, nowMs - startedMs)
237
- : 0;
238
- const summary = completionSummary({
239
- key,
240
- input,
241
- running,
242
- now,
243
- durationMs,
244
- });
245
- state.completed = boundedCompleted([...state.completed, summary]);
246
- output = stopOutput({
247
- summary,
248
- runningCount: Object.keys(state.agents).length,
249
- completedCount: state.completed.length,
250
- });
251
- }
252
-
253
- state.updatedAt = now.toISOString();
254
- state.completed = boundedCompleted(state.completed);
255
- writeState(statePath, state);
256
- return output;
257
- },
258
- lockTimeoutMsFromOptions(opts),
259
- );
260
- }
261
-
262
- function main() {
263
- const raw = readStdin();
264
- if (!raw.trim()) return;
265
-
266
- let input;
267
- try {
268
- input = JSON.parse(raw);
269
- } catch {
270
- return;
271
- }
272
-
273
- const output = recordLifecycle(input);
274
- if (output) process.stdout.write(JSON.stringify(output));
275
- }
276
-
277
- try {
278
- main();
279
- } catch {
280
- process.exit(0);
281
- }
@@ -1,85 +0,0 @@
1
- #!/usr/bin/env node
2
- // hooks/subagent-verifier.mjs — SubagentStop 훅
3
- //
4
- // 서브에이전트 완료 시 결과 품질을 체크한다:
5
- // - 빈 결과 감지 → 재시도 제안
6
- // - 에러 종료 감지 → 원인 분석 컨텍스트 주입
7
- // - 과도한 토큰 사용 감지 → 효율성 알림
8
-
9
- import { readFileSync } from "node:fs";
10
-
11
- function readStdin() {
12
- try {
13
- return readFileSync(0, "utf8");
14
- } catch {
15
- return "";
16
- }
17
- }
18
-
19
- function main() {
20
- const raw = readStdin();
21
- if (!raw.trim()) process.exit(0);
22
-
23
- let input;
24
- try {
25
- input = JSON.parse(raw);
26
- } catch {
27
- process.exit(0);
28
- }
29
-
30
- if (input.hook_event_name && input.hook_event_name !== "SubagentStop") {
31
- process.exit(0);
32
- }
33
-
34
- const agentType = input.agent_type || input.subagent_type || "unknown";
35
- const result = input.tool_output || input.result || "";
36
- const resultStr =
37
- typeof result === "string" ? result : JSON.stringify(result);
38
-
39
- const issues = [];
40
-
41
- // 1. 빈 결과 체크
42
- if (!resultStr.trim() || resultStr.trim().length < 20) {
43
- issues.push(
44
- `서브에이전트(${agentType})가 거의 빈 결과를 반환했습니다. ` +
45
- "프롬프트를 더 구체적으로 작성하거나, 다른 subagent_type을 시도하세요.",
46
- );
47
- }
48
-
49
- // 2. 에러 키워드 감지
50
- const errorPatterns = [
51
- /error:|exception:|traceback|failed to|fatal:/i,
52
- /❌|FAILED|ERROR/,
53
- ];
54
- const hasError = errorPatterns.some((p) => p.test(resultStr));
55
- if (hasError && resultStr.length > 50) {
56
- issues.push(
57
- `서브에이전트(${agentType}) 결과에 에러 신호가 감지되었습니다. ` +
58
- "결과를 검토하고, 필요 시 다른 접근 방식을 사용하세요.",
59
- );
60
- }
61
-
62
- // 3. 결과가 너무 길면 요약 필요 알림
63
- if (resultStr.length > 15000) {
64
- issues.push(
65
- `서브에이전트(${agentType}) 결과가 ${Math.round(resultStr.length / 1000)}K 자입니다. ` +
66
- "핵심만 추출하여 컨텍스트 윈도우를 절약하세요.",
67
- );
68
- }
69
-
70
- if (issues.length === 0) process.exit(0);
71
-
72
- const output = {
73
- systemMessage:
74
- `[subagent-verifier] ${agentType} 완료 — 주의사항:\n` +
75
- issues.map((i) => ` → ${i}`).join("\n"),
76
- };
77
-
78
- process.stdout.write(JSON.stringify(output));
79
- }
80
-
81
- try {
82
- main();
83
- } catch {
84
- process.exit(0);
85
- }
@@ -1,59 +0,0 @@
1
- import { join } from "node:path";
2
-
3
- export const SESSION_TTL_SEC = 30 * 60;
4
- export const STATE_REL_PATH = join(".triflux", "state", "cross-review.json");
5
-
6
- export function readStdin() {
7
- return new Promise((resolve) => {
8
- let raw = "";
9
- process.stdin.setEncoding("utf8");
10
- process.stdin.on("data", (chunk) => {
11
- raw += chunk;
12
- });
13
- process.stdin.on("end", () => resolve(raw));
14
- process.stdin.on("error", () => resolve(""));
15
- });
16
- }
17
-
18
- export function parseJson(raw) {
19
- try {
20
- return JSON.parse(raw);
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- export function nowSec() {
27
- return Math.floor(Date.now() / 1000);
28
- }
29
-
30
- export function resolveBaseDir(payload) {
31
- if (typeof payload?.cwd === "string" && payload.cwd.trim())
32
- return payload.cwd;
33
- if (typeof payload?.directory === "string" && payload.directory.trim())
34
- return payload.directory;
35
- return process.cwd();
36
- }
37
-
38
- export function shouldTrackPath(filePath) {
39
- if (typeof filePath !== "string" || !filePath.trim()) return false;
40
-
41
- const lower = filePath.toLowerCase();
42
- if (
43
- lower.startsWith(".triflux/") ||
44
- lower.startsWith(".omc/") ||
45
- lower.startsWith(".claude/")
46
- )
47
- return false;
48
- if (lower === "package-lock.json" || lower.endsWith("/package-lock.json"))
49
- return false;
50
- if (/\.(md|lock|yml|yaml)$/i.test(lower)) return false;
51
- return true;
52
- }
53
-
54
- export function expectedReviewer(author) {
55
- if (author === "claude") return "codex";
56
- if (author === "codex") return "claude";
57
- if (author === "gemini") return "claude";
58
- return "";
59
- }
@@ -1,16 +0,0 @@
1
- export function nudge(message) {
2
- process.stdout.write(
3
- JSON.stringify({
4
- hookSpecificOutput: {
5
- hookEventName: "PreToolUse",
6
- additionalContext: message,
7
- },
8
- }),
9
- );
10
- process.exit(0);
11
- }
12
-
13
- export function deny(reason) {
14
- process.stderr.write(reason);
15
- process.exit(2);
16
- }