triflux 10.0.6 → 10.1.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/CLAUDE.md +22 -0
- package/bin/triflux.mjs +30 -20
- package/hooks/error-context.mjs +35 -7
- package/hooks/hook-orchestrator.mjs +14 -0
- package/hooks/hook-registry.json +256 -246
- package/hooks/safety-guard.mjs +70 -1
- package/hub/assign-callbacks.mjs +4 -4
- package/hub/bridge.mjs +1 -1
- package/hub/codex-adapter.mjs +1 -1
- package/hub/codex-compat.mjs +1 -0
- package/hub/fullcycle.mjs +1 -0
- package/hub/intent.mjs +4 -1
- package/hub/lib/memory-store.mjs +22 -0
- package/hub/promote-penalties.mjs +120 -0
- package/hub/public/dashboard.html +7 -7
- package/hub/quality/deslop.mjs +1 -1
- package/hub/reflexion.mjs +40 -56
- package/hub/research.mjs +1 -0
- package/hub/router.mjs +1 -1
- package/hub/server.mjs +23 -0
- package/hub/session-fingerprint.mjs +2 -2
- package/hub/store-adapter.mjs +51 -20
- package/hub/store.mjs +8 -8
- package/hub/team/cli/commands/start/start-headless.mjs +5 -3
- package/hub/team/cli/services/hub-client.mjs +1 -1
- package/hub/team/conductor.mjs +1 -1
- package/hub/team/dashboard-open.mjs +1 -1
- package/hub/team/headless.mjs +4 -4
- package/hub/team/orchestrator.mjs +2 -2
- package/hub/team/psmux.mjs +67 -5
- package/hub/team/remote-probe.mjs +1 -1
- package/hub/team/remote-watcher.mjs +1 -1
- package/hub/team/session.mjs +1 -1
- package/hub/team/swarm-hypervisor.mjs +3 -4
- package/hub/team/tui-remote-adapter.mjs +3 -3
- package/hub/team/tui-viewer.mjs +0 -1
- package/hub/team/tui.mjs +12 -15
- package/hub/team/worktree-lifecycle.mjs +1 -1
- package/hub/token-mode.mjs +4 -1
- package/hub/workers/delegator-mcp.mjs +1 -2
- package/package.json +1 -1
- package/scripts/__tests__/skill-template.test.mjs +0 -1
- package/scripts/claudemd-sync.mjs +1 -1
- package/scripts/cli-route.sh +3 -3
- package/scripts/cross-review-gate.mjs +1 -1
- package/scripts/headless-guard.mjs +44 -16
- package/scripts/lib/mcp-filter.mjs +2 -2
- package/scripts/lib/mcp-guard-engine.mjs +1 -1
- package/scripts/lib/psmux-info.mjs +2 -2
- package/scripts/lib/skill-template.mjs +4 -4
- package/scripts/mcp-check.mjs +1 -1
- package/scripts/mcp-gateway-config.mjs +1 -1
- package/scripts/mcp-gateway-ensure.mjs +1 -1
- package/scripts/mcp-gateway-start.mjs +1 -1
- package/scripts/notion-read.mjs +3 -3
- package/scripts/remote-spawn.mjs +1 -1
- package/scripts/session-spawn-helper.mjs +1 -3
- package/scripts/setup.mjs +1 -1
- package/scripts/test-lock.mjs +1 -1
- package/scripts/tfx-route-post.mjs +6 -6
- package/scripts/tfx-route.sh +6 -0
package/CLAUDE.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<!-- prompt-hygiene:ignore line_count_warning -->
|
|
1
2
|
# triflux — Claude Code 운영 가이드
|
|
2
3
|
|
|
3
4
|
<core-systems>
|
|
@@ -81,6 +82,12 @@ tfx-deep-review, tfx-deep-qa, tfx-deep-plan, tfx-deep-research, tfx-consensus, t
|
|
|
81
82
|
- "auto" 단독 → tfx-auto. "알아서 해" → tfx-autopilot
|
|
82
83
|
- "코드에서 찾아" → tfx-find. "알아봐" → tfx-research
|
|
83
84
|
- 복합 의도: "구현하고 리뷰까지" → tfx-auto → cross-review hook
|
|
85
|
+
|
|
86
|
+
### Q-Learning 동적 라우팅 (실험적)
|
|
87
|
+
|
|
88
|
+
- `TRIFLUX_DYNAMIC_ROUTING=true` 설정 시 Q-Learning 기반 동적 스킬 라우팅 활성화
|
|
89
|
+
- `routing-weights.json` + Q-table로 스킬 선택 최적화
|
|
90
|
+
- 기본 비활성
|
|
84
91
|
</routing>
|
|
85
92
|
|
|
86
93
|
<session-context>
|
|
@@ -161,6 +168,21 @@ codex를 SSH 너머로 직접 실행하지 않는다. config.toml 충돌 + TTY
|
|
|
161
168
|
- `~` → `$HOME` 변환 필수, 원격 기본 셸 = PowerShell
|
|
162
169
|
</remote>
|
|
163
170
|
|
|
171
|
+
<headless-retrieval>
|
|
172
|
+
## Headless 결과 회수
|
|
173
|
+
|
|
174
|
+
background로 실행한 headless 결과는 **반드시 task-notification 완료 후** 읽는다.
|
|
175
|
+
|
|
176
|
+
| 패턴 | 올바름 | 이유 |
|
|
177
|
+
|------|--------|------|
|
|
178
|
+
| task-notification 후 output 파일 읽기 | YES | 프로세스 종료 = 워커 전부 완료 |
|
|
179
|
+
| task-notification 전 output 파일 tail | NO | 시작 메시지만 보이고 "실패"로 오진 |
|
|
180
|
+
| psmux capture-pane으로 중간 체크 | NO | 워커 진행 중이면 빈 화면일 수 있음 |
|
|
181
|
+
|
|
182
|
+
완료 마커: `=== HEADLESS_COMPLETE succeeded=N failed=N total=N ===`
|
|
183
|
+
워커 상세: `$TMPDIR/tfx-headless/{sessionName}-worker-N.txt`
|
|
184
|
+
</headless-retrieval>
|
|
185
|
+
|
|
164
186
|
<cross-review>
|
|
165
187
|
## 교차 검증
|
|
166
188
|
|
package/bin/triflux.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
import {
|
|
28
28
|
SYNC_MAP, SKILL_ALIASES, REQUIRED_CODEX_PROFILES, LEGACY_CODEX_MODELS,
|
|
29
29
|
syncAliasedSkillDir, hasProfileSection, replaceProfileSection,
|
|
30
|
-
ensureCodexProfiles, getVersion, cleanupStaleSkills,
|
|
30
|
+
ensureCodexProfiles, getVersion, cleanupStaleSkills,
|
|
31
31
|
extractManagedHookFilename, getManagedRegistryHooks, ensureHooksInSettings,
|
|
32
32
|
ensureCodexHubServerConfig,
|
|
33
33
|
} from "../scripts/setup.mjs";
|
|
@@ -65,14 +65,14 @@ const GREEN_BRIGHT = "\x1b[38;5;82m";
|
|
|
65
65
|
const RED_BRIGHT = "\x1b[38;5;196m";
|
|
66
66
|
|
|
67
67
|
// ── 브랜드 요소 ──
|
|
68
|
-
const
|
|
68
|
+
const _BRAND = `${AMBER}${BOLD}triflux${RESET}`;
|
|
69
69
|
const VER = `${DIM}v${PKG.version}${RESET}`;
|
|
70
70
|
const LINE = `${GRAY}${"─".repeat(48)}${RESET}`;
|
|
71
|
-
const
|
|
71
|
+
const _DOT = `${GRAY}·${RESET}`;
|
|
72
72
|
const STALE_TEAM_MAX_AGE_SEC = 3600;
|
|
73
73
|
const ANSI_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const _EXIT_SUCCESS = 0;
|
|
76
76
|
const EXIT_ERROR = 1;
|
|
77
77
|
const EXIT_ARG_ERROR = 2;
|
|
78
78
|
const EXIT_CLI_MISSING = 3;
|
|
@@ -719,12 +719,11 @@ function previewMcpRegistrationActions(mcpUrl) {
|
|
|
719
719
|
function previewClaudeRoutingAction() {
|
|
720
720
|
const globalClaudePath = join(CLAUDE_DIR, "CLAUDE.md");
|
|
721
721
|
const projectClaudePath = join(PKG_ROOT, "CLAUDE.md");
|
|
722
|
-
const projectContent = existsSync(projectClaudePath)
|
|
723
|
-
? readFileSync(projectClaudePath, "utf8")
|
|
724
|
-
: "";
|
|
725
|
-
const projectSection = extractMarkdownSection(projectContent, TFX_SECTION_HEADING);
|
|
726
722
|
|
|
727
|
-
|
|
723
|
+
let _routingTable;
|
|
724
|
+
try {
|
|
725
|
+
_routingTable = getLatestRoutingTable();
|
|
726
|
+
} catch {
|
|
728
727
|
return {
|
|
729
728
|
type: "claude-guidance",
|
|
730
729
|
path: globalClaudePath,
|
|
@@ -734,18 +733,23 @@ function previewClaudeRoutingAction() {
|
|
|
734
733
|
};
|
|
735
734
|
}
|
|
736
735
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
736
|
+
if (!existsSync(globalClaudePath)) {
|
|
737
|
+
return {
|
|
738
|
+
type: "claude-guidance",
|
|
739
|
+
path: globalClaudePath,
|
|
740
|
+
source: projectClaudePath,
|
|
741
|
+
change: "create",
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const globalContent = readFileSync(globalClaudePath, "utf8");
|
|
746
|
+
const hasRouting = globalContent.includes("<routing>") || globalContent.includes("## triflux CLI 라우팅");
|
|
741
747
|
|
|
742
748
|
return {
|
|
743
749
|
type: "claude-guidance",
|
|
744
750
|
path: globalClaudePath,
|
|
745
751
|
source: projectClaudePath,
|
|
746
|
-
change:
|
|
747
|
-
heading: TFX_SECTION_HEADING,
|
|
748
|
-
summary: TFX_GLOBAL_SUMMARY_SECTION,
|
|
752
|
+
change: hasRouting ? "noop" : "create",
|
|
749
753
|
};
|
|
750
754
|
}
|
|
751
755
|
|
|
@@ -2073,7 +2077,7 @@ async function cmdDoctor(options = {}) {
|
|
|
2073
2077
|
// 14. Stale Teams (Claude teams/ + tasks/ 자동 감지)
|
|
2074
2078
|
section("Stale Teams");
|
|
2075
2079
|
const teamsDir = join(CLAUDE_DIR, "teams");
|
|
2076
|
-
const
|
|
2080
|
+
const _tasksDir = join(CLAUDE_DIR, "tasks");
|
|
2077
2081
|
if (existsSync(teamsDir)) {
|
|
2078
2082
|
try {
|
|
2079
2083
|
const teamDirs = readdirSync(teamsDir).filter(d => {
|
|
@@ -2130,8 +2134,8 @@ async function cmdDoctor(options = {}) {
|
|
|
2130
2134
|
// 프로세스 명령줄에서 세션 ID 매칭 (tmux 없는 in-process 팀 지원)
|
|
2131
2135
|
if (!hasActiveMember && teamConfig.leadSessionId) {
|
|
2132
2136
|
try {
|
|
2133
|
-
const
|
|
2134
|
-
const safeToken = teamConfig.leadSessionId.slice(0, 8).replace(/[^a-zA-Z0-9
|
|
2137
|
+
const _sessionToken = teamConfig.leadSessionId.toLowerCase();
|
|
2138
|
+
const safeToken = teamConfig.leadSessionId.slice(0, 8).replace(/[^a-zA-Z0-9-]/g, '');
|
|
2135
2139
|
// Claude Code 프로세스에서 세션 ID 검색
|
|
2136
2140
|
if (process.platform === "win32") {
|
|
2137
2141
|
const psOut = execSync(
|
|
@@ -3723,7 +3727,7 @@ async function cmdHub(args = [], options = {}) {
|
|
|
3723
3727
|
process.kill(info.pid, "SIGTERM");
|
|
3724
3728
|
try { unlinkSync(HUB_PID_FILE); } catch {}
|
|
3725
3729
|
console.log(`\n ${GREEN_BRIGHT}✓${RESET} hub 종료됨 (PID ${info.pid})\n`);
|
|
3726
|
-
} catch (
|
|
3730
|
+
} catch (_e) {
|
|
3727
3731
|
try { unlinkSync(HUB_PID_FILE); } catch {}
|
|
3728
3732
|
console.log(`\n ${DIM}hub 프로세스 없음 — PID 파일 정리됨${RESET}\n`);
|
|
3729
3733
|
}
|
|
@@ -3884,6 +3888,12 @@ async function main() {
|
|
|
3884
3888
|
case "hub":
|
|
3885
3889
|
await cmdHub(cmdArgs, { json: JSON_OUTPUT && (cmdArgs[0] || "status") === "status" });
|
|
3886
3890
|
return;
|
|
3891
|
+
case "monitor": {
|
|
3892
|
+
const { createMonitor } = await import("../tui/monitor.mjs");
|
|
3893
|
+
const mon = createMonitor();
|
|
3894
|
+
await mon.start();
|
|
3895
|
+
break;
|
|
3896
|
+
}
|
|
3887
3897
|
case "tray": {
|
|
3888
3898
|
const trayUrl = new URL("../hub/tray.mjs", import.meta.url);
|
|
3889
3899
|
const trayPath = fileURLToPath(trayUrl);
|
package/hooks/error-context.mjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// 도구 실패 시 에러 패턴을 분석하여 해결 힌트를 additionalContext로 주입한다.
|
|
5
5
|
// Claude가 동일 에러를 반복하지 않도록 구체적 가이드를 제공.
|
|
6
6
|
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
8
9
|
|
|
9
10
|
// ── 에러 패턴 → 해결 힌트 매핑 ─────────────────────────────
|
|
10
11
|
const ERROR_HINTS = [
|
|
@@ -128,16 +129,43 @@ function main() {
|
|
|
128
129
|
JSON.stringify(input.tool_result || ""),
|
|
129
130
|
].join("\n");
|
|
130
131
|
|
|
132
|
+
// ── reflexion 적응형 학습: safety-guard/headless-guard 차단을 패널티로 기록 ──
|
|
133
|
+
const isSafetyBlock = /\[(?:safety-guard|headless-guard)\].*(?:BLOCKED|차단)/i.test(errorText);
|
|
134
|
+
if (isSafetyBlock) {
|
|
135
|
+
try {
|
|
136
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
137
|
+
const penaltyDir = join(home, ".triflux", "reflexion");
|
|
138
|
+
mkdirSync(penaltyDir, { recursive: true });
|
|
139
|
+
const penaltyFile = join(penaltyDir, "pending-penalties.jsonl");
|
|
140
|
+
const command = input.tool_input?.command || "";
|
|
141
|
+
const entry = {
|
|
142
|
+
ts: new Date().toISOString(),
|
|
143
|
+
type: "guard_block",
|
|
144
|
+
tool: input.tool_name || "Bash",
|
|
145
|
+
error_pattern: errorText.match(/\[.*?\]\s*(.{0,120})/)?.[1] || errorText.slice(0, 120),
|
|
146
|
+
command_preview: command.slice(0, 200),
|
|
147
|
+
source: errorText.includes("safety-guard") ? "safety-guard" : "headless-guard",
|
|
148
|
+
};
|
|
149
|
+
writeFileSync(penaltyFile, JSON.stringify(entry) + "\n", { flag: "a" });
|
|
150
|
+
} catch { /* reflexion 기록 실패는 무시 — 힌트 출력에 영향 주지 않음 */ }
|
|
151
|
+
}
|
|
152
|
+
|
|
131
153
|
const hints = findHints(errorText);
|
|
132
|
-
|
|
154
|
+
// safety-guard 차단에는 guard가 이미 구체적 안내를 제공하므로 추가 힌트 불필요
|
|
155
|
+
if (hints.length === 0 && !isSafetyBlock) process.exit(0);
|
|
133
156
|
|
|
134
157
|
const toolName = input.tool_name || "Unknown";
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
const parts = [];
|
|
159
|
+
if (hints.length > 0) {
|
|
160
|
+
parts.push(`[error-context] ${toolName} 실패 — 해결 힌트:\n` + hints.map((h) => ` → ${h}`).join("\n"));
|
|
161
|
+
}
|
|
162
|
+
if (isSafetyBlock) {
|
|
163
|
+
parts.push("[reflexion] 이 패턴이 적응형 학습에 기록되었습니다. 다음 세션에서 동일 패턴 시 사전 차단됩니다.");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (parts.length === 0) process.exit(0);
|
|
140
167
|
|
|
168
|
+
const output = { systemMessage: parts.join("\n") };
|
|
141
169
|
process.stdout.write(JSON.stringify(output));
|
|
142
170
|
}
|
|
143
171
|
|
|
@@ -340,6 +340,20 @@ async function main() {
|
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
// ── PostToolUse:Skill 완료 시 라우팅 가중치 기록 ──
|
|
344
|
+
if (eventName === "PostToolUse" && toolName === "Skill" && !blocked) {
|
|
345
|
+
try {
|
|
346
|
+
const input = JSON.parse(stdinRaw);
|
|
347
|
+
const skillName = input.tool_input?.skill || "";
|
|
348
|
+
if (skillName && skillName.startsWith("tfx-")) {
|
|
349
|
+
const mode = skillName.replace(/^tfx-/, "");
|
|
350
|
+
const gitRoot = process.env.GIT_WORK_TREE || process.cwd();
|
|
351
|
+
const slug = gitRoot.split(/[\\/]/).pop() || "unknown";
|
|
352
|
+
recordRouteOutcome(slug, mode, "completion");
|
|
353
|
+
}
|
|
354
|
+
} catch { /* 가중치 기록 실패 무시 */ }
|
|
355
|
+
}
|
|
356
|
+
|
|
343
357
|
// 결과 출력
|
|
344
358
|
if (blocked) {
|
|
345
359
|
process.exit(2);
|