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
@@ -9,7 +9,7 @@
9
9
  // - Hub 시작 시 (server.mjs에서 import)
10
10
  // - 수동: node hub/promote-penalties.mjs
11
11
 
12
- import { readFileSync, writeFileSync, existsSync, } from "node:fs";
12
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { adaptiveRuleFromError, promoteRule } from "./reflexion.mjs";
15
15
 
@@ -24,14 +24,24 @@ function loadPenalties() {
24
24
  .split("\n")
25
25
  .filter(Boolean)
26
26
  .map((line) => {
27
- try { return JSON.parse(line); } catch { return null; }
27
+ try {
28
+ return JSON.parse(line);
29
+ } catch {
30
+ return null;
31
+ }
28
32
  })
29
33
  .filter(Boolean);
30
- } catch { return []; }
34
+ } catch {
35
+ return [];
36
+ }
31
37
  }
32
38
 
33
39
  function clearPenalties() {
34
- try { writeFileSync(PENALTY_FILE, "", "utf8"); } catch { /* ignore */ }
40
+ try {
41
+ writeFileSync(PENALTY_FILE, "", "utf8");
42
+ } catch {
43
+ /* ignore */
44
+ }
35
45
  }
36
46
 
37
47
  /**
@@ -70,7 +80,10 @@ export function promotePenalties(store, options = {}) {
70
80
 
71
81
  try {
72
82
  if (store.addAdaptiveRule) {
73
- const existing = store.findAdaptiveRule?.(projectSlug, rule.error_pattern);
83
+ const existing = store.findAdaptiveRule?.(
84
+ projectSlug,
85
+ rule.error_pattern,
86
+ );
74
87
  if (existing) {
75
88
  // 기존 규칙 → confidence 승격
76
89
  promoteRule(store, projectSlug, rule.error_pattern);
@@ -81,7 +94,10 @@ export function promotePenalties(store, options = {}) {
81
94
  pattern: rule.error_pattern,
82
95
  error_message: rule.error_message,
83
96
  solution: rule.solution,
84
- context: typeof rule.context === "string" ? rule.context : JSON.stringify(rule.context),
97
+ context:
98
+ typeof rule.context === "string"
99
+ ? rule.context
100
+ : JSON.stringify(rule.context),
85
101
  confidence: rule.confidence,
86
102
  hit_count: rule.hit_count,
87
103
  last_seen_ms: Date.now(),
@@ -109,7 +125,9 @@ export function dryRun() {
109
125
  const penalties = loadPenalties();
110
126
  console.log(`[promote-penalties] ${penalties.length} pending penalties`);
111
127
  for (const p of penalties) {
112
- console.log(` ${p.ts?.slice(0, 19)} [${p.source}] ${p.error_pattern?.slice(0, 80)}`);
128
+ console.log(
129
+ ` ${p.ts?.slice(0, 19)} [${p.source}] ${p.error_pattern?.slice(0, 80)}`,
130
+ );
113
131
  }
114
132
  return penalties;
115
133
  }
@@ -3,57 +3,59 @@
3
3
  * AI 생성 코드에서 불필요한 요소를 자동 탐지/제거하는 정적 분석 모듈
4
4
  */
5
5
 
6
- import { readdir, readFile, writeFile, stat } from 'node:fs/promises';
7
- import { join, } from 'node:path';
6
+ import { readdir, readFile, stat, writeFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
8
 
9
9
  /** @type {ReadonlyArray<{type: string, pattern: RegExp, severity: string, autoFixable: boolean, multiline: boolean}>} */
10
10
  export const SLOP_PATTERNS = Object.freeze([
11
11
  {
12
- type: 'trivial_comment',
12
+ type: "trivial_comment",
13
13
  pattern: /^\s*\/\/\s*(import|define|set|get|return|export)\s/i,
14
- severity: 'low',
14
+ severity: "low",
15
15
  autoFixable: true,
16
16
  multiline: false,
17
17
  },
18
18
  {
19
- type: 'empty_catch',
19
+ type: "empty_catch",
20
20
  pattern: /catch\s*\([^)]*\)\s*\{\s*\}/,
21
- severity: 'med',
21
+ severity: "med",
22
22
  autoFixable: false,
23
23
  multiline: true,
24
24
  },
25
25
  {
26
- type: 'console_debug',
26
+ type: "console_debug",
27
27
  pattern: /^\s*console\.(log|debug|info)\(/,
28
- severity: 'low',
28
+ severity: "low",
29
29
  autoFixable: true,
30
30
  multiline: false,
31
31
  },
32
32
  {
33
- type: 'useless_jsdoc',
33
+ type: "useless_jsdoc",
34
34
  pattern: /\/\*\*\s*\n\s*\*\s*\n\s*\*\//,
35
- severity: 'low',
35
+ severity: "low",
36
36
  autoFixable: true,
37
37
  multiline: true,
38
38
  },
39
39
  {
40
- type: 'rethrow_only',
40
+ type: "rethrow_only",
41
41
  pattern: /catch\s*\((\w+)\)\s*\{\s*throw\s+\1\s*;?\s*\}/,
42
- severity: 'med',
42
+ severity: "med",
43
43
  autoFixable: false,
44
44
  multiline: true,
45
45
  },
46
46
  {
47
- type: 'redundant_type',
48
- pattern: /:\s*(string|number|boolean)\s*=\s*('[^']*'|"[^"]*"|\d+|true|false)/,
49
- severity: 'low',
47
+ type: "redundant_type",
48
+ pattern:
49
+ /:\s*(string|number|boolean)\s*=\s*('[^']*'|"[^"]*"|\d+|true|false)/,
50
+ severity: "low",
50
51
  autoFixable: false,
51
52
  multiline: false,
52
53
  },
53
54
  {
54
- type: 'commented_code',
55
- pattern: /^\s*\/\/\s*(const |let |var |function |class |if\s*\(|for\s*\(|while\s*\(|return |await )/,
56
- severity: 'low',
55
+ type: "commented_code",
56
+ pattern:
57
+ /^\s*\/\/\s*(const |let |var |function |class |if\s*\(|for\s*\(|while\s*\(|return |await )/,
58
+ severity: "low",
57
59
  autoFixable: true,
58
60
  multiline: false,
59
61
  },
@@ -67,8 +69,8 @@ const SEVERITY_WEIGHT = { low: 2, med: 5 };
67
69
  * @param {string} [filePath] - 파일 경로 (보고용)
68
70
  * @returns {{ issues: Array<{line: number, type: string, severity: string, suggestion: string, text: string, autoFixable: boolean}>, score: number }}
69
71
  */
70
- export function detectSlop(content, filePath = '') {
71
- const lines = content.split('\n');
72
+ export function detectSlop(content, filePath = "") {
73
+ const lines = content.split("\n");
72
74
  const issues = [];
73
75
 
74
76
  for (const sp of SLOP_PATTERNS) {
@@ -90,16 +92,19 @@ export function detectSlop(content, filePath = '') {
90
92
 
91
93
  for (const sp of SLOP_PATTERNS) {
92
94
  if (!sp.multiline) continue;
93
- const regex = new RegExp(sp.pattern.source, sp.pattern.flags.replace('g', '') + 'g');
95
+ const regex = new RegExp(
96
+ sp.pattern.source,
97
+ sp.pattern.flags.replace("g", "") + "g",
98
+ );
94
99
  let match;
95
100
  while ((match = regex.exec(content)) !== null) {
96
- const line = content.substring(0, match.index).split('\n').length;
101
+ const line = content.substring(0, match.index).split("\n").length;
97
102
  issues.push({
98
103
  line,
99
104
  type: sp.type,
100
105
  severity: sp.severity,
101
106
  suggestion: `${sp.type} 패턴 감지`,
102
- text: match[0].split('\n')[0].trim(),
107
+ text: match[0].split("\n")[0].trim(),
103
108
  autoFixable: sp.autoFixable,
104
109
  file: filePath,
105
110
  });
@@ -108,7 +113,10 @@ export function detectSlop(content, filePath = '') {
108
113
 
109
114
  issues.sort((a, b) => a.line - b.line);
110
115
 
111
- const totalPenalty = issues.reduce((sum, i) => sum + (SEVERITY_WEIGHT[i.severity] || 2), 0);
116
+ const totalPenalty = issues.reduce(
117
+ (sum, i) => sum + (SEVERITY_WEIGHT[i.severity] || 2),
118
+ 0,
119
+ );
112
120
  const score = Math.max(0, 100 - totalPenalty);
113
121
 
114
122
  return { issues, score };
@@ -121,9 +129,10 @@ export function detectSlop(content, filePath = '') {
121
129
  * @returns {{ fixed: string, applied: number, skipped: number }}
122
130
  */
123
131
  export function autoFixSlop(content, issues) {
124
- if (!issues || issues.length === 0) return { fixed: content, applied: 0, skipped: 0 };
132
+ if (!issues || issues.length === 0)
133
+ return { fixed: content, applied: 0, skipped: 0 };
125
134
 
126
- const fixable = issues.filter(i => i.autoFixable);
135
+ const fixable = issues.filter((i) => i.autoFixable);
127
136
  const skipped = issues.length - fixable.length;
128
137
 
129
138
  if (fixable.length === 0) return { fixed: content, applied: 0, skipped };
@@ -132,10 +141,10 @@ export function autoFixSlop(content, issues) {
132
141
  let applied = 0;
133
142
 
134
143
  // Multi-line: useless_jsdoc 제거
135
- if (fixable.some(i => i.type === 'useless_jsdoc')) {
144
+ if (fixable.some((i) => i.type === "useless_jsdoc")) {
136
145
  const matches = fixed.match(/\/\*\*\s*\n\s*\*\s*\n\s*\*\/\n?/g);
137
146
  if (matches) {
138
- fixed = fixed.replace(/\/\*\*\s*\n\s*\*\s*\n\s*\*\/\n?/g, '');
147
+ fixed = fixed.replace(/\/\*\*\s*\n\s*\*\s*\n\s*\*\/\n?/g, "");
139
148
  applied += matches.length;
140
149
  }
141
150
  }
@@ -143,13 +152,15 @@ export function autoFixSlop(content, issues) {
143
152
  // Line-level: trivial_comment, console_debug, commented_code 제거
144
153
  const lineTypes = new Set(
145
154
  fixable
146
- .filter(i => ['trivial_comment', 'console_debug', 'commented_code'].includes(i.type))
147
- .map(i => i.type),
155
+ .filter((i) =>
156
+ ["trivial_comment", "console_debug", "commented_code"].includes(i.type),
157
+ )
158
+ .map((i) => i.type),
148
159
  );
149
160
 
150
161
  if (lineTypes.size > 0) {
151
- const linePatterns = SLOP_PATTERNS.filter(p => lineTypes.has(p.type));
152
- const lines = fixed.split('\n');
162
+ const linePatterns = SLOP_PATTERNS.filter((p) => lineTypes.has(p.type));
163
+ const lines = fixed.split("\n");
153
164
  const result = [];
154
165
  for (const line of lines) {
155
166
  let remove = false;
@@ -162,23 +173,23 @@ export function autoFixSlop(content, issues) {
162
173
  }
163
174
  if (!remove) result.push(line);
164
175
  }
165
- fixed = result.join('\n');
176
+ fixed = result.join("\n");
166
177
  }
167
178
 
168
179
  return { fixed, applied, skipped };
169
180
  }
170
181
 
171
182
  function matchesGlob(filePath, pattern) {
172
- const normalized = '/' + filePath.replace(/\\/g, '/');
183
+ const normalized = "/" + filePath.replace(/\\/g, "/");
173
184
 
174
185
  // **/*.ext → 확장자 매칭
175
- if (pattern.startsWith('**/*.')) {
186
+ if (pattern.startsWith("**/*.")) {
176
187
  const ext = pattern.slice(4);
177
188
  return normalized.endsWith(ext);
178
189
  }
179
190
 
180
191
  // *.ext → 확장자 매칭 (디렉토리 무관)
181
- if (pattern.startsWith('*.') && !pattern.includes('/')) {
192
+ if (pattern.startsWith("*.") && !pattern.includes("/")) {
182
193
  const ext = pattern.slice(1);
183
194
  return normalized.endsWith(ext);
184
195
  }
@@ -186,7 +197,7 @@ function matchesGlob(filePath, pattern) {
186
197
  // **/dir/** → 디렉토리 포함 여부
187
198
  const dirMatch = pattern.match(/^\*\*\/([^*]+)\/\*\*$/);
188
199
  if (dirMatch) {
189
- return normalized.includes('/' + dirMatch[1] + '/');
200
+ return normalized.includes("/" + dirMatch[1] + "/");
190
201
  }
191
202
 
192
203
  return false;
@@ -203,8 +214,8 @@ function matchesGlob(filePath, pattern) {
203
214
  */
204
215
  export async function scanDirectory(dirPath, opts = {}) {
205
216
  const {
206
- include = ['**/*.mjs', '**/*.js', '**/*.ts'],
207
- exclude = ['**/node_modules/**', '**/dist/**', '**/.git/**'],
217
+ include = ["**/*.mjs", "**/*.js", "**/*.ts"],
218
+ exclude = ["**/node_modules/**", "**/dist/**", "**/.git/**"],
208
219
  autoFix = false,
209
220
  } = opts;
210
221
 
@@ -212,32 +223,37 @@ export async function scanDirectory(dirPath, opts = {}) {
212
223
  const files = [];
213
224
 
214
225
  for (const entry of entries) {
215
- const normalized = entry.replace(/\\/g, '/');
226
+ const normalized = entry.replace(/\\/g, "/");
216
227
  const fullPath = join(dirPath, entry);
217
228
 
218
229
  let st;
219
- try { st = await stat(fullPath); } catch { continue; }
230
+ try {
231
+ st = await stat(fullPath);
232
+ } catch {
233
+ continue;
234
+ }
220
235
  if (!st.isFile()) continue;
221
236
 
222
- const included = include.some(p => matchesGlob(normalized, p));
223
- const excluded = exclude.some(p => matchesGlob(normalized, p));
237
+ const included = include.some((p) => matchesGlob(normalized, p));
238
+ const excluded = exclude.some((p) => matchesGlob(normalized, p));
224
239
  if (!included || excluded) continue;
225
240
 
226
- const fileContent = await readFile(fullPath, 'utf-8');
241
+ const fileContent = await readFile(fullPath, "utf-8");
227
242
  const { issues, score } = detectSlop(fileContent, normalized);
228
243
 
229
244
  if (autoFix && issues.length > 0) {
230
245
  const { fixed, applied } = autoFixSlop(fileContent, issues);
231
- if (applied > 0) await writeFile(fullPath, fixed, 'utf-8');
246
+ if (applied > 0) await writeFile(fullPath, fixed, "utf-8");
232
247
  }
233
248
 
234
249
  files.push({ path: normalized, issues, score });
235
250
  }
236
251
 
237
252
  const totalIssues = files.reduce((sum, f) => sum + f.issues.length, 0);
238
- const avgScore = files.length > 0
239
- ? Math.round(files.reduce((sum, f) => sum + f.score, 0) / files.length)
240
- : 100;
253
+ const avgScore =
254
+ files.length > 0
255
+ ? Math.round(files.reduce((sum, f) => sum + f.score, 0) / files.length)
256
+ : 100;
241
257
 
242
258
  const byType = {};
243
259
  for (const f of files) {
@@ -248,6 +264,11 @@ export async function scanDirectory(dirPath, opts = {}) {
248
264
 
249
265
  return {
250
266
  files,
251
- summary: { totalFiles: files.length, totalIssues, averageScore: avgScore, byType },
267
+ summary: {
268
+ totalFiles: files.length,
269
+ totalIssues,
270
+ averageScore: avgScore,
271
+ byType,
272
+ },
252
273
  };
253
274
  }
package/hub/research.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  // hub/research.mjs — 자율 웹 리서치 엔진 코어
2
2
  // 검색 쿼리 생성 → 결과 정규화 → 보고서 빌드 → 저장
3
3
 
4
- import { mkdirSync, writeFileSync } from 'node:fs';
5
- import { join, resolve } from 'node:path';
6
- import { TFX_REPORTS_DIR } from './paths.mjs';
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ import { TFX_REPORTS_DIR } from "./paths.mjs";
7
7
 
8
8
  /**
9
9
  * @experimental 런타임 미연결 — 향후 통합 예정
@@ -14,13 +14,13 @@ import { TFX_REPORTS_DIR } from './paths.mjs';
14
14
  * @param {'ko'|'en'|'auto'} [lang='auto'] - 언어 힌트
15
15
  * @returns {string[]} 검색 쿼리 배열
16
16
  */
17
- export function generateQueries(topic, lang = 'auto') {
18
- if (!topic || typeof topic !== 'string' || !topic.trim()) return [];
17
+ export function generateQueries(topic, lang = "auto") {
18
+ if (!topic || typeof topic !== "string" || !topic.trim()) return [];
19
19
 
20
20
  const t = topic.trim();
21
- const detectedLang = lang === 'auto' ? detectLang(t) : lang;
21
+ const detectedLang = lang === "auto" ? detectLang(t) : lang;
22
22
 
23
- if (detectedLang === 'ko') {
23
+ if (detectedLang === "ko") {
24
24
  return [
25
25
  `${t} 정리`,
26
26
  `${t} 비교 분석`,
@@ -50,10 +50,10 @@ export function normalizeResults(rawResults) {
50
50
  const out = [];
51
51
 
52
52
  for (const r of rawResults) {
53
- if (!r || typeof r !== 'object') continue;
54
- const url = (r.url || r.link || '').trim();
55
- const title = (r.title || r.name || '').trim();
56
- const snippet = (r.snippet || r.description || r.content || '').trim();
53
+ if (!r || typeof r !== "object") continue;
54
+ const url = (r.url || r.link || "").trim();
55
+ const title = (r.title || r.name || "").trim();
56
+ const snippet = (r.snippet || r.description || r.content || "").trim();
57
57
 
58
58
  if (!url || seen.has(url)) continue;
59
59
  seen.add(url);
@@ -71,13 +71,16 @@ export function normalizeResults(rawResults) {
71
71
  * @returns {string} 마크다운 문자열
72
72
  */
73
73
  export function buildReport(topic, findings, sources) {
74
- const date = new Date().toISOString().split('T')[0];
74
+ const date = new Date().toISOString().split("T")[0];
75
75
  const findingsSection = (findings || [])
76
76
  .map((f, i) => `${i + 1}. ${f}`)
77
- .join('\n');
77
+ .join("\n");
78
78
  const sourcesSection = (sources || [])
79
- .map((s) => `- [${s.title || s.url}](${s.url})${s.snippet ? ` — ${s.snippet}` : ''}`)
80
- .join('\n');
79
+ .map(
80
+ (s) =>
81
+ `- [${s.title || s.url}](${s.url})${s.snippet ? ` — ${s.snippet}` : ""}`,
82
+ )
83
+ .join("\n");
81
84
 
82
85
  return `# Research: ${topic}
83
86
  Date: ${date}
@@ -86,13 +89,13 @@ Date: ${date}
86
89
  ${topic}에 대한 자동 리서치 결과입니다.
87
90
 
88
91
  ## Key Findings
89
- ${findingsSection || '_발견 없음_'}
92
+ ${findingsSection || "_발견 없음_"}
90
93
 
91
94
  ## Actionable Recommendations
92
95
  리서치 결과를 바탕으로 다음 단계를 검토하세요.
93
96
 
94
97
  ## Sources
95
- ${sourcesSection || '_출처 없음_'}
98
+ ${sourcesSection || "_출처 없음_"}
96
99
  `;
97
100
  }
98
101
 
@@ -108,20 +111,24 @@ export function saveReport(topic, content, baseDir = process.cwd()) {
108
111
  const resolvedDir = resolve(dir);
109
112
  const expectedBase = resolve(baseDir || TFX_REPORTS_DIR);
110
113
  if (!resolvedDir.startsWith(expectedBase)) {
111
- throw new Error('Invalid report directory: path traversal detected');
114
+ throw new Error("Invalid report directory: path traversal detected");
112
115
  }
113
116
  mkdirSync(dir, { recursive: true });
114
117
 
115
- const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
116
- const slug = (topic || 'untitled')
117
- .replace(/[^a-zA-Z0-9가-힣\s-]/g, '')
118
- .replace(/\s+/g, '-')
118
+ const ts = new Date()
119
+ .toISOString()
120
+ .replace(/[:.]/g, "-")
121
+ .replace("T", "_")
122
+ .slice(0, 19);
123
+ const slug = (topic || "untitled")
124
+ .replace(/[^a-zA-Z0-9가-힣\s-]/g, "")
125
+ .replace(/\s+/g, "-")
119
126
  .slice(0, 40)
120
127
  .toLowerCase();
121
128
  const filename = `research-${ts}-${slug}.md`;
122
129
  const filepath = join(dir, filename);
123
130
 
124
- writeFileSync(filepath, content, 'utf-8');
131
+ writeFileSync(filepath, content, "utf-8");
125
132
  return filepath;
126
133
  }
127
134
 
@@ -133,7 +140,7 @@ export function saveReport(topic, content, baseDir = process.cwd()) {
133
140
  * @returns {'ko'|'en'}
134
141
  */
135
142
  function detectLang(text) {
136
- return /[가-힣]/.test(text) ? 'ko' : 'en';
143
+ return /[가-힣]/.test(text) ? "ko" : "en";
137
144
  }
138
145
 
139
146
  /**
@@ -143,6 +150,6 @@ function detectLang(text) {
143
150
  * @returns {string}
144
151
  */
145
152
  function toEnglishQuery(text) {
146
- const eng = text.replace(/[가-힣\s]+/g, ' ').trim();
153
+ const eng = text.replace(/[가-힣\s]+/g, " ").trim();
147
154
  return eng || text;
148
155
  }