triflux 8.12.2 → 8.12.5
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/bin/triflux.mjs +64 -0
- package/hub/team/backend.mjs +2 -1
- package/hub/team/cli/commands/start/index.mjs +2 -2
- package/hub/team/cli/commands/start/parse-args.mjs +10 -0
- package/hub/workers/delegator-mcp.mjs +2 -5
- package/package.json +1 -1
- package/scripts/cache-buildup.mjs +24 -395
- package/scripts/cache-doctor.mjs +149 -0
- package/scripts/cache-warmup.mjs +514 -0
- package/scripts/headless-guard.mjs +38 -0
- package/scripts/lib/env-probe.mjs +130 -0
- package/scripts/lib/mcp-filter.mjs +730 -720
- package/scripts/lib/mcp-manifest.mjs +79 -0
- package/scripts/mcp-gateway-config.mjs +104 -7
- package/scripts/mcp-gateway-start.mjs +7 -0
- package/scripts/mcp-gateway-verify.mjs +15 -1
- package/scripts/preflight-cache.mjs +68 -137
- package/scripts/setup.mjs +7 -8
- package/scripts/tfx-route-worker.mjs +59 -1
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex-swarm/SKILL.md +17 -10
- package/skills/tfx-doctor/SKILL.md +5 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-setup/SKILL.md +182 -7
package/bin/triflux.mjs
CHANGED
|
@@ -1030,6 +1030,20 @@ async function cmdDoctor(options = {}) {
|
|
|
1030
1030
|
warn("Gemini 쿼터 캐시 재생성 실패");
|
|
1031
1031
|
}
|
|
1032
1032
|
}
|
|
1033
|
+
try {
|
|
1034
|
+
const { buildAll } = await import("../scripts/cache-warmup.mjs");
|
|
1035
|
+
const warmupSummary = buildAll({ cwd: process.cwd(), force: true });
|
|
1036
|
+
if (warmupSummary.ok) {
|
|
1037
|
+
report.actions.push({ type: "rebuild", name: "warmup-caches", status: "ok", built: warmupSummary.built });
|
|
1038
|
+
ok("Phase 1 웜업 캐시 재생성됨");
|
|
1039
|
+
} else {
|
|
1040
|
+
report.actions.push({ type: "rebuild", name: "warmup-caches", status: "failed" });
|
|
1041
|
+
warn("Phase 1 웜업 캐시 재생성 실패");
|
|
1042
|
+
}
|
|
1043
|
+
} catch {
|
|
1044
|
+
report.actions.push({ type: "rebuild", name: "warmup-caches", status: "failed" });
|
|
1045
|
+
warn("Phase 1 웜업 캐시 재생성 실패");
|
|
1046
|
+
}
|
|
1033
1047
|
console.log(`\n ${LINE}`);
|
|
1034
1048
|
console.log(` ${GREEN_BRIGHT}${BOLD}✓ 캐시 초기화 + 재생성 완료${RESET}\n`);
|
|
1035
1049
|
report.status = report.actions.some((action) => action.status === "failed") ? "issues" : "ok";
|
|
@@ -1083,6 +1097,19 @@ async function cmdDoctor(options = {}) {
|
|
|
1083
1097
|
} catch { try { unlinkSync(fp); cleaned++; ok(`손상된 캐시 정리: ${name}`); } catch {} }
|
|
1084
1098
|
}
|
|
1085
1099
|
if (cleaned === 0) info("에러 캐시 없음");
|
|
1100
|
+
try {
|
|
1101
|
+
const { fixCaches } = await import("../scripts/cache-doctor.mjs");
|
|
1102
|
+
const cacheRepair = await fixCaches({ cwd: process.cwd() });
|
|
1103
|
+
if (cacheRepair.fixed.length > 0 && cacheRepair.ok) {
|
|
1104
|
+
ok(`웜업 캐시 자동 복구: ${cacheRepair.fixed.join(", ")}`);
|
|
1105
|
+
} else if (cacheRepair.fixed.length > 0) {
|
|
1106
|
+
warn(`웜업 캐시 자동 복구 실패: ${cacheRepair.fixed.join(", ")}`);
|
|
1107
|
+
} else {
|
|
1108
|
+
info("웜업 캐시: 이미 정상 상태");
|
|
1109
|
+
}
|
|
1110
|
+
} catch {
|
|
1111
|
+
warn("웜업 캐시 자동 복구 실패");
|
|
1112
|
+
}
|
|
1086
1113
|
console.log(`\n ${LINE}`);
|
|
1087
1114
|
info("수정 완료 — 아래 진단 결과를 확인하세요");
|
|
1088
1115
|
console.log("");
|
|
@@ -1257,6 +1284,43 @@ async function cmdDoctor(options = {}) {
|
|
|
1257
1284
|
info(`수동: node ${join(PKG_ROOT, "scripts", "mcp-check.mjs")}`);
|
|
1258
1285
|
}
|
|
1259
1286
|
|
|
1287
|
+
// 9.5. Phase 1 웜업 캐시
|
|
1288
|
+
section("Warmup Cache");
|
|
1289
|
+
try {
|
|
1290
|
+
const { verifyCaches } = await import("../scripts/cache-doctor.mjs");
|
|
1291
|
+
const cacheVerification = verifyCaches({ cwd: process.cwd() });
|
|
1292
|
+
const brokenCaches = cacheVerification.results.filter((result) => result.status !== "ok");
|
|
1293
|
+
|
|
1294
|
+
addDoctorCheck(report, {
|
|
1295
|
+
name: "warmup-cache",
|
|
1296
|
+
status: cacheVerification.ok ? "ok" : "issues",
|
|
1297
|
+
files: cacheVerification.results.map((result) => ({
|
|
1298
|
+
target: result.target,
|
|
1299
|
+
status: result.status,
|
|
1300
|
+
path: result.file,
|
|
1301
|
+
})),
|
|
1302
|
+
...(cacheVerification.ok ? {} : { fix: "tfx doctor --fix" }),
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
if (brokenCaches.length === 0) {
|
|
1306
|
+
ok("4개 웜업 캐시 정상");
|
|
1307
|
+
} else {
|
|
1308
|
+
warn(`${brokenCaches.length}개 웜업 캐시 이슈 발견`);
|
|
1309
|
+
for (const entry of brokenCaches) {
|
|
1310
|
+
info(`${entry.target}: ${entry.status}`);
|
|
1311
|
+
}
|
|
1312
|
+
if (!fix) issues += brokenCaches.length;
|
|
1313
|
+
}
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
addDoctorCheck(report, {
|
|
1316
|
+
name: "warmup-cache",
|
|
1317
|
+
status: "invalid",
|
|
1318
|
+
fix: "node scripts/cache-doctor.mjs --fix",
|
|
1319
|
+
});
|
|
1320
|
+
warn(`웜업 캐시 검사 실패: ${error.message}`);
|
|
1321
|
+
issues++;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1260
1324
|
// 10. CLI 이슈 트래커
|
|
1261
1325
|
section("CLI Issues");
|
|
1262
1326
|
const issuesFile = join(CLAUDE_DIR, "cache", "cli-issues.jsonl");
|
package/hub/team/backend.mjs
CHANGED
|
@@ -19,9 +19,10 @@ export class CodexBackend {
|
|
|
19
19
|
*/
|
|
20
20
|
buildArgs(prompt, resultFile, opts = {}) {
|
|
21
21
|
const modelFlag = opts.model ? ` --model '${opts.model}'` : "";
|
|
22
|
+
const cwdFlag = opts.cwd ? ` --cwd '${opts.cwd}'` : "";
|
|
22
23
|
// Codex 0.117.0+: config.toml에 sandbox 설정이 있으면 CLI 플래그 중복 불가
|
|
23
24
|
// --dangerously-bypass-approvals-and-sandbox 대신 exec 서브커맨드만 사용 (config가 sandbox 관리)
|
|
24
|
-
return `codex exec ${prompt} --output-last-message '${resultFile}' --color never${modelFlag}`;
|
|
25
|
+
return `codex exec ${prompt} --output-last-message '${resultFile}' --color never${modelFlag}${cwdFlag}`;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
env() { return {}; }
|
|
@@ -40,7 +40,7 @@ function renderTmuxInstallHelp() {
|
|
|
40
40
|
export { parseTeamArgs };
|
|
41
41
|
|
|
42
42
|
export async function teamStart(args = []) {
|
|
43
|
-
const { agents, lead, layout, teammateMode, task: rawTask, assigns, autoAttach, progressive, timeoutSec, verbose, dashboard, dashboardLayout, dashboardSize, dashboardAnchor, mcpProfile, model } = parseTeamArgs(args);
|
|
43
|
+
const { agents, lead, layout, teammateMode, task: rawTask, assigns, autoAttach, progressive, timeoutSec, verbose, dashboard, dashboardLayout, dashboardSize, dashboardAnchor, mcpProfile, model, cwd } = parseTeamArgs(args);
|
|
44
44
|
// --assign 사용 시 task를 자동 생성
|
|
45
45
|
const task = rawTask || (assigns.length > 0 ? assigns.map(a => a.prompt).join(" + ") : "");
|
|
46
46
|
if (!task) return printStartUsage();
|
|
@@ -84,7 +84,7 @@ export async function teamStart(args = []) {
|
|
|
84
84
|
const state = effectiveMode === "in-process"
|
|
85
85
|
? await startInProcessTeam({ sessionId, task, lead, agents, subtasks, hubUrl })
|
|
86
86
|
: effectiveMode === "headless"
|
|
87
|
-
? await startHeadlessTeam({ sessionId, task, lead, agents, subtasks, layout, assigns, autoAttach, progressive, timeoutSec, verbose, dashboard, dashboardLayout, dashboardSize, dashboardAnchor, mcpProfile, model })
|
|
87
|
+
? await startHeadlessTeam({ sessionId, task, lead, agents, subtasks, layout, assigns, autoAttach, progressive, timeoutSec, verbose, dashboard, dashboardLayout, dashboardSize, dashboardAnchor, mcpProfile, model, cwd })
|
|
88
88
|
: effectiveMode === "wt"
|
|
89
89
|
? await startWtTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl })
|
|
90
90
|
: await startMuxTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl, teammateMode: effectiveMode });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import { normalizeLayout, normalizeTeammateMode } from "../../services/runtime-mode.mjs";
|
|
2
3
|
import { parseDashboardLayout } from "../../../dashboard-layout.mjs";
|
|
3
4
|
import { parseDashboardAnchor } from "../../../dashboard-anchor.mjs";
|
|
@@ -54,6 +55,7 @@ export function parseTeamArgs(args = []) {
|
|
|
54
55
|
let dashboardAnchor = "window";
|
|
55
56
|
let mcpProfile = "";
|
|
56
57
|
let model = "";
|
|
58
|
+
let cwd = "";
|
|
57
59
|
|
|
58
60
|
for (let index = 0; index < args.length; index += 1) {
|
|
59
61
|
const current = args[index];
|
|
@@ -92,6 +94,13 @@ export function parseTeamArgs(args = []) {
|
|
|
92
94
|
mcpProfile = args[++index].trim();
|
|
93
95
|
} else if ((current === "--model" || current === "-m") && args[index + 1]) {
|
|
94
96
|
model = args[++index].trim();
|
|
97
|
+
} else if (current === "--cwd" && args[index + 1]) {
|
|
98
|
+
let p = args[++index].trim();
|
|
99
|
+
// MSYS/Git Bash 드라이브 문자 변환: /c/... → C:/...
|
|
100
|
+
if (process.platform === "win32" && /^\/[a-zA-Z]\//.test(p)) {
|
|
101
|
+
p = p[1].toUpperCase() + ":" + p.slice(2);
|
|
102
|
+
}
|
|
103
|
+
cwd = resolve(p);
|
|
95
104
|
} else if (current.startsWith("-")) {
|
|
96
105
|
console.warn(` ⚠ 미인식 플래그 무시: ${current}`);
|
|
97
106
|
} else {
|
|
@@ -116,5 +125,6 @@ export function parseTeamArgs(args = []) {
|
|
|
116
125
|
dashboardAnchor,
|
|
117
126
|
mcpProfile,
|
|
118
127
|
model,
|
|
128
|
+
cwd,
|
|
119
129
|
};
|
|
120
130
|
}
|
|
@@ -204,7 +204,7 @@ function loadAvailableServersFromSearchEngineCache(cwd = process.cwd()) {
|
|
|
204
204
|
|
|
205
205
|
try {
|
|
206
206
|
const parsed = JSON.parse(readFileSync(cacheFile, 'utf8'));
|
|
207
|
-
if (!Array.isArray(parsed?.engines)) return
|
|
207
|
+
if (!Array.isArray(parsed?.engines)) return null;
|
|
208
208
|
return parsed.engines
|
|
209
209
|
.filter((engine) => engine?.status === 'available')
|
|
210
210
|
.map((engine) => (typeof engine?.name === 'string' ? engine.name.trim() : ''))
|
|
@@ -647,16 +647,13 @@ export class DelegatorMcpWorker {
|
|
|
647
647
|
}
|
|
648
648
|
|
|
649
649
|
_getMcpPolicyOptions(args) {
|
|
650
|
-
const cachedAvailableServers = loadAvailableServersFromSearchEngineCache(args.cwd || this.cwd);
|
|
651
650
|
return {
|
|
652
651
|
agentType: args.agentType || 'executor',
|
|
653
652
|
requestedProfile: args.mcpProfile || 'auto',
|
|
654
653
|
searchTool: args.searchTool,
|
|
655
654
|
workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : undefined,
|
|
656
655
|
taskText: withContext(String(args.prompt ?? ''), args.contextFile),
|
|
657
|
-
availableServers: Array.isArray(args.availableServers)
|
|
658
|
-
? args.availableServers
|
|
659
|
-
: cachedAvailableServers ?? undefined,
|
|
656
|
+
availableServers: Array.isArray(args.availableServers) ? args.availableServers : undefined,
|
|
660
657
|
};
|
|
661
658
|
}
|
|
662
659
|
|
package/package.json
CHANGED
|
@@ -1,401 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// scripts/cache-buildup.mjs —
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function writeJSON(path, data) {
|
|
24
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ── 1. Codex 스킬 스캔 (Task D: 일반화된 발견) ──
|
|
28
|
-
|
|
29
|
-
const ROLE_KEYWORDS = {
|
|
30
|
-
plan: ["plan", "계획", "decompos", "strategy", "설계"],
|
|
31
|
-
auto: ["autonomous", "자율", "auto-execute", "autopilot", "full auto"],
|
|
32
|
-
persist: ["loop", "반복", "completion", "persist", "until", "끝까지"],
|
|
33
|
-
investigate: ["investigate", "research", "조사", "분석", "analysis"],
|
|
34
|
-
review: ["review", "리뷰", "inspect", "검수"],
|
|
2
|
+
// scripts/cache-buildup.mjs — legacy wrapper for cache-warmup
|
|
3
|
+
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
buildAll,
|
|
7
|
+
checkSearchEngines,
|
|
8
|
+
extractProjectMeta,
|
|
9
|
+
formatBuildSummary,
|
|
10
|
+
probeTierEnvironment,
|
|
11
|
+
scanCodexSkills,
|
|
12
|
+
} from "./cache-warmup.mjs";
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
buildAll,
|
|
16
|
+
checkSearchEngines,
|
|
17
|
+
extractProjectMeta,
|
|
18
|
+
probeTierEnvironment,
|
|
19
|
+
scanCodexSkills,
|
|
35
20
|
};
|
|
36
21
|
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
return "general";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseSkillFrontmatter(content) {
|
|
46
|
-
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
47
|
-
if (!match) return null;
|
|
48
|
-
|
|
49
|
-
const fm = match[1];
|
|
50
|
-
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() || null;
|
|
51
|
-
const desc = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() || null;
|
|
52
|
-
return name ? { name, description: desc } : null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function scanCodexSkills() {
|
|
56
|
-
const codexSkillsDir = join(HOME, ".codex", "skills");
|
|
57
|
-
const skills = [];
|
|
58
|
-
|
|
59
|
-
// 1) OMX/커스텀 스킬 (~/.codex/skills/*)
|
|
60
|
-
if (existsSync(codexSkillsDir)) {
|
|
61
|
-
for (const entry of readdirSync(codexSkillsDir, { withFileTypes: true })) {
|
|
62
|
-
if (!entry.isDirectory()) continue;
|
|
63
|
-
const skillMd = join(codexSkillsDir, entry.name, "SKILL.md");
|
|
64
|
-
if (!existsSync(skillMd)) continue;
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const content = readFileSync(skillMd, "utf8");
|
|
68
|
-
const fm = parseSkillFrontmatter(content);
|
|
69
|
-
const name = fm?.name || entry.name;
|
|
70
|
-
const description = fm?.description || "";
|
|
71
|
-
skills.push({
|
|
72
|
-
name,
|
|
73
|
-
role: classifyRole(description),
|
|
74
|
-
description: description.slice(0, 200),
|
|
75
|
-
source: "custom",
|
|
76
|
-
path: skillMd,
|
|
77
|
-
});
|
|
78
|
-
} catch {
|
|
79
|
-
skills.push({ name: entry.name, role: "general", description: "", source: "custom", path: skillMd });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 2) OpenAI 빌트인 스킬 (codex에 내장된 /명령들)
|
|
85
|
-
const BUILTIN_SKILLS = [
|
|
86
|
-
{ name: "web-clone", role: "general", description: "Clone and analyze web pages" },
|
|
87
|
-
{ name: "help", role: "general", description: "Show available commands" },
|
|
88
|
-
{ name: "note", role: "general", description: "Save notes during session" },
|
|
89
|
-
{ name: "worker", role: "auto", description: "Spawn background worker for tasks" },
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
for (const builtin of BUILTIN_SKILLS) {
|
|
93
|
-
// 이미 커스텀으로 오버라이드된 경우 스킵
|
|
94
|
-
if (skills.some((s) => s.name === builtin.name)) continue;
|
|
95
|
-
// 실제 디렉토리 존재 확인
|
|
96
|
-
if (existsSync(join(codexSkillsDir, builtin.name))) continue;
|
|
97
|
-
skills.push({ ...builtin, source: "builtin" });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
scanned_at: new Date().toISOString(),
|
|
102
|
-
codex_skills_dir: codexSkillsDir,
|
|
103
|
-
total: skills.length,
|
|
104
|
-
skills,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ── 2. Tier 환경 프로브 ──
|
|
109
|
-
|
|
110
|
-
function probeCliVersion(command, runExec = execSync) {
|
|
111
|
-
try {
|
|
112
|
-
runExec(command, { stdio: "ignore", timeout: 3000, windowsHide: true });
|
|
113
|
-
return true;
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function resolveCachedCliCheck(preflightCheck, command, runExec = execSync) {
|
|
120
|
-
if (typeof preflightCheck?.ok === "boolean") {
|
|
121
|
-
return preflightCheck.ok;
|
|
122
|
-
}
|
|
123
|
-
return probeCliVersion(command, runExec);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function probeTierEnvironment({
|
|
127
|
-
readPreflight = readPreflightCache,
|
|
128
|
-
runExec = execSync,
|
|
129
|
-
platform = process.platform,
|
|
130
|
-
} = {}) {
|
|
131
|
-
const preflight = readPreflight();
|
|
132
|
-
const checks = {
|
|
133
|
-
psmux: false,
|
|
134
|
-
hub: false,
|
|
135
|
-
codex: resolveCachedCliCheck(preflight?.codex, "codex --version", runExec),
|
|
136
|
-
gemini: resolveCachedCliCheck(preflight?.gemini, "gemini --version", runExec),
|
|
137
|
-
wt: false,
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// psmux
|
|
141
|
-
try {
|
|
142
|
-
runExec("psmux --version", { stdio: "ignore", timeout: 3000, windowsHide: true });
|
|
143
|
-
checks.psmux = true;
|
|
144
|
-
} catch {}
|
|
145
|
-
|
|
146
|
-
// hub (preflight에서 가져오거나 직접 확인)
|
|
147
|
-
if (preflight?.hub?.ok) {
|
|
148
|
-
checks.hub = true;
|
|
149
|
-
} else {
|
|
150
|
-
try {
|
|
151
|
-
runExec("curl -sf http://127.0.0.1:27888/status", { stdio: "ignore", timeout: 2000, windowsHide: true });
|
|
152
|
-
checks.hub = true;
|
|
153
|
-
} catch {}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Windows Terminal
|
|
157
|
-
if (platform === "win32") {
|
|
158
|
-
try {
|
|
159
|
-
runExec("where wt.exe", { stdio: "ignore", timeout: 2000, windowsHide: true });
|
|
160
|
-
checks.wt = true;
|
|
161
|
-
} catch {}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Tier 분류
|
|
165
|
-
let tier = "minimal"; // claude-only
|
|
166
|
-
if (checks.codex || checks.gemini) tier = "standard"; // multi-cli
|
|
167
|
-
if (checks.psmux && checks.hub && (checks.codex || checks.gemini)) tier = "full"; // orchestration-ready
|
|
168
|
-
|
|
169
|
-
const agents = [];
|
|
170
|
-
agents.push("claude");
|
|
171
|
-
if (checks.codex) agents.push("codex");
|
|
172
|
-
if (checks.gemini) agents.push("gemini");
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
probed_at: new Date().toISOString(),
|
|
176
|
-
tier,
|
|
177
|
-
checks,
|
|
178
|
-
available_agents: agents,
|
|
179
|
-
codex_plan: preflight?.codex_plan || { plan: "unknown" },
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ── 3. 프로젝트 메타 추출 ──
|
|
184
|
-
|
|
185
|
-
export function extractProjectMeta() {
|
|
186
|
-
let name = basename(CWD);
|
|
187
|
-
let description = "";
|
|
188
|
-
let lang = "unknown";
|
|
189
|
-
let testCmd = null;
|
|
190
|
-
let isGit = false;
|
|
191
|
-
|
|
192
|
-
// git
|
|
193
|
-
try {
|
|
194
|
-
const toplevel = execSync("git rev-parse --show-toplevel", {
|
|
195
|
-
encoding: "utf8", timeout: 3000, windowsHide: true, stdio: ["pipe", "pipe", "ignore"],
|
|
196
|
-
}).trim();
|
|
197
|
-
name = basename(toplevel);
|
|
198
|
-
isGit = true;
|
|
199
|
-
} catch {}
|
|
200
|
-
|
|
201
|
-
// package.json
|
|
202
|
-
const pkgPath = join(CWD, "package.json");
|
|
203
|
-
if (existsSync(pkgPath)) {
|
|
204
|
-
try {
|
|
205
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
206
|
-
description = pkg.description || "";
|
|
207
|
-
testCmd = pkg.scripts?.test || null;
|
|
208
|
-
lang = "JavaScript/ESM (Node.js)";
|
|
209
|
-
} catch {}
|
|
210
|
-
} else if (existsSync(join(CWD, "pyproject.toml")) || existsSync(join(CWD, "setup.py"))) {
|
|
211
|
-
lang = "Python";
|
|
212
|
-
} else if (existsSync(join(CWD, "Cargo.toml"))) {
|
|
213
|
-
lang = "Rust";
|
|
214
|
-
} else if (existsSync(join(CWD, "go.mod"))) {
|
|
215
|
-
lang = "Go";
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
extracted_at: new Date().toISOString(),
|
|
220
|
-
name,
|
|
221
|
-
description: description.slice(0, 300),
|
|
222
|
-
lang,
|
|
223
|
-
test_cmd: testCmd,
|
|
224
|
-
is_git: isGit,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ── 4. 검색 엔진 확인 ──
|
|
229
|
-
|
|
230
|
-
function loadMcpInventory() {
|
|
231
|
-
const invPath = join(HOME, ".claude", "cache", "mcp-inventory.json");
|
|
232
|
-
try {
|
|
233
|
-
return JSON.parse(readFileSync(invPath, "utf8"));
|
|
234
|
-
} catch {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function loadMcpConfigs() {
|
|
240
|
-
const servers = {};
|
|
241
|
-
// Claude 설정 파일들에서 MCP 서버 읽기
|
|
242
|
-
const configPaths = [
|
|
243
|
-
join(HOME, ".claude", "settings.json"),
|
|
244
|
-
join(HOME, ".claude", "settings.local.json"),
|
|
245
|
-
join(CWD, ".mcp.json"),
|
|
246
|
-
join(CWD, ".claude", ".mcp.json"),
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
for (const cfgPath of configPaths) {
|
|
250
|
-
try {
|
|
251
|
-
const data = JSON.parse(readFileSync(cfgPath, "utf8"));
|
|
252
|
-
const mcpServers = data.mcpServers || {};
|
|
253
|
-
for (const [name, config] of Object.entries(mcpServers)) {
|
|
254
|
-
servers[name] = { configured: true, source: basename(cfgPath), ...config };
|
|
255
|
-
}
|
|
256
|
-
} catch {}
|
|
257
|
-
}
|
|
258
|
-
return servers;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export function checkSearchEngines() {
|
|
262
|
-
const inventory = loadMcpInventory();
|
|
263
|
-
const configuredServers = loadMcpConfigs();
|
|
264
|
-
const engines = [];
|
|
265
|
-
|
|
266
|
-
// 알려진 검색 서버 (우선순위순)
|
|
267
|
-
const KNOWN_SEARCH_SERVERS = [...SEARCH_SERVER_ORDER, "context7"];
|
|
268
|
-
|
|
269
|
-
// MCP 인벤토리에서 검색 가능 서버 탐지
|
|
270
|
-
const allServerNames = new Set(KNOWN_SEARCH_SERVERS);
|
|
271
|
-
|
|
272
|
-
// 인벤토리의 Claude MCP 서버들도 확인
|
|
273
|
-
if (inventory) {
|
|
274
|
-
for (const scope of ["codex", "gemini", "claude"]) {
|
|
275
|
-
const scopeData = inventory[scope];
|
|
276
|
-
if (!scopeData?.servers) continue;
|
|
277
|
-
for (const srv of scopeData.servers) {
|
|
278
|
-
const tags = srv.domain_tags || MCP_SERVER_DOMAIN_TAGS[srv.name] || [];
|
|
279
|
-
if (tags.includes("search") || tags.includes("web") || tags.includes("research")) {
|
|
280
|
-
allServerNames.add(srv.name);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// 설정된 서버도 추가
|
|
287
|
-
for (const name of Object.keys(configuredServers)) {
|
|
288
|
-
const tags = MCP_SERVER_DOMAIN_TAGS[name] || [];
|
|
289
|
-
if (tags.includes("search") || tags.includes("web") || KNOWN_SEARCH_SERVERS.includes(name)) {
|
|
290
|
-
allServerNames.add(name);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// 각 검색 서버의 상태 판정
|
|
295
|
-
for (const name of allServerNames) {
|
|
296
|
-
const configured = !!configuredServers[name];
|
|
297
|
-
const tags = MCP_SERVER_DOMAIN_TAGS[name] || [];
|
|
298
|
-
|
|
299
|
-
// 인벤토리에서 실제 활성 상태 확인
|
|
300
|
-
let inventoryStatus = null;
|
|
301
|
-
if (inventory) {
|
|
302
|
-
for (const scope of ["codex", "gemini", "claude"]) {
|
|
303
|
-
const srv = inventory[scope]?.servers?.find((s) => s.name === name);
|
|
304
|
-
if (srv) {
|
|
305
|
-
inventoryStatus = { scope, status: srv.status, tool_count: srv.tool_count };
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
let status = "unavailable";
|
|
312
|
-
if (inventoryStatus?.status === "enabled" || inventoryStatus?.status === "configured" || inventoryStatus?.status === "available") {
|
|
313
|
-
status = "available";
|
|
314
|
-
} else if (configured) {
|
|
315
|
-
status = "configured"; // 설정은 있지만 인벤토리 미확인
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
engines.push({
|
|
319
|
-
name,
|
|
320
|
-
status,
|
|
321
|
-
domain_tags: tags,
|
|
322
|
-
configured,
|
|
323
|
-
inventory: inventoryStatus,
|
|
324
|
-
source: configuredServers[name]?.source || null,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// 우선순위순 정렬: SEARCH_SERVER_ORDER 먼저, 나머지 알파벳순
|
|
329
|
-
engines.sort((a, b) => {
|
|
330
|
-
const ai = SEARCH_SERVER_ORDER.indexOf(a.name);
|
|
331
|
-
const bi = SEARCH_SERVER_ORDER.indexOf(b.name);
|
|
332
|
-
if (ai !== -1 && bi !== -1) return ai - bi;
|
|
333
|
-
if (ai !== -1) return -1;
|
|
334
|
-
if (bi !== -1) return 1;
|
|
335
|
-
return a.name.localeCompare(b.name);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
const available = engines.filter((e) => e.status === "available");
|
|
339
|
-
const primary = available[0]?.name || null;
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
checked_at: new Date().toISOString(),
|
|
343
|
-
primary_engine: primary,
|
|
344
|
-
available_count: available.length,
|
|
345
|
-
total_count: engines.length,
|
|
346
|
-
engines,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// ── 메인 실행 ──
|
|
351
|
-
|
|
352
|
-
function main() {
|
|
353
|
-
ensureDirs();
|
|
354
|
-
|
|
355
|
-
const results = {};
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
const skills = scanCodexSkills();
|
|
359
|
-
writeJSON(join(CACHE_DIR, "codex-skills.json"), skills);
|
|
360
|
-
results.codex_skills = { ok: true, count: skills.total };
|
|
361
|
-
} catch (e) {
|
|
362
|
-
results.codex_skills = { ok: false, error: e.message };
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
const tier = probeTierEnvironment();
|
|
367
|
-
writeJSON(join(STATE_DIR, "tier-environment.json"), tier);
|
|
368
|
-
results.tier = { ok: true, tier: tier.tier };
|
|
369
|
-
} catch (e) {
|
|
370
|
-
results.tier = { ok: false, error: e.message };
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
try {
|
|
374
|
-
const meta = extractProjectMeta();
|
|
375
|
-
writeJSON(join(CACHE_DIR, "project-meta.json"), meta);
|
|
376
|
-
results.project_meta = { ok: true, name: meta.name };
|
|
377
|
-
} catch (e) {
|
|
378
|
-
results.project_meta = { ok: false, error: e.message };
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
const search = checkSearchEngines();
|
|
383
|
-
writeJSON(join(STATE_DIR, "search-engines.json"), search);
|
|
384
|
-
results.search_engines = { ok: true, primary: search.primary_engine, available: search.available_count };
|
|
385
|
-
} catch (e) {
|
|
386
|
-
results.search_engines = { ok: false, error: e.message };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// 간결 stdout (hook/로그용)
|
|
390
|
-
const ok = Object.values(results).every((r) => r.ok);
|
|
391
|
-
const summary = ok ? "cache-buildup: ok" : "cache-buildup: partial";
|
|
392
|
-
const details = [];
|
|
393
|
-
if (results.codex_skills?.ok) details.push(`skills:${results.codex_skills.count}`);
|
|
394
|
-
if (results.tier?.ok) details.push(`tier:${results.tier.tier}`);
|
|
395
|
-
if (results.search_engines?.ok) details.push(`search:${results.search_engines.primary || "none"}(${results.search_engines.available})`);
|
|
396
|
-
console.log(details.length ? `${summary} (${details.join(", ")})` : summary);
|
|
22
|
+
async function main() {
|
|
23
|
+
const summary = buildAll({ force: process.argv.includes("--force") });
|
|
24
|
+
console.log(formatBuildSummary(summary, { label: "cache-buildup" }));
|
|
25
|
+
if (!summary.ok) process.exitCode = 1;
|
|
397
26
|
}
|
|
398
27
|
|
|
399
|
-
if (process.argv[1]
|
|
400
|
-
main();
|
|
28
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
29
|
+
await main();
|
|
401
30
|
}
|