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.
Files changed (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ // scripts/config-audit.mjs — 설정 정적 보안/성능 감사
3
+ //
4
+ // triflux doctor --audit 또는 단독 실행.
5
+ // settings.json, CLAUDE.md, MCP, 훅 설정을 스캔하여 위험/성능 이슈 탐지.
6
+ //
7
+ // 출력: JSON (--json) 또는 마크다운 테이블
8
+
9
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join, basename } from "node:path";
12
+
13
+ const HOME = homedir();
14
+ const CLAUDE_DIR = join(HOME, ".claude");
15
+ const JSON_OUTPUT = process.argv.includes("--json");
16
+
17
+ // ── 결과 수집 ──
18
+ const findings = [];
19
+
20
+ function addFinding(category, severity, message, detail = "") {
21
+ findings.push({ category, severity, message, detail });
22
+ }
23
+
24
+ // ── 1. settings.json 감사 ──
25
+ function auditSettings() {
26
+ const settingsPath = join(CLAUDE_DIR, "settings.json");
27
+ if (!existsSync(settingsPath)) {
28
+ addFinding("settings", "info", "settings.json 없음", settingsPath);
29
+ return;
30
+ }
31
+
32
+ let settings;
33
+ try {
34
+ settings = JSON.parse(readFileSync(settingsPath, "utf8"));
35
+ } catch {
36
+ addFinding("settings", "warn", "settings.json 파싱 실패", settingsPath);
37
+ return;
38
+ }
39
+
40
+ // 권한 모드
41
+ const mode = settings.permissions?.defaultMode;
42
+ if (mode === "bypassPermissions" || mode === "acceptEdits") {
43
+ addFinding("settings", "warn", `defaultMode: "${mode}" — 도구 승인 없이 실행됨`, "보안 리뷰 시 주의");
44
+ }
45
+
46
+ // 환경 변수 내 시크릿 패턴
47
+ const env = settings.env || {};
48
+ const SECRET_PATTERNS = [
49
+ /(?:api[_-]?key|secret|token|password|credential)s?\s*[:=]/i,
50
+ /(?:sk-|ghp_|gho_|github_pat_|xoxb-|xoxp-)/i,
51
+ /ANTHROPIC_API_KEY/i,
52
+ /OPENAI_API_KEY/i,
53
+ ];
54
+ for (const [key, value] of Object.entries(env)) {
55
+ for (const pattern of SECRET_PATTERNS) {
56
+ if (pattern.test(key) || pattern.test(String(value))) {
57
+ addFinding("settings", "critical", `env에 시크릿 패턴 감지: ${key}`, "환경변수로 분리 권장");
58
+ break;
59
+ }
60
+ }
61
+ }
62
+
63
+ // 훅 timeout 검사
64
+ const hooks = settings.hooks || {};
65
+ let totalHooks = 0;
66
+ let longTimeouts = 0;
67
+ for (const [event, matchers] of Object.entries(hooks)) {
68
+ for (const matcher of Array.isArray(matchers) ? matchers : []) {
69
+ for (const hook of matcher.hooks || []) {
70
+ totalHooks++;
71
+ if (hook.timeout && hook.timeout > 15) {
72
+ longTimeouts++;
73
+ addFinding("hooks", "warn", `${event} 훅 timeout ${hook.timeout}s (>15s)`, hook.command?.slice(0, 80));
74
+ }
75
+ }
76
+ }
77
+ }
78
+ if (totalHooks > 10) {
79
+ addFinding("hooks", "info", `settings.json에 훅 ${totalHooks}개 등록`, "SessionStart 성능에 영향 가능");
80
+ }
81
+ }
82
+
83
+ // ── 2. CLAUDE.md 시크릿 스캔 ──
84
+ function auditClaudeMd() {
85
+ const candidates = [
86
+ join(process.cwd(), "CLAUDE.md"),
87
+ join(CLAUDE_DIR, "CLAUDE.md"),
88
+ ];
89
+
90
+ for (const mdPath of candidates) {
91
+ if (!existsSync(mdPath)) continue;
92
+ let content;
93
+ try {
94
+ content = readFileSync(mdPath, "utf8");
95
+ } catch { continue; }
96
+
97
+ const SECRET_LINE_PATTERNS = [
98
+ /(?:api[_-]?key|secret|password)\s*[:=]\s*["']?\S{8,}/i,
99
+ /(?:^|[\s"'=:])(?:sk-|ghp_|gho_|github_pat_|xoxb-|xoxp-)[A-Za-z0-9_-]{10,}/,
100
+ /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/,
101
+ ];
102
+
103
+ const lines = content.split("\n");
104
+ for (let i = 0; i < lines.length; i++) {
105
+ for (const pattern of SECRET_LINE_PATTERNS) {
106
+ if (pattern.test(lines[i])) {
107
+ addFinding("claude-md", "critical", `CLAUDE.md:${i + 1} 시크릿 패턴 감지`, basename(mdPath));
108
+ break;
109
+ }
110
+ }
111
+ }
112
+
113
+ // 길이 경고
114
+ const tokenEstimate = Math.ceil(Buffer.byteLength(content, "utf8") / 4);
115
+ if (tokenEstimate > 3000) {
116
+ addFinding("claude-md", "warn", `CLAUDE.md ~${tokenEstimate} 토큰 (>3000)`, `${basename(mdPath)} — 컨텍스트 로트 위험`);
117
+ }
118
+ }
119
+ }
120
+
121
+ // ── 3. MCP 설정 감사 ──
122
+ function auditMcp() {
123
+ const mcpPaths = [
124
+ join(CLAUDE_DIR, "mcp_servers.json"),
125
+ join(CLAUDE_DIR, ".mcp.json"),
126
+ join(process.cwd(), ".mcp.json"),
127
+ ];
128
+
129
+ let totalServers = 0;
130
+ let stdioCount = 0;
131
+
132
+ for (const mcpPath of mcpPaths) {
133
+ if (!existsSync(mcpPath)) continue;
134
+ let config;
135
+ try {
136
+ config = JSON.parse(readFileSync(mcpPath, "utf8"));
137
+ } catch { continue; }
138
+
139
+ const servers = config.mcpServers || config.servers || config;
140
+ for (const [name, def] of Object.entries(servers)) {
141
+ if (!def || typeof def !== "object") continue;
142
+ totalServers++;
143
+
144
+ if (def.type === "stdio" || def.command) {
145
+ stdioCount++;
146
+ }
147
+
148
+ // 위험 패턴: 쉘 실행, eval, 알 수 없는 npx 패키지
149
+ const cmd = String(def.command || "");
150
+ if (/\beval\b|\bexec\b.*sh\b|\bcurl\b.*\|\s*(?:bash|sh)\b/i.test(cmd)) {
151
+ addFinding("mcp", "critical", `MCP "${name}": 위험한 명령 패턴`, cmd.slice(0, 100));
152
+ }
153
+ }
154
+ }
155
+
156
+ if (totalServers > 10) {
157
+ addFinding("mcp", "warn", `MCP 서버 ${totalServers}개 등록 (>10)`, "컨텍스트 윈도우 압박 가능");
158
+ }
159
+ if (stdioCount > 5) {
160
+ addFinding("mcp", "info", `stdio MCP ${stdioCount}개 — 프로세스 spawn 부하`, "불필요한 서버 비활성화 권장");
161
+ }
162
+ }
163
+
164
+ // ── 4. 훅 레지스트리 감사 ──
165
+ function auditHookRegistry() {
166
+ const registryPath = join(process.cwd(), "hooks", "hook-registry.json");
167
+ if (!existsSync(registryPath)) return;
168
+
169
+ let registry;
170
+ try {
171
+ registry = JSON.parse(readFileSync(registryPath, "utf8"));
172
+ } catch { return; }
173
+
174
+ const events = registry.events || {};
175
+ let blockingCount = 0;
176
+ let externalCount = 0;
177
+
178
+ for (const [event, hooks] of Object.entries(events)) {
179
+ for (const hook of hooks) {
180
+ if (hook.blocking) blockingCount++;
181
+ if (hook.source !== "triflux" && hook.source !== "omc") externalCount++;
182
+
183
+ // 외부 훅의 명령에 위험 패턴
184
+ if (hook.source !== "triflux") {
185
+ const cmd = String(hook.command || "");
186
+ if (/\brm\b|\bdel\b|\bformat\b|\bgit\s+push\b/i.test(cmd)) {
187
+ addFinding("hooks", "warn", `외부 훅 "${hook.id}": 위험 명령 패턴`, cmd.slice(0, 80));
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ if (blockingCount > 5) {
194
+ addFinding("hooks", "info", `blocking 훅 ${blockingCount}개 — 도구 실행 지연 가능`, "불필요한 blocking 해제 검토");
195
+ }
196
+ if (externalCount > 0) {
197
+ addFinding("hooks", "info", `외부 훅 ${externalCount}개 등록`, "출처 확인 권장");
198
+ }
199
+ }
200
+
201
+ // ── 실행 ──
202
+ auditSettings();
203
+ auditClaudeMd();
204
+ auditMcp();
205
+ auditHookRegistry();
206
+
207
+ // ── 출력 ──
208
+ const summary = {
209
+ total: findings.length,
210
+ critical: findings.filter(f => f.severity === "critical").length,
211
+ warn: findings.filter(f => f.severity === "warn").length,
212
+ info: findings.filter(f => f.severity === "info").length,
213
+ };
214
+
215
+ if (JSON_OUTPUT) {
216
+ console.log(JSON.stringify({ summary, findings }, null, 2));
217
+ } else {
218
+ const SEVERITY_ICON = { critical: "🔴", warn: "🟡", info: "🔵" };
219
+ console.log("\n ⬡ triflux config audit\n");
220
+ if (findings.length === 0) {
221
+ console.log(" ✅ 이슈 없음\n");
222
+ } else {
223
+ console.log(` | 심각도 | 카테고리 | 이슈 | 상세 |`);
224
+ console.log(` |--------|----------|------|------|`);
225
+ for (const f of findings) {
226
+ console.log(` | ${SEVERITY_ICON[f.severity] || "⚪"} ${f.severity} | ${f.category} | ${f.message} | ${f.detail} |`);
227
+ }
228
+ console.log(`\n 합계: critical=${summary.critical} warn=${summary.warn} info=${summary.info}\n`);
229
+ }
230
+ }
231
+
232
+ process.exit(summary.critical > 0 ? 1 : 0);
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Converts SKILL.md files to SKILL.md.tmpl format.
4
+ * - Replaces skill name in title with {{SKILL_NAME}}
5
+ * - Removes expanded base block (ARGUMENTS + Telemetry)
6
+ * - Inserts {{> base}} after the title line
7
+ */
8
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
9
+ import { join } from "path";
10
+
11
+ const SKILLS_DIR = join(import.meta.dirname, "..", "skills");
12
+
13
+ const BASE_BLOCK_RE =
14
+ /\n> \*\*ARGUMENTS 처리\*\*.*?\n> 워크플로우의 첫 단계.*?기존 절차대로.*?\n\n> \*\*Telemetry\*\*\n>\n> - Skill:.*?\n> - Description:.*?\n> - Session:.*?\n> - Errors:.*?\n/s;
15
+
16
+ let converted = 0;
17
+ let skipped = 0;
18
+
19
+ for (const name of readdirSync(SKILLS_DIR)) {
20
+ const dir = join(SKILLS_DIR, name);
21
+ const skillMd = join(dir, "SKILL.md");
22
+ const tmplMd = join(dir, "SKILL.md.tmpl");
23
+
24
+ if (name.startsWith("_")) continue;
25
+ if (!existsSync(skillMd)) continue;
26
+ if (existsSync(tmplMd)) {
27
+ skipped++;
28
+ continue;
29
+ }
30
+
31
+ let content = readFileSync(skillMd, "utf8");
32
+
33
+ // Replace skill name in title: # tfx-foo — Title -> # {{SKILL_NAME}} — Title
34
+ content = content.replace(
35
+ new RegExp(`^(# )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}( —)`, "m"),
36
+ "$1{{SKILL_NAME}}$2",
37
+ );
38
+
39
+ // Remove expanded base block if present
40
+ if (BASE_BLOCK_RE.test(content)) {
41
+ content = content.replace(BASE_BLOCK_RE, "\n\n{{> base}}\n\n");
42
+ } else {
43
+ // Insert {{> base}} after the title line
44
+ content = content.replace(/^(# .+\n)/, "$1\n{{> base}}\n");
45
+ }
46
+
47
+ writeFileSync(tmplMd, content);
48
+ converted++;
49
+ console.log(`✅ ${name}`);
50
+ }
51
+
52
+ console.log(
53
+ `\nConverted: ${converted}, Skipped (already has .tmpl): ${skipped}`,
54
+ );
@@ -2,17 +2,17 @@
2
2
 
3
3
  import { existsSync, readFileSync, unlinkSync } from "node:fs";
4
4
  import { join } from "node:path";
5
- import { nudge, deny } from "./lib/hook-utils.mjs";
6
5
  import {
7
- readStdin,
8
- parseJson,
6
+ expectedReviewer,
9
7
  nowSec,
8
+ parseJson,
9
+ readStdin,
10
10
  resolveBaseDir,
11
- shouldTrackPath,
12
- expectedReviewer,
13
11
  SESSION_TTL_SEC,
14
12
  STATE_REL_PATH,
13
+ shouldTrackPath,
15
14
  } from "./lib/cross-review-utils.mjs";
15
+ import { deny, nudge } from "./lib/hook-utils.mjs";
16
16
 
17
17
  function loadState(statePath) {
18
18
  if (!existsSync(statePath)) return null;
@@ -84,17 +84,37 @@ async function main() {
84
84
 
85
85
  // tracker가 설정한 self_approved 플래그 명시적 체크
86
86
  if (meta.self_approved === true) {
87
- selfApproved.push({ path, author, reviewer: meta.reviewer || author, expectedReviewer: requiredReviewer });
87
+ selfApproved.push({
88
+ path,
89
+ author,
90
+ reviewer: meta.reviewer || author,
91
+ expectedReviewer: requiredReviewer,
92
+ });
88
93
  continue;
89
94
  }
90
95
 
91
96
  if (reviewed && reviewer && reviewer === author) {
92
- selfApproved.push({ path, author, reviewer, expectedReviewer: requiredReviewer });
97
+ selfApproved.push({
98
+ path,
99
+ author,
100
+ reviewer,
101
+ expectedReviewer: requiredReviewer,
102
+ });
93
103
  continue;
94
104
  }
95
105
 
96
- if (reviewed && requiredReviewer && reviewer && reviewer !== requiredReviewer) {
97
- selfApproved.push({ path, author, reviewer, expectedReviewer: requiredReviewer });
106
+ if (
107
+ reviewed &&
108
+ requiredReviewer &&
109
+ reviewer &&
110
+ reviewer !== requiredReviewer
111
+ ) {
112
+ selfApproved.push({
113
+ path,
114
+ author,
115
+ reviewer,
116
+ expectedReviewer: requiredReviewer,
117
+ });
98
118
  continue;
99
119
  }
100
120
 
@@ -105,18 +125,21 @@ async function main() {
105
125
 
106
126
  if (selfApproved.length > 0) {
107
127
  const lines = selfApproved
108
- .map((item) => ` * ${item.path} (author=${item.author}, reviewer=${item.reviewer}, required=${item.expectedReviewer || "n/a"})`)
128
+ .map(
129
+ (item) =>
130
+ ` * ${item.path} (author=${item.author}, reviewer=${item.reviewer}, required=${item.expectedReviewer || "n/a"})`,
131
+ )
109
132
  .join("\n");
110
133
  deny(
111
134
  `[cross-review] self-approve 차단: 동일/비허용 reviewer가 감지되었습니다.\n${lines}\n` +
112
- "규칙: author=claude -> reviewer=codex, author=codex -> reviewer=claude",
135
+ "규칙: author=claude -> reviewer=codex, author=codex -> reviewer=claude",
113
136
  );
114
137
  }
115
138
 
116
139
  if (pending.length > 0) {
117
140
  nudge(
118
141
  `[cross-review] git commit 전에 교차 검증이 필요합니다.\n${summarizePending(pending)}\n` +
119
- "규칙: author=claude -> reviewer=codex, author=codex -> reviewer=claude",
142
+ "규칙: author=claude -> reviewer=codex, author=codex -> reviewer=claude",
120
143
  );
121
144
  }
122
145
 
@@ -1,16 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ unlinkSync,
8
+ writeFileSync,
9
+ } from "node:fs";
4
10
  import { dirname, isAbsolute, join, relative } from "node:path";
5
11
  import {
6
- readStdin,
7
- parseJson,
12
+ expectedReviewer,
8
13
  nowSec,
14
+ parseJson,
15
+ readStdin,
9
16
  resolveBaseDir,
10
- shouldTrackPath,
11
- expectedReviewer,
12
17
  SESSION_TTL_SEC,
13
18
  STATE_REL_PATH,
19
+ shouldTrackPath,
14
20
  } from "./lib/cross-review-utils.mjs";
15
21
 
16
22
  function resolveStatePath(baseDir) {
@@ -39,7 +45,8 @@ function loadState(statePath) {
39
45
  }
40
46
 
41
47
  return {
42
- files: parsed?.files && typeof parsed.files === "object" ? parsed.files : {},
48
+ files:
49
+ parsed?.files && typeof parsed.files === "object" ? parsed.files : {},
43
50
  session_start: sessionStart,
44
51
  };
45
52
  } catch {
@@ -69,7 +76,8 @@ function normalizePath(filePath, baseDir) {
69
76
 
70
77
  function extractFilePath(toolInput) {
71
78
  if (!toolInput || typeof toolInput !== "object") return "";
72
- const candidate = toolInput.file_path ?? toolInput.path ?? toolInput.filePath ?? "";
79
+ const candidate =
80
+ toolInput.file_path ?? toolInput.path ?? toolInput.filePath ?? "";
73
81
  return typeof candidate === "string" ? candidate : "";
74
82
  }
75
83
 
@@ -81,7 +89,12 @@ function extractCandidatePaths(payload, baseDir) {
81
89
  const trimmed = value.trim();
82
90
  if (!trimmed || /\s/.test(trimmed)) return false;
83
91
  if (trimmed.length > 260) return false;
84
- if (!trimmed.includes(".") && !trimmed.includes("/") && !trimmed.includes("\\")) return false;
92
+ if (
93
+ !trimmed.includes(".") &&
94
+ !trimmed.includes("/") &&
95
+ !trimmed.includes("\\")
96
+ )
97
+ return false;
85
98
  return /^[./\\A-Za-z0-9_-]/.test(trimmed);
86
99
  };
87
100
 
package/scripts/demo.mjs CHANGED
@@ -3,14 +3,6 @@
3
3
  import childProcess from "node:child_process";
4
4
  import { parseArgs } from "node:util";
5
5
 
6
- const { values: flags } = parseArgs({
7
- options: {
8
- "dry-run": { type: "boolean", default: false },
9
- keep: { type: "boolean", default: false },
10
- },
11
- strict: false,
12
- });
13
-
14
6
  const SESSION_NAME = "triflux-demo";
15
7
 
16
8
  const WORKERS = [
@@ -46,7 +38,10 @@ const WORKERS = [
46
38
  export function checkPsmux(opts = {}) {
47
39
  if (opts.dryRun) return false;
48
40
  try {
49
- childProcess.execFileSync("psmux", ["-V"], { encoding: "utf8", stdio: "pipe" });
41
+ childProcess.execFileSync("psmux", ["-V"], {
42
+ encoding: "utf8",
43
+ stdio: "pipe",
44
+ });
50
45
  return true;
51
46
  } catch {
52
47
  return false;
@@ -60,9 +55,19 @@ export function createDemoSession(sessionName, opts = {}) {
60
55
  console.log(`[dry-run] psmux split-window -h -t ${sessionName}`);
61
56
  return;
62
57
  }
63
- childProcess.execFileSync("psmux", ["new-session", "-d", "-s", sessionName], { stdio: "pipe" });
64
- childProcess.execFileSync("psmux", ["split-window", "-h", "-t", sessionName], { stdio: "pipe" });
65
- childProcess.execFileSync("psmux", ["split-window", "-h", "-t", sessionName], { stdio: "pipe" });
58
+ childProcess.execFileSync("psmux", ["new-session", "-d", "-s", sessionName], {
59
+ stdio: "pipe",
60
+ });
61
+ childProcess.execFileSync(
62
+ "psmux",
63
+ ["split-window", "-h", "-t", sessionName],
64
+ { stdio: "pipe" },
65
+ );
66
+ childProcess.execFileSync(
67
+ "psmux",
68
+ ["split-window", "-h", "-t", sessionName],
69
+ { stdio: "pipe" },
70
+ );
66
71
  }
67
72
 
68
73
  export function simulateWorker(pane, agentName, messages, opts = {}) {
@@ -70,11 +75,19 @@ export function simulateWorker(pane, agentName, messages, opts = {}) {
70
75
  for (const msg of messages) {
71
76
  const escapedMsg = msg.replace(/'/g, "'\\''");
72
77
  if (opts.dryRun) {
73
- console.log(`[dry-run] psmux send-keys -t ${sessionName}:0.${pane} "echo '${escapedMsg}'" Enter`);
78
+ console.log(
79
+ `[dry-run] psmux send-keys -t ${sessionName}:0.${pane} "echo '${escapedMsg}'" Enter`,
80
+ );
74
81
  } else {
75
82
  childProcess.execFileSync(
76
83
  "psmux",
77
- ["send-keys", "-t", `${sessionName}:0.${pane}`, `echo '${escapedMsg}'`, "Enter"],
84
+ [
85
+ "send-keys",
86
+ "-t",
87
+ `${sessionName}:0.${pane}`,
88
+ `echo '${escapedMsg}'`,
89
+ "Enter",
90
+ ],
78
91
  { stdio: "pipe" },
79
92
  );
80
93
  }
@@ -102,7 +115,9 @@ export function cleanup(sessionName, opts = {}) {
102
115
  return;
103
116
  }
104
117
  try {
105
- childProcess.execFileSync("psmux", ["kill-session", "-t", sessionName], { stdio: "pipe" });
118
+ childProcess.execFileSync("psmux", ["kill-session", "-t", sessionName], {
119
+ stdio: "pipe",
120
+ });
106
121
  } catch {
107
122
  // session may already be gone
108
123
  }
@@ -143,7 +158,7 @@ async function main() {
143
158
  if (!opts.dryRun) {
144
159
  await wait(2000);
145
160
  }
146
-
161
+
147
162
  showSummary();
148
163
 
149
164
  if (!opts.keep) {
@@ -155,7 +170,10 @@ async function main() {
155
170
  // Normalize both paths to forward-slash for cross-platform comparison
156
171
  function isDirectExec() {
157
172
  if (!process.argv[1]) return false;
158
- const scriptPath = new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
173
+ const scriptPath = new URL(import.meta.url).pathname.replace(
174
+ /^\/([A-Za-z]:)/,
175
+ "$1",
176
+ );
159
177
  const argv1 = process.argv[1].replace(/\\/g, "/");
160
178
  const norm = scriptPath.replace(/\\/g, "/");
161
179
  return argv1 === norm || argv1.endsWith(norm);