@triflux/core 10.50.1 → 10.50.2

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 (55) hide show
  1. package/hub/index.mjs +2 -8
  2. package/hub/lib/cto-env.mjs +0 -23
  3. package/hub/paths.mjs +0 -6
  4. package/hub/pipeline/index.mjs +0 -190
  5. package/hub/pipeline/transitions.mjs +0 -10
  6. package/hub/reflexion.mjs +1 -84
  7. package/hud/cli-policy.mjs +1 -5
  8. package/hud/colors.mjs +0 -13
  9. package/hud/constants.mjs +4 -135
  10. package/hud/context-monitor.mjs +3 -64
  11. package/hud/hud-qos-status.mjs +43 -225
  12. package/hud/providers/claude.mjs +4 -16
  13. package/hud/providers/gemini.mjs +7 -698
  14. package/hud/renderers.mjs +40 -294
  15. package/hud/terminal.mjs +1 -63
  16. package/hud/utils.mjs +0 -24
  17. package/package.json +1 -2
  18. package/scripts/lib/gemini-profiles.mjs +1 -1
  19. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  20. package/scripts/lib/psmux-info.mjs +18 -49
  21. package/scripts/lib/skill-template.mjs +0 -26
  22. package/hub/codex-compat.mjs +0 -12
  23. package/hub/fullcycle.mjs +0 -113
  24. package/hub/gemini-adapter.mjs +0 -174
  25. package/hub/intent.mjs +0 -321
  26. package/hub/lib/mcp-response-cache.mjs +0 -239
  27. package/hub/lib/path-utils.mjs +0 -165
  28. package/hub/lib/timeline-adapter.mjs +0 -190
  29. package/hub/pipeline/gates/confidence.mjs +0 -75
  30. package/hub/pipeline/gates/consensus.mjs +0 -106
  31. package/hub/pipeline/gates/index.mjs +0 -9
  32. package/hub/pipeline/gates/selfcheck.mjs +0 -120
  33. package/hub/quality/deslop.mjs +0 -274
  34. package/hub/research.mjs +0 -155
  35. package/hub/role-control-events.mjs +0 -113
  36. package/hub/routing/complexity.mjs +0 -269
  37. package/hub/routing/index.mjs +0 -122
  38. package/hub/routing/q-learning.mjs +0 -387
  39. package/hub/token-mode.mjs +0 -228
  40. package/hud/mission-board.mjs +0 -56
  41. package/hud/providers/cto.mjs +0 -76
  42. package/scripts/lib/agent-json.mjs +0 -27
  43. package/scripts/lib/async.mjs +0 -174
  44. package/scripts/lib/cli-agy.mjs +0 -85
  45. package/scripts/lib/cli-codex.mjs +0 -62
  46. package/scripts/lib/cli-gemini.mjs +0 -37
  47. package/scripts/lib/env.mjs +0 -14
  48. package/scripts/lib/hub.mjs +0 -48
  49. package/scripts/lib/pid.mjs +0 -63
  50. package/scripts/lib/quota.mjs +0 -47
  51. package/scripts/lib/skill-state.mjs +0 -220
  52. package/scripts/lib/team.mjs +0 -75
  53. package/scripts/lib/timeout.mjs +0 -66
  54. package/scripts/lib/tmp.mjs +0 -26
  55. package/scripts/lib/toml.mjs +0 -132
package/hub/intent.mjs DELETED
@@ -1,321 +0,0 @@
1
- // hub/intent.mjs — Intent Classification Engine
2
- // 사용자 요청의 "진짜 의도"를 분석 → 카테고리 분류 → 최적 에이전트/모델 자동 선택
3
-
4
- import { execFileSync } from "node:child_process";
5
- import crypto from "node:crypto";
6
- import { resolveCodexAgentPolicy } from "../scripts/lib/agent-route-policy.mjs";
7
- import { whichCommand } from "./platform.mjs";
8
-
9
- /** 캐시 엔트리: { category, confidence, ts } */
10
- const _intentCache = new Map();
11
- const CACHE_TTL_MS = 5 * 60 * 1000; // 5분
12
-
13
- /** codex 설치 여부 (프로세스당 1회 확인) */
14
- let _codexAvailable = null;
15
-
16
- /** @experimental 런타임 미연결 — 향후 통합 예정 */
17
- function _isCodexAvailable() {
18
- if (_codexAvailable !== null) return _codexAvailable;
19
- _codexAvailable = Boolean(whichCommand("codex"));
20
- return _codexAvailable;
21
- }
22
-
23
- function _promptHash(prompt) {
24
- return crypto.createHash("md5").update(prompt).digest("hex");
25
- }
26
-
27
- function _getCached(hash) {
28
- const entry = _intentCache.get(hash);
29
- if (!entry) return null;
30
- if (Date.now() - entry.ts > CACHE_TTL_MS) {
31
- _intentCache.delete(hash);
32
- return null;
33
- }
34
- return entry;
35
- }
36
-
37
- function _tryCodexClassify(prompt) {
38
- try {
39
- const raw = execFileSync(
40
- "codex",
41
- ["exec", `Classify intent: ${prompt}. Reply JSON: {intent, confidence}`],
42
- { timeout: 8000, encoding: "utf8" },
43
- );
44
- // JSON 블록 추출 (응답에 다른 텍스트가 섞일 수 있음)
45
- const match = raw.match(/\{[\s\S]*?\}/);
46
- if (!match) return null;
47
- const parsed = JSON.parse(match[0]);
48
- const intent = typeof parsed.intent === "string" ? parsed.intent : null;
49
- const confidence =
50
- typeof parsed.confidence === "number" ? parsed.confidence : null;
51
- if (!intent || confidence === null) return null;
52
- // intent가 알려진 카테고리여야 함
53
- if (!INTENT_CATEGORIES[intent]) return null;
54
- return { category: intent, confidence };
55
- } catch {
56
- return null;
57
- }
58
- }
59
-
60
- /**
61
- * @experimental
62
- * triflux 특화 의도 카테고리 (10개)
63
- */
64
- export const INTENT_CATEGORIES = {
65
- implement: { agent: "executor", mcp: "implement" },
66
- debug: { agent: "debugger", mcp: "implement" },
67
- analyze: { agent: "analyst", mcp: "analyze" },
68
- design: { agent: "architect", mcp: "analyze" },
69
- review: { agent: "code-reviewer", mcp: "review" },
70
- document: { agent: "writer", mcp: "docs" },
71
- research: { agent: "scientist", mcp: "analyze" },
72
- "quick-fix": { agent: "build-fixer", mcp: "implement" },
73
- explain: { agent: "writer", mcp: "docs" },
74
- test: { agent: "test-engineer", mcp: null },
75
- };
76
-
77
- function routingFor(category) {
78
- const route = INTENT_CATEGORIES[category] || INTENT_CATEGORIES.implement;
79
- return {
80
- agent: route.agent,
81
- mcp: route.mcp,
82
- effort: resolveCodexAgentPolicy(route.agent).profile,
83
- };
84
- }
85
-
86
- /** @internal 키워드 → 카테고리 매핑 패턴 */
87
- const KEYWORD_PATTERNS = [
88
- {
89
- category: "implement",
90
- keywords: [
91
- "구현",
92
- "만들",
93
- "추가",
94
- "생성",
95
- "작성",
96
- "빌드",
97
- "implement",
98
- "create",
99
- "add",
100
- "build",
101
- "make",
102
- "develop",
103
- ],
104
- weight: 1.0,
105
- },
106
- {
107
- category: "debug",
108
- keywords: [
109
- "버그",
110
- "에러",
111
- "오류",
112
- "고쳐",
113
- "수정",
114
- "디버그",
115
- "fix",
116
- "bug",
117
- "error",
118
- "debug",
119
- "troubleshoot",
120
- "crash",
121
- "broken",
122
- ],
123
- weight: 1.0,
124
- },
125
- {
126
- category: "analyze",
127
- keywords: [
128
- "분석",
129
- "조사",
130
- "파악",
131
- "analyze",
132
- "investigate",
133
- "examine",
134
- "inspect",
135
- ],
136
- weight: 0.9,
137
- },
138
- {
139
- category: "design",
140
- keywords: [
141
- "설계",
142
- "아키텍처",
143
- "디자인",
144
- "구조",
145
- "design",
146
- "architect",
147
- "structure",
148
- ],
149
- weight: 0.9,
150
- },
151
- {
152
- category: "review",
153
- keywords: ["리뷰", "검토", "코드리뷰", "review", "code review", "audit"],
154
- weight: 1.0,
155
- },
156
- {
157
- category: "document",
158
- keywords: [
159
- "문서",
160
- "도큐먼트",
161
- "문서화",
162
- "document",
163
- "docs",
164
- "documentation",
165
- "readme",
166
- ],
167
- weight: 0.9,
168
- },
169
- {
170
- category: "research",
171
- keywords: ["리서치", "연구", "탐색", "research", "explore", "study"],
172
- weight: 0.8,
173
- },
174
- {
175
- category: "quick-fix",
176
- keywords: ["빠르게", "간단히", "급한", "quick fix", "hotfix", "quick"],
177
- weight: 0.85,
178
- },
179
- {
180
- category: "explain",
181
- keywords: [
182
- "설명",
183
- "뭐야",
184
- "알려",
185
- "이해",
186
- "explain",
187
- "what is",
188
- "how does",
189
- "tell me",
190
- "describe",
191
- ],
192
- weight: 1.0,
193
- },
194
- {
195
- category: "test",
196
- keywords: [
197
- "테스트",
198
- "테스팅",
199
- "시험",
200
- "검증",
201
- "test",
202
- "testing",
203
- "spec",
204
- "unit test",
205
- ],
206
- weight: 1.0,
207
- },
208
- ];
209
-
210
- /**
211
- * 키워드 기반 빠른 분류 (0-cost, Codex 호출 없이)
212
- * 고신뢰(>0.8) 시 Codex triage 건너뜀
213
- * @param {string} prompt - 사용자 프롬프트
214
- * @returns {{ category: string, confidence: number }}
215
- */
216
- export function quickClassify(prompt) {
217
- if (!prompt || typeof prompt !== "string" || !prompt.trim()) {
218
- return { category: "implement", confidence: 0.1 };
219
- }
220
-
221
- const lower = prompt.toLowerCase().trim();
222
- let bestCategory = null;
223
- let bestScore = 0;
224
- let bestMatchCount = 0;
225
-
226
- for (const { category, keywords, weight } of KEYWORD_PATTERNS) {
227
- let matchCount = 0;
228
- for (const kw of keywords) {
229
- if (lower.includes(kw)) matchCount++;
230
- }
231
- if (matchCount > 0) {
232
- const score = (matchCount / keywords.length) * weight;
233
- if (score > bestScore) {
234
- bestScore = score;
235
- bestCategory = category;
236
- bestMatchCount = matchCount;
237
- }
238
- }
239
- }
240
-
241
- if (!bestCategory) {
242
- return { category: "implement", confidence: 0.3 };
243
- }
244
-
245
- // 매칭 품질 기반 신뢰도 (0.5~0.95 범위) — matchCount 기준으로 정규화 (3개 매칭이면 최대)
246
- const confidence = Math.min(
247
- 0.95,
248
- 0.5 + (Math.min(bestMatchCount, 3) / 3) * 0.45,
249
- );
250
- return { category: bestCategory, confidence };
251
- }
252
-
253
- /**
254
- * 전체 의도 분류 — routing 정보 포함
255
- * Codex triage 경로: codex 설치 시 실행, confidence > 0.8이면 즉시 반환
256
- * quickClassify가 고신뢰(>0.8)이면 Codex 건너뜀
257
- * 결과는 md5 해시 기반 Map에 5분 TTL로 캐싱
258
- * @param {string} prompt
259
- * @returns {{ category: string, confidence: number, reasoning: string, routing: { agent: string, mcp: string|null, effort: string|null } }}
260
- */
261
- export function classifyIntent(prompt) {
262
- const hash = _promptHash(String(prompt ?? ""));
263
-
264
- // 캐시 확인
265
- const cached = _getCached(hash);
266
- if (cached) {
267
- const routing = routingFor(cached.category);
268
- return {
269
- category: cached.category,
270
- confidence: cached.confidence,
271
- reasoning: `cache-hit: ${cached.category} (${cached.confidence.toFixed(2)})`,
272
- routing,
273
- };
274
- }
275
-
276
- // quickClassify 먼저
277
- const quick = quickClassify(prompt);
278
-
279
- let category = quick.category;
280
- let confidence = quick.confidence;
281
- let reasoning;
282
-
283
- // quickClassify가 고신뢰(>0.8)이면 Codex 건너뜀
284
- if (quick.confidence > 0.8) {
285
- reasoning = `keyword-match: ${category} (${confidence.toFixed(2)})`;
286
- } else if (_isCodexAvailable()) {
287
- // Codex triage
288
- const codexResult = _tryCodexClassify(String(prompt ?? ""));
289
- if (codexResult && codexResult.confidence > 0.8) {
290
- category = codexResult.category;
291
- confidence = codexResult.confidence;
292
- reasoning = `codex-triage: ${category} (${confidence.toFixed(2)})`;
293
- } else {
294
- reasoning = `keyword-match(codex-fallback): ${category} (${confidence.toFixed(2)})`;
295
- }
296
- } else {
297
- reasoning = `keyword-match: ${category} (${confidence.toFixed(2)})`;
298
- }
299
-
300
- // 캐시 저장
301
- _intentCache.set(hash, { category, confidence, ts: Date.now() });
302
-
303
- const routing = routingFor(category);
304
- return {
305
- category,
306
- confidence,
307
- reasoning,
308
- routing,
309
- };
310
- }
311
-
312
- /**
313
- * 분류 히스토리 기반 학습 (reflexion 연동 가능)
314
- * @param {string} prompt
315
- * @param {string} actualCategory - 실제 카테고리
316
- */
317
- export function refineClassification(prompt, actualCategory) {
318
- // reflexion 연동 시 store에 오분류 기록 저장 예정
319
- void prompt;
320
- void actualCategory;
321
- }
@@ -1,239 +0,0 @@
1
- // hub/lib/mcp-response-cache.mjs — MCP 응답 캐시 (LRU + TTL)
2
- // Deep 스킬에서 3개 모델이 같은 context7 문서를 요청하면 1번만 호출, 나머지는 캐시 hit.
3
- // 파일 기반 영속 + 메모리 기반 핫캐시 이중 구조.
4
-
5
- import { createHash } from "node:crypto";
6
- import {
7
- existsSync,
8
- mkdirSync,
9
- readdirSync,
10
- readFileSync,
11
- unlinkSync,
12
- writeFileSync,
13
- } from "node:fs";
14
- import { homedir } from "node:os";
15
- import { join } from "node:path";
16
-
17
- const DEFAULT_CACHE_DIR = join(homedir(), ".triflux", "mcp-cache");
18
- const DEFAULT_MAX_ENTRIES = 500;
19
-
20
- // 서버별 TTL (ms)
21
- const SERVER_TTL = Object.freeze({
22
- context7: 60 * 60 * 1000, // 1시간 — 라이브러리 문서는 자주 안 바뀜
23
- "brave-search": 10 * 60 * 1000, // 10분 — 웹 검색
24
- exa: 10 * 60 * 1000, // 10분
25
- tavily: 10 * 60 * 1000, // 10분
26
- serena: 5 * 60 * 1000, // 5분 — 코드 분석 (코드 변경 가능)
27
- jira: 2 * 60 * 1000, // 2분 — 이슈 상태 자주 변경
28
- notion: 5 * 60 * 1000, // 5분
29
- _default: 5 * 60 * 1000, // 기본 5분
30
- });
31
-
32
- function hashKey(server, method, params) {
33
- const payload = JSON.stringify({ server, method, params });
34
- return createHash("sha256").update(payload).digest("hex").slice(0, 16);
35
- }
36
-
37
- /**
38
- * MCP 응답 캐시 생성.
39
- * @param {object} [opts]
40
- * @param {string} [opts.cacheDir] — 캐시 디렉토리
41
- * @param {number} [opts.maxEntries] — 최대 엔트리 수 (LRU)
42
- * @returns {McpResponseCache}
43
- */
44
- export function createMcpResponseCache(opts = {}) {
45
- const cacheDir = opts.cacheDir || DEFAULT_CACHE_DIR;
46
- const maxEntries = opts.maxEntries || DEFAULT_MAX_ENTRIES;
47
-
48
- mkdirSync(cacheDir, { recursive: true });
49
-
50
- // 메모리 핫캐시 (Map — insertion order = LRU)
51
- const hot = new Map();
52
-
53
- let hits = 0;
54
- let misses = 0;
55
-
56
- function getTtl(server) {
57
- return SERVER_TTL[server] || SERVER_TTL._default;
58
- }
59
-
60
- function getCachePath(key) {
61
- return join(cacheDir, `${key}.json`);
62
- }
63
-
64
- /**
65
- * 캐시 조회.
66
- * @param {string} server — MCP 서버 이름
67
- * @param {string} method — MCP 메서드
68
- * @param {*} params — 요청 파라미터
69
- * @returns {{ hit: boolean, data?: * }}
70
- */
71
- function get(server, method, params) {
72
- const key = hashKey(server, method, params);
73
- const ttl = getTtl(server);
74
-
75
- // 1) 메모리 핫캐시
76
- if (hot.has(key)) {
77
- const entry = hot.get(key);
78
- if (Date.now() - entry.ts < ttl) {
79
- hits++;
80
- // LRU: 삭제 후 재삽입 (Map은 insertion order)
81
- hot.delete(key);
82
- hot.set(key, entry);
83
- return { hit: true, data: entry.data };
84
- }
85
- hot.delete(key);
86
- }
87
-
88
- // 2) 파일 캐시
89
- const path = getCachePath(key);
90
- if (existsSync(path)) {
91
- try {
92
- const entry = JSON.parse(readFileSync(path, "utf8"));
93
- if (Date.now() - entry.ts < ttl) {
94
- hits++;
95
- hot.set(key, entry);
96
- evictIfNeeded();
97
- return { hit: true, data: entry.data };
98
- }
99
- // expired — 삭제
100
- try {
101
- unlinkSync(path);
102
- } catch {
103
- /* ignore */
104
- }
105
- } catch {
106
- /* corrupt file */
107
- }
108
- }
109
-
110
- misses++;
111
- return { hit: false };
112
- }
113
-
114
- /**
115
- * 캐시 저장.
116
- * @param {string} server
117
- * @param {string} method
118
- * @param {*} params
119
- * @param {*} data — 응답 데이터
120
- */
121
- function set(server, method, params, data) {
122
- const key = hashKey(server, method, params);
123
- const entry = { ts: Date.now(), server, method, data };
124
-
125
- // 메모리
126
- hot.set(key, entry);
127
- evictIfNeeded();
128
-
129
- // 파일
130
- try {
131
- writeFileSync(getCachePath(key), JSON.stringify(entry), "utf8");
132
- } catch {
133
- /* best-effort */
134
- }
135
- }
136
-
137
- function evictIfNeeded() {
138
- while (hot.size > maxEntries) {
139
- const oldest = hot.keys().next().value;
140
- hot.delete(oldest);
141
- }
142
- }
143
-
144
- /**
145
- * 만료된 파일 캐시 정리.
146
- * @returns {number} 삭제된 파일 수
147
- */
148
- function prune() {
149
- let pruned = 0;
150
- try {
151
- for (const name of readdirSync(cacheDir)) {
152
- if (!name.endsWith(".json")) continue;
153
- const path = join(cacheDir, name);
154
- try {
155
- const entry = JSON.parse(readFileSync(path, "utf8"));
156
- const ttl = getTtl(entry.server || "_default");
157
- if (Date.now() - entry.ts >= ttl) {
158
- unlinkSync(path);
159
- pruned++;
160
- }
161
- } catch {
162
- // corrupt — 삭제
163
- try {
164
- unlinkSync(path);
165
- pruned++;
166
- } catch {
167
- /* ignore */
168
- }
169
- }
170
- }
171
- } catch {
172
- /* ignore */
173
- }
174
- return pruned;
175
- }
176
-
177
- /**
178
- * 캐시 통계.
179
- * @returns {{ hits, misses, hotSize, diskFiles, hitRate }}
180
- */
181
- function stats() {
182
- let diskFiles = 0;
183
- try {
184
- diskFiles = readdirSync(cacheDir).filter((n) =>
185
- n.endsWith(".json"),
186
- ).length;
187
- } catch {
188
- /* ignore */
189
- }
190
-
191
- const total = hits + misses;
192
- return {
193
- hits,
194
- misses,
195
- hotSize: hot.size,
196
- diskFiles,
197
- hitRate: total > 0 ? `${((hits / total) * 100).toFixed(1)}%` : "0%",
198
- };
199
- }
200
-
201
- /** 전체 캐시 초기화. */
202
- function clear() {
203
- hot.clear();
204
- try {
205
- for (const name of readdirSync(cacheDir)) {
206
- if (name.endsWith(".json")) {
207
- try {
208
- unlinkSync(join(cacheDir, name));
209
- } catch {
210
- /* ignore */
211
- }
212
- }
213
- }
214
- } catch {
215
- /* ignore */
216
- }
217
- hits = 0;
218
- misses = 0;
219
- }
220
-
221
- return Object.freeze({
222
- get,
223
- set,
224
- prune,
225
- stats,
226
- clear,
227
- get cacheDir() {
228
- return cacheDir;
229
- },
230
- });
231
- }
232
-
233
- // 싱글톤 인스턴스 (Hub 프로세스 내에서 공유)
234
- let _instance = null;
235
-
236
- export function getMcpResponseCache(opts) {
237
- if (!_instance) _instance = createMcpResponseCache(opts);
238
- return _instance;
239
- }