triflux 9.7.13 → 9.8.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 (50) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +2 -0
  4. package/README.md +2 -0
  5. package/bin/triflux.mjs +297 -47
  6. package/hooks/hook-registry.json +4 -4
  7. package/hub/fullcycle.mjs +96 -0
  8. package/hub/paths.mjs +30 -28
  9. package/hub/pipeline/index.mjs +318 -318
  10. package/hub/schema.sql +146 -146
  11. package/hub/team/cli/commands/kill.mjs +37 -37
  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/services/hub-client.mjs +208 -208
  15. package/hub/team/cli/services/native-control.mjs +118 -118
  16. package/hub/team/cli/services/runtime-mode.mjs +62 -62
  17. package/hub/team/cli/services/state-store.mjs +48 -48
  18. package/hub/team/dashboard.mjs +274 -274
  19. package/hub/team/native.mjs +649 -649
  20. package/hub/team/psmux.mjs +68 -13
  21. package/hub/tools.mjs +554 -554
  22. package/hub/workers/claude-worker.mjs +423 -423
  23. package/hub/workers/codex-mcp.mjs +410 -410
  24. package/hub/workers/gemini-worker.mjs +429 -429
  25. package/hub/workers/interface.mjs +40 -40
  26. package/package.json +1 -1
  27. package/scripts/__tests__/remote-spawn-transfer.test.mjs +1 -1
  28. package/scripts/cache-warmup.mjs +1 -0
  29. package/scripts/claude-logged.ps1 +54 -0
  30. package/scripts/demo-tui.mjs +59 -0
  31. package/scripts/headless-guard.mjs +4 -7
  32. package/scripts/hub-ensure.mjs +120 -120
  33. package/scripts/lib/psmux-info.mjs +119 -0
  34. package/scripts/lib/remote-spawn-transfer.mjs +1 -1
  35. package/scripts/setup.mjs +150 -6
  36. package/scripts/tfx-route-post.mjs +90 -13
  37. package/scripts/token-snapshot.mjs +575 -575
  38. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  39. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  40. package/skills/.omc/state/last-tool-error.json +7 -0
  41. package/skills/.omc/state/subagent-tracking.json +7 -0
  42. package/skills/tfx-codex-swarm/SKILL.md +40 -5
  43. package/skills/tfx-codex-swarm/mcp-daemon/register-autostart.ps1 +32 -0
  44. package/skills/tfx-doctor/SKILL.md +3 -0
  45. package/skills/tfx-fullcycle/SKILL.md +79 -4
  46. package/skills/tfx-hub/SKILL.md +3 -1
  47. package/skills/tfx-psmux-rules/SKILL.md +53 -31
  48. package/skills/tfx-remote-spawn/references/hosts.json +16 -16
  49. package/skills/tfx-setup/SKILL.md +9 -0
  50. package/tui/doctor.mjs +1 -0
@@ -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';