@triflux/core 10.0.0-alpha.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.
Files changed (75) hide show
  1. package/hooks/agent-route-guard.mjs +109 -0
  2. package/hooks/cross-review-tracker.mjs +122 -0
  3. package/hooks/error-context.mjs +148 -0
  4. package/hooks/hook-manager.mjs +352 -0
  5. package/hooks/hook-orchestrator.mjs +312 -0
  6. package/hooks/hook-registry.json +213 -0
  7. package/hooks/hooks.json +89 -0
  8. package/hooks/keyword-rules.json +581 -0
  9. package/hooks/lib/resolve-root.mjs +59 -0
  10. package/hooks/mcp-config-watcher.mjs +85 -0
  11. package/hooks/pipeline-stop.mjs +76 -0
  12. package/hooks/safety-guard.mjs +106 -0
  13. package/hooks/subagent-verifier.mjs +80 -0
  14. package/hub/assign-callbacks.mjs +133 -0
  15. package/hub/bridge.mjs +799 -0
  16. package/hub/cli-adapter-base.mjs +192 -0
  17. package/hub/codex-adapter.mjs +190 -0
  18. package/hub/codex-compat.mjs +78 -0
  19. package/hub/codex-preflight.mjs +147 -0
  20. package/hub/delegator/contracts.mjs +37 -0
  21. package/hub/delegator/index.mjs +14 -0
  22. package/hub/delegator/schema/delegator-tools.schema.json +250 -0
  23. package/hub/delegator/service.mjs +307 -0
  24. package/hub/delegator/tool-definitions.mjs +35 -0
  25. package/hub/fullcycle.mjs +96 -0
  26. package/hub/gemini-adapter.mjs +179 -0
  27. package/hub/hitl.mjs +143 -0
  28. package/hub/intent.mjs +193 -0
  29. package/hub/lib/process-utils.mjs +361 -0
  30. package/hub/middleware/request-logger.mjs +81 -0
  31. package/hub/paths.mjs +30 -0
  32. package/hub/pipeline/gates/confidence.mjs +56 -0
  33. package/hub/pipeline/gates/consensus.mjs +94 -0
  34. package/hub/pipeline/gates/index.mjs +5 -0
  35. package/hub/pipeline/gates/selfcheck.mjs +82 -0
  36. package/hub/pipeline/index.mjs +318 -0
  37. package/hub/pipeline/state.mjs +191 -0
  38. package/hub/pipeline/transitions.mjs +124 -0
  39. package/hub/platform.mjs +225 -0
  40. package/hub/quality/deslop.mjs +253 -0
  41. package/hub/reflexion.mjs +372 -0
  42. package/hub/research.mjs +146 -0
  43. package/hub/router.mjs +791 -0
  44. package/hub/routing/complexity.mjs +166 -0
  45. package/hub/routing/index.mjs +117 -0
  46. package/hub/routing/q-learning.mjs +336 -0
  47. package/hub/session-fingerprint.mjs +352 -0
  48. package/hub/state.mjs +245 -0
  49. package/hub/team-bridge.mjs +25 -0
  50. package/hub/token-mode.mjs +224 -0
  51. package/hub/workers/worker-utils.mjs +104 -0
  52. package/hud/colors.mjs +88 -0
  53. package/hud/constants.mjs +81 -0
  54. package/hud/hud-qos-status.mjs +206 -0
  55. package/hud/providers/claude.mjs +309 -0
  56. package/hud/providers/codex.mjs +151 -0
  57. package/hud/providers/gemini.mjs +320 -0
  58. package/hud/renderers.mjs +424 -0
  59. package/hud/terminal.mjs +140 -0
  60. package/hud/utils.mjs +287 -0
  61. package/package.json +31 -0
  62. package/scripts/lib/claudemd-manager.mjs +325 -0
  63. package/scripts/lib/context.mjs +67 -0
  64. package/scripts/lib/cross-review-utils.mjs +51 -0
  65. package/scripts/lib/env-probe.mjs +241 -0
  66. package/scripts/lib/gemini-profiles.mjs +85 -0
  67. package/scripts/lib/hook-utils.mjs +14 -0
  68. package/scripts/lib/keyword-rules.mjs +166 -0
  69. package/scripts/lib/logger.mjs +105 -0
  70. package/scripts/lib/mcp-filter.mjs +739 -0
  71. package/scripts/lib/mcp-guard-engine.mjs +940 -0
  72. package/scripts/lib/mcp-manifest.mjs +79 -0
  73. package/scripts/lib/mcp-server-catalog.mjs +118 -0
  74. package/scripts/lib/psmux-info.mjs +119 -0
  75. package/scripts/lib/remote-spawn-transfer.mjs +196 -0
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ // hooks/agent-route-guard.mjs — PreToolUse:Agent 훅
3
+ // 서브에이전트 스폰 시 triflux 컨텍스트를 구조화 JSON으로 주입한다.
4
+ // - subagent_type별 최적 라우팅 가이드
5
+ // - tfx-multi 활성 상태 시 headless dispatch 강제
6
+ // - 프로젝트 컨텍스트 자동 첨부
7
+
8
+ import { readFileSync, existsSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+
12
+ const TFX_MULTI_STATE = join(tmpdir(), "tfx-multi-state.json");
13
+ const EXPIRE_MS = 30 * 60 * 1000; // 30분
14
+
15
+ // 서브에이전트 타입별 라우팅 힌트
16
+ const AGENT_HINTS = {
17
+ "general-purpose": "범용 에이전트. tfx 스킬이 활성이면 스킬 MD의 라우팅을 우선한다.",
18
+ Explore: "탐색 전용. 파일 수정 불가. Glob/Grep/Read만 사용.",
19
+ Plan: "설계 전용. 파일 수정 불가. 구현 계획 반환.",
20
+ "oh-my-claudecode:executor": "OMC executor. triflux 프로젝트에서는 tfx-auto 라우팅을 우선.",
21
+ "oh-my-claudecode:code-reviewer": "OMC 리뷰어. 교차 리뷰 시 CLAUDE.md 교차 검증 규칙 준수.",
22
+ "oh-my-claudecode:architect": "OMC 아키텍트. READ-ONLY.",
23
+ "oh-my-claudecode:debugger": "OMC 디버거. 근본 원인 분석 집중.",
24
+ "oh-my-claudecode:test-engineer": "OMC 테스트. npm test 실행 후 결과 반환.",
25
+ };
26
+
27
+ function readStdin() {
28
+ try {
29
+ return readFileSync(0, "utf8");
30
+ } catch {
31
+ return "";
32
+ }
33
+ }
34
+
35
+ function getTfxMultiState() {
36
+ if (!existsSync(TFX_MULTI_STATE)) return null;
37
+ try {
38
+ const state = JSON.parse(readFileSync(TFX_MULTI_STATE, "utf8"));
39
+ if (Date.now() - state.activatedAt > EXPIRE_MS) return null;
40
+ return state.active ? state : null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function buildContext(agentType, prompt) {
47
+ const parts = [];
48
+
49
+ // 1. tfx-multi 활성 상태 확인
50
+ const multiState = getTfxMultiState();
51
+ if (multiState) {
52
+ parts.push(
53
+ "[tfx-multi ACTIVE] headless dispatch 모드. " +
54
+ "CLI 작업은 Bash(tfx-route.sh)를 통해 실행하세요."
55
+ );
56
+ }
57
+
58
+ // 2. 에이전트 타입별 힌트
59
+ const hint = AGENT_HINTS[agentType];
60
+ if (hint) {
61
+ parts.push(`[Agent:${agentType}] ${hint}`);
62
+ }
63
+
64
+ // 3. 프로젝트 컨텍스트
65
+ parts.push(
66
+ "triflux 프로젝트: subagent_type 미지정 시 'general-purpose' 기본. " +
67
+ "tfx-* 스킬 활성 시 스킬 MD 라우팅 우선."
68
+ );
69
+
70
+ return parts.join("\n");
71
+ }
72
+
73
+ function main() {
74
+ const raw = readStdin();
75
+ if (!raw.trim()) process.exit(0);
76
+
77
+ let input;
78
+ try {
79
+ input = JSON.parse(raw);
80
+ } catch {
81
+ process.exit(0);
82
+ }
83
+
84
+ if (input.tool_name !== "Agent") process.exit(0);
85
+
86
+ const toolInput = input.tool_input || {};
87
+ const agentType = toolInput.subagent_type || toolInput.agent || "general-purpose";
88
+ const prompt = toolInput.prompt || "";
89
+
90
+ const context = buildContext(agentType, prompt);
91
+
92
+ // 구조화된 hookSpecificOutput 반환
93
+ const output = {
94
+ hookSpecificOutput: {
95
+ hookEventName: "PreToolUse",
96
+ permissionDecision: "allow",
97
+ additionalContext: context,
98
+ },
99
+ };
100
+
101
+ process.stdout.write(JSON.stringify(output));
102
+ }
103
+
104
+ try {
105
+ main();
106
+ } catch {
107
+ // 훅 실패 시 블로킹하지 않음
108
+ process.exit(0);
109
+ }
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ // hooks/cross-review-tracker.mjs — PostToolUse:Edit|Write 훅
3
+ //
4
+ // 파일 수정을 추적하여 교차 리뷰 미검증 파일을 감지한다.
5
+ // CLAUDE.md 규칙: "Claude 작성 코드 → Codex 리뷰, Codex 작성 → Claude 리뷰"
6
+ //
7
+ // 동작:
8
+ // 1. Edit/Write 성공 시 수정된 파일 경로를 상태 파일에 누적
9
+ // 2. 일정 수(REVIEW_THRESHOLD) 이상 미검증 파일이 쌓이면 nudge 메시지 주입
10
+ // 3. git commit 전 미검증 파일 경고
11
+
12
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
13
+ import { join, relative } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+
16
+ const STATE_DIR = join(tmpdir(), "tfx-cross-review");
17
+ const STATE_FILE = join(STATE_DIR, "pending-review.json");
18
+ const REVIEW_THRESHOLD = 5; // 이 수 이상 미검증 파일 → nudge
19
+ const EXPIRE_MS = 60 * 60 * 1000; // 1시간 후 자동 만료
20
+
21
+ // 코드 파일만 추적 (설정/문서/빌드 산출물 제외)
22
+ const CODE_EXTENSIONS = new Set([
23
+ ".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx",
24
+ ".py", ".rs", ".go", ".java", ".c", ".cpp", ".h",
25
+ ".vue", ".svelte", ".sh", ".bash", ".ps1",
26
+ ]);
27
+
28
+ function isCodeFile(filePath) {
29
+ if (!filePath) return false;
30
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
31
+ return CODE_EXTENSIONS.has(ext);
32
+ }
33
+
34
+ function loadState() {
35
+ if (!existsSync(STATE_FILE)) {
36
+ return { files: {}, startedAt: Date.now() };
37
+ }
38
+ try {
39
+ const state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
40
+ // 만료 체크
41
+ if (Date.now() - state.startedAt > EXPIRE_MS) {
42
+ return { files: {}, startedAt: Date.now() };
43
+ }
44
+ return state;
45
+ } catch {
46
+ return { files: {}, startedAt: Date.now() };
47
+ }
48
+ }
49
+
50
+ function saveState(state) {
51
+ mkdirSync(STATE_DIR, { recursive: true });
52
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
53
+ }
54
+
55
+ function readStdin() {
56
+ try {
57
+ return readFileSync(0, "utf8");
58
+ } catch {
59
+ return "";
60
+ }
61
+ }
62
+
63
+ function main() {
64
+ const raw = readStdin();
65
+ if (!raw.trim()) process.exit(0);
66
+
67
+ let input;
68
+ try {
69
+ input = JSON.parse(raw);
70
+ } catch {
71
+ process.exit(0);
72
+ }
73
+
74
+ const toolName = input.tool_name || "";
75
+ if (toolName !== "Edit" && toolName !== "Write") process.exit(0);
76
+
77
+ const toolInput = input.tool_input || {};
78
+ const filePath = toolInput.file_path || "";
79
+
80
+ if (!filePath || !isCodeFile(filePath)) process.exit(0);
81
+
82
+ // 프로젝트 루트 기준 상대 경로
83
+ const cwd = input.cwd || process.cwd();
84
+ const relPath = relative(cwd, filePath) || filePath;
85
+
86
+ // 상태 갱신: 파일 추가
87
+ const state = loadState();
88
+ state.files[relPath] = {
89
+ tool: toolName,
90
+ modifiedAt: Date.now(),
91
+ reviewed: false,
92
+ };
93
+ saveState(state);
94
+
95
+ // 미검증 파일 수 체크
96
+ const unreviewed = Object.entries(state.files).filter(
97
+ ([, v]) => !v.reviewed
98
+ );
99
+ const count = unreviewed.length;
100
+
101
+ if (count >= REVIEW_THRESHOLD) {
102
+ // nudge 메시지 주입
103
+ const fileList = unreviewed
104
+ .slice(0, 8)
105
+ .map(([f]) => ` - ${f}`)
106
+ .join("\n");
107
+
108
+ const output = {
109
+ systemMessage:
110
+ `[교차 리뷰 nudge] 미검증 코드 파일 ${count}개:\n${fileList}\n` +
111
+ (count > 8 ? ` ... 외 ${count - 8}개\n` : "") +
112
+ `커밋 전 교차 리뷰를 권장합니다. (Claude→Codex 또는 Codex→Claude)`,
113
+ };
114
+ process.stdout.write(JSON.stringify(output));
115
+ }
116
+ }
117
+
118
+ try {
119
+ main();
120
+ } catch {
121
+ process.exit(0);
122
+ }
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ // hooks/error-context.mjs — PostToolUseFailure 훅
3
+ //
4
+ // 도구 실패 시 에러 패턴을 분석하여 해결 힌트를 additionalContext로 주입한다.
5
+ // Claude가 동일 에러를 반복하지 않도록 구체적 가이드를 제공.
6
+
7
+ import { readFileSync } from "node:fs";
8
+
9
+ // ── 에러 패턴 → 해결 힌트 매핑 ─────────────────────────────
10
+ const ERROR_HINTS = [
11
+ // Node.js / npm
12
+ {
13
+ pattern: /ENOENT.*no such file or directory/i,
14
+ hint: "파일/디렉토리가 존재하지 않습니다. 경로를 확인하거나 mkdir -p로 디렉토리를 먼저 생성하세요.",
15
+ },
16
+ {
17
+ pattern: /EACCES.*permission denied/i,
18
+ hint: "권한 부족. Windows에서는 관리자 권한, Unix에서는 chmod/sudo를 확인하세요.",
19
+ },
20
+ {
21
+ pattern: /EADDRINUSE/i,
22
+ hint: "포트가 이미 사용 중입니다. lsof -i :{port} 또는 netstat -ano | findstr :{port}로 확인 후 프로세스를 종료하세요.",
23
+ },
24
+ {
25
+ pattern: /ERR_MODULE_NOT_FOUND|Cannot find module/i,
26
+ hint: "모듈을 찾을 수 없습니다. npm install을 실행하거나, import 경로에 .mjs/.js 확장자를 명시하세요.",
27
+ },
28
+ {
29
+ pattern: /ETARGET|ERR_INVALID_PACKAGE_TARGET/i,
30
+ hint: "패키지 버전 해석 실패. package.json의 exports 필드 또는 의존성 버전을 확인하세요.",
31
+ },
32
+ {
33
+ pattern: /npm ERR! code E40[134]/i,
34
+ hint: "npm 인증 오류. npm login 또는 .npmrc 토큰을 확인하세요.",
35
+ },
36
+
37
+ // Git
38
+ {
39
+ pattern: /fatal: not a git repository/i,
40
+ hint: "git 저장소가 아닙니다. git init 또는 올바른 디렉토리로 이동하세요.",
41
+ },
42
+ {
43
+ pattern: /merge conflict|CONFLICT.*Merge/i,
44
+ hint: "병합 충돌 발생. 충돌 파일을 수동 해결 후 git add + git commit 하세요.",
45
+ },
46
+ {
47
+ pattern: /rejected.*non-fast-forward/i,
48
+ hint: "원격에 새 커밋이 있습니다. git pull --rebase 후 다시 push하세요.",
49
+ },
50
+ {
51
+ pattern: /fatal: refusing to merge unrelated histories/i,
52
+ hint: "--allow-unrelated-histories 플래그가 필요할 수 있습니다.",
53
+ },
54
+
55
+ // Python
56
+ {
57
+ pattern: /ModuleNotFoundError/i,
58
+ hint: "Python 모듈 미설치. pip install 또는 가상환경 활성화를 확인하세요.",
59
+ },
60
+ {
61
+ pattern: /SyntaxError.*invalid syntax/i,
62
+ hint: "Python 문법 오류. Python 버전(2 vs 3) 호환성도 확인하세요.",
63
+ },
64
+
65
+ // Windows 특이
66
+ {
67
+ pattern: /is not recognized as an internal or external command/i,
68
+ hint: "명령어를 찾을 수 없습니다. PATH 환경변수를 확인하거나 절대 경로를 사용하세요.",
69
+ },
70
+ {
71
+ pattern: /execution policy/i,
72
+ hint: "PowerShell 실행 정책 제한. -ExecutionPolicy Bypass 플래그를 추가하세요.",
73
+ },
74
+ {
75
+ pattern: /The process cannot access the file because it is being used/i,
76
+ hint: "파일이 다른 프로세스에 의해 잠겨 있습니다. 해당 프로세스를 종료하거나 잠시 후 재시도하세요.",
77
+ },
78
+
79
+ // 일반
80
+ {
81
+ pattern: /timeout|timed out|ETIMEDOUT/i,
82
+ hint: "타임아웃 발생. 네트워크 상태를 확인하거나 timeout 값을 늘리세요.",
83
+ },
84
+ {
85
+ pattern: /out of memory|heap|ENOMEM/i,
86
+ hint: "메모리 부족. --max-old-space-size를 늘리거나 데이터 크기를 줄이세요.",
87
+ },
88
+ {
89
+ pattern: /ECONNREFUSED/i,
90
+ hint: "연결 거부. 대상 서버/서비스가 실행 중인지 확인하세요.",
91
+ },
92
+ ];
93
+
94
+ function readStdin() {
95
+ try {
96
+ return readFileSync(0, "utf8");
97
+ } catch {
98
+ return "";
99
+ }
100
+ }
101
+
102
+ function findHints(errorText) {
103
+ const hints = [];
104
+ for (const rule of ERROR_HINTS) {
105
+ if (rule.pattern.test(errorText)) {
106
+ hints.push(rule.hint);
107
+ }
108
+ }
109
+ return hints;
110
+ }
111
+
112
+ function main() {
113
+ const raw = readStdin();
114
+ if (!raw.trim()) process.exit(0);
115
+
116
+ let input;
117
+ try {
118
+ input = JSON.parse(raw);
119
+ } catch {
120
+ process.exit(0);
121
+ }
122
+
123
+ // tool_output 또는 error 필드에서 에러 텍스트 추출
124
+ const errorText = [
125
+ input.tool_output || "",
126
+ input.error || "",
127
+ input.tool_input?.command || "",
128
+ JSON.stringify(input.tool_result || ""),
129
+ ].join("\n");
130
+
131
+ const hints = findHints(errorText);
132
+ if (hints.length === 0) process.exit(0);
133
+
134
+ const toolName = input.tool_name || "Unknown";
135
+ const output = {
136
+ systemMessage:
137
+ `[error-context] ${toolName} 실패 — 해결 힌트:\n` +
138
+ hints.map((h) => ` → ${h}`).join("\n"),
139
+ };
140
+
141
+ process.stdout.write(JSON.stringify(output));
142
+ }
143
+
144
+ try {
145
+ main();
146
+ } catch {
147
+ process.exit(0);
148
+ }