@triflux/core 10.43.0 → 10.45.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/codex-session-hook.mjs +67 -1
- package/hooks/cto-north-star-brief.mjs +3 -16
- package/hooks/error-context.mjs +3 -6
- package/hooks/hook-manager.mjs +1 -1
- package/hooks/hook-registry.json +0 -11
- package/hooks/keyword-rules.json +147 -23
- package/hooks/permission-safe-allow.mjs +36 -2
- package/hooks/session-start-fast.mjs +50 -1
- package/hooks/session-start-lake.mjs +3 -16
- package/hub/bridge.mjs +111 -5
- package/hub/cli-adapter-base.mjs +11 -2
- package/hub/codex-adapter.mjs +3 -0
- package/hub/gemini-adapter.mjs +29 -11
- package/hub/intent.mjs +24 -32
- package/hub/lib/memory-store.mjs +66 -30
- package/hub/lib/tfx-route-args.mjs +41 -4
- package/hub/reflexion.mjs +0 -57
- package/hub/router.mjs +474 -24
- package/hub/team/claude-daemon-control.mjs +2 -0
- package/hub/team/intervention.mjs +1 -1
- package/hub/team/retry-state-machine.mjs +41 -13
- package/hub/team/synapse-http.mjs +149 -4
- package/hud/cli-policy.mjs +69 -0
- package/hud/constants.mjs +20 -17
- package/hud/context-monitor.mjs +31 -21
- package/hud/hud-qos-status.mjs +81 -91
- package/hud/providers/claude.mjs +150 -84
- package/hud/providers/codex.mjs +10 -1
- package/hud/renderers.mjs +21 -59
- package/hud/utils.mjs +1 -15
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +367 -0
- package/scripts/lib/cli-agy.mjs +24 -4
- package/scripts/lib/cli-codex.mjs +22 -160
- package/scripts/lib/codex-profile-config.mjs +48 -4
- package/scripts/lib/gemini-profiles.mjs +113 -29
- package/scripts/lib/keyword-rules.mjs +24 -0
- package/scripts/lib/logger.mjs +60 -43
- package/scripts/lib/machine-profile.mjs +293 -0
- package/scripts/lib/mcp-health.mjs +44 -18
- package/hub/log-retention.mjs +0 -726
|
@@ -4,169 +4,23 @@
|
|
|
4
4
|
// 산출한다. 실제 spawn 은 Phase 1 에서 이관된다 (현재는 tfx-route.sh 가 수행).
|
|
5
5
|
//
|
|
6
6
|
// 진입점: tfx-route.mjs → selectAdapter("codex") → plan({...}).
|
|
7
|
-
// 출처:
|
|
7
|
+
// 출처: agent-route-policy.mjs 의 canonical Codex role policy.
|
|
8
8
|
// timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
|
|
9
9
|
|
|
10
10
|
export const id = "codex";
|
|
11
11
|
export const cliType = "codex";
|
|
12
12
|
export const command = "codex";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
mcpHint: "implement",
|
|
21
|
-
},
|
|
22
|
-
codex: {
|
|
23
|
-
profile: "gpt56_terra_high",
|
|
24
|
-
timeoutSec: 1080,
|
|
25
|
-
runMode: "fg",
|
|
26
|
-
opusOversight: false,
|
|
27
|
-
mcpHint: "implement",
|
|
28
|
-
},
|
|
29
|
-
"build-fixer": {
|
|
30
|
-
profile: "gpt56_luna_low",
|
|
31
|
-
timeoutSec: 540,
|
|
32
|
-
runMode: "fg",
|
|
33
|
-
opusOversight: false,
|
|
34
|
-
mcpHint: "implement",
|
|
35
|
-
},
|
|
36
|
-
cleanup: {
|
|
37
|
-
profile: "gpt56_terra_med",
|
|
38
|
-
timeoutSec: 540,
|
|
39
|
-
runMode: "fg",
|
|
40
|
-
opusOversight: false,
|
|
41
|
-
mcpHint: "implement",
|
|
42
|
-
},
|
|
43
|
-
deslop: {
|
|
44
|
-
profile: "gpt56_terra_med",
|
|
45
|
-
timeoutSec: 540,
|
|
46
|
-
runMode: "fg",
|
|
47
|
-
opusOversight: false,
|
|
48
|
-
mcpHint: "implement",
|
|
49
|
-
},
|
|
50
|
-
debugger: {
|
|
51
|
-
profile: "gpt56_sol_xhigh",
|
|
52
|
-
timeoutSec: 900,
|
|
53
|
-
runMode: "bg",
|
|
54
|
-
opusOversight: false,
|
|
55
|
-
mcpHint: "implement",
|
|
56
|
-
},
|
|
57
|
-
"deep-executor": {
|
|
58
|
-
profile: "gpt56_sol_xhigh",
|
|
59
|
-
timeoutSec: 3600,
|
|
60
|
-
runMode: "bg",
|
|
61
|
-
opusOversight: true,
|
|
62
|
-
mcpHint: "implement",
|
|
63
|
-
},
|
|
64
|
-
architect: {
|
|
65
|
-
profile: "gpt56_sol_xhigh",
|
|
66
|
-
timeoutSec: 3600,
|
|
67
|
-
runMode: "bg",
|
|
68
|
-
opusOversight: true,
|
|
69
|
-
mcpHint: "analyze",
|
|
70
|
-
},
|
|
71
|
-
critic: {
|
|
72
|
-
profile: "gpt56_sol_xhigh",
|
|
73
|
-
timeoutSec: 3600,
|
|
74
|
-
runMode: "bg",
|
|
75
|
-
opusOversight: true,
|
|
76
|
-
mcpHint: "analyze",
|
|
77
|
-
},
|
|
78
|
-
planner: {
|
|
79
|
-
profile: "gpt56_sol_xhigh",
|
|
80
|
-
timeoutSec: 3600,
|
|
81
|
-
runMode: "fg",
|
|
82
|
-
opusOversight: true,
|
|
83
|
-
mcpHint: "analyze",
|
|
84
|
-
},
|
|
85
|
-
analyst: {
|
|
86
|
-
profile: "gpt56_sol_xhigh",
|
|
87
|
-
timeoutSec: 3600,
|
|
88
|
-
runMode: "fg",
|
|
89
|
-
opusOversight: true,
|
|
90
|
-
mcpHint: "analyze",
|
|
91
|
-
},
|
|
92
|
-
"code-reviewer": {
|
|
93
|
-
profile: "gpt56_terra_high",
|
|
94
|
-
timeoutSec: 1800,
|
|
95
|
-
runMode: "bg",
|
|
96
|
-
opusOversight: false,
|
|
97
|
-
mcpHint: "review",
|
|
98
|
-
subcommand: "review",
|
|
99
|
-
},
|
|
100
|
-
"quality-reviewer": {
|
|
101
|
-
profile: "gpt56_terra_high",
|
|
102
|
-
timeoutSec: 1800,
|
|
103
|
-
runMode: "bg",
|
|
104
|
-
opusOversight: false,
|
|
105
|
-
mcpHint: "review",
|
|
106
|
-
subcommand: "review",
|
|
107
|
-
},
|
|
108
|
-
"security-reviewer": {
|
|
109
|
-
profile: "gpt56_sol_xhigh",
|
|
110
|
-
timeoutSec: 1800,
|
|
111
|
-
runMode: "bg",
|
|
112
|
-
opusOversight: true,
|
|
113
|
-
mcpHint: "review",
|
|
114
|
-
subcommand: "review",
|
|
115
|
-
},
|
|
116
|
-
scientist: {
|
|
117
|
-
profile: "gpt56_terra_high",
|
|
118
|
-
timeoutSec: 1440,
|
|
119
|
-
runMode: "bg",
|
|
120
|
-
opusOversight: false,
|
|
121
|
-
mcpHint: "analyze",
|
|
122
|
-
},
|
|
123
|
-
"document-specialist": {
|
|
124
|
-
profile: "gpt56_terra_high",
|
|
125
|
-
timeoutSec: 1440,
|
|
126
|
-
runMode: "bg",
|
|
127
|
-
opusOversight: false,
|
|
128
|
-
mcpHint: "analyze",
|
|
129
|
-
},
|
|
130
|
-
"scientist-deep": {
|
|
131
|
-
profile: "gpt56_sol_xhigh",
|
|
132
|
-
timeoutSec: 3600,
|
|
133
|
-
runMode: "bg",
|
|
134
|
-
opusOversight: false,
|
|
135
|
-
mcpHint: "analyze",
|
|
136
|
-
},
|
|
137
|
-
verifier: {
|
|
138
|
-
profile: "gpt56_terra_high",
|
|
139
|
-
timeoutSec: 1200,
|
|
140
|
-
runMode: "fg",
|
|
141
|
-
opusOversight: false,
|
|
142
|
-
mcpHint: "review",
|
|
143
|
-
subcommand: "review",
|
|
144
|
-
},
|
|
145
|
-
"test-engineer": {
|
|
146
|
-
profile: "gpt56_terra_high",
|
|
147
|
-
timeoutSec: 1200,
|
|
148
|
-
runMode: "bg",
|
|
149
|
-
opusOversight: false,
|
|
150
|
-
mcpHint: "implement",
|
|
151
|
-
},
|
|
152
|
-
"qa-tester": {
|
|
153
|
-
profile: "gpt56_terra_high",
|
|
154
|
-
timeoutSec: 1200,
|
|
155
|
-
runMode: "bg",
|
|
156
|
-
opusOversight: false,
|
|
157
|
-
mcpHint: "review",
|
|
158
|
-
subcommand: "review",
|
|
159
|
-
},
|
|
160
|
-
spark: {
|
|
161
|
-
profile: "gpt56_luna_low",
|
|
162
|
-
timeoutSec: 180,
|
|
163
|
-
runMode: "fg",
|
|
164
|
-
opusOversight: false,
|
|
165
|
-
mcpHint: "implement",
|
|
166
|
-
},
|
|
167
|
-
};
|
|
14
|
+
import {
|
|
15
|
+
CODEX_AGENT_POLICY,
|
|
16
|
+
resolveCodexAgentPolicy,
|
|
17
|
+
resolveCodexAgentProfile,
|
|
18
|
+
resolveNestedCodexAgentProfile,
|
|
19
|
+
} from "./agent-route-policy.mjs";
|
|
168
20
|
|
|
169
|
-
|
|
21
|
+
// Compatibility re-exports for launchers which historically imported these
|
|
22
|
+
// helpers from this adapter. The policy module owns the semantics.
|
|
23
|
+
export { resolveCodexAgentProfile, resolveNestedCodexAgentProfile };
|
|
170
24
|
|
|
171
25
|
export function plan({
|
|
172
26
|
agent,
|
|
@@ -174,18 +28,26 @@ export function plan({
|
|
|
174
28
|
mcpProfile = "auto",
|
|
175
29
|
timeoutSec,
|
|
176
30
|
contextFile,
|
|
31
|
+
profileOverride = "auto",
|
|
32
|
+
retryProfile = null,
|
|
33
|
+
nested = false,
|
|
177
34
|
} = {}) {
|
|
178
35
|
if (!agent) {
|
|
179
36
|
throw new Error("[cli-codex] agent required");
|
|
180
37
|
}
|
|
181
|
-
const cfg =
|
|
38
|
+
const cfg = resolveCodexAgentPolicy(agent);
|
|
39
|
+
const profile = resolveCodexAgentProfile(agent, {
|
|
40
|
+
profileOverride,
|
|
41
|
+
retryProfile,
|
|
42
|
+
nested,
|
|
43
|
+
});
|
|
182
44
|
const effectiveTimeoutSec =
|
|
183
45
|
Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
|
|
184
46
|
return {
|
|
185
47
|
command,
|
|
186
48
|
subcommand: cfg.subcommand ?? "exec",
|
|
187
|
-
profile
|
|
188
|
-
effort:
|
|
49
|
+
profile,
|
|
50
|
+
effort: profile,
|
|
189
51
|
timeoutMs: effectiveTimeoutSec * 1000,
|
|
190
52
|
runMode: cfg.runMode,
|
|
191
53
|
opusOversight: cfg.opusOversight,
|
|
@@ -196,5 +58,5 @@ export function plan({
|
|
|
196
58
|
}
|
|
197
59
|
|
|
198
60
|
export function describe() {
|
|
199
|
-
return { id, cliType, command, agents: Object.keys(
|
|
61
|
+
return { id, cliType, command, agents: Object.keys(CODEX_AGENT_POLICY) };
|
|
200
62
|
}
|
|
@@ -9,6 +9,8 @@ import { homedir } from "node:os";
|
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
|
|
11
11
|
const EFFORT_BY_SUFFIX = {
|
|
12
|
+
ultra: "ultra",
|
|
13
|
+
max: "max",
|
|
12
14
|
xhigh: "xhigh",
|
|
13
15
|
high: "high",
|
|
14
16
|
med: "medium",
|
|
@@ -16,6 +18,26 @@ const EFFORT_BY_SUFFIX = {
|
|
|
16
18
|
low: "low",
|
|
17
19
|
};
|
|
18
20
|
|
|
21
|
+
const CANONICAL_PROFILE_VALUES = Object.freeze({
|
|
22
|
+
gpt56_luna_low: { model: "gpt-5.6-luna", effort: "low" },
|
|
23
|
+
gpt56_terra_med: { model: "gpt-5.6-terra", effort: "medium" },
|
|
24
|
+
gpt56_terra_high: { model: "gpt-5.6-terra", effort: "high" },
|
|
25
|
+
gpt6_astra_xhigh: { model: "gpt-6-astra", effort: "xhigh" },
|
|
26
|
+
gpt6_astra_max: { model: "gpt-6-astra", effort: "max" },
|
|
27
|
+
gpt6_astra_ultra: { model: "gpt-6-astra", effort: "ultra" },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const LEGACY_CANONICAL_PROFILE_ALIASES = Object.freeze({
|
|
31
|
+
gpt56_sol_xhigh: "gpt6_astra_xhigh",
|
|
32
|
+
gpt56_sol_max: "gpt6_astra_max",
|
|
33
|
+
gpt56_sol_ultra: "gpt6_astra_ultra",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function normalizeCodexProfileName(profileName) {
|
|
37
|
+
const normalized = String(profileName ?? "").trim();
|
|
38
|
+
return LEGACY_CANONICAL_PROFILE_ALIASES[normalized] || normalized;
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
function readProfileScalar(raw, key) {
|
|
20
42
|
const match = String(raw).match(
|
|
21
43
|
new RegExp(
|
|
@@ -49,23 +71,45 @@ function readProfileScalar(raw, key) {
|
|
|
49
71
|
* @param {{ codexHome?: string }} [opts]
|
|
50
72
|
* @returns {string[]} e.g. ['model="gpt-5.6-terra"', 'model_reasoning_effort="high"']
|
|
51
73
|
*/
|
|
52
|
-
export function
|
|
53
|
-
if (typeof profileName !== "string" || !profileName)
|
|
74
|
+
export function resolveCodexProfileConfigValues(profileName, opts = {}) {
|
|
75
|
+
if (typeof profileName !== "string" || !profileName) {
|
|
76
|
+
return { model: null, effort: null };
|
|
77
|
+
}
|
|
78
|
+
const normalizedProfileName = normalizeCodexProfileName(profileName);
|
|
54
79
|
const codexHome =
|
|
55
80
|
opts.codexHome || process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
56
81
|
let model = null;
|
|
57
82
|
let effort = null;
|
|
58
83
|
try {
|
|
59
84
|
const raw = readFileSync(
|
|
60
|
-
join(codexHome, `${
|
|
85
|
+
join(codexHome, `${normalizedProfileName}.config.toml`),
|
|
61
86
|
"utf8",
|
|
62
87
|
);
|
|
63
88
|
model = readProfileScalar(raw, "model");
|
|
64
89
|
effort = readProfileScalar(raw, "model_reasoning_effort");
|
|
65
90
|
} catch {
|
|
66
|
-
const suffix = /_(xhigh|high|med|medium|low)$/.exec(
|
|
91
|
+
const suffix = /_(ultra|max|xhigh|high|med|medium|low)$/.exec(
|
|
92
|
+
normalizedProfileName,
|
|
93
|
+
);
|
|
67
94
|
if (suffix) effort = EFFORT_BY_SUFFIX[suffix[1]] || null;
|
|
68
95
|
}
|
|
96
|
+
return { model, effort };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function codexProfileConfigOverrides(profileName, opts = {}) {
|
|
100
|
+
const normalizedProfileName = normalizeCodexProfileName(profileName);
|
|
101
|
+
let { model, effort } = resolveCodexProfileConfigValues(
|
|
102
|
+
normalizedProfileName,
|
|
103
|
+
opts,
|
|
104
|
+
);
|
|
105
|
+
if (opts.enforceCanonicalProfile === true) {
|
|
106
|
+
const canonical = CANONICAL_PROFILE_VALUES[normalizedProfileName];
|
|
107
|
+
if (canonical) ({ model, effort } = canonical);
|
|
108
|
+
}
|
|
109
|
+
if (opts.disallowUltra === true && effort?.toLowerCase() === "ultra") {
|
|
110
|
+
model = "gpt-6-astra";
|
|
111
|
+
effort = "max";
|
|
112
|
+
}
|
|
69
113
|
const overrides = [];
|
|
70
114
|
if (model) overrides.push(`model="${model}"`);
|
|
71
115
|
if (effort) overrides.push(`model_reasoning_effort="${effort}"`);
|
|
@@ -8,48 +8,124 @@ import {
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
11
|
-
// 모델명 SSOT. 값은
|
|
11
|
+
// 모델명 SSOT. 값은 `agy models` 가 출력하는 display name 형식이다
|
|
12
12
|
// (`--model` 인자가 이 문자열을 받는다). 옛 `gemini-*-preview` ID 형식이 아님.
|
|
13
|
+
// 카탈로그 확인: agy 1.1.27 `agy models` (2026-09-07). 운영 정책은 3.8 Flash 한 세대만
|
|
14
|
+
// 쓰고 effort(Low/Medium/High)로 차등한다. Pro 와 이전 세대 Flash 는 기본 프로필에서 뺐다.
|
|
13
15
|
const DEFAULT_GEMINI_PROFILES = {
|
|
14
|
-
model: "Gemini 3.
|
|
16
|
+
model: "Gemini 3.8 Flash (Medium)",
|
|
15
17
|
profiles: {
|
|
16
|
-
|
|
17
|
-
model: "Gemini 3.
|
|
18
|
-
hint: "3.
|
|
18
|
+
flash38_low: {
|
|
19
|
+
model: "Gemini 3.8 Flash (Low)",
|
|
20
|
+
hint: "3.8 Flash (Low) — 경량·저비용",
|
|
19
21
|
},
|
|
20
|
-
|
|
21
|
-
model: "Gemini 3.
|
|
22
|
-
hint: "3.
|
|
22
|
+
flash38: {
|
|
23
|
+
model: "Gemini 3.8 Flash (Medium)",
|
|
24
|
+
hint: "3.8 Flash (Medium) — 기본, 비용·속도 균형",
|
|
23
25
|
},
|
|
24
|
-
|
|
25
|
-
model: "Gemini 3.
|
|
26
|
-
hint: "3.
|
|
27
|
-
},
|
|
28
|
-
pro31_low: {
|
|
29
|
-
model: "Gemini 3.1 Pro (Low)",
|
|
30
|
-
hint: "3.1 Pro (Low) — 플래그십 경량",
|
|
31
|
-
},
|
|
32
|
-
pro31: {
|
|
33
|
-
model: "Gemini 3.1 Pro (High)",
|
|
34
|
-
hint: "3.1 Pro (High) — 플래그십 최고 (긴 컨텍스트/복잡 추론)",
|
|
35
|
-
},
|
|
36
|
-
flash3: {
|
|
37
|
-
model: "Gemini 3 Flash",
|
|
38
|
-
hint: "3.0 Flash — 레거시 호환",
|
|
26
|
+
flash38_high: {
|
|
27
|
+
model: "Gemini 3.8 Flash (High)",
|
|
28
|
+
hint: "3.8 Flash (High) — 코드/추론 강화",
|
|
39
29
|
},
|
|
40
30
|
},
|
|
41
31
|
};
|
|
42
32
|
|
|
43
33
|
// 1회 마이그레이션 테이블: 옛 ID 형식 모델값 → agy display name.
|
|
44
|
-
// null =
|
|
34
|
+
// null = 카탈로그에서 빠진 모델(2.5 계열, 3 Flash) → 해당 프로필 제거 대상.
|
|
45
35
|
const LEGACY_MODEL_MIGRATION = {
|
|
46
36
|
"gemini-3.1-pro-preview": "Gemini 3.1 Pro (High)",
|
|
47
|
-
"gemini-3-flash-preview":
|
|
37
|
+
"gemini-3-flash-preview": null,
|
|
38
|
+
"Gemini 3 Flash": null,
|
|
48
39
|
"gemini-2.5-pro": null,
|
|
49
40
|
"gemini-2.5-flash": null,
|
|
50
41
|
"gemini-2.5-flash-lite": null,
|
|
51
42
|
};
|
|
52
|
-
|
|
43
|
+
// 과거 setup 이 자동 생성했던 프로필 이름. 정책상 더 쓰지 않으므로 제거한다.
|
|
44
|
+
// 사용자가 직접 이름 붙인 프로필은 건드리지 않는다.
|
|
45
|
+
const LEGACY_PROFILE_NAMES = [
|
|
46
|
+
"pro25",
|
|
47
|
+
"flash25",
|
|
48
|
+
"lite25",
|
|
49
|
+
"flash3",
|
|
50
|
+
"pro31",
|
|
51
|
+
"pro31_low",
|
|
52
|
+
"flash35",
|
|
53
|
+
"flash35_high",
|
|
54
|
+
"flash35_low",
|
|
55
|
+
];
|
|
56
|
+
// 과거 setup 이 자동 생성한 기본 model 값. 사용자가 고른 값이 아니므로 새 기본으로 올린다.
|
|
57
|
+
const LEGACY_DEFAULT_MODELS = new Set(["Gemini 3.5 Flash (Medium)"]);
|
|
58
|
+
|
|
59
|
+
// ── 용도별 effort (SSOT) ──
|
|
60
|
+
// 한 세대(3.8 Flash)만 쓰고 역할에 따라 effort 를 나눈다. 키는 tfx 에이전트/역할 이름
|
|
61
|
+
// (agent-map.json 의 에이전트, tfx multi --assign 의 role). bash(tfx-route.sh)와
|
|
62
|
+
// node(execution-mode, gemini-adapter, cli-agy)가 모두 이 표를 읽는다.
|
|
63
|
+
// High : 판단이 결과를 좌우하는 역할 (검토, 설계, 디버깅, 분석)
|
|
64
|
+
// Medium : 생성/작성/실행 (기본값)
|
|
65
|
+
// Low : 분류, 요약, 추출, 번역처럼 짧고 기계적인 작업
|
|
66
|
+
const GEMINI_PROFILE_BY_PURPOSE = Object.freeze({
|
|
67
|
+
designer: "flash38_high",
|
|
68
|
+
reviewer: "flash38_high",
|
|
69
|
+
"code-reviewer": "flash38_high",
|
|
70
|
+
"security-reviewer": "flash38_high",
|
|
71
|
+
"quality-reviewer": "flash38_high",
|
|
72
|
+
critic: "flash38_high",
|
|
73
|
+
architect: "flash38_high",
|
|
74
|
+
verifier: "flash38_high",
|
|
75
|
+
analyst: "flash38_high",
|
|
76
|
+
debugger: "flash38_high",
|
|
77
|
+
planner: "flash38_high",
|
|
78
|
+
scientist: "flash38_high",
|
|
79
|
+
"test-engineer": "flash38_high",
|
|
80
|
+
writer: "flash38",
|
|
81
|
+
executor: "flash38",
|
|
82
|
+
worker: "flash38",
|
|
83
|
+
"document-specialist": "flash38",
|
|
84
|
+
researcher: "flash38",
|
|
85
|
+
antigravity: "flash38",
|
|
86
|
+
agy: "flash38",
|
|
87
|
+
gemini: "flash38",
|
|
88
|
+
summarizer: "flash38_low",
|
|
89
|
+
classifier: "flash38_low",
|
|
90
|
+
triage: "flash38_low",
|
|
91
|
+
translator: "flash38_low",
|
|
92
|
+
formatter: "flash38_low",
|
|
93
|
+
explore: "flash38_low",
|
|
94
|
+
});
|
|
95
|
+
const GEMINI_DEFAULT_PURPOSE_PROFILE = "flash38";
|
|
96
|
+
|
|
97
|
+
function resolveGeminiProfileForPurpose(name) {
|
|
98
|
+
const key = String(name ?? "")
|
|
99
|
+
.trim()
|
|
100
|
+
.toLowerCase();
|
|
101
|
+
return GEMINI_PROFILE_BY_PURPOSE[key] || GEMINI_DEFAULT_PURPOSE_PROFILE;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 프로필 이름 → agy display name. 사용자 파일(~/.gemini/triflux-profiles.json)이
|
|
105
|
+
// 우선하고, 없으면 DEFAULT_GEMINI_PROFILES 로 떨어진다. 이미 모델명이면 그대로 돌려준다.
|
|
106
|
+
function resolveGeminiModel(profileOrModel, opts = {}) {
|
|
107
|
+
const raw = String(profileOrModel ?? "").trim();
|
|
108
|
+
if (!raw) return DEFAULT_GEMINI_PROFILES.model;
|
|
109
|
+
if (/^gemini[\s-]/i.test(raw)) return raw;
|
|
110
|
+
const profilesPath =
|
|
111
|
+
opts.profilesPath ||
|
|
112
|
+
process.env.GEMINI_PROFILES_PATH ||
|
|
113
|
+
join(homedir(), ".gemini", "triflux-profiles.json");
|
|
114
|
+
let cfg = null;
|
|
115
|
+
try {
|
|
116
|
+
cfg = JSON.parse(readFileSync(profilesPath, "utf8"));
|
|
117
|
+
} catch {
|
|
118
|
+
cfg = null;
|
|
119
|
+
}
|
|
120
|
+
const entry = cfg?.profiles?.[raw];
|
|
121
|
+
const fromFile = typeof entry === "string" ? entry : entry?.model;
|
|
122
|
+
if (typeof fromFile === "string" && fromFile.trim()) return fromFile.trim();
|
|
123
|
+
const fromDefault = DEFAULT_GEMINI_PROFILES.profiles[raw];
|
|
124
|
+
if (fromDefault) return fromDefault.model;
|
|
125
|
+
if (typeof cfg?.model === "string" && cfg.model.trim())
|
|
126
|
+
return cfg.model.trim();
|
|
127
|
+
return DEFAULT_GEMINI_PROFILES.model;
|
|
128
|
+
}
|
|
53
129
|
|
|
54
130
|
const DEFAULT_PROFILE_COUNT = Object.keys(
|
|
55
131
|
DEFAULT_GEMINI_PROFILES.profiles,
|
|
@@ -132,9 +208,10 @@ function ensureGeminiProfiles({
|
|
|
132
208
|
}
|
|
133
209
|
if (
|
|
134
210
|
typeof cfg.model === "string" &&
|
|
135
|
-
Object.hasOwn(LEGACY_MODEL_MIGRATION, cfg.model)
|
|
211
|
+
(Object.hasOwn(LEGACY_MODEL_MIGRATION, cfg.model) ||
|
|
212
|
+
LEGACY_DEFAULT_MODELS.has(cfg.model))
|
|
136
213
|
) {
|
|
137
|
-
// 옛 ID
|
|
214
|
+
// 옛 ID 형식/이전 세대 자동 생성 기본값은 새 기본(DEFAULT)으로 정규화한다.
|
|
138
215
|
cfg.model = DEFAULT_GEMINI_PROFILES.model;
|
|
139
216
|
migrated = true;
|
|
140
217
|
}
|
|
@@ -180,4 +257,11 @@ function ensureGeminiProfiles({
|
|
|
180
257
|
}
|
|
181
258
|
}
|
|
182
259
|
|
|
183
|
-
export {
|
|
260
|
+
export {
|
|
261
|
+
DEFAULT_GEMINI_PROFILES,
|
|
262
|
+
ensureGeminiProfiles,
|
|
263
|
+
GEMINI_DEFAULT_PURPOSE_PROFILE,
|
|
264
|
+
GEMINI_PROFILE_BY_PURPOSE,
|
|
265
|
+
resolveGeminiModel,
|
|
266
|
+
resolveGeminiProfileForPurpose,
|
|
267
|
+
};
|
|
@@ -26,6 +26,7 @@ function normalizeState(state) {
|
|
|
26
26
|
|
|
27
27
|
function normalizeRule(rule) {
|
|
28
28
|
if (!rule || typeof rule !== "object") return null;
|
|
29
|
+
if (rule.disabled === true) return null;
|
|
29
30
|
if (typeof rule.id !== "string" || !rule.id.trim()) return null;
|
|
30
31
|
if (!Array.isArray(rule.patterns) || rule.patterns.length === 0) return null;
|
|
31
32
|
if (typeof rule.priority !== "number" || !Number.isFinite(rule.priority))
|
|
@@ -61,6 +62,23 @@ function normalizeRule(rule) {
|
|
|
61
62
|
const state = normalizeState(rule.state);
|
|
62
63
|
if (rule.state != null && state == null) return null;
|
|
63
64
|
|
|
65
|
+
// repo_scope: path segment 화이트리스트 (빈 배열 = 전역). explicit: 종결 라우팅 마커.
|
|
66
|
+
const repoScope = Array.isArray(rule.repo_scope)
|
|
67
|
+
? rule.repo_scope
|
|
68
|
+
.filter((s) => typeof s === "string" && s.trim())
|
|
69
|
+
.map((s) => s.trim())
|
|
70
|
+
: [];
|
|
71
|
+
|
|
72
|
+
// platform: process.platform 화이트리스트 (빈 배열 = 전 플랫폼). Windows 전용
|
|
73
|
+
// 스킬(tfx-wt 등)이 macOS/Linux 프롬프트에 오탐하는 것을 막는다. 배열이 아닌
|
|
74
|
+
// 값은 규칙 자체를 무효로 본다.
|
|
75
|
+
if (rule.platform != null && !Array.isArray(rule.platform)) return null;
|
|
76
|
+
const platform = Array.isArray(rule.platform)
|
|
77
|
+
? rule.platform
|
|
78
|
+
.filter((s) => typeof s === "string" && s.trim())
|
|
79
|
+
.map((s) => s.trim())
|
|
80
|
+
: [];
|
|
81
|
+
|
|
64
82
|
return {
|
|
65
83
|
id: rule.id.trim(),
|
|
66
84
|
patterns,
|
|
@@ -72,6 +90,9 @@ function normalizeRule(rule) {
|
|
|
72
90
|
exclusive: rule.exclusive === true,
|
|
73
91
|
state,
|
|
74
92
|
mcp_route: mcpRoute,
|
|
93
|
+
repo_scope: repoScope,
|
|
94
|
+
platform,
|
|
95
|
+
explicit: rule.explicit === true,
|
|
75
96
|
};
|
|
76
97
|
}
|
|
77
98
|
|
|
@@ -149,6 +170,9 @@ export function matchRules(compiledRules, cleanText) {
|
|
|
149
170
|
exclusive: rule.exclusive === true,
|
|
150
171
|
state: rule.state || null,
|
|
151
172
|
mcp_route: rule.mcp_route || null,
|
|
173
|
+
repo_scope: rule.repo_scope || [],
|
|
174
|
+
platform: rule.platform || [],
|
|
175
|
+
explicit: rule.explicit === true,
|
|
152
176
|
});
|
|
153
177
|
}
|
|
154
178
|
|
package/scripts/lib/logger.mjs
CHANGED
|
@@ -31,54 +31,71 @@ import pino from "pino";
|
|
|
31
31
|
|
|
32
32
|
const isDev = process.env.NODE_ENV !== "production";
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// 로그는 항상 stderr(fd 2)로 보낸다.
|
|
35
|
+
//
|
|
36
|
+
// Claude Code 훅은 stdout 을 훅의 JSON 페이로드로 파싱한다. 로그가 stdout 으로
|
|
37
|
+
// 나가면 페이로드와 한 스트림에 섞여, 훅이 돌려준 hookSpecificOutput 이 JSON 으로
|
|
38
|
+
// 해석되지 않고 그냥 본문 텍스트가 된다. Claude Code 2.1.257 부터는 stdout 이
|
|
39
|
+
// "{" 로 시작하는데 유효한 JSON 이 아니면 훅 오류로 보고하므로, 로그가 먼저 나가느냐
|
|
40
|
+
// 페이로드가 먼저 나가느냐에 따라 훅 전체가 실패로 뒤집힌다.
|
|
41
|
+
//
|
|
42
|
+
// stderr 로 보내면 Claude Code 가 verbose/transcript 에서 그대로 보여주고 stdout 은
|
|
43
|
+
// 페이로드만 남는다. CLI 관례상으로도 구조화 로그는 stderr 가 맞다.
|
|
44
|
+
const LOG_FD = 2;
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
env: process.env.NODE_ENV || "development",
|
|
41
|
-
},
|
|
46
|
+
export const logger = pino(
|
|
47
|
+
{
|
|
48
|
+
level: process.env.LOG_LEVEL || (isDev ? "debug" : "info"),
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
// 모든 로그에 포함되는 기본 필드
|
|
51
|
+
base: {
|
|
52
|
+
service: process.env.SERVICE_NAME || "triflux",
|
|
53
|
+
env: process.env.NODE_ENV || "development",
|
|
54
|
+
},
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
// 레벨을 대문자로 출력 (AI 파싱 용이)
|
|
57
|
+
formatters: {
|
|
58
|
+
level: (label) => ({ level: label.toUpperCase() }),
|
|
59
|
+
},
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
paths: [
|
|
54
|
-
"password",
|
|
55
|
-
"token",
|
|
56
|
-
"apiKey",
|
|
57
|
-
"secret",
|
|
58
|
-
"authorization",
|
|
59
|
-
"*.password",
|
|
60
|
-
"*.token",
|
|
61
|
-
"*.apiKey",
|
|
62
|
-
"*.secret",
|
|
63
|
-
"req.headers.authorization",
|
|
64
|
-
"req.headers.cookie",
|
|
65
|
-
"hubToken",
|
|
66
|
-
],
|
|
67
|
-
remove: true,
|
|
68
|
-
},
|
|
61
|
+
// ISO 8601 타임스탬프
|
|
62
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
69
63
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
// 민감정보 자동 필터링
|
|
65
|
+
redact: {
|
|
66
|
+
paths: [
|
|
67
|
+
"password",
|
|
68
|
+
"token",
|
|
69
|
+
"apiKey",
|
|
70
|
+
"secret",
|
|
71
|
+
"authorization",
|
|
72
|
+
"*.password",
|
|
73
|
+
"*.token",
|
|
74
|
+
"*.apiKey",
|
|
75
|
+
"*.secret",
|
|
76
|
+
"req.headers.authorization",
|
|
77
|
+
"req.headers.cookie",
|
|
78
|
+
"hubToken",
|
|
79
|
+
],
|
|
80
|
+
remove: true,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// 개발 환경: 컬러 콘솔 출력 (stderr)
|
|
84
|
+
transport: isDev
|
|
85
|
+
? {
|
|
86
|
+
target: "pino-pretty",
|
|
87
|
+
options: {
|
|
88
|
+
colorize: true,
|
|
89
|
+
translateTime: "yyyy-mm-dd HH:MM:ss",
|
|
90
|
+
ignore: "pid,hostname",
|
|
91
|
+
destination: LOG_FD,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
: undefined,
|
|
95
|
+
},
|
|
96
|
+
// 운영 환경(transport 미사용)에서도 stdout 이 아니라 stderr 로 쓴다.
|
|
97
|
+
isDev ? undefined : pino.destination({ dest: LOG_FD, sync: false }),
|
|
98
|
+
);
|
|
82
99
|
|
|
83
100
|
/**
|
|
84
101
|
* 모듈별 Child Logger 생성.
|