@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.
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
@@ -0,0 +1,230 @@
1
+ import {
2
+ constants,
3
+ copyFileSync,
4
+ existsSync,
5
+ lstatSync,
6
+ readFileSync,
7
+ realpathSync,
8
+ renameSync,
9
+ statSync,
10
+ unlinkSync,
11
+ writeFileSync,
12
+ } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { dirname, join } from "node:path";
15
+
16
+ // 설치된 Triflux 실행 대상만 식별한다.
17
+ const MANAGED_FILENAMES = [
18
+ "hook-orchestrator.mjs",
19
+ "claude-cwd-projection-refresh.mjs",
20
+ "permission-safe-allow.mjs",
21
+ "safety-guard.mjs",
22
+ "pipeline-stop.mjs",
23
+ "agent-route-guard.mjs",
24
+ "cross-review-tracker.mjs",
25
+ "cross-review-gate.mjs",
26
+ "tfx-gate-activate.mjs",
27
+ "subagent-tracker.mjs",
28
+ "subagent-verifier.mjs",
29
+ "error-context.mjs",
30
+ "hook-adaptive-collector.mjs",
31
+ "post-tool-tips.mjs",
32
+ "pre-compact-snapshot.mjs",
33
+ "cto-north-star-brief.mjs",
34
+ "session-start-lake.mjs",
35
+ "session-start-fast.mjs",
36
+ "session-end-cleanup.mjs",
37
+ "mcp-config-watcher.mjs",
38
+ "keyword-detector.mjs",
39
+ "setup.mjs",
40
+ "hub-ensure.mjs",
41
+ "preflight-cache.mjs",
42
+ "mcp-gateway-ensure.mjs",
43
+ "mcp-safety-guard.mjs",
44
+ "session-stale-cleanup.mjs",
45
+ ];
46
+
47
+ const targetNames = MANAGED_FILENAMES.join("|").replaceAll(".", "\\.");
48
+ const uniqueTargetNames = MANAGED_FILENAMES.filter(
49
+ (name) =>
50
+ ![
51
+ "setup.mjs",
52
+ "hub-ensure.mjs",
53
+ "preflight-cache.mjs",
54
+ "mcp-gateway-ensure.mjs",
55
+ "mcp-safety-guard.mjs",
56
+ "session-stale-cleanup.mjs",
57
+ ].includes(name),
58
+ )
59
+ .join("|")
60
+ .replaceAll(".", "\\.");
61
+ const packageTarget = new RegExp(
62
+ String.raw`[/\\]triflux(?:[/\\](?:\.worktrees[/\\][^/\\]+|v?\d+\.\d+\.\d+[^/\\]*))?[/\\](?:hooks|scripts)[/\\](?:${targetNames})$`,
63
+ );
64
+ const pluginRootTarget = new RegExp(
65
+ String.raw`^\$\{(?:CLAUDE_)?PLUGIN_ROOT\}[/\\](?:hooks|scripts)[/\\](?:${uniqueTargetNames})$`,
66
+ );
67
+ const gateSuffix = ".claude/scripts/tfx-gate-activate.mjs";
68
+
69
+ function isInstalledGateTarget(target) {
70
+ const normalized = target.replaceAll("\\", "/");
71
+ if (
72
+ ["${HOME}", "$HOME", "~"].some(
73
+ (root) => normalized === `${root}/${gateSuffix}`,
74
+ )
75
+ )
76
+ return true;
77
+ const installedTargets = [join(homedir(), gateSuffix)];
78
+ if (process.env.CLAUDE_CONFIG_DIR)
79
+ installedTargets.push(
80
+ join(process.env.CLAUDE_CONFIG_DIR, "scripts/tfx-gate-activate.mjs"),
81
+ );
82
+ return installedTargets.some(
83
+ (installed) => normalized === installed.replaceAll("\\", "/"),
84
+ );
85
+ }
86
+
87
+ function readCommandToken(command) {
88
+ const match = command.match(/^\s*(?:"([^"]*)"|'([^']*)'|(\S+))(.*)$/s);
89
+ return match
90
+ ? { value: match[1] ?? match[2] ?? match[3], rest: match[4] }
91
+ : null;
92
+ }
93
+
94
+ function matchesTarget(target) {
95
+ const resolvedFallback = target.replace(
96
+ /^\$\{(?:CLAUDE_)?PLUGIN_ROOT:-([^}]+)\}/,
97
+ "$1",
98
+ );
99
+ return (
100
+ packageTarget.test(resolvedFallback) ||
101
+ pluginRootTarget.test(target) ||
102
+ isInstalledGateTarget(target)
103
+ );
104
+ }
105
+
106
+ export function isLegacyTrifluxHook(hook) {
107
+ if (hook?.type !== "command" || typeof hook.command !== "string")
108
+ return false;
109
+ const command = hook.command;
110
+ const executable = readCommandToken(command);
111
+ if (!executable || !/(?:^|[/\\])node(?:\.exe)?$/.test(executable.value))
112
+ return false;
113
+ const target = readCommandToken(executable.rest);
114
+ if (!target) return false;
115
+ // 옛 inline bootstrap만 제거한다.
116
+ if (target.value === "-e" || target.value === "--eval")
117
+ return (
118
+ command.includes(".tfx-pkg-root") &&
119
+ command.includes("hook-orchestrator.mjs") &&
120
+ command.includes("spawnSync") &&
121
+ command.includes("path.join(root")
122
+ );
123
+ if (matchesTarget(target.value)) return true;
124
+ // run.cjs의 keyword-detector 인자를 확인한다.
125
+ if (/[/\\]scripts[/\\]run\.cjs$/.test(target.value)) {
126
+ const script = readCommandToken(target.rest);
127
+ return (
128
+ script?.value.endsWith("/scripts/keyword-detector.mjs") &&
129
+ (matchesTarget(script.value) || pluginRootTarget.test(script.value))
130
+ );
131
+ }
132
+ return false;
133
+ }
134
+
135
+ function removeManagedHooks(settings) {
136
+ let removed = 0;
137
+ const events = settings?.hooks;
138
+ if (!events || typeof events !== "object" || Array.isArray(events))
139
+ return removed;
140
+ for (const [event, entries] of Object.entries(events)) {
141
+ if (!Array.isArray(entries)) continue;
142
+ const kept = [];
143
+ for (const entry of entries) {
144
+ if (!Array.isArray(entry?.hooks)) {
145
+ kept.push(entry);
146
+ continue;
147
+ }
148
+ const hooks = entry.hooks.filter((hook) => {
149
+ if (!isLegacyTrifluxHook(hook)) return true;
150
+ removed++;
151
+ return false;
152
+ });
153
+ if (hooks.length)
154
+ kept.push(
155
+ hooks.length === entry.hooks.length ? entry : { ...entry, hooks },
156
+ );
157
+ }
158
+ if (kept.length) events[event] = kept;
159
+ else delete events[event];
160
+ }
161
+ if (Object.keys(events).length === 0) delete settings.hooks;
162
+ return removed;
163
+ }
164
+
165
+ /** Claude 설정의 이전 Triflux command hook을 정리한다. */
166
+ export function cleanupLegacyHooks({
167
+ settingsPath = join(
168
+ process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"),
169
+ "settings.json",
170
+ ),
171
+ dryRun = false,
172
+ } = {}) {
173
+ const result = {
174
+ ok: true,
175
+ changed: false,
176
+ wouldChange: false,
177
+ removed: 0,
178
+ backupPath: null,
179
+ error: null,
180
+ };
181
+ let original;
182
+ let settings;
183
+ try {
184
+ const pathInfo = lstatSync(settingsPath, { throwIfNoEntry: false });
185
+ if (!pathInfo) return result;
186
+ // symlink는 유지하고 실제 파일을 교체한다.
187
+ const targetPath = pathInfo.isSymbolicLink()
188
+ ? realpathSync(settingsPath)
189
+ : settingsPath;
190
+ original = readFileSync(targetPath, "utf8");
191
+ settings = JSON.parse(original);
192
+ if (!settings || typeof settings !== "object" || Array.isArray(settings))
193
+ throw new Error("settings.json must contain an object");
194
+ result.removed = removeManagedHooks(settings);
195
+ result.wouldChange = result.removed > 0;
196
+ if (!result.wouldChange || dryRun) return result;
197
+
198
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
199
+ let backupPath = `${settingsPath}.tfx-bak-${stamp}`;
200
+ for (let suffix = 1; existsSync(backupPath); suffix++)
201
+ backupPath = `${settingsPath}.tfx-bak-${stamp}-${suffix}`;
202
+ copyFileSync(settingsPath, backupPath, constants.COPYFILE_EXCL);
203
+ result.backupPath = backupPath;
204
+
205
+ const temporary = join(
206
+ dirname(targetPath),
207
+ `.settings.json.tfx-${process.pid}-${Date.now()}.tmp`,
208
+ );
209
+ try {
210
+ writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}\n`, {
211
+ encoding: "utf8",
212
+ flag: "wx",
213
+ mode: statSync(targetPath).mode,
214
+ });
215
+ renameSync(temporary, targetPath);
216
+ } catch (error) {
217
+ try {
218
+ unlinkSync(temporary);
219
+ } catch {
220
+ /* 임시 파일 없음 */
221
+ }
222
+ throw error;
223
+ }
224
+ result.changed = true;
225
+ } catch (error) {
226
+ result.ok = false;
227
+ result.error = error.message;
228
+ }
229
+ return result;
230
+ }