triflux 8.11.2 → 8.12.2

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 (103) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/hooks/hooks.json +56 -51
  5. package/hub/pipeline/index.mjs +318 -318
  6. package/hub/routing/q-learning.mjs +2 -1
  7. package/hub/schema.sql +146 -146
  8. package/hub/team/cli/commands/kill.mjs +37 -37
  9. package/hub/team/cli/commands/start/index.mjs +3 -2
  10. package/hub/team/cli/commands/start/parse-args.mjs +5 -0
  11. package/hub/team/cli/commands/start/start-headless.mjs +2 -1
  12. package/hub/team/cli/commands/stop.mjs +31 -31
  13. package/hub/team/cli/commands/task.mjs +30 -30
  14. package/hub/team/cli/help.mjs +41 -40
  15. package/hub/team/cli/services/hub-client.mjs +208 -208
  16. package/hub/team/cli/services/native-control.mjs +118 -118
  17. package/hub/team/cli/services/runtime-mode.mjs +62 -62
  18. package/hub/team/cli/services/state-store.mjs +48 -48
  19. package/hub/team/dashboard-anchor.mjs +14 -0
  20. package/hub/team/dashboard.mjs +274 -274
  21. package/hub/team/headless.mjs +44 -19
  22. package/hub/team/native.mjs +649 -649
  23. package/hub/tools.mjs +554 -554
  24. package/hub/workers/delegator-mcp.mjs +21 -1
  25. package/package.json +1 -1
  26. package/scripts/__tests__/remote-spawn-transfer.test.mjs +117 -0
  27. package/scripts/__tests__/remote-spawn.test.mjs +78 -0
  28. package/scripts/cache-buildup.mjs +401 -0
  29. package/scripts/headless-guard.mjs +10 -3
  30. package/scripts/hub-ensure.mjs +120 -120
  31. package/scripts/lib/mcp-filter.mjs +720 -720
  32. package/scripts/lib/remote-spawn-transfer.mjs +196 -0
  33. package/scripts/mcp-check.mjs +237 -127
  34. package/scripts/mcp-gateway-ensure.mjs +6 -8
  35. package/scripts/mcp-gateway-integration-test.mjs +228 -0
  36. package/scripts/mcp-gateway-start.mjs +42 -16
  37. package/scripts/mcp-gateway-start.ps1 +17 -14
  38. package/scripts/mcp-gateway-verify.mjs +1 -1
  39. package/scripts/preflight-cache.mjs +137 -137
  40. package/scripts/remote-spawn.mjs +404 -40
  41. package/scripts/setup.mjs +18 -0
  42. package/scripts/tfx-route-worker.mjs +165 -165
  43. package/scripts/tfx-route.sh +85 -16
  44. package/scripts/token-snapshot.mjs +575 -575
  45. package/skills/tfx-analysis/SKILL.md +101 -101
  46. package/skills/tfx-autopilot/SKILL.md +112 -112
  47. package/skills/tfx-autoresearch/SKILL.md +1 -0
  48. package/skills/tfx-autoroute/SKILL.md +184 -184
  49. package/skills/tfx-codex-swarm/SKILL.md +449 -0
  50. package/skills/tfx-codex-swarm/evals/evals.json +26 -0
  51. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +33 -0
  52. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +42 -0
  53. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +11 -0
  54. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +87 -0
  55. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +35 -0
  56. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +275 -0
  57. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +56 -0
  58. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +5 -0
  59. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +11 -0
  60. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +92 -0
  61. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +71 -0
  62. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +264 -0
  63. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +113 -0
  64. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +5 -0
  65. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +32 -0
  66. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +9 -0
  67. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +96 -0
  68. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +38 -0
  69. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +151 -0
  70. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +51 -0
  71. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +5 -0
  72. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +9 -0
  73. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +127 -0
  74. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +57 -0
  75. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +129 -0
  76. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +84 -0
  77. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +5 -0
  78. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +27 -0
  79. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +8 -0
  80. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +98 -0
  81. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +65 -0
  82. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +123 -0
  83. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +66 -0
  84. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +5 -0
  85. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +8 -0
  86. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +88 -0
  87. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +40 -0
  88. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +130 -0
  89. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +61 -0
  90. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +5 -0
  91. package/skills/tfx-profile/SKILL.md +141 -51
  92. package/skills/tfx-qa/SKILL.md +117 -117
  93. package/skills/tfx-remote-spawn/references/hosts.json +16 -16
  94. package/skills/tfx-review/SKILL.md +51 -51
  95. package/skills/tfx-setup/SKILL.md +20 -0
  96. package/tui/gemini-profile.mjs +254 -0
  97. package/tui/setup.mjs +36 -1
  98. package/scripts/claude-logged.ps1 +0 -54
  99. package/scripts/demo-tui.mjs +0 -59
  100. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +0 -1
  101. package/skills/.omc/state/idle-notif-cooldown.json +0 -3
  102. package/skills/.omc/state/last-tool-error.json +0 -7
  103. package/skills/.omc/state/subagent-tracking.json +0 -7
@@ -1,318 +1,318 @@
1
- // hub/pipeline/index.mjs — 파이프라인 매니저
2
- //
3
- // 상태(state.mjs) + 전이(transitions.mjs) 통합 인터페이스
4
-
5
- import { writeFileSync, mkdirSync } from 'node:fs';
6
- import { join, resolve } from 'node:path';
7
- import { homedir } from 'node:os';
8
-
9
- import { canTransition, transitionPhase, ralphRestart, TERMINAL } from './transitions.mjs';
10
- import {
11
- ensurePipelineTable,
12
- initPipelineState,
13
- readPipelineState,
14
- updatePipelineState,
15
- removePipelineState,
16
- } from './state.mjs';
17
- import { runConfidenceCheck } from './gates/confidence.mjs';
18
- import { runSelfCheck } from './gates/selfcheck.mjs';
19
- import { classifyIntent as _classifyIntent } from '../intent.mjs';
20
- // deslop gate: 호출자가 scanDirectory/detectSlop 결과를 전달
21
-
22
- /**
23
- * 파이프라인 매니저 생성
24
- * @param {object} db - better-sqlite3 인스턴스 (store.db)
25
- * @param {string} teamName
26
- * @param {object} opts - { fix_max?, ralph_max? }
27
- * @returns {object} 파이프라인 API
28
- */
29
- export function createPipeline(db, teamName, opts = {}) {
30
- ensurePipelineTable(db);
31
-
32
- // 기존 상태가 있으면 로드, 없으면 초기화
33
- let state = readPipelineState(db, teamName);
34
- if (!state) {
35
- state = initPipelineState(db, teamName, opts);
36
- }
37
-
38
- return {
39
- /**
40
- * 현재 상태 조회
41
- */
42
- getState() {
43
- state = readPipelineState(db, teamName) || state;
44
- return { ...state };
45
- },
46
-
47
- /**
48
- * 다음 단계로 전이 가능 여부
49
- * @param {string} phase
50
- */
51
- canAdvance(phase) {
52
- const current = readPipelineState(db, teamName);
53
- return current ? canTransition(current.phase, phase) : false;
54
- },
55
-
56
- /**
57
- * 다음 단계로 전이
58
- * @param {string} nextPhase
59
- * @returns {{ ok: boolean, state?: object, error?: string }}
60
- */
61
- advance(nextPhase) {
62
- const current = readPipelineState(db, teamName);
63
- if (!current) {
64
- return { ok: false, error: `파이프라인 없음: ${teamName}` };
65
- }
66
-
67
- const result = transitionPhase(current, nextPhase);
68
- if (!result.ok) return result;
69
-
70
- state = updatePipelineState(db, teamName, result.state);
71
- return { ok: true, state: { ...state } };
72
- },
73
-
74
- /**
75
- * ralph loop 재시작 (plan부터 다시)
76
- * @returns {{ ok: boolean, state?: object, error?: string }}
77
- */
78
- restart() {
79
- const current = readPipelineState(db, teamName);
80
- if (!current) {
81
- return { ok: false, error: `파이프라인 없음: ${teamName}` };
82
- }
83
-
84
- const result = ralphRestart(current);
85
- if (!result.ok) return result;
86
-
87
- state = updatePipelineState(db, teamName, result.state);
88
- return { ok: true, state: { ...state } };
89
- },
90
-
91
- /**
92
- * DAG 컨텍스트를 파이프라인 상태에 저장
93
- * @param {{ dag_width: number, levels: Record<number, string[]>, edges: Array<{from:string, to:string}>, max_complexity: string, taskResults: Record<string, *> }} dagContext
94
- */
95
- setDagContext(dagContext) {
96
- const current = readPipelineState(db, teamName);
97
- if (!current) return;
98
- const artifacts = { ...(current.artifacts || {}), dagContext };
99
- state = updatePipelineState(db, teamName, { artifacts });
100
- },
101
-
102
- /**
103
- * DAG 컨텍스트 조회 (편의 메서드)
104
- * @returns {{ dag_width: number, levels: Record<number, string[]>, edges: Array<{from:string, to:string}>, max_complexity: string, taskResults: Record<string, *> } | null}
105
- */
106
- getDagContext() {
107
- const current = readPipelineState(db, teamName) || state;
108
- return current?.artifacts?.dagContext || null;
109
- },
110
-
111
- /**
112
- * artifact 저장 (plan_path, prd_path, verify_report 등)
113
- * @param {string} key
114
- * @param {*} value
115
- */
116
- setArtifact(key, value) {
117
- const current = readPipelineState(db, teamName);
118
- if (!current) return;
119
- const artifacts = { ...(current.artifacts || {}), [key]: value };
120
- state = updatePipelineState(db, teamName, { artifacts });
121
- },
122
-
123
- /**
124
- * Plan 파일을 .tfx/plans/{teamName}-plan.md 에 기록하고
125
- * artifact('plan_path')에 절대 경로를 저장한다.
126
- * @param {string} content - Plan markdown 내용
127
- * @returns {string} 절대 경로
128
- */
129
- writePlanFile(content) {
130
- const safeName = teamName.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_');
131
- const planDir = resolve(process.cwd(), '.tfx', 'plans');
132
- mkdirSync(planDir, { recursive: true });
133
- const planPath = join(planDir, `${safeName}-plan.md`);
134
- writeFileSync(planPath, content, 'utf8');
135
- this.setArtifact('plan_path', planPath);
136
- return planPath;
137
- },
138
-
139
- /**
140
- * 터미널 상태 여부
141
- */
142
- isTerminal() {
143
- const current = readPipelineState(db, teamName);
144
- return current ? TERMINAL.has(current.phase) : true;
145
- },
146
-
147
- /**
148
- * 파이프라인 초기화 (리셋)
149
- */
150
- reset() {
151
- state = initPipelineState(db, teamName, opts);
152
- return { ...state };
153
- },
154
-
155
- /**
156
- * 파이프라인 삭제
157
- */
158
- remove() {
159
- return removePipelineState(db, teamName);
160
- },
161
-
162
- /**
163
- * Confidence Gate 실행 + 자동 전이
164
- * prd → confidence → exec/failed
165
- * @param {string|object} planArtifact
166
- * @param {object} context - { checks?, codebaseFiles?, existingTests? }
167
- * @returns {{ ok: boolean, gate: object, state?: object, error?: string }}
168
- */
169
- runConfidenceGate(planArtifact, context = {}) {
170
- const current = readPipelineState(db, teamName);
171
- if (!current) return { ok: false, error: `파이프라인 없음: ${teamName}` };
172
-
173
- if (current.phase !== 'confidence') {
174
- return { ok: false, error: `confidence gate는 confidence 단계에서만 실행 가능 (현재: ${current.phase})` };
175
- }
176
-
177
- const gate = runConfidenceCheck(planArtifact, context);
178
- this.setArtifact('confidence_result', gate);
179
-
180
- if (gate.decision === 'abort') {
181
- const result = this.advance('failed');
182
- return { ok: true, gate, state: result.state };
183
- }
184
-
185
- // proceed 또는 alternative → exec로 전이
186
- const result = this.advance('exec');
187
- return { ok: result.ok, gate, state: result.state, error: result.error };
188
- },
189
-
190
- /**
191
- * Deslop Gate 실행 + 자동 전이
192
- * exec → deslop → verify
193
- * 호출자가 미리 deslop 결과를 생성하여 전달.
194
- * @param {object} [deslopResult] - scanDirectory() 또는 detectSlop() 결과
195
- * @returns {{ ok: boolean, gate: object, state?: object, error?: string }}
196
- */
197
- runDeslopGate(deslopResult = null) {
198
- const current = readPipelineState(db, teamName);
199
- if (!current) return { ok: false, error: `파이프라인 없음: ${teamName}` };
200
-
201
- if (current.phase !== 'deslop') {
202
- return { ok: false, error: `deslop gate는 deslop 단계에서만 실행 가능 (현재: ${current.phase})` };
203
- }
204
-
205
- const gate = deslopResult || { files: [], summary: { total: 0, clean: 0 } };
206
- this.setArtifact('deslop_result', gate);
207
-
208
- // deslop은 항상 verify로 전이 (정보 제공 게이트, 차단 없음)
209
- const result = this.advance('verify');
210
- return { ok: result.ok, gate, state: result.state, error: result.error };
211
- },
212
-
213
- /**
214
- * Self-Check Gate 실행 + 자동 전이
215
- * verify → selfcheck → complete/fix
216
- * @param {string|object} execResult
217
- * @param {string|object} verifyResult
218
- * @param {object} requirements - { hasDiff?, evidence? }
219
- * @returns {{ ok: boolean, gate: object, state?: object, error?: string }}
220
- */
221
- runSelfCheckGate(execResult, verifyResult, requirements = {}) {
222
- const current = readPipelineState(db, teamName);
223
- if (!current) return { ok: false, error: `파이프라인 없음: ${teamName}` };
224
-
225
- if (current.phase !== 'selfcheck') {
226
- return { ok: false, error: `selfcheck gate는 selfcheck 단계에서만 실행 가능 (현재: ${current.phase})` };
227
- }
228
-
229
- const gate = runSelfCheck(execResult, verifyResult, requirements);
230
- this.setArtifact('selfcheck_result', gate);
231
-
232
- if (gate.passed) {
233
- const result = this.advance('complete');
234
- return { ok: result.ok, gate, state: result.state, error: result.error };
235
- }
236
-
237
- // Red Flag 탐지 또는 필수 질문 실패 → fix
238
- const result = this.advance('fix');
239
- return { ok: result.ok, gate, state: result.state, error: result.error };
240
- },
241
- };
242
- }
243
-
244
- // ── 토큰 벤치마크 훅 ──
245
-
246
- let _tokenSnapshotMod = null;
247
-
248
- async function loadTokenSnapshot() {
249
- if (_tokenSnapshotMod) return _tokenSnapshotMod;
250
- try {
251
- _tokenSnapshotMod = await import('../../scripts/token-snapshot.mjs');
252
- } catch {
253
- _tokenSnapshotMod = null;
254
- }
255
- return _tokenSnapshotMod;
256
- }
257
-
258
- /**
259
- * 파이프라인 시작 시 토큰 스냅샷 캡처
260
- * @param {string} label - 스냅샷 라벨 (e.g. teamName + timestamp)
261
- * @returns {Promise<{label: string, snapshot: object}|null>}
262
- */
263
- export async function benchmarkStart(label) {
264
- const mod = await loadTokenSnapshot();
265
- if (!mod?.takeSnapshot) return null;
266
- try {
267
- const snapshot = mod.takeSnapshot(label);
268
- return { label, snapshot };
269
- } catch { return null; }
270
- }
271
-
272
- /**
273
- * 파이프라인 종료 시 diff 계산 + 결과 저장
274
- * @param {string} preLabel - 시작 스냅샷 라벨
275
- * @param {string} postLabel - 종료 스냅샷 라벨
276
- * @param {object} options - { agent?, cli?, id? }
277
- * @returns {Promise<object|null>} diff 결과
278
- */
279
- export async function benchmarkEnd(preLabel, postLabel, options = {}) {
280
- const mod = await loadTokenSnapshot();
281
- if (!mod?.takeSnapshot || !mod?.computeDiff) return null;
282
- try {
283
- // 종료 스냅샷 캡처
284
- mod.takeSnapshot(postLabel);
285
- // diff 계산 (결과는 DIFFS_DIR에 자동 저장됨)
286
- const diff = mod.computeDiff(preLabel, postLabel, options);
287
-
288
- // 추가로 타임스탬프 기반 사본 저장
289
- const diffsDir = join(homedir(), '.omc', 'state', 'cx-auto-tokens', 'diffs');
290
- mkdirSync(diffsDir, { recursive: true });
291
- const ts = new Date().toISOString().replace(/[:.]/g, '-');
292
- const outPath = join(diffsDir, `${ts}.json`);
293
- writeFileSync(outPath, JSON.stringify(diff, null, 2));
294
-
295
- return diff;
296
- } catch { return null; }
297
- }
298
-
299
- /**
300
- * 트리아지 통합: quickClassify 고신뢰 시 Codex 분류 스킵 판정
301
- * @param {string} prompt
302
- * @param {number} [threshold=0.8]
303
- * @returns {{ skip: boolean, routing: object|null, classification: object }}
304
- */
305
- export function triageWithIntent(prompt, threshold = 0.8) {
306
- const classification = _classifyIntent(prompt);
307
- if (classification.confidence >= threshold) {
308
- return { skip: true, routing: classification.routing, classification };
309
- }
310
- return { skip: false, routing: null, classification };
311
- }
312
-
313
- export { ensurePipelineTable } from './state.mjs';
314
- export { PHASES, TERMINAL, ALLOWED, canTransition } from './transitions.mjs';
315
- export { CRITERIA, runConfidenceCheck } from './gates/confidence.mjs';
316
- export { RED_FLAGS, QUESTIONS, runSelfCheck } from './gates/selfcheck.mjs';
317
- export { detectSlop, autoFixSlop, scanDirectory } from '../quality/deslop.mjs';
318
- export { quickClassify, classifyIntent, INTENT_CATEGORIES } from '../intent.mjs';
1
+ // hub/pipeline/index.mjs — 파이프라인 매니저
2
+ //
3
+ // 상태(state.mjs) + 전이(transitions.mjs) 통합 인터페이스
4
+
5
+ import { writeFileSync, mkdirSync } from 'node:fs';
6
+ import { join, resolve } from 'node:path';
7
+ import { homedir } from 'node:os';
8
+
9
+ import { canTransition, transitionPhase, ralphRestart, TERMINAL } from './transitions.mjs';
10
+ import {
11
+ ensurePipelineTable,
12
+ initPipelineState,
13
+ readPipelineState,
14
+ updatePipelineState,
15
+ removePipelineState,
16
+ } from './state.mjs';
17
+ import { runConfidenceCheck } from './gates/confidence.mjs';
18
+ import { runSelfCheck } from './gates/selfcheck.mjs';
19
+ import { classifyIntent as _classifyIntent } from '../intent.mjs';
20
+ // deslop gate: 호출자가 scanDirectory/detectSlop 결과를 전달
21
+
22
+ /**
23
+ * 파이프라인 매니저 생성
24
+ * @param {object} db - better-sqlite3 인스턴스 (store.db)
25
+ * @param {string} teamName
26
+ * @param {object} opts - { fix_max?, ralph_max? }
27
+ * @returns {object} 파이프라인 API
28
+ */
29
+ export function createPipeline(db, teamName, opts = {}) {
30
+ ensurePipelineTable(db);
31
+
32
+ // 기존 상태가 있으면 로드, 없으면 초기화
33
+ let state = readPipelineState(db, teamName);
34
+ if (!state) {
35
+ state = initPipelineState(db, teamName, opts);
36
+ }
37
+
38
+ return {
39
+ /**
40
+ * 현재 상태 조회
41
+ */
42
+ getState() {
43
+ state = readPipelineState(db, teamName) || state;
44
+ return { ...state };
45
+ },
46
+
47
+ /**
48
+ * 다음 단계로 전이 가능 여부
49
+ * @param {string} phase
50
+ */
51
+ canAdvance(phase) {
52
+ const current = readPipelineState(db, teamName);
53
+ return current ? canTransition(current.phase, phase) : false;
54
+ },
55
+
56
+ /**
57
+ * 다음 단계로 전이
58
+ * @param {string} nextPhase
59
+ * @returns {{ ok: boolean, state?: object, error?: string }}
60
+ */
61
+ advance(nextPhase) {
62
+ const current = readPipelineState(db, teamName);
63
+ if (!current) {
64
+ return { ok: false, error: `파이프라인 없음: ${teamName}` };
65
+ }
66
+
67
+ const result = transitionPhase(current, nextPhase);
68
+ if (!result.ok) return result;
69
+
70
+ state = updatePipelineState(db, teamName, result.state);
71
+ return { ok: true, state: { ...state } };
72
+ },
73
+
74
+ /**
75
+ * ralph loop 재시작 (plan부터 다시)
76
+ * @returns {{ ok: boolean, state?: object, error?: string }}
77
+ */
78
+ restart() {
79
+ const current = readPipelineState(db, teamName);
80
+ if (!current) {
81
+ return { ok: false, error: `파이프라인 없음: ${teamName}` };
82
+ }
83
+
84
+ const result = ralphRestart(current);
85
+ if (!result.ok) return result;
86
+
87
+ state = updatePipelineState(db, teamName, result.state);
88
+ return { ok: true, state: { ...state } };
89
+ },
90
+
91
+ /**
92
+ * DAG 컨텍스트를 파이프라인 상태에 저장
93
+ * @param {{ dag_width: number, levels: Record<number, string[]>, edges: Array<{from:string, to:string}>, max_complexity: string, taskResults: Record<string, *> }} dagContext
94
+ */
95
+ setDagContext(dagContext) {
96
+ const current = readPipelineState(db, teamName);
97
+ if (!current) return;
98
+ const artifacts = { ...(current.artifacts || {}), dagContext };
99
+ state = updatePipelineState(db, teamName, { artifacts });
100
+ },
101
+
102
+ /**
103
+ * DAG 컨텍스트 조회 (편의 메서드)
104
+ * @returns {{ dag_width: number, levels: Record<number, string[]>, edges: Array<{from:string, to:string}>, max_complexity: string, taskResults: Record<string, *> } | null}
105
+ */
106
+ getDagContext() {
107
+ const current = readPipelineState(db, teamName) || state;
108
+ return current?.artifacts?.dagContext || null;
109
+ },
110
+
111
+ /**
112
+ * artifact 저장 (plan_path, prd_path, verify_report 등)
113
+ * @param {string} key
114
+ * @param {*} value
115
+ */
116
+ setArtifact(key, value) {
117
+ const current = readPipelineState(db, teamName);
118
+ if (!current) return;
119
+ const artifacts = { ...(current.artifacts || {}), [key]: value };
120
+ state = updatePipelineState(db, teamName, { artifacts });
121
+ },
122
+
123
+ /**
124
+ * Plan 파일을 .tfx/plans/{teamName}-plan.md 에 기록하고
125
+ * artifact('plan_path')에 절대 경로를 저장한다.
126
+ * @param {string} content - Plan markdown 내용
127
+ * @returns {string} 절대 경로
128
+ */
129
+ writePlanFile(content) {
130
+ const safeName = teamName.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_');
131
+ const planDir = resolve(process.cwd(), '.tfx', 'plans');
132
+ mkdirSync(planDir, { recursive: true });
133
+ const planPath = join(planDir, `${safeName}-plan.md`);
134
+ writeFileSync(planPath, content, 'utf8');
135
+ this.setArtifact('plan_path', planPath);
136
+ return planPath;
137
+ },
138
+
139
+ /**
140
+ * 터미널 상태 여부
141
+ */
142
+ isTerminal() {
143
+ const current = readPipelineState(db, teamName);
144
+ return current ? TERMINAL.has(current.phase) : true;
145
+ },
146
+
147
+ /**
148
+ * 파이프라인 초기화 (리셋)
149
+ */
150
+ reset() {
151
+ state = initPipelineState(db, teamName, opts);
152
+ return { ...state };
153
+ },
154
+
155
+ /**
156
+ * 파이프라인 삭제
157
+ */
158
+ remove() {
159
+ return removePipelineState(db, teamName);
160
+ },
161
+
162
+ /**
163
+ * Confidence Gate 실행 + 자동 전이
164
+ * prd → confidence → exec/failed
165
+ * @param {string|object} planArtifact
166
+ * @param {object} context - { checks?, codebaseFiles?, existingTests? }
167
+ * @returns {{ ok: boolean, gate: object, state?: object, error?: string }}
168
+ */
169
+ runConfidenceGate(planArtifact, context = {}) {
170
+ const current = readPipelineState(db, teamName);
171
+ if (!current) return { ok: false, error: `파이프라인 없음: ${teamName}` };
172
+
173
+ if (current.phase !== 'confidence') {
174
+ return { ok: false, error: `confidence gate는 confidence 단계에서만 실행 가능 (현재: ${current.phase})` };
175
+ }
176
+
177
+ const gate = runConfidenceCheck(planArtifact, context);
178
+ this.setArtifact('confidence_result', gate);
179
+
180
+ if (gate.decision === 'abort') {
181
+ const result = this.advance('failed');
182
+ return { ok: true, gate, state: result.state };
183
+ }
184
+
185
+ // proceed 또는 alternative → exec로 전이
186
+ const result = this.advance('exec');
187
+ return { ok: result.ok, gate, state: result.state, error: result.error };
188
+ },
189
+
190
+ /**
191
+ * Deslop Gate 실행 + 자동 전이
192
+ * exec → deslop → verify
193
+ * 호출자가 미리 deslop 결과를 생성하여 전달.
194
+ * @param {object} [deslopResult] - scanDirectory() 또는 detectSlop() 결과
195
+ * @returns {{ ok: boolean, gate: object, state?: object, error?: string }}
196
+ */
197
+ runDeslopGate(deslopResult = null) {
198
+ const current = readPipelineState(db, teamName);
199
+ if (!current) return { ok: false, error: `파이프라인 없음: ${teamName}` };
200
+
201
+ if (current.phase !== 'deslop') {
202
+ return { ok: false, error: `deslop gate는 deslop 단계에서만 실행 가능 (현재: ${current.phase})` };
203
+ }
204
+
205
+ const gate = deslopResult || { files: [], summary: { total: 0, clean: 0 } };
206
+ this.setArtifact('deslop_result', gate);
207
+
208
+ // deslop은 항상 verify로 전이 (정보 제공 게이트, 차단 없음)
209
+ const result = this.advance('verify');
210
+ return { ok: result.ok, gate, state: result.state, error: result.error };
211
+ },
212
+
213
+ /**
214
+ * Self-Check Gate 실행 + 자동 전이
215
+ * verify → selfcheck → complete/fix
216
+ * @param {string|object} execResult
217
+ * @param {string|object} verifyResult
218
+ * @param {object} requirements - { hasDiff?, evidence? }
219
+ * @returns {{ ok: boolean, gate: object, state?: object, error?: string }}
220
+ */
221
+ runSelfCheckGate(execResult, verifyResult, requirements = {}) {
222
+ const current = readPipelineState(db, teamName);
223
+ if (!current) return { ok: false, error: `파이프라인 없음: ${teamName}` };
224
+
225
+ if (current.phase !== 'selfcheck') {
226
+ return { ok: false, error: `selfcheck gate는 selfcheck 단계에서만 실행 가능 (현재: ${current.phase})` };
227
+ }
228
+
229
+ const gate = runSelfCheck(execResult, verifyResult, requirements);
230
+ this.setArtifact('selfcheck_result', gate);
231
+
232
+ if (gate.passed) {
233
+ const result = this.advance('complete');
234
+ return { ok: result.ok, gate, state: result.state, error: result.error };
235
+ }
236
+
237
+ // Red Flag 탐지 또는 필수 질문 실패 → fix
238
+ const result = this.advance('fix');
239
+ return { ok: result.ok, gate, state: result.state, error: result.error };
240
+ },
241
+ };
242
+ }
243
+
244
+ // ── 토큰 벤치마크 훅 ──
245
+
246
+ let _tokenSnapshotMod = null;
247
+
248
+ async function loadTokenSnapshot() {
249
+ if (_tokenSnapshotMod) return _tokenSnapshotMod;
250
+ try {
251
+ _tokenSnapshotMod = await import('../../scripts/token-snapshot.mjs');
252
+ } catch {
253
+ _tokenSnapshotMod = null;
254
+ }
255
+ return _tokenSnapshotMod;
256
+ }
257
+
258
+ /**
259
+ * 파이프라인 시작 시 토큰 스냅샷 캡처
260
+ * @param {string} label - 스냅샷 라벨 (e.g. teamName + timestamp)
261
+ * @returns {Promise<{label: string, snapshot: object}|null>}
262
+ */
263
+ export async function benchmarkStart(label) {
264
+ const mod = await loadTokenSnapshot();
265
+ if (!mod?.takeSnapshot) return null;
266
+ try {
267
+ const snapshot = mod.takeSnapshot(label);
268
+ return { label, snapshot };
269
+ } catch { return null; }
270
+ }
271
+
272
+ /**
273
+ * 파이프라인 종료 시 diff 계산 + 결과 저장
274
+ * @param {string} preLabel - 시작 스냅샷 라벨
275
+ * @param {string} postLabel - 종료 스냅샷 라벨
276
+ * @param {object} options - { agent?, cli?, id? }
277
+ * @returns {Promise<object|null>} diff 결과
278
+ */
279
+ export async function benchmarkEnd(preLabel, postLabel, options = {}) {
280
+ const mod = await loadTokenSnapshot();
281
+ if (!mod?.takeSnapshot || !mod?.computeDiff) return null;
282
+ try {
283
+ // 종료 스냅샷 캡처
284
+ mod.takeSnapshot(postLabel);
285
+ // diff 계산 (결과는 DIFFS_DIR에 자동 저장됨)
286
+ const diff = mod.computeDiff(preLabel, postLabel, options);
287
+
288
+ // 추가로 타임스탬프 기반 사본 저장
289
+ const diffsDir = join(homedir(), '.omc', 'state', 'cx-auto-tokens', 'diffs');
290
+ mkdirSync(diffsDir, { recursive: true });
291
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
292
+ const outPath = join(diffsDir, `${ts}.json`);
293
+ writeFileSync(outPath, JSON.stringify(diff, null, 2));
294
+
295
+ return diff;
296
+ } catch { return null; }
297
+ }
298
+
299
+ /**
300
+ * 트리아지 통합: quickClassify 고신뢰 시 Codex 분류 스킵 판정
301
+ * @param {string} prompt
302
+ * @param {number} [threshold=0.8]
303
+ * @returns {{ skip: boolean, routing: object|null, classification: object }}
304
+ */
305
+ export function triageWithIntent(prompt, threshold = 0.8) {
306
+ const classification = _classifyIntent(prompt);
307
+ if (classification.confidence >= threshold) {
308
+ return { skip: true, routing: classification.routing, classification };
309
+ }
310
+ return { skip: false, routing: null, classification };
311
+ }
312
+
313
+ export { ensurePipelineTable } from './state.mjs';
314
+ export { PHASES, TERMINAL, ALLOWED, canTransition } from './transitions.mjs';
315
+ export { CRITERIA, runConfidenceCheck } from './gates/confidence.mjs';
316
+ export { RED_FLAGS, QUESTIONS, runSelfCheck } from './gates/selfcheck.mjs';
317
+ export { detectSlop, autoFixSlop, scanDirectory } from '../quality/deslop.mjs';
318
+ export { quickClassify, classifyIntent, INTENT_CATEGORIES } from '../intent.mjs';
@@ -130,7 +130,8 @@ export class QLearningRouter {
130
130
  this._gamma = opts.discountFactor ?? 0.9;
131
131
  this._epsilon = opts.epsilon ?? 0.3;
132
132
  this._epsilonDecay = opts.epsilonDecay ?? 0.995;
133
- this._epsilonMin = opts.epsilonMin ?? 0.05;
133
+ // epsilon=0 시에도 최소 탐색 보장 (pure-exploit 방지)
134
+ this._epsilonMin = opts.epsilonMin ?? Math.max(0.01, Math.min(0.05, this._epsilon));
134
135
  this._minConfidence = opts.minConfidence ?? 0.6;
135
136
  this._modelPath = opts.modelPath ?? join(homedir(), '.omc', 'routing-model.json');
136
137