@triflux/core 10.0.3 → 10.25.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.
- package/hooks/agent-route-guard.mjs +14 -10
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +47 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +279 -33
- package/hooks/hook-registry.json +104 -7
- package/hooks/hooks.json +49 -1
- package/hooks/keyword-rules.json +183 -23
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/permission-safe-allow.mjs +233 -0
- package/hooks/pipeline-stop.mjs +369 -22
- package/hooks/post-tool-tips.mjs +306 -0
- package/hooks/pre-compact-snapshot.mjs +181 -0
- package/hooks/safety-guard.mjs +308 -18
- package/hooks/session-end-cleanup.mjs +345 -0
- package/hooks/session-start-fast.mjs +214 -0
- package/hooks/subagent-tracker.mjs +267 -0
- package/hooks/subagent-verifier.mjs +9 -4
- package/hub/account-broker.mjs +1036 -66
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +96 -58
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +163 -126
- package/hub/bridge.mjs +832 -291
- package/hub/cli-adapter-base.mjs +321 -97
- package/hub/codex-adapter.mjs +179 -93
- package/hub/codex-compat.mjs +4 -3
- package/hub/codex-preflight.mjs +113 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/dynamic-routing-engine.mjs +511 -0
- package/hub/fullcycle.mjs +34 -17
- package/hub/gemini-adapter.mjs +77 -95
- package/hub/hitl.mjs +202 -143
- package/hub/intent.mjs +171 -39
- package/hub/lib/bash-path.mjs +73 -0
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/env-detect.mjs +145 -0
- package/hub/lib/hosts-compat.mjs +319 -0
- package/hub/lib/mcp-response-cache.mjs +239 -0
- package/hub/lib/memory-store.mjs +303 -107
- package/hub/lib/path-utils.mjs +165 -0
- package/hub/lib/phase-manager.mjs +493 -0
- package/hub/lib/process-utils.mjs +797 -104
- package/hub/lib/prompt-tmp.mjs +33 -0
- package/hub/lib/risk-tier.mjs +53 -0
- package/hub/lib/spawn-trace.mjs +561 -0
- package/hub/lib/ssh-command.mjs +44 -24
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/staged-file-detect.mjs +43 -0
- package/hub/lib/state-snapshot.mjs +293 -0
- package/hub/lib/tfx-route-args.mjs +309 -0
- package/hub/lib/timeline-adapter.mjs +190 -0
- package/hub/lib/trace-recorder.mjs +153 -0
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/quota-middleware.mjs +70 -0
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +65 -48
- package/hub/quality/deslop.mjs +70 -49
- package/hub/reflexion.mjs +116 -58
- package/hub/research.mjs +34 -25
- package/hub/router.mjs +941 -791
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/routing-snapshot.mjs +261 -0
- package/hub/session-fingerprint.mjs +129 -63
- package/hub/state.mjs +86 -44
- package/hub/team/retry-state-machine.mjs +267 -0
- package/hub/token-mode.mjs +76 -72
- package/hub/workers/worker-utils.mjs +22 -16
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +184 -27
- package/hud/context-monitor.mjs +133 -44
- package/hud/hud-qos-status.mjs +153 -50
- package/hud/mission-board.mjs +56 -0
- package/hud/providers/claude.mjs +398 -114
- package/hud/providers/codex.mjs +96 -21
- package/hud/providers/gemini.mjs +184 -51
- package/hud/renderers.mjs +413 -123
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +81 -39
- package/mesh/index.mjs +69 -0
- package/mesh/mesh-budget.mjs +137 -0
- package/mesh/mesh-heartbeat.mjs +100 -0
- package/mesh/mesh-protocol.mjs +96 -0
- package/mesh/mesh-queue.mjs +163 -0
- package/mesh/mesh-registry.mjs +78 -0
- package/mesh/mesh-router.mjs +77 -0
- package/package.json +5 -1
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +67 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +13 -5
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +58 -30
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +48 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +393 -249
- package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
- package/scripts/lib/mcp-guard-engine.mjs +1282 -116
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +24 -14
- package/scripts/lib/mcp-server-catalog.mjs +355 -118
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +72 -12
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +36 -11
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
|
@@ -10,42 +10,126 @@ const COMPLEXITY_INDICATORS = {
|
|
|
10
10
|
// 높은 복잡도 (0.7-1.0)
|
|
11
11
|
high: {
|
|
12
12
|
keywords: [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
"refactor",
|
|
14
|
+
"architecture",
|
|
15
|
+
"security",
|
|
16
|
+
"migration",
|
|
17
|
+
"distributed",
|
|
18
|
+
"concurrent",
|
|
19
|
+
"parallel",
|
|
20
|
+
"optimization",
|
|
21
|
+
"performance",
|
|
22
|
+
"scalability",
|
|
23
|
+
"cryptograph",
|
|
24
|
+
"encryption",
|
|
25
|
+
"authentication",
|
|
26
|
+
"authorization",
|
|
27
|
+
"database schema",
|
|
28
|
+
"data model",
|
|
29
|
+
"state machine",
|
|
30
|
+
"event-driven",
|
|
31
|
+
"microservice",
|
|
32
|
+
"orchestrat",
|
|
33
|
+
"pipeline",
|
|
34
|
+
"workflow",
|
|
18
35
|
// 한국어
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
"리팩터링",
|
|
37
|
+
"리팩토링",
|
|
38
|
+
"아키텍처",
|
|
39
|
+
"보안",
|
|
40
|
+
"마이그레이션",
|
|
41
|
+
"분산",
|
|
42
|
+
"동시성",
|
|
43
|
+
"병렬",
|
|
44
|
+
"최적화",
|
|
45
|
+
"성능",
|
|
46
|
+
"확장성",
|
|
47
|
+
"암호화",
|
|
48
|
+
"인증",
|
|
49
|
+
"인가",
|
|
50
|
+
"데이터베이스 스키마",
|
|
51
|
+
"데이터 모델",
|
|
52
|
+
"상태 머신",
|
|
53
|
+
"이벤트 드리븐",
|
|
54
|
+
"마이크로서비스",
|
|
55
|
+
"오케스트레이션",
|
|
23
56
|
],
|
|
24
57
|
weight: 0.85,
|
|
25
58
|
},
|
|
26
59
|
// 중간 복잡도 (0.4-0.7)
|
|
27
60
|
medium: {
|
|
28
61
|
keywords: [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
62
|
+
"implement",
|
|
63
|
+
"integrate",
|
|
64
|
+
"api",
|
|
65
|
+
"endpoint",
|
|
66
|
+
"middleware",
|
|
67
|
+
"validation",
|
|
68
|
+
"error handling",
|
|
69
|
+
"testing",
|
|
70
|
+
"debug",
|
|
71
|
+
"fix bug",
|
|
72
|
+
"configuration",
|
|
73
|
+
"deploy",
|
|
74
|
+
"ci/cd",
|
|
75
|
+
"docker",
|
|
76
|
+
"container",
|
|
77
|
+
"cache",
|
|
78
|
+
"queue",
|
|
79
|
+
"webhook",
|
|
80
|
+
"notification",
|
|
81
|
+
"logging",
|
|
33
82
|
// 한국어
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
83
|
+
"구현",
|
|
84
|
+
"통합",
|
|
85
|
+
"엔드포인트",
|
|
86
|
+
"미들웨어",
|
|
87
|
+
"유효성 검사",
|
|
88
|
+
"에러 처리",
|
|
89
|
+
"오류 처리",
|
|
90
|
+
"테스트",
|
|
91
|
+
"디버깅",
|
|
92
|
+
"버그 수정",
|
|
93
|
+
"설정",
|
|
94
|
+
"배포",
|
|
95
|
+
"컨테이너",
|
|
96
|
+
"캐시",
|
|
97
|
+
"알림",
|
|
98
|
+
"로깅",
|
|
37
99
|
],
|
|
38
100
|
weight: 0.55,
|
|
39
101
|
},
|
|
40
102
|
// 낮은 복잡도 (0.1-0.4)
|
|
41
103
|
low: {
|
|
42
104
|
keywords: [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
105
|
+
"readme",
|
|
106
|
+
"comment",
|
|
107
|
+
"typo",
|
|
108
|
+
"rename",
|
|
109
|
+
"format",
|
|
110
|
+
"lint",
|
|
111
|
+
"update version",
|
|
112
|
+
"bump",
|
|
113
|
+
"add dependency",
|
|
114
|
+
"install",
|
|
115
|
+
"simple",
|
|
116
|
+
"trivial",
|
|
117
|
+
"minor",
|
|
118
|
+
"small change",
|
|
119
|
+
"one-liner",
|
|
46
120
|
// 한국어
|
|
47
|
-
|
|
48
|
-
|
|
121
|
+
"문서화",
|
|
122
|
+
"주석",
|
|
123
|
+
"오타",
|
|
124
|
+
"이름 변경",
|
|
125
|
+
"포맷",
|
|
126
|
+
"버전 업데이트",
|
|
127
|
+
"의존성 추가",
|
|
128
|
+
"설치",
|
|
129
|
+
"간단",
|
|
130
|
+
"사소한",
|
|
131
|
+
"소규모",
|
|
132
|
+
"한 줄",
|
|
49
133
|
],
|
|
50
134
|
weight: 0.2,
|
|
51
135
|
},
|
|
@@ -98,7 +182,7 @@ function semanticDepth(text) {
|
|
|
98
182
|
* @returns {number} 0-1
|
|
99
183
|
*/
|
|
100
184
|
function taskScope(text) {
|
|
101
|
-
const lines = text.split(
|
|
185
|
+
const lines = text.split("\n").filter((l) => l.trim().length > 0);
|
|
102
186
|
const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
103
187
|
const fileRefs = (text.match(/[\w\-/]+\.\w{1,5}/g) || []).length;
|
|
104
188
|
|
|
@@ -118,9 +202,22 @@ function taskScope(text) {
|
|
|
118
202
|
function uncertainty(text) {
|
|
119
203
|
const lower = text.toLowerCase();
|
|
120
204
|
const uncertainWords = [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
205
|
+
"maybe",
|
|
206
|
+
"perhaps",
|
|
207
|
+
"might",
|
|
208
|
+
"could",
|
|
209
|
+
"possibly",
|
|
210
|
+
"unclear",
|
|
211
|
+
"not sure",
|
|
212
|
+
"investigate",
|
|
213
|
+
"explore",
|
|
214
|
+
"research",
|
|
215
|
+
"try",
|
|
216
|
+
"consider",
|
|
217
|
+
"evaluate",
|
|
218
|
+
"assess",
|
|
219
|
+
"determine",
|
|
220
|
+
"figure out",
|
|
124
221
|
];
|
|
125
222
|
let count = 0;
|
|
126
223
|
for (const w of uncertainWords) {
|
|
@@ -138,11 +235,17 @@ function uncertainty(text) {
|
|
|
138
235
|
* @returns {{ score: number, breakdown: { lexical: number, semantic: number, scope: number, uncertainty: number } }}
|
|
139
236
|
*/
|
|
140
237
|
export function scoreComplexity(taskDescription) {
|
|
141
|
-
if (!taskDescription || typeof taskDescription !==
|
|
142
|
-
return {
|
|
238
|
+
if (!taskDescription || typeof taskDescription !== "string") {
|
|
239
|
+
return {
|
|
240
|
+
score: 0,
|
|
241
|
+
breakdown: { lexical: 0, semantic: 0, scope: 0, uncertainty: 0 },
|
|
242
|
+
};
|
|
143
243
|
}
|
|
144
244
|
|
|
145
|
-
const words = taskDescription
|
|
245
|
+
const words = taskDescription
|
|
246
|
+
.toLowerCase()
|
|
247
|
+
.split(/\s+/)
|
|
248
|
+
.filter((w) => w.length > 0);
|
|
146
249
|
const lexical = lexicalComplexity(words);
|
|
147
250
|
const semantic = semanticDepth(taskDescription);
|
|
148
251
|
const scope = taskScope(taskDescription);
|
|
@@ -150,7 +253,7 @@ export function scoreComplexity(taskDescription) {
|
|
|
150
253
|
|
|
151
254
|
// 가중 합산: 어휘(20%) + 시맨틱(35%) + 범위(25%) + 불확실성(20%)
|
|
152
255
|
const score = Math.min(
|
|
153
|
-
lexical * 0.
|
|
256
|
+
lexical * 0.2 + semantic * 0.35 + scope * 0.25 + uncertain * 0.2,
|
|
154
257
|
1,
|
|
155
258
|
);
|
|
156
259
|
|
package/hub/routing/index.mjs
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
// Q-Learning 동적 라우팅 + agent-map.json 정적 폴백
|
|
3
3
|
// 환경변수 TRIFLUX_DYNAMIC_ROUTING=true 로 옵트인 (기본 false)
|
|
4
4
|
|
|
5
|
-
import { createRequire } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { scoreComplexity } from "./complexity.mjs";
|
|
7
|
+
import { QLearningRouter } from "./q-learning.mjs";
|
|
8
8
|
|
|
9
9
|
const _require = createRequire(import.meta.url);
|
|
10
10
|
|
|
11
11
|
/** agent-map.json 정적 매핑 */
|
|
12
12
|
let AGENT_MAP;
|
|
13
13
|
try {
|
|
14
|
-
AGENT_MAP = _require(
|
|
14
|
+
AGENT_MAP = _require("../team/agent-map.json");
|
|
15
15
|
} catch {
|
|
16
16
|
AGENT_MAP = {};
|
|
17
17
|
}
|
|
@@ -37,7 +37,7 @@ function getRouter() {
|
|
|
37
37
|
*/
|
|
38
38
|
function isDynamicRoutingEnabled() {
|
|
39
39
|
const env = process.env.TRIFLUX_DYNAMIC_ROUTING;
|
|
40
|
-
return env ===
|
|
40
|
+
return env === "true" || env === "1";
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -48,19 +48,19 @@ function isDynamicRoutingEnabled() {
|
|
|
48
48
|
* @param {string} [taskDescription=''] — 작업 설명 (동적 라우팅용)
|
|
49
49
|
* @returns {{ cliType: string, source: 'dynamic' | 'static', confidence: number, complexity: number }}
|
|
50
50
|
*/
|
|
51
|
-
export function resolveRoute(agentType, taskDescription =
|
|
51
|
+
export function resolveRoute(agentType, taskDescription = "") {
|
|
52
52
|
// 정적 기본값
|
|
53
53
|
const staticCli = AGENT_MAP[agentType] || agentType;
|
|
54
54
|
const { score: complexity } = scoreComplexity(taskDescription);
|
|
55
55
|
|
|
56
56
|
// 동적 라우팅 비활성화 시 정적 매핑 반환
|
|
57
57
|
if (!isDynamicRoutingEnabled()) {
|
|
58
|
-
return { cliType: staticCli, source:
|
|
58
|
+
return { cliType: staticCli, source: "static", confidence: 1, complexity };
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// 작업 설명 없으면 정적 폴백
|
|
62
62
|
if (!taskDescription || taskDescription.trim().length === 0) {
|
|
63
|
-
return { cliType: staticCli, source:
|
|
63
|
+
return { cliType: staticCli, source: "static", confidence: 1, complexity };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const router = getRouter();
|
|
@@ -68,12 +68,17 @@ export function resolveRoute(agentType, taskDescription = '') {
|
|
|
68
68
|
|
|
69
69
|
// 신뢰도 기준 미달 시 정적 폴백
|
|
70
70
|
if (prediction.confidence < 0.6) {
|
|
71
|
-
return {
|
|
71
|
+
return {
|
|
72
|
+
cliType: staticCli,
|
|
73
|
+
source: "static",
|
|
74
|
+
confidence: prediction.confidence,
|
|
75
|
+
complexity,
|
|
76
|
+
};
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
return {
|
|
75
80
|
cliType: prediction.action,
|
|
76
|
-
source:
|
|
81
|
+
source: "dynamic",
|
|
77
82
|
confidence: prediction.confidence,
|
|
78
83
|
complexity,
|
|
79
84
|
};
|
|
@@ -113,5 +118,5 @@ export function routerStatus() {
|
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
// re-export for convenience
|
|
116
|
-
export { scoreComplexity } from
|
|
117
|
-
export {
|
|
121
|
+
export { scoreComplexity } from "./complexity.mjs";
|
|
122
|
+
export { ACTIONS, QLearningRouter } from "./q-learning.mjs";
|
|
@@ -2,44 +2,84 @@
|
|
|
2
2
|
// agent-map.json 폴백을 유지하면서, 작업 결과 피드백으로 가중치를 학습한다.
|
|
3
3
|
// 외부 의존성 없음 (fs, path, crypto만 사용)
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { scoreComplexity } from
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { scoreComplexity } from "./complexity.mjs";
|
|
11
11
|
|
|
12
12
|
const _require = createRequire(import.meta.url);
|
|
13
13
|
|
|
14
14
|
/** agent-map.json 정적 매핑 (폴백용) */
|
|
15
15
|
let AGENT_MAP;
|
|
16
16
|
try {
|
|
17
|
-
AGENT_MAP = _require(
|
|
17
|
+
AGENT_MAP = _require("../team/agent-map.json");
|
|
18
18
|
} catch {
|
|
19
19
|
AGENT_MAP = {};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/** 사용 가능한 CLI 액션 */
|
|
23
|
-
const ACTIONS = [
|
|
23
|
+
const ACTIONS = ["codex", "gemini", "claude", "haiku", "sonnet"];
|
|
24
24
|
|
|
25
25
|
/** 특성 벡터 키워드 (48차원, 에이전트 타입 기반) */
|
|
26
26
|
const FEATURE_KEYWORDS = [
|
|
27
27
|
// 실행/구현 (codex 친화)
|
|
28
|
-
|
|
28
|
+
"implement",
|
|
29
|
+
"execute",
|
|
30
|
+
"build",
|
|
31
|
+
"fix",
|
|
32
|
+
"debug",
|
|
33
|
+
"code",
|
|
34
|
+
"refactor",
|
|
35
|
+
"test",
|
|
29
36
|
// 분석/설계 (claude/sonnet 친화)
|
|
30
|
-
|
|
37
|
+
"analyze",
|
|
38
|
+
"architect",
|
|
39
|
+
"plan",
|
|
40
|
+
"review",
|
|
41
|
+
"security",
|
|
42
|
+
"optimize",
|
|
43
|
+
"research",
|
|
44
|
+
"evaluate",
|
|
31
45
|
// 디자인/문서 (gemini 친화)
|
|
32
|
-
|
|
46
|
+
"design",
|
|
47
|
+
"ui",
|
|
48
|
+
"ux",
|
|
49
|
+
"frontend",
|
|
50
|
+
"visual",
|
|
51
|
+
"document",
|
|
52
|
+
"write",
|
|
53
|
+
"explain",
|
|
33
54
|
// 간단/빠른 (haiku 친화)
|
|
34
|
-
|
|
55
|
+
"simple",
|
|
56
|
+
"quick",
|
|
57
|
+
"trivial",
|
|
58
|
+
"rename",
|
|
59
|
+
"format",
|
|
60
|
+
"lint",
|
|
61
|
+
"typo",
|
|
62
|
+
"minor",
|
|
35
63
|
// 한국어 — 실행/구현 (codex 친화)
|
|
36
|
-
|
|
64
|
+
"구현",
|
|
65
|
+
"빌드",
|
|
66
|
+
"수정",
|
|
67
|
+
"디버깅",
|
|
68
|
+
"리팩터링",
|
|
69
|
+
"테스트",
|
|
37
70
|
// 한국어 — 분석/설계 (claude/sonnet 친화)
|
|
38
|
-
|
|
71
|
+
"분석",
|
|
72
|
+
"아키텍처",
|
|
73
|
+
"설계",
|
|
74
|
+
"검토",
|
|
75
|
+
"보안",
|
|
76
|
+
"최적화",
|
|
39
77
|
// 한국어 — 디자인/문서 (gemini 친화)
|
|
40
|
-
|
|
78
|
+
"디자인",
|
|
79
|
+
"문서화",
|
|
41
80
|
// 한국어 — 간단/빠른 (haiku 친화)
|
|
42
|
-
|
|
81
|
+
"간단",
|
|
82
|
+
"사소한",
|
|
43
83
|
];
|
|
44
84
|
|
|
45
85
|
/**
|
|
@@ -66,7 +106,7 @@ function extractFeatures(text) {
|
|
|
66
106
|
* @returns {string}
|
|
67
107
|
*/
|
|
68
108
|
function stateKey(features) {
|
|
69
|
-
const hash = createHash(
|
|
109
|
+
const hash = createHash("sha256").update(features.join(",")).digest("hex");
|
|
70
110
|
return hash.slice(0, 16);
|
|
71
111
|
}
|
|
72
112
|
|
|
@@ -131,9 +171,11 @@ export class QLearningRouter {
|
|
|
131
171
|
this._epsilon = opts.epsilon ?? 0.3;
|
|
132
172
|
this._epsilonDecay = opts.epsilonDecay ?? 0.995;
|
|
133
173
|
// epsilon=0 시에도 최소 탐색 보장 (pure-exploit 방지)
|
|
134
|
-
this._epsilonMin =
|
|
174
|
+
this._epsilonMin =
|
|
175
|
+
opts.epsilonMin ?? Math.max(0.01, Math.min(0.05, this._epsilon));
|
|
135
176
|
this._minConfidence = opts.minConfidence ?? 0.6;
|
|
136
|
-
this._modelPath =
|
|
177
|
+
this._modelPath =
|
|
178
|
+
opts.modelPath ?? join(homedir(), ".omc", "routing-model.json");
|
|
137
179
|
|
|
138
180
|
/** @type {Map<string, Map<string, number>>} state -> (action -> Q-value) */
|
|
139
181
|
this._qTable = new Map();
|
|
@@ -200,13 +242,16 @@ export class QLearningRouter {
|
|
|
200
242
|
}
|
|
201
243
|
|
|
202
244
|
// 신뢰도 계산: 방문 횟수 기반 (최소 10회 이상이면 안정)
|
|
203
|
-
const confidence = visits >= 10
|
|
204
|
-
? Math.min(visits / 50, 1)
|
|
205
|
-
: visits / 10;
|
|
245
|
+
const confidence = visits >= 10 ? Math.min(visits / 50, 1) : visits / 10;
|
|
206
246
|
|
|
207
247
|
const { score: complexity } = scoreComplexity(taskDescription);
|
|
208
248
|
|
|
209
|
-
const result = {
|
|
249
|
+
const result = {
|
|
250
|
+
action,
|
|
251
|
+
confidence,
|
|
252
|
+
exploration: isExploration,
|
|
253
|
+
complexity,
|
|
254
|
+
};
|
|
210
255
|
// 탐색(랜덤) 결과는 캐싱하지 않음 — 매번 새로운 랜덤 액션 생성
|
|
211
256
|
if (!isExploration) this._cache.set(state, result);
|
|
212
257
|
return result;
|
|
@@ -241,7 +286,10 @@ export class QLearningRouter {
|
|
|
241
286
|
this._totalUpdates++;
|
|
242
287
|
|
|
243
288
|
// 엡실론 감쇠
|
|
244
|
-
this._epsilon = Math.max(
|
|
289
|
+
this._epsilon = Math.max(
|
|
290
|
+
this._epsilonMin,
|
|
291
|
+
this._epsilon * this._epsilonDecay,
|
|
292
|
+
);
|
|
245
293
|
|
|
246
294
|
// 캐시 무효화 (해당 상태)
|
|
247
295
|
this._cache.clear();
|
|
@@ -251,7 +299,7 @@ export class QLearningRouter {
|
|
|
251
299
|
* Q-table을 JSON 파일로 영속화
|
|
252
300
|
*/
|
|
253
301
|
save() {
|
|
254
|
-
const dir = join(this._modelPath,
|
|
302
|
+
const dir = join(this._modelPath, "..");
|
|
255
303
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
256
304
|
|
|
257
305
|
const data = {
|
|
@@ -269,7 +317,7 @@ export class QLearningRouter {
|
|
|
269
317
|
data.visitCounts[state] = count;
|
|
270
318
|
}
|
|
271
319
|
|
|
272
|
-
writeFileSync(this._modelPath, JSON.stringify(data, null, 2),
|
|
320
|
+
writeFileSync(this._modelPath, JSON.stringify(data, null, 2), "utf8");
|
|
273
321
|
}
|
|
274
322
|
|
|
275
323
|
/**
|
|
@@ -280,7 +328,7 @@ export class QLearningRouter {
|
|
|
280
328
|
if (!existsSync(this._modelPath)) return false;
|
|
281
329
|
|
|
282
330
|
try {
|
|
283
|
-
const raw = readFileSync(this._modelPath,
|
|
331
|
+
const raw = readFileSync(this._modelPath, "utf8");
|
|
284
332
|
const data = JSON.parse(raw);
|
|
285
333
|
if (data.version !== 1) return false;
|
|
286
334
|
|
|
@@ -333,4 +381,4 @@ export class QLearningRouter {
|
|
|
333
381
|
}
|
|
334
382
|
|
|
335
383
|
// 모듈 레벨 export
|
|
336
|
-
export { ACTIONS, FEATURE_KEYWORDS,
|
|
384
|
+
export { ACTIONS, extractFeatures, FEATURE_KEYWORDS, LRUCache, stateKey };
|