triflux 10.3.4 → 10.7.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/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
|
@@ -5,20 +5,23 @@
|
|
|
5
5
|
// - tfx-multi 활성 상태 시 headless dispatch 강제
|
|
6
6
|
// - 프로젝트 컨텍스트 자동 첨부
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { join } from "node:path";
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
9
|
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
11
|
|
|
12
12
|
const TFX_MULTI_STATE = join(tmpdir(), "tfx-multi-state.json");
|
|
13
13
|
const EXPIRE_MS = 30 * 60 * 1000; // 30분
|
|
14
14
|
|
|
15
15
|
// 서브에이전트 타입별 라우팅 힌트
|
|
16
16
|
const AGENT_HINTS = {
|
|
17
|
-
"general-purpose":
|
|
17
|
+
"general-purpose":
|
|
18
|
+
"범용 에이전트. tfx 스킬이 활성이면 스킬 MD의 라우팅을 우선한다.",
|
|
18
19
|
Explore: "탐색 전용. 파일 수정 불가. Glob/Grep/Read만 사용.",
|
|
19
20
|
Plan: "설계 전용. 파일 수정 불가. 구현 계획 반환.",
|
|
20
|
-
"oh-my-claudecode:executor":
|
|
21
|
-
|
|
21
|
+
"oh-my-claudecode:executor":
|
|
22
|
+
"OMC executor. triflux 프로젝트에서는 tfx-auto 라우팅을 우선.",
|
|
23
|
+
"oh-my-claudecode:code-reviewer":
|
|
24
|
+
"OMC 리뷰어. 교차 리뷰 시 CLAUDE.md 교차 검증 규칙 준수.",
|
|
22
25
|
"oh-my-claudecode:architect": "OMC 아키텍트. READ-ONLY.",
|
|
23
26
|
"oh-my-claudecode:debugger": "OMC 디버거. 근본 원인 분석 집중.",
|
|
24
27
|
"oh-my-claudecode:test-engineer": "OMC 테스트. npm test 실행 후 결과 반환.",
|
|
@@ -51,7 +54,7 @@ function buildContext(agentType, prompt) {
|
|
|
51
54
|
if (multiState) {
|
|
52
55
|
parts.push(
|
|
53
56
|
"[tfx-multi ACTIVE] headless dispatch 모드. " +
|
|
54
|
-
"CLI 작업은 Bash(tfx-route.sh)를 통해 실행하세요."
|
|
57
|
+
"CLI 작업은 Bash(tfx-route.sh)를 통해 실행하세요.",
|
|
55
58
|
);
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -64,7 +67,7 @@ function buildContext(agentType, prompt) {
|
|
|
64
67
|
// 3. 프로젝트 컨텍스트
|
|
65
68
|
parts.push(
|
|
66
69
|
"triflux 프로젝트: subagent_type 미지정 시 'general-purpose' 기본. " +
|
|
67
|
-
"tfx-* 스킬 활성 시 스킬 MD 라우팅 우선."
|
|
70
|
+
"tfx-* 스킬 활성 시 스킬 MD 라우팅 우선.",
|
|
68
71
|
);
|
|
69
72
|
|
|
70
73
|
return parts.join("\n");
|
|
@@ -84,7 +87,8 @@ function main() {
|
|
|
84
87
|
if (input.tool_name !== "Agent") process.exit(0);
|
|
85
88
|
|
|
86
89
|
const toolInput = input.tool_input || {};
|
|
87
|
-
const agentType =
|
|
90
|
+
const agentType =
|
|
91
|
+
toolInput.subagent_type || toolInput.agent || "general-purpose";
|
|
88
92
|
const prompt = toolInput.prompt || "";
|
|
89
93
|
|
|
90
94
|
const context = buildContext(agentType, prompt);
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// 2. 일정 수(REVIEW_THRESHOLD) 이상 미검증 파일이 쌓이면 nudge 메시지 주입
|
|
10
10
|
// 3. git commit 전 미검증 파일 경고
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import { join, relative } from "node:path";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
13
|
import { tmpdir } from "node:os";
|
|
14
|
+
import { join, relative } from "node:path";
|
|
15
15
|
|
|
16
16
|
const STATE_DIR = join(tmpdir(), "tfx-cross-review");
|
|
17
17
|
const STATE_FILE = join(STATE_DIR, "pending-review.json");
|
|
@@ -20,9 +20,24 @@ const EXPIRE_MS = 60 * 60 * 1000; // 1시간 후 자동 만료
|
|
|
20
20
|
|
|
21
21
|
// 코드 파일만 추적 (설정/문서/빌드 산출물 제외)
|
|
22
22
|
const CODE_EXTENSIONS = new Set([
|
|
23
|
-
".js",
|
|
24
|
-
".
|
|
25
|
-
".
|
|
23
|
+
".js",
|
|
24
|
+
".mjs",
|
|
25
|
+
".cjs",
|
|
26
|
+
".ts",
|
|
27
|
+
".tsx",
|
|
28
|
+
".jsx",
|
|
29
|
+
".py",
|
|
30
|
+
".rs",
|
|
31
|
+
".go",
|
|
32
|
+
".java",
|
|
33
|
+
".c",
|
|
34
|
+
".cpp",
|
|
35
|
+
".h",
|
|
36
|
+
".vue",
|
|
37
|
+
".svelte",
|
|
38
|
+
".sh",
|
|
39
|
+
".bash",
|
|
40
|
+
".ps1",
|
|
26
41
|
]);
|
|
27
42
|
|
|
28
43
|
function isCodeFile(filePath) {
|
|
@@ -93,9 +108,7 @@ function main() {
|
|
|
93
108
|
saveState(state);
|
|
94
109
|
|
|
95
110
|
// 미검증 파일 수 체크
|
|
96
|
-
const unreviewed = Object.entries(state.files).filter(
|
|
97
|
-
([, v]) => !v.reviewed
|
|
98
|
-
);
|
|
111
|
+
const unreviewed = Object.entries(state.files).filter(([, v]) => !v.reviewed);
|
|
99
112
|
const count = unreviewed.length;
|
|
100
113
|
|
|
101
114
|
if (count >= REVIEW_THRESHOLD) {
|
package/hooks/error-context.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 도구 실패 시 에러 패턴을 분석하여 해결 힌트를 additionalContext로 주입한다.
|
|
5
5
|
// Claude가 동일 에러를 반복하지 않도록 구체적 가이드를 제공.
|
|
6
6
|
|
|
7
|
-
import { readFileSync, writeFileSync
|
|
7
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
|
|
10
10
|
// ── 에러 패턴 → 해결 힌트 매핑 ─────────────────────────────
|
|
@@ -130,7 +130,8 @@ function main() {
|
|
|
130
130
|
].join("\n");
|
|
131
131
|
|
|
132
132
|
// ── reflexion 적응형 학습: safety-guard/headless-guard 차단을 패널티로 기록 ──
|
|
133
|
-
const isSafetyBlock =
|
|
133
|
+
const isSafetyBlock =
|
|
134
|
+
/\[(?:safety-guard|headless-guard)\].*(?:BLOCKED|차단)/i.test(errorText);
|
|
134
135
|
if (isSafetyBlock) {
|
|
135
136
|
try {
|
|
136
137
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
@@ -142,12 +143,18 @@ function main() {
|
|
|
142
143
|
ts: new Date().toISOString(),
|
|
143
144
|
type: "guard_block",
|
|
144
145
|
tool: input.tool_name || "Bash",
|
|
145
|
-
error_pattern:
|
|
146
|
+
error_pattern:
|
|
147
|
+
errorText.match(/\[.*?\]\s*(.{0,120})/)?.[1] ||
|
|
148
|
+
errorText.slice(0, 120),
|
|
146
149
|
command_preview: command.slice(0, 200),
|
|
147
|
-
source: errorText.includes("safety-guard")
|
|
150
|
+
source: errorText.includes("safety-guard")
|
|
151
|
+
? "safety-guard"
|
|
152
|
+
: "headless-guard",
|
|
148
153
|
};
|
|
149
154
|
writeFileSync(penaltyFile, JSON.stringify(entry) + "\n", { flag: "a" });
|
|
150
|
-
} catch {
|
|
155
|
+
} catch {
|
|
156
|
+
/* reflexion 기록 실패는 무시 — 힌트 출력에 영향 주지 않음 */
|
|
157
|
+
}
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
const hints = findHints(errorText);
|
|
@@ -157,10 +164,15 @@ function main() {
|
|
|
157
164
|
const toolName = input.tool_name || "Unknown";
|
|
158
165
|
const parts = [];
|
|
159
166
|
if (hints.length > 0) {
|
|
160
|
-
parts.push(
|
|
167
|
+
parts.push(
|
|
168
|
+
`[error-context] ${toolName} 실패 — 해결 힌트:\n` +
|
|
169
|
+
hints.map((h) => ` → ${h}`).join("\n"),
|
|
170
|
+
);
|
|
161
171
|
}
|
|
162
172
|
if (isSafetyBlock) {
|
|
163
|
-
parts.push(
|
|
173
|
+
parts.push(
|
|
174
|
+
"[reflexion] 이 패턴이 적응형 학습에 기록되었습니다. 다음 세션에서 동일 패턴 시 사전 차단됩니다.",
|
|
175
|
+
);
|
|
164
176
|
}
|
|
165
177
|
|
|
166
178
|
if (parts.length === 0) process.exit(0);
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, readFileSync } from
|
|
4
|
-
import { basename, join } from
|
|
5
|
-
import { pathToFileURL } from
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { basename, join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
6
|
|
|
7
|
-
import { createAdaptiveEngine } from
|
|
7
|
+
import { createAdaptiveEngine } from "../hub/adaptive.mjs";
|
|
8
8
|
|
|
9
9
|
let engine = null;
|
|
10
10
|
let createEngine = createAdaptiveEngine;
|
|
11
11
|
|
|
12
12
|
function readStdin() {
|
|
13
13
|
try {
|
|
14
|
-
return readFileSync(0,
|
|
14
|
+
return readFileSync(0, "utf8");
|
|
15
15
|
} catch {
|
|
16
|
-
return
|
|
16
|
+
return "";
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function inferProjectSlug(cwd = process.cwd()) {
|
|
21
|
-
const packagePath = join(cwd,
|
|
21
|
+
const packagePath = join(cwd, "package.json");
|
|
22
22
|
if (existsSync(packagePath)) {
|
|
23
23
|
try {
|
|
24
|
-
const pkg = JSON.parse(readFileSync(packagePath,
|
|
25
|
-
if (typeof pkg.name ===
|
|
24
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
25
|
+
if (typeof pkg.name === "string" && pkg.name.trim())
|
|
26
|
+
return pkg.name.trim();
|
|
26
27
|
} catch {}
|
|
27
28
|
}
|
|
28
|
-
return basename(cwd) ||
|
|
29
|
+
return basename(cwd) || "default";
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function getEngine() {
|
|
@@ -41,22 +42,22 @@ function getEngine() {
|
|
|
41
42
|
function buildErrorContext(event = {}) {
|
|
42
43
|
return {
|
|
43
44
|
exitCode: event.exitCode,
|
|
44
|
-
stderr: String(event.stderr ||
|
|
45
|
+
stderr: String(event.stderr || "").slice(0, 500),
|
|
45
46
|
tool: event.tool,
|
|
46
|
-
command: String(event.command ||
|
|
47
|
+
command: String(event.command || "").slice(0, 200),
|
|
47
48
|
timestamp: new Date().toISOString(),
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export default function hookAdaptiveCollector(event = {}) {
|
|
52
53
|
if (Number(event.exitCode) === 0) return null;
|
|
53
|
-
if (!event.tool || event.tool ===
|
|
54
|
+
if (!event.tool || event.tool === "Read") return null;
|
|
54
55
|
|
|
55
56
|
const result = getEngine().handleError(buildErrorContext(event));
|
|
56
57
|
if (result?.diagnosed) {
|
|
57
|
-
console.error(`[adaptive] 에러 패턴 감지: ${result.rule?.id ||
|
|
58
|
+
console.error(`[adaptive] 에러 패턴 감지: ${result.rule?.id || "unknown"}`);
|
|
58
59
|
if (result.promoted) {
|
|
59
|
-
console.error(`[adaptive] 규칙 승격 → Tier ${result.rule?.tier ??
|
|
60
|
+
console.error(`[adaptive] 규칙 승격 → Tier ${result.rule?.tier ?? "?"}`);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
return result;
|
|
@@ -80,7 +81,8 @@ function main() {
|
|
|
80
81
|
} catch {}
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
const isEntrypoint =
|
|
84
|
+
const isEntrypoint =
|
|
85
|
+
process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url;
|
|
84
86
|
if (isEntrypoint) {
|
|
85
87
|
main();
|
|
86
88
|
}
|
package/hooks/hook-manager.mjs
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
//
|
|
13
13
|
// Claude 대화에서 AskUserQuestion으로 UI를 제공하며 내부적으로 이 명령들을 호출합니다.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { PLUGIN_ROOT } from "./lib/resolve-root.mjs";
|
|
19
19
|
|
|
@@ -27,7 +27,11 @@ const REGISTRY_PATH = join(HOME, ".claude", "cache", "hook-registry.json");
|
|
|
27
27
|
function ensureUserRegistry() {
|
|
28
28
|
if (!existsSync(REGISTRY_PATH) && existsSync(BUNDLED_REGISTRY_PATH)) {
|
|
29
29
|
mkdirSync(dirname(REGISTRY_PATH), { recursive: true });
|
|
30
|
-
writeFileSync(
|
|
30
|
+
writeFileSync(
|
|
31
|
+
REGISTRY_PATH,
|
|
32
|
+
readFileSync(BUNDLED_REGISTRY_PATH, "utf8"),
|
|
33
|
+
"utf8",
|
|
34
|
+
);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -56,7 +60,11 @@ function scan() {
|
|
|
56
60
|
ensureUserRegistry();
|
|
57
61
|
const settings = loadJSON(SETTINGS_PATH);
|
|
58
62
|
if (!settings?.hooks) {
|
|
59
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
status: "no_hooks",
|
|
65
|
+
message: "settings.json에 훅이 없습니다.",
|
|
66
|
+
events: {},
|
|
67
|
+
};
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
const registry = loadJSON(REGISTRY_PATH);
|
|
@@ -81,7 +89,7 @@ function scan() {
|
|
|
81
89
|
// 레지스트리에서 매칭 찾기
|
|
82
90
|
if (registry?.events?.[event]) {
|
|
83
91
|
const match = registry.events[event].find(
|
|
84
|
-
(r) => normalizeCmd(resolveVars(r.command)) === normalizeCmd(cmd)
|
|
92
|
+
(r) => normalizeCmd(resolveVars(r.command)) === normalizeCmd(cmd),
|
|
85
93
|
);
|
|
86
94
|
if (match) {
|
|
87
95
|
hookInfo.registryMatch = { id: match.id, priority: match.priority };
|
|
@@ -100,7 +108,8 @@ function scan() {
|
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
function identifySource(cmd) {
|
|
103
|
-
if (/triflux/i.test(cmd) || /\$\{?CLAUDE_PLUGIN_ROOT\}?/i.test(cmd))
|
|
111
|
+
if (/triflux/i.test(cmd) || /\$\{?CLAUDE_PLUGIN_ROOT\}?/i.test(cmd))
|
|
112
|
+
return "triflux";
|
|
104
113
|
if (/oh-my-claudecode|omc/i.test(cmd)) return "omc";
|
|
105
114
|
if (/session-vault/i.test(cmd)) return "session-vault";
|
|
106
115
|
if (/compact-helper/i.test(cmd)) return "compact-helper";
|
|
@@ -110,7 +119,12 @@ function identifySource(cmd) {
|
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
function normalizeCmd(cmd) {
|
|
113
|
-
return cmd
|
|
122
|
+
return cmd
|
|
123
|
+
.replace(/["']/g, "")
|
|
124
|
+
.replace(/\\/g, "/")
|
|
125
|
+
.replace(/\s+/g, " ")
|
|
126
|
+
.trim()
|
|
127
|
+
.toLowerCase();
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
function resolveVars(cmd) {
|
|
@@ -140,11 +154,21 @@ function diff() {
|
|
|
140
154
|
const currentHooks = settings.hooks[event] || [];
|
|
141
155
|
const registryHooks = registry.events[event] || [];
|
|
142
156
|
|
|
143
|
-
const currentCount = currentHooks.reduce(
|
|
144
|
-
|
|
157
|
+
const currentCount = currentHooks.reduce(
|
|
158
|
+
(n, m) => n + (m.hooks?.length || 0),
|
|
159
|
+
0,
|
|
160
|
+
);
|
|
161
|
+
const registryCount = registryHooks.filter(
|
|
162
|
+
(h) => h.enabled !== false,
|
|
163
|
+
).length;
|
|
145
164
|
|
|
146
165
|
if (currentCount === 1 && isOrchestrator(currentHooks)) {
|
|
147
|
-
changes.push({
|
|
166
|
+
changes.push({
|
|
167
|
+
event,
|
|
168
|
+
action: "already_orchestrated",
|
|
169
|
+
currentCount,
|
|
170
|
+
registryCount,
|
|
171
|
+
});
|
|
148
172
|
} else if (currentCount > 0 || registryCount > 0) {
|
|
149
173
|
changes.push({
|
|
150
174
|
event,
|
|
@@ -162,7 +186,10 @@ function diff() {
|
|
|
162
186
|
function isOrchestrator(matchers) {
|
|
163
187
|
if (!matchers || matchers.length !== 1) return false;
|
|
164
188
|
const hooks = matchers[0]?.hooks || [];
|
|
165
|
-
return
|
|
189
|
+
return (
|
|
190
|
+
hooks.length === 1 &&
|
|
191
|
+
(hooks[0]?.command || "").includes("hook-orchestrator")
|
|
192
|
+
);
|
|
166
193
|
}
|
|
167
194
|
|
|
168
195
|
// ── apply: 오케스트레이터 적용 ──────────────────────────────
|
|
@@ -170,14 +197,22 @@ function isOrchestrator(matchers) {
|
|
|
170
197
|
function apply() {
|
|
171
198
|
ensureUserRegistry();
|
|
172
199
|
const settings = loadJSON(SETTINGS_PATH);
|
|
173
|
-
if (!settings)
|
|
200
|
+
if (!settings)
|
|
201
|
+
return { status: "error", message: "settings.json을 찾을 수 없습니다." };
|
|
174
202
|
|
|
175
203
|
const registry = loadJSON(REGISTRY_PATH);
|
|
176
|
-
if (!registry)
|
|
204
|
+
if (!registry)
|
|
205
|
+
return {
|
|
206
|
+
status: "error",
|
|
207
|
+
message: "hook-registry.json을 찾을 수 없습니다.",
|
|
208
|
+
};
|
|
177
209
|
|
|
178
210
|
// 백업
|
|
179
211
|
if (settings.hooks && !existsSync(BACKUP_PATH)) {
|
|
180
|
-
saveJSON(BACKUP_PATH, {
|
|
212
|
+
saveJSON(BACKUP_PATH, {
|
|
213
|
+
hooks: settings.hooks,
|
|
214
|
+
backedUpAt: new Date().toISOString(),
|
|
215
|
+
});
|
|
181
216
|
}
|
|
182
217
|
|
|
183
218
|
// 오케스트레이터 명령 생성
|
|
@@ -201,7 +236,8 @@ function apply() {
|
|
|
201
236
|
if (enabledEntries.length > 0) {
|
|
202
237
|
// 레지스트리에 있으면 → 오케스트레이터로 교체
|
|
203
238
|
// 가장 큰 timeout을 기준으로 오케스트레이터 timeout 설정
|
|
204
|
-
const maxTimeout =
|
|
239
|
+
const maxTimeout =
|
|
240
|
+
Math.max(...enabledEntries.map((h) => h.timeout || 10)) + 5;
|
|
205
241
|
|
|
206
242
|
newHooks[event] = [
|
|
207
243
|
{
|
|
@@ -238,7 +274,10 @@ function apply() {
|
|
|
238
274
|
|
|
239
275
|
function restore() {
|
|
240
276
|
if (!existsSync(BACKUP_PATH)) {
|
|
241
|
-
return {
|
|
277
|
+
return {
|
|
278
|
+
status: "no_backup",
|
|
279
|
+
message: "백업 파일이 없습니다. apply 전에는 복원할 수 없습니다.",
|
|
280
|
+
};
|
|
242
281
|
}
|
|
243
282
|
|
|
244
283
|
const backup = loadJSON(BACKUP_PATH);
|
|
@@ -247,7 +286,8 @@ function restore() {
|
|
|
247
286
|
}
|
|
248
287
|
|
|
249
288
|
const settings = loadJSON(SETTINGS_PATH);
|
|
250
|
-
if (!settings)
|
|
289
|
+
if (!settings)
|
|
290
|
+
return { status: "error", message: "settings.json을 찾을 수 없습니다." };
|
|
251
291
|
|
|
252
292
|
settings.hooks = backup.hooks;
|
|
253
293
|
saveJSON(SETTINGS_PATH, settings);
|
|
@@ -263,10 +303,12 @@ function restore() {
|
|
|
263
303
|
function setPriority(hookId, priority) {
|
|
264
304
|
ensureUserRegistry();
|
|
265
305
|
const registry = loadJSON(REGISTRY_PATH);
|
|
266
|
-
if (!registry)
|
|
306
|
+
if (!registry)
|
|
307
|
+
return { status: "error", message: "레지스트리를 찾을 수 없습니다." };
|
|
267
308
|
|
|
268
309
|
const numPriority = parseInt(priority, 10);
|
|
269
|
-
if (isNaN(numPriority))
|
|
310
|
+
if (Number.isNaN(numPriority))
|
|
311
|
+
return { status: "error", message: "priority는 숫자여야 합니다." };
|
|
270
312
|
|
|
271
313
|
let found = false;
|
|
272
314
|
for (const hooks of Object.values(registry.events)) {
|
|
@@ -278,10 +320,17 @@ function setPriority(hookId, priority) {
|
|
|
278
320
|
}
|
|
279
321
|
}
|
|
280
322
|
|
|
281
|
-
if (!found)
|
|
323
|
+
if (!found)
|
|
324
|
+
return {
|
|
325
|
+
status: "not_found",
|
|
326
|
+
message: `훅 '${hookId}'를 찾을 수 없습니다.`,
|
|
327
|
+
};
|
|
282
328
|
|
|
283
329
|
saveJSON(REGISTRY_PATH, registry);
|
|
284
|
-
return {
|
|
330
|
+
return {
|
|
331
|
+
status: "ok",
|
|
332
|
+
message: `${hookId}의 우선순위가 ${numPriority}로 변경되었습니다.`,
|
|
333
|
+
};
|
|
285
334
|
}
|
|
286
335
|
|
|
287
336
|
// ── toggle: 활성/비활성 토글 ────────────────────────────────
|
|
@@ -289,7 +338,8 @@ function setPriority(hookId, priority) {
|
|
|
289
338
|
function toggle(hookId) {
|
|
290
339
|
ensureUserRegistry();
|
|
291
340
|
const registry = loadJSON(REGISTRY_PATH);
|
|
292
|
-
if (!registry)
|
|
341
|
+
if (!registry)
|
|
342
|
+
return { status: "error", message: "레지스트리를 찾을 수 없습니다." };
|
|
293
343
|
|
|
294
344
|
let found = false;
|
|
295
345
|
let newState = false;
|
|
@@ -303,10 +353,17 @@ function toggle(hookId) {
|
|
|
303
353
|
}
|
|
304
354
|
}
|
|
305
355
|
|
|
306
|
-
if (!found)
|
|
356
|
+
if (!found)
|
|
357
|
+
return {
|
|
358
|
+
status: "not_found",
|
|
359
|
+
message: `훅 '${hookId}'를 찾을 수 없습니다.`,
|
|
360
|
+
};
|
|
307
361
|
|
|
308
362
|
saveJSON(REGISTRY_PATH, registry);
|
|
309
|
-
return {
|
|
363
|
+
return {
|
|
364
|
+
status: "ok",
|
|
365
|
+
message: `${hookId}: ${newState ? "활성화" : "비활성화"}`,
|
|
366
|
+
};
|
|
310
367
|
}
|
|
311
368
|
|
|
312
369
|
// ── status: 현재 적용 상태 ──────────────────────────────────
|
|
@@ -318,7 +375,7 @@ function status() {
|
|
|
318
375
|
let orchestrated = 0;
|
|
319
376
|
let individual = 0;
|
|
320
377
|
|
|
321
|
-
for (const [
|
|
378
|
+
for (const [_event, matchers] of Object.entries(settings.hooks)) {
|
|
322
379
|
if (isOrchestrator(matchers)) {
|
|
323
380
|
orchestrated++;
|
|
324
381
|
} else {
|
|
@@ -333,9 +390,10 @@ function status() {
|
|
|
333
390
|
orchestratedEvents: orchestrated,
|
|
334
391
|
individualEvents: individual,
|
|
335
392
|
hasBackup,
|
|
336
|
-
message:
|
|
337
|
-
|
|
338
|
-
|
|
393
|
+
message:
|
|
394
|
+
orchestrated > 0
|
|
395
|
+
? `오케스트레이터 적용 중: ${orchestrated}개 이벤트 통합, ${individual}개 개별 유지`
|
|
396
|
+
: `오케스트레이터 미적용. ${individual}개 이벤트가 개별 훅으로 실행 중`,
|
|
339
397
|
};
|
|
340
398
|
}
|
|
341
399
|
|
|
@@ -354,10 +412,13 @@ const commands = {
|
|
|
354
412
|
};
|
|
355
413
|
|
|
356
414
|
if (!command || !commands[command]) {
|
|
357
|
-
console.log(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
415
|
+
console.log(
|
|
416
|
+
JSON.stringify({
|
|
417
|
+
error:
|
|
418
|
+
"사용법: node hook-manager.mjs <scan|diff|apply|restore|set-priority|toggle|status>",
|
|
419
|
+
commands: Object.keys(commands),
|
|
420
|
+
}),
|
|
421
|
+
);
|
|
361
422
|
process.exit(1);
|
|
362
423
|
}
|
|
363
424
|
|