@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
@@ -1,274 +0,0 @@
1
- /**
2
- * Anti-Slop Code Pass
3
- * AI 생성 코드에서 불필요한 요소를 자동 탐지/제거하는 정적 분석 모듈
4
- */
5
-
6
- import { readdir, readFile, stat, writeFile } from "node:fs/promises";
7
- import { join } from "node:path";
8
-
9
- /** @type {ReadonlyArray<{type: string, pattern: RegExp, severity: string, autoFixable: boolean, multiline: boolean}>} */
10
- export const SLOP_PATTERNS = Object.freeze([
11
- {
12
- type: "trivial_comment",
13
- pattern: /^\s*\/\/\s*(import|define|set|get|return|export)\s/i,
14
- severity: "low",
15
- autoFixable: true,
16
- multiline: false,
17
- },
18
- {
19
- type: "empty_catch",
20
- pattern: /catch\s*\([^)]*\)\s*\{\s*\}/,
21
- severity: "med",
22
- autoFixable: false,
23
- multiline: true,
24
- },
25
- {
26
- type: "console_debug",
27
- pattern: /^\s*console\.(log|debug|info)\(/,
28
- severity: "low",
29
- autoFixable: true,
30
- multiline: false,
31
- },
32
- {
33
- type: "useless_jsdoc",
34
- pattern: /\/\*\*\s*\n\s*\*\s*\n\s*\*\//,
35
- severity: "low",
36
- autoFixable: true,
37
- multiline: true,
38
- },
39
- {
40
- type: "rethrow_only",
41
- pattern: /catch\s*\((\w+)\)\s*\{\s*throw\s+\1\s*;?\s*\}/,
42
- severity: "med",
43
- autoFixable: false,
44
- multiline: true,
45
- },
46
- {
47
- type: "redundant_type",
48
- pattern:
49
- /:\s*(string|number|boolean)\s*=\s*('[^']*'|"[^"]*"|\d+|true|false)/,
50
- severity: "low",
51
- autoFixable: false,
52
- multiline: false,
53
- },
54
- {
55
- type: "commented_code",
56
- pattern:
57
- /^\s*\/\/\s*(const |let |var |function |class |if\s*\(|for\s*\(|while\s*\(|return |await )/,
58
- severity: "low",
59
- autoFixable: true,
60
- multiline: false,
61
- },
62
- ]);
63
-
64
- const SEVERITY_WEIGHT = { low: 2, med: 5 };
65
-
66
- /**
67
- * 파일 내용에서 slop 패턴 탐지
68
- * @param {string} content - 파일 내용
69
- * @param {string} [filePath] - 파일 경로 (보고용)
70
- * @returns {{ issues: Array<{line: number, type: string, severity: string, suggestion: string, text: string, autoFixable: boolean}>, score: number }}
71
- */
72
- export function detectSlop(content, filePath = "") {
73
- const lines = content.split("\n");
74
- const issues = [];
75
-
76
- for (const sp of SLOP_PATTERNS) {
77
- if (sp.multiline) continue;
78
- for (let i = 0; i < lines.length; i++) {
79
- if (sp.pattern.test(lines[i])) {
80
- issues.push({
81
- line: i + 1,
82
- type: sp.type,
83
- severity: sp.severity,
84
- suggestion: `${sp.type} 패턴 감지`,
85
- text: lines[i].trim(),
86
- autoFixable: sp.autoFixable,
87
- file: filePath,
88
- });
89
- }
90
- }
91
- }
92
-
93
- for (const sp of SLOP_PATTERNS) {
94
- if (!sp.multiline) continue;
95
- const regex = new RegExp(
96
- sp.pattern.source,
97
- sp.pattern.flags.replace("g", "") + "g",
98
- );
99
- let match;
100
- while ((match = regex.exec(content)) !== null) {
101
- const line = content.substring(0, match.index).split("\n").length;
102
- issues.push({
103
- line,
104
- type: sp.type,
105
- severity: sp.severity,
106
- suggestion: `${sp.type} 패턴 감지`,
107
- text: match[0].split("\n")[0].trim(),
108
- autoFixable: sp.autoFixable,
109
- file: filePath,
110
- });
111
- }
112
- }
113
-
114
- issues.sort((a, b) => a.line - b.line);
115
-
116
- const totalPenalty = issues.reduce(
117
- (sum, i) => sum + (SEVERITY_WEIGHT[i.severity] || 2),
118
- 0,
119
- );
120
- const score = Math.max(0, 100 - totalPenalty);
121
-
122
- return { issues, score };
123
- }
124
-
125
- /**
126
- * 자동 수정 (safe transforms만)
127
- * @param {string} content - 파일 내용
128
- * @param {Array<{type: string, autoFixable: boolean}>} issues - detectSlop 결과
129
- * @returns {{ fixed: string, applied: number, skipped: number }}
130
- */
131
- export function autoFixSlop(content, issues) {
132
- if (!issues || issues.length === 0)
133
- return { fixed: content, applied: 0, skipped: 0 };
134
-
135
- const fixable = issues.filter((i) => i.autoFixable);
136
- const skipped = issues.length - fixable.length;
137
-
138
- if (fixable.length === 0) return { fixed: content, applied: 0, skipped };
139
-
140
- let fixed = content;
141
- let applied = 0;
142
-
143
- // Multi-line: useless_jsdoc 제거
144
- if (fixable.some((i) => i.type === "useless_jsdoc")) {
145
- const matches = fixed.match(/\/\*\*\s*\n\s*\*\s*\n\s*\*\/\n?/g);
146
- if (matches) {
147
- fixed = fixed.replace(/\/\*\*\s*\n\s*\*\s*\n\s*\*\/\n?/g, "");
148
- applied += matches.length;
149
- }
150
- }
151
-
152
- // Line-level: trivial_comment, console_debug, commented_code 제거
153
- const lineTypes = new Set(
154
- fixable
155
- .filter((i) =>
156
- ["trivial_comment", "console_debug", "commented_code"].includes(i.type),
157
- )
158
- .map((i) => i.type),
159
- );
160
-
161
- if (lineTypes.size > 0) {
162
- const linePatterns = SLOP_PATTERNS.filter((p) => lineTypes.has(p.type));
163
- const lines = fixed.split("\n");
164
- const result = [];
165
- for (const line of lines) {
166
- let remove = false;
167
- for (const p of linePatterns) {
168
- if (p.pattern.test(line)) {
169
- remove = true;
170
- applied++;
171
- break;
172
- }
173
- }
174
- if (!remove) result.push(line);
175
- }
176
- fixed = result.join("\n");
177
- }
178
-
179
- return { fixed, applied, skipped };
180
- }
181
-
182
- function matchesGlob(filePath, pattern) {
183
- const normalized = "/" + filePath.replace(/\\/g, "/");
184
-
185
- // **/*.ext → 확장자 매칭
186
- if (pattern.startsWith("**/*.")) {
187
- const ext = pattern.slice(4);
188
- return normalized.endsWith(ext);
189
- }
190
-
191
- // *.ext → 확장자 매칭 (디렉토리 무관)
192
- if (pattern.startsWith("*.") && !pattern.includes("/")) {
193
- const ext = pattern.slice(1);
194
- return normalized.endsWith(ext);
195
- }
196
-
197
- // **/dir/** → 디렉토리 포함 여부
198
- const dirMatch = pattern.match(/^\*\*\/([^*]+)\/\*\*$/);
199
- if (dirMatch) {
200
- return normalized.includes("/" + dirMatch[1] + "/");
201
- }
202
-
203
- return false;
204
- }
205
-
206
- /**
207
- * 디렉토리 전체 스캔
208
- * @param {string} dirPath - 스캔 대상 디렉토리
209
- * @param {object} [opts]
210
- * @param {string[]} [opts.include] - 포함할 glob 패턴
211
- * @param {string[]} [opts.exclude] - 제외할 glob 패턴
212
- * @param {boolean} [opts.autoFix] - 자동 수정 여부
213
- * @returns {Promise<{ files: Array<{path: string, issues: Array, score: number}>, summary: object }>}
214
- */
215
- export async function scanDirectory(dirPath, opts = {}) {
216
- const {
217
- include = ["**/*.mjs", "**/*.js", "**/*.ts"],
218
- exclude = ["**/node_modules/**", "**/dist/**", "**/.git/**"],
219
- autoFix = false,
220
- } = opts;
221
-
222
- const entries = await readdir(dirPath, { recursive: true });
223
- const files = [];
224
-
225
- for (const entry of entries) {
226
- const normalized = entry.replace(/\\/g, "/");
227
- const fullPath = join(dirPath, entry);
228
-
229
- let st;
230
- try {
231
- st = await stat(fullPath);
232
- } catch {
233
- continue;
234
- }
235
- if (!st.isFile()) continue;
236
-
237
- const included = include.some((p) => matchesGlob(normalized, p));
238
- const excluded = exclude.some((p) => matchesGlob(normalized, p));
239
- if (!included || excluded) continue;
240
-
241
- const fileContent = await readFile(fullPath, "utf-8");
242
- const { issues, score } = detectSlop(fileContent, normalized);
243
-
244
- if (autoFix && issues.length > 0) {
245
- const { fixed, applied } = autoFixSlop(fileContent, issues);
246
- if (applied > 0) await writeFile(fullPath, fixed, "utf-8");
247
- }
248
-
249
- files.push({ path: normalized, issues, score });
250
- }
251
-
252
- const totalIssues = files.reduce((sum, f) => sum + f.issues.length, 0);
253
- const avgScore =
254
- files.length > 0
255
- ? Math.round(files.reduce((sum, f) => sum + f.score, 0) / files.length)
256
- : 100;
257
-
258
- const byType = {};
259
- for (const f of files) {
260
- for (const issue of f.issues) {
261
- byType[issue.type] = (byType[issue.type] || 0) + 1;
262
- }
263
- }
264
-
265
- return {
266
- files,
267
- summary: {
268
- totalFiles: files.length,
269
- totalIssues,
270
- averageScore: avgScore,
271
- byType,
272
- },
273
- };
274
- }
package/hub/research.mjs DELETED
@@ -1,155 +0,0 @@
1
- // hub/research.mjs — 자율 웹 리서치 엔진 코어
2
- // 검색 쿼리 생성 → 결과 정규화 → 보고서 빌드 → 저장
3
-
4
- import { mkdirSync, writeFileSync } from "node:fs";
5
- import { join, resolve } from "node:path";
6
- import { TFX_REPORTS_DIR } from "./paths.mjs";
7
-
8
- /**
9
- * @experimental 런타임 미연결 — 향후 통합 예정
10
- * @experimental
11
- * 주제에서 검색 쿼리 3-5개를 자동 생성한다.
12
- * 한국어 주제 → 한국어 + 영어 혼합, 영어 주제 → 영어 쿼리.
13
- * @param {string} topic - 리서치 주제
14
- * @param {'ko'|'en'|'auto'} [lang='auto'] - 언어 힌트
15
- * @returns {string[]} 검색 쿼리 배열
16
- */
17
- export function generateQueries(topic, lang = "auto") {
18
- if (!topic || typeof topic !== "string" || !topic.trim()) return [];
19
-
20
- const t = topic.trim();
21
- const detectedLang = lang === "auto" ? detectLang(t) : lang;
22
-
23
- if (detectedLang === "ko") {
24
- return [
25
- `${t} 정리`,
26
- `${t} 비교 분석`,
27
- `${t} 최신 동향 ${new Date().getFullYear()}`,
28
- `${toEnglishQuery(t)} overview`,
29
- `${toEnglishQuery(t)} comparison ${new Date().getFullYear()}`,
30
- ];
31
- }
32
-
33
- return [
34
- `${t} overview`,
35
- `${t} comparison`,
36
- `${t} best practices ${new Date().getFullYear()}`,
37
- `${t} pros and cons`,
38
- ];
39
- }
40
-
41
- /**
42
- * 검색 원시 결과를 정규화한다. 중복 URL 제거 + 빈/null 필터링.
43
- * @param {Array<object|null|undefined>} rawResults - 검색 엔진 원시 결과
44
- * @returns {Array<{title: string, url: string, snippet: string}>}
45
- */
46
- export function normalizeResults(rawResults) {
47
- if (!Array.isArray(rawResults)) return [];
48
-
49
- const seen = new Set();
50
- const out = [];
51
-
52
- for (const r of rawResults) {
53
- if (!r || typeof r !== "object") continue;
54
- const url = (r.url || r.link || "").trim();
55
- const title = (r.title || r.name || "").trim();
56
- const snippet = (r.snippet || r.description || r.content || "").trim();
57
-
58
- if (!url || seen.has(url)) continue;
59
- seen.add(url);
60
- out.push({ title, url, snippet });
61
- }
62
-
63
- return out;
64
- }
65
-
66
- /**
67
- * 리서치 보고서를 마크다운으로 빌드한다.
68
- * @param {string} topic - 리서치 주제
69
- * @param {string[]} findings - 핵심 발견 목록
70
- * @param {Array<{title: string, url: string, snippet: string}>} sources - 출처 목록
71
- * @returns {string} 마크다운 문자열
72
- */
73
- export function buildReport(topic, findings, sources) {
74
- const date = new Date().toISOString().split("T")[0];
75
- const findingsSection = (findings || [])
76
- .map((f, i) => `${i + 1}. ${f}`)
77
- .join("\n");
78
- const sourcesSection = (sources || [])
79
- .map(
80
- (s) =>
81
- `- [${s.title || s.url}](${s.url})${s.snippet ? ` — ${s.snippet}` : ""}`,
82
- )
83
- .join("\n");
84
-
85
- return `# Research: ${topic}
86
- Date: ${date}
87
-
88
- ## Executive Summary
89
- ${topic}에 대한 자동 리서치 결과입니다.
90
-
91
- ## Key Findings
92
- ${findingsSection || "_발견 없음_"}
93
-
94
- ## Actionable Recommendations
95
- 리서치 결과를 바탕으로 다음 단계를 검토하세요.
96
-
97
- ## Sources
98
- ${sourcesSection || "_출처 없음_"}
99
- `;
100
- }
101
-
102
- /**
103
- * 보고서를 .tfx/reports/research-{timestamp}.md에 저장한다.
104
- * @param {string} topic - 리서치 주제 (파일명 생성용)
105
- * @param {string} content - 마크다운 보고서 내용
106
- * @param {string} [baseDir=process.cwd()] - 프로젝트 루트 경로
107
- * @returns {string} 저장된 파일 경로
108
- */
109
- export function saveReport(topic, content, baseDir = process.cwd()) {
110
- const dir = join(baseDir, TFX_REPORTS_DIR);
111
- const resolvedDir = resolve(dir);
112
- const expectedBase = resolve(baseDir || TFX_REPORTS_DIR);
113
- if (!resolvedDir.startsWith(expectedBase)) {
114
- throw new Error("Invalid report directory: path traversal detected");
115
- }
116
- mkdirSync(dir, { recursive: true });
117
-
118
- const ts = new Date()
119
- .toISOString()
120
- .replace(/[:.]/g, "-")
121
- .replace("T", "_")
122
- .slice(0, 19);
123
- const slug = (topic || "untitled")
124
- .replace(/[^a-zA-Z0-9가-힣\s-]/g, "")
125
- .replace(/\s+/g, "-")
126
- .slice(0, 40)
127
- .toLowerCase();
128
- const filename = `research-${ts}-${slug}.md`;
129
- const filepath = join(dir, filename);
130
-
131
- writeFileSync(filepath, content, "utf-8");
132
- return filepath;
133
- }
134
-
135
- // ── internal helpers ──
136
-
137
- /**
138
- * 텍스트에 한글이 포함되어 있으면 'ko', 아니면 'en'
139
- * @param {string} text
140
- * @returns {'ko'|'en'}
141
- */
142
- function detectLang(text) {
143
- return /[가-힣]/.test(text) ? "ko" : "en";
144
- }
145
-
146
- /**
147
- * 한국어 토픽에서 영어 검색 쿼리용 문자열 추출.
148
- * 영문/숫자만 남기고, 없으면 원문 그대로 반환.
149
- * @param {string} text
150
- * @returns {string}
151
- */
152
- function toEnglishQuery(text) {
153
- const eng = text.replace(/[가-힣\s]+/g, " ").trim();
154
- return eng || text;
155
- }
@@ -1,113 +0,0 @@
1
- const EVENT_NAMES = new Set(["capability.degraded", "drift.detected"]);
2
- const CONTROL_ACTIONS = new Set([
3
- "log_only",
4
- "candidate_exclude",
5
- "adapter_disable",
6
- "role_failover",
7
- "dispatch_block",
8
- "manager_disable",
9
- ]);
10
- const OPTIONAL_FIELDS = [
11
- "project_id",
12
- "role_key_wire",
13
- "candidate_agent_id",
14
- "transport_id",
15
- "capability",
16
- "expected_version",
17
- "observed_version",
18
- "trace_id",
19
- "error_code",
20
- ];
21
-
22
- export const ROLE_CONTROL_EVENT_SCHEMA_VERSION = "tfx.role-control-event.v1";
23
- export const ROLE_CONTROL_EVENT_FIELDS = Object.freeze([
24
- "schema_version",
25
- "event",
26
- "event_id",
27
- "module",
28
- "dur_ms",
29
- "control_action",
30
- "reason_code",
31
- ...OPTIONAL_FIELDS,
32
- ]);
33
-
34
- function eventError(code, message = code) {
35
- const error = new Error(message);
36
- error.code = code;
37
- return error;
38
- }
39
-
40
- function defaultEventId() {
41
- return (
42
- globalThis.crypto?.randomUUID?.() ??
43
- `evt_${Math.random().toString(36).slice(2)}`
44
- );
45
- }
46
-
47
- function validateRequired(event) {
48
- if (!EVENT_NAMES.has(event.event))
49
- throw eventError("ROLE_CONTROL_EVENT_INVALID");
50
- if (typeof event.event_id !== "string" || !event.event_id) {
51
- throw eventError("ROLE_CONTROL_EVENT_REQUIRED");
52
- }
53
- if (typeof event.module !== "string" || !event.module) {
54
- throw eventError("ROLE_CONTROL_EVENT_REQUIRED");
55
- }
56
- if (!Number.isFinite(event.dur_ms) || event.dur_ms < 0) {
57
- throw eventError("ROLE_CONTROL_EVENT_REQUIRED");
58
- }
59
- if (!CONTROL_ACTIONS.has(event.control_action)) {
60
- throw eventError("ROLE_CONTROL_EVENT_REQUIRED");
61
- }
62
- if (typeof event.reason_code !== "string" || !event.reason_code) {
63
- throw eventError("ROLE_CONTROL_EVENT_REQUIRED");
64
- }
65
- }
66
-
67
- /**
68
- * Build an allowlisted event object. Unknown domain fields are intentionally
69
- * discarded before the event reaches a logger.
70
- */
71
- export function buildRoleControlEvent(
72
- input = {},
73
- { eventId = defaultEventId } = {},
74
- ) {
75
- const event = {
76
- schema_version: ROLE_CONTROL_EVENT_SCHEMA_VERSION,
77
- event: input.event,
78
- event_id: input.event_id ?? eventId(),
79
- module: input.module,
80
- dur_ms: input.dur_ms,
81
- control_action: input.control_action,
82
- reason_code: input.reason_code,
83
- };
84
- for (const field of OPTIONAL_FIELDS) {
85
- if (input[field] !== undefined) event[field] = input[field];
86
- }
87
- validateRequired(event);
88
- return event;
89
- }
90
-
91
- export function buildCapabilityDegradedEvent(input = {}, options = {}) {
92
- return buildRoleControlEvent(
93
- { ...input, event: "capability.degraded" },
94
- options,
95
- );
96
- }
97
-
98
- export function buildDriftDetectedEvent(input = {}, options = {}) {
99
- return buildRoleControlEvent({ ...input, event: "drift.detected" }, options);
100
- }
101
-
102
- /**
103
- * Pino-style loggers receive event as an object field and as the message.
104
- */
105
- export function emitRoleControlEvent(log, event, level = "warn") {
106
- if (!log || typeof log[level] !== "function") {
107
- throw eventError("ROLE_CONTROL_LOGGER_INVALID");
108
- }
109
- const sanitized = buildRoleControlEvent(event, {
110
- eventId: () => event?.event_id,
111
- });
112
- log[level](sanitized, sanitized.event);
113
- }