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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// - Hub 시작 시 (server.mjs에서 import)
|
|
10
10
|
// - 수동: node hub/promote-penalties.mjs
|
|
11
11
|
|
|
12
|
-
import { readFileSync, writeFileSync
|
|
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 {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(line);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
28
32
|
})
|
|
29
33
|
.filter(Boolean);
|
|
30
|
-
} catch {
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
function clearPenalties() {
|
|
34
|
-
try {
|
|
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?.(
|
|
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:
|
|
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(
|
|
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
|
}
|
package/hub/quality/deslop.mjs
CHANGED
|
@@ -3,57 +3,59 @@
|
|
|
3
3
|
* AI 생성 코드에서 불필요한 요소를 자동 탐지/제거하는 정적 분석 모듈
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readdir, readFile,
|
|
7
|
-
import { join
|
|
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:
|
|
12
|
+
type: "trivial_comment",
|
|
13
13
|
pattern: /^\s*\/\/\s*(import|define|set|get|return|export)\s/i,
|
|
14
|
-
severity:
|
|
14
|
+
severity: "low",
|
|
15
15
|
autoFixable: true,
|
|
16
16
|
multiline: false,
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
type:
|
|
19
|
+
type: "empty_catch",
|
|
20
20
|
pattern: /catch\s*\([^)]*\)\s*\{\s*\}/,
|
|
21
|
-
severity:
|
|
21
|
+
severity: "med",
|
|
22
22
|
autoFixable: false,
|
|
23
23
|
multiline: true,
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
type:
|
|
26
|
+
type: "console_debug",
|
|
27
27
|
pattern: /^\s*console\.(log|debug|info)\(/,
|
|
28
|
-
severity:
|
|
28
|
+
severity: "low",
|
|
29
29
|
autoFixable: true,
|
|
30
30
|
multiline: false,
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
type:
|
|
33
|
+
type: "useless_jsdoc",
|
|
34
34
|
pattern: /\/\*\*\s*\n\s*\*\s*\n\s*\*\//,
|
|
35
|
-
severity:
|
|
35
|
+
severity: "low",
|
|
36
36
|
autoFixable: true,
|
|
37
37
|
multiline: true,
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
type:
|
|
40
|
+
type: "rethrow_only",
|
|
41
41
|
pattern: /catch\s*\((\w+)\)\s*\{\s*throw\s+\1\s*;?\s*\}/,
|
|
42
|
-
severity:
|
|
42
|
+
severity: "med",
|
|
43
43
|
autoFixable: false,
|
|
44
44
|
multiline: true,
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
|
-
type:
|
|
48
|
-
pattern:
|
|
49
|
-
|
|
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:
|
|
55
|
-
pattern:
|
|
56
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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)
|
|
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 ===
|
|
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 =>
|
|
147
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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 = [
|
|
207
|
-
exclude = [
|
|
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 {
|
|
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,
|
|
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,
|
|
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 =
|
|
239
|
-
|
|
240
|
-
|
|
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: {
|
|
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
|
|
5
|
-
import { join, resolve } from
|
|
6
|
-
import { TFX_REPORTS_DIR } from
|
|
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 =
|
|
18
|
-
if (!topic || typeof topic !==
|
|
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 ===
|
|
21
|
+
const detectedLang = lang === "auto" ? detectLang(t) : lang;
|
|
22
22
|
|
|
23
|
-
if (detectedLang ===
|
|
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 !==
|
|
54
|
-
const url = (r.url || r.link ||
|
|
55
|
-
const title = (r.title || r.name ||
|
|
56
|
-
const snippet = (r.snippet || r.description || r.content ||
|
|
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(
|
|
74
|
+
const date = new Date().toISOString().split("T")[0];
|
|
75
75
|
const findingsSection = (findings || [])
|
|
76
76
|
.map((f, i) => `${i + 1}. ${f}`)
|
|
77
|
-
.join(
|
|
77
|
+
.join("\n");
|
|
78
78
|
const sourcesSection = (sources || [])
|
|
79
|
-
.map(
|
|
80
|
-
|
|
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(
|
|
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()
|
|
116
|
-
|
|
117
|
-
.replace(/[
|
|
118
|
-
.replace(
|
|
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,
|
|
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) ?
|
|
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,
|
|
153
|
+
const eng = text.replace(/[가-힣\s]+/g, " ").trim();
|
|
147
154
|
return eng || text;
|
|
148
155
|
}
|