@triflux/core 10.1.0 → 10.26.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 (145) hide show
  1. package/hooks/agent-route-guard.mjs +14 -10
  2. package/hooks/cross-review-tracker.mjs +21 -8
  3. package/hooks/error-context.mjs +20 -8
  4. package/hooks/hook-adaptive-collector.mjs +18 -16
  5. package/hooks/hook-manager.mjs +93 -32
  6. package/hooks/hook-orchestrator.mjs +267 -35
  7. package/hooks/hook-registry.json +343 -256
  8. package/hooks/hooks.json +49 -1
  9. package/hooks/keyword-rules.json +191 -31
  10. package/hooks/lib/resolve-root.mjs +1 -1
  11. package/hooks/mcp-config-watcher.mjs +6 -2
  12. package/hooks/permission-safe-allow.mjs +233 -0
  13. package/hooks/pipeline-stop.mjs +369 -22
  14. package/hooks/post-tool-tips.mjs +306 -0
  15. package/hooks/pre-compact-snapshot.mjs +181 -0
  16. package/hooks/safety-guard.mjs +262 -34
  17. package/hooks/session-end-cleanup.mjs +345 -0
  18. package/hooks/session-start-fast.mjs +214 -0
  19. package/hooks/subagent-tracker.mjs +267 -0
  20. package/hooks/subagent-verifier.mjs +9 -4
  21. package/hub/account-broker.mjs +1036 -66
  22. package/hub/adaptive-diagnostic.mjs +75 -48
  23. package/hub/adaptive-inject.mjs +96 -58
  24. package/hub/adaptive-memory.mjs +156 -42
  25. package/hub/adaptive.mjs +60 -31
  26. package/hub/assign-callbacks.mjs +163 -126
  27. package/hub/bridge.mjs +1051 -290
  28. package/hub/cli-adapter-base.mjs +321 -97
  29. package/hub/codex-adapter.mjs +179 -93
  30. package/hub/codex-compat.mjs +4 -4
  31. package/hub/codex-preflight.mjs +113 -37
  32. package/hub/delegator/contracts.mjs +22 -21
  33. package/hub/delegator/index.mjs +14 -14
  34. package/hub/delegator/schema/delegator-tools.schema.json +1 -1
  35. package/hub/delegator/service.mjs +93 -64
  36. package/hub/delegator/tool-definitions.mjs +35 -35
  37. package/hub/dynamic-routing-engine.mjs +511 -0
  38. package/hub/fullcycle.mjs +34 -18
  39. package/hub/gemini-adapter.mjs +82 -108
  40. package/hub/hitl.mjs +202 -143
  41. package/hub/intent.mjs +167 -38
  42. package/hub/lib/bash-path.mjs +73 -0
  43. package/hub/lib/cache-guard.mjs +43 -17
  44. package/hub/lib/env-detect.mjs +145 -0
  45. package/hub/lib/hosts-compat.mjs +319 -0
  46. package/hub/lib/mcp-response-cache.mjs +239 -0
  47. package/hub/lib/memory-store.mjs +285 -111
  48. package/hub/lib/path-utils.mjs +165 -0
  49. package/hub/lib/phase-manager.mjs +493 -0
  50. package/hub/lib/process-utils.mjs +797 -104
  51. package/hub/lib/prompt-tmp.mjs +33 -0
  52. package/hub/lib/risk-tier.mjs +53 -0
  53. package/hub/lib/spawn-trace.mjs +561 -0
  54. package/hub/lib/ssh-command.mjs +44 -24
  55. package/hub/lib/ssh-retry.mjs +5 -1
  56. package/hub/lib/staged-file-detect.mjs +43 -0
  57. package/hub/lib/state-snapshot.mjs +293 -0
  58. package/hub/lib/tfx-route-args.mjs +309 -0
  59. package/hub/lib/timeline-adapter.mjs +190 -0
  60. package/hub/lib/trace-recorder.mjs +153 -0
  61. package/hub/lib/uuidv7.mjs +4 -3
  62. package/hub/memory-doctor.mjs +266 -106
  63. package/hub/middleware/quota-middleware.mjs +70 -0
  64. package/hub/middleware/request-logger.mjs +61 -34
  65. package/hub/paths.mjs +9 -9
  66. package/hub/pipeline/gates/confidence.mjs +34 -15
  67. package/hub/pipeline/gates/consensus.mjs +27 -15
  68. package/hub/pipeline/gates/index.mjs +7 -3
  69. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  70. package/hub/pipeline/index.mjs +77 -42
  71. package/hub/pipeline/state.mjs +10 -10
  72. package/hub/pipeline/transitions.mjs +40 -23
  73. package/hub/platform.mjs +65 -48
  74. package/hub/quality/deslop.mjs +70 -49
  75. package/hub/reflexion.mjs +87 -13
  76. package/hub/research.mjs +33 -25
  77. package/hub/router.mjs +941 -791
  78. package/hub/routing/complexity.mjs +132 -29
  79. package/hub/routing/index.mjs +17 -12
  80. package/hub/routing/q-learning.mjs +78 -30
  81. package/hub/routing-snapshot.mjs +261 -0
  82. package/hub/session-fingerprint.mjs +127 -61
  83. package/hub/state.mjs +86 -44
  84. package/hub/team/claude-daemon-control.mjs +1130 -0
  85. package/hub/team/retry-state-machine.mjs +336 -0
  86. package/hub/token-mode.mjs +72 -71
  87. package/hub/workers/worker-utils.mjs +23 -17
  88. package/hud/colors.mjs +27 -9
  89. package/hud/constants.mjs +190 -27
  90. package/hud/context-monitor.mjs +133 -44
  91. package/hud/hud-qos-status.mjs +157 -50
  92. package/hud/mission-board.mjs +56 -0
  93. package/hud/providers/claude.mjs +398 -114
  94. package/hud/providers/codex.mjs +96 -21
  95. package/hud/providers/gemini.mjs +210 -52
  96. package/hud/renderers.mjs +440 -127
  97. package/hud/terminal.mjs +101 -31
  98. package/hud/utils.mjs +81 -39
  99. package/mesh/index.mjs +69 -0
  100. package/mesh/mesh-budget.mjs +137 -0
  101. package/mesh/mesh-heartbeat.mjs +100 -0
  102. package/mesh/mesh-protocol.mjs +96 -0
  103. package/mesh/mesh-queue.mjs +163 -0
  104. package/mesh/mesh-registry.mjs +78 -0
  105. package/mesh/mesh-router.mjs +77 -0
  106. package/package.json +5 -1
  107. package/scripts/lib/agent-json.mjs +27 -0
  108. package/scripts/lib/async.mjs +174 -0
  109. package/scripts/lib/claudemd-scanner.mjs +6 -1
  110. package/scripts/lib/cli-agy.mjs +62 -0
  111. package/scripts/lib/cli-claude.mjs +78 -0
  112. package/scripts/lib/cli-codex.mjs +199 -0
  113. package/scripts/lib/cli-gemini.mjs +37 -0
  114. package/scripts/lib/codex-recovery.sh +50 -0
  115. package/scripts/lib/context.mjs +3 -3
  116. package/scripts/lib/cross-review-utils.mjs +13 -5
  117. package/scripts/lib/doctor-env-checks.mjs +121 -0
  118. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  119. package/scripts/lib/env-probe.mjs +58 -30
  120. package/scripts/lib/env.mjs +14 -0
  121. package/scripts/lib/gemini-profiles.mjs +44 -10
  122. package/scripts/lib/handoff.mjs +33 -17
  123. package/scripts/lib/hook-utils.mjs +8 -6
  124. package/scripts/lib/hub.mjs +48 -0
  125. package/scripts/lib/keyword-rules.mjs +49 -20
  126. package/scripts/lib/logger.mjs +24 -24
  127. package/scripts/lib/mcp-filter.mjs +391 -247
  128. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  129. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  130. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  131. package/scripts/lib/mcp-guard-engine.mjs +1281 -115
  132. package/scripts/lib/mcp-health.mjs +773 -0
  133. package/scripts/lib/mcp-manifest.mjs +24 -14
  134. package/scripts/lib/mcp-server-catalog.mjs +355 -118
  135. package/scripts/lib/pid.mjs +63 -0
  136. package/scripts/lib/process-utils.mjs +26 -0
  137. package/scripts/lib/psmux-info.mjs +117 -12
  138. package/scripts/lib/quota.mjs +47 -0
  139. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  140. package/scripts/lib/skill-state.mjs +220 -0
  141. package/scripts/lib/skill-template.mjs +32 -7
  142. package/scripts/lib/team.mjs +75 -0
  143. package/scripts/lib/timeout.mjs +66 -0
  144. package/scripts/lib/tmp.mjs +26 -0
  145. package/scripts/lib/toml.mjs +113 -0
@@ -0,0 +1,336 @@
1
+ // hub/team/retry-state-machine.mjs
2
+ // Phase 3 Step A — true ralph / auto-escalate retry state machine.
3
+ // 설계 문서: .triflux/plans/phase3-lead-codex-ralph-escalate.md
4
+ //
5
+ // Modes:
6
+ // bounded — --retry 1 (기본): maxIterations 도달 시 BUDGET_EXCEEDED.
7
+ // ralph — --retry ralph: maxIterations=0 이면 unlimited. stuck detector 만 종료.
8
+ // auto-escalate — --retry auto-escalate: BUDGET_EXCEEDED 시 다음 CLI 로 전이.
9
+ //
10
+ // 상태 전이:
11
+ // PLANNING → EXECUTING → VERIFYING.success → DONE
12
+ // → VERIFYING.fail → DIAGNOSING → EXECUTING …
13
+ // → stuckCounter≥3 → STUCK
14
+ // → iter≥max → BUDGET_EXCEEDED (escalate 모드는 다음 CLI)
15
+
16
+ import { EventEmitter } from "node:events";
17
+ import {
18
+ appendFileSync,
19
+ existsSync,
20
+ mkdirSync,
21
+ readFileSync,
22
+ writeFileSync,
23
+ } from "node:fs";
24
+ import { dirname, join } from "node:path";
25
+
26
+ export const STATES = Object.freeze({
27
+ PLANNING: "PLANNING",
28
+ EXECUTING: "EXECUTING",
29
+ DIAGNOSING: "DIAGNOSING",
30
+ STUCK: "STUCK",
31
+ BUDGET_EXCEEDED: "BUDGET_EXCEEDED",
32
+ DONE: "DONE",
33
+ });
34
+
35
+ export const MODES = Object.freeze({
36
+ BOUNDED: "bounded",
37
+ RALPH: "ralph",
38
+ ESCALATE: "auto-escalate",
39
+ });
40
+
41
+ const ESCALATION_CHAIN_CONFIG_PATH = ".triflux/config/escalation-chain.json";
42
+
43
+ // Escalation chain (2026-05-27 정책):
44
+ // 1. codex gpt-5.5 — 코드/추론/비용에서 mini/5.3-codex 중간 단계를 대체
45
+ // 2. claude opus-4-7 — 최종 수단
46
+ const DEFAULT_ESCALATION_CHAIN = Object.freeze([
47
+ Object.freeze({ cli: "codex", model: "gpt-5.5" }),
48
+ Object.freeze({ cli: "claude", model: "opus-4-7" }),
49
+ ]);
50
+
51
+ const STUCK_THRESHOLD = 3;
52
+
53
+ export function createRetryStateMachine(options = {}) {
54
+ const mode = options.mode || MODES.BOUNDED;
55
+ const cliChain = resolveEscalationChain(options);
56
+ const maxIterationsInput =
57
+ typeof options.maxIterations === "number" ? options.maxIterations : null;
58
+ const maxIterations =
59
+ maxIterationsInput !== null
60
+ ? maxIterationsInput
61
+ : mode === MODES.BOUNDED
62
+ ? 3
63
+ : 0;
64
+ const stateFile = options.stateFile || null;
65
+ const sessionId = options.sessionId || null;
66
+
67
+ const emitter = new EventEmitter();
68
+ const state = {
69
+ current: STATES.PLANNING,
70
+ iterations: 0,
71
+ maxIterations,
72
+ stuckCounter: 0,
73
+ lastFailureReason: null,
74
+ cliIndex: 0,
75
+ cliChain,
76
+ mode,
77
+ sessionId,
78
+ stateFile,
79
+ history: [],
80
+ };
81
+
82
+ function transition(next, meta = {}) {
83
+ const prev = state.current;
84
+ state.current = next;
85
+ const entry = {
86
+ t: Date.now(),
87
+ from: prev,
88
+ to: next,
89
+ iteration: state.iterations,
90
+ cliIndex: state.cliIndex,
91
+ ...meta,
92
+ };
93
+ state.history.push(entry);
94
+ if (stateFile) persistTransition(stateFile, entry);
95
+ emitter.emit("transition", entry);
96
+ return entry;
97
+ }
98
+
99
+ function startIteration() {
100
+ state.iterations += 1;
101
+ return transition(STATES.EXECUTING, { iteration: state.iterations });
102
+ }
103
+
104
+ function reportVerifySuccess() {
105
+ return transition(STATES.DONE);
106
+ }
107
+
108
+ function reportVerifyFail(failureReason) {
109
+ const reason = String(failureReason || "unknown");
110
+ if (reason === state.lastFailureReason) {
111
+ state.stuckCounter += 1;
112
+ } else {
113
+ state.stuckCounter = 1;
114
+ state.lastFailureReason = reason;
115
+ }
116
+
117
+ if (state.stuckCounter >= STUCK_THRESHOLD) {
118
+ return transition(STATES.STUCK, {
119
+ reason,
120
+ stuckCounter: state.stuckCounter,
121
+ });
122
+ }
123
+
124
+ if (state.maxIterations > 0 && state.iterations >= state.maxIterations) {
125
+ if (state.mode === MODES.ESCALATE) {
126
+ return escalate();
127
+ }
128
+ return transition(STATES.BUDGET_EXCEEDED, {
129
+ reason,
130
+ iterations: state.iterations,
131
+ });
132
+ }
133
+
134
+ return transition(STATES.DIAGNOSING, { reason });
135
+ }
136
+
137
+ function escalate() {
138
+ if (state.cliIndex + 1 >= state.cliChain.length) {
139
+ return transition(STATES.BUDGET_EXCEEDED, {
140
+ reason: "escalation-chain-exhausted",
141
+ chain: state.cliChain,
142
+ });
143
+ }
144
+ state.cliIndex += 1;
145
+ state.iterations = 0;
146
+ state.stuckCounter = 0;
147
+ state.lastFailureReason = null;
148
+ return transition(STATES.EXECUTING, {
149
+ cli: state.cliChain[state.cliIndex],
150
+ escalated: true,
151
+ });
152
+ }
153
+
154
+ function getCurrent() {
155
+ return {
156
+ current: state.current,
157
+ iterations: state.iterations,
158
+ maxIterations: state.maxIterations,
159
+ stuckCounter: state.stuckCounter,
160
+ lastFailureReason: state.lastFailureReason,
161
+ cliIndex: state.cliIndex,
162
+ cliChain: state.cliChain.slice(),
163
+ mode: state.mode,
164
+ sessionId: state.sessionId,
165
+ history: state.history.slice(),
166
+ };
167
+ }
168
+
169
+ function on(event, listener) {
170
+ emitter.on(event, listener);
171
+ return () => emitter.off(event, listener);
172
+ }
173
+
174
+ function serialize() {
175
+ return {
176
+ version: 1,
177
+ current: state.current,
178
+ iterations: state.iterations,
179
+ maxIterations: state.maxIterations,
180
+ stuckCounter: state.stuckCounter,
181
+ lastFailureReason: state.lastFailureReason,
182
+ cliIndex: state.cliIndex,
183
+ cliChain: state.cliChain.slice(),
184
+ mode: state.mode,
185
+ sessionId: state.sessionId,
186
+ history: state.history.slice(),
187
+ };
188
+ }
189
+
190
+ function applySnapshot(snapshot) {
191
+ if (!snapshot || typeof snapshot !== "object") return;
192
+ state.current = snapshot.current || STATES.PLANNING;
193
+ state.iterations = Number(snapshot.iterations) || 0;
194
+ state.maxIterations =
195
+ snapshot.maxIterations !== undefined
196
+ ? Number(snapshot.maxIterations)
197
+ : state.maxIterations;
198
+ state.stuckCounter = Number(snapshot.stuckCounter) || 0;
199
+ state.lastFailureReason = snapshot.lastFailureReason || null;
200
+ state.cliIndex = Number(snapshot.cliIndex) || 0;
201
+ if (Array.isArray(snapshot.cliChain) && snapshot.cliChain.length > 0) {
202
+ state.cliChain = normalizeEscalationChain(
203
+ snapshot.cliChain,
204
+ "snapshot.cliChain",
205
+ );
206
+ }
207
+ if (snapshot.mode) state.mode = snapshot.mode;
208
+ if (snapshot.sessionId !== undefined) state.sessionId = snapshot.sessionId;
209
+ if (Array.isArray(snapshot.history))
210
+ state.history = snapshot.history.slice();
211
+ }
212
+
213
+ return {
214
+ STATES,
215
+ MODES,
216
+ getCurrent,
217
+ startIteration,
218
+ reportVerifySuccess,
219
+ reportVerifyFail,
220
+ escalate,
221
+ on,
222
+ serialize,
223
+ applySnapshot,
224
+ };
225
+ }
226
+
227
+ function resolveEscalationChain(options) {
228
+ if (Array.isArray(options.cliChain)) {
229
+ return normalizeEscalationChain(options.cliChain, "options.cliChain");
230
+ }
231
+
232
+ const projectRoot = options.projectRoot || process.cwd();
233
+ return (
234
+ loadEscalationChainOverride(projectRoot) ||
235
+ normalizeEscalationChain(
236
+ DEFAULT_ESCALATION_CHAIN,
237
+ "DEFAULT_ESCALATION_CHAIN",
238
+ )
239
+ );
240
+ }
241
+
242
+ export function loadEscalationChainOverride(projectRoot = process.cwd()) {
243
+ const configPath = join(projectRoot, ESCALATION_CHAIN_CONFIG_PATH);
244
+ if (!existsSync(configPath)) return null;
245
+
246
+ const raw = readFileSync(configPath, "utf8");
247
+ let parsed;
248
+ try {
249
+ parsed = JSON.parse(raw);
250
+ } catch (err) {
251
+ throw new Error(
252
+ `escalation-chain override 파싱 실패: ${configPath} — 원인: ${err.message} — 수정: JSON 문법 확인 또는 파일 삭제로 DEFAULT_ESCALATION_CHAIN 사용`,
253
+ { cause: err },
254
+ );
255
+ }
256
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
257
+ throw new Error(`${configPath}: expected object`);
258
+ }
259
+ if (parsed.version !== undefined && parsed.version !== 1) {
260
+ throw new Error(`${configPath}: unsupported version ${parsed.version}`);
261
+ }
262
+ return normalizeEscalationChain(parsed.chain, configPath);
263
+ }
264
+
265
+ function normalizeEscalationChain(chain, source) {
266
+ if (!Array.isArray(chain) || chain.length === 0) {
267
+ throw new Error(`${source}: chain must be a non-empty array`);
268
+ }
269
+ return chain.map((entry, index) =>
270
+ normalizeEscalationChainEntry(entry, index, source),
271
+ );
272
+ }
273
+
274
+ function normalizeEscalationChainEntry(entry, index, source) {
275
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
276
+ throw new Error(`${source}: chain[${index}] must be an object`);
277
+ }
278
+ if (typeof entry.cli !== "string" || entry.cli.trim() === "") {
279
+ throw new Error(
280
+ `${source}: chain[${index}].cli must be a non-empty string`,
281
+ );
282
+ }
283
+ if (typeof entry.model !== "string" || entry.model.trim() === "") {
284
+ throw new Error(
285
+ `${source}: chain[${index}].model must be a non-empty string`,
286
+ );
287
+ }
288
+
289
+ const normalized = {
290
+ cli: entry.cli,
291
+ model: entry.model,
292
+ };
293
+ if (typeof entry.profile === "string" && entry.profile.trim() !== "") {
294
+ normalized.profile = entry.profile;
295
+ }
296
+ return normalized;
297
+ }
298
+
299
+ function persistTransition(stateFile, entry) {
300
+ const dir = dirname(stateFile);
301
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
302
+ appendFileSync(stateFile, `${JSON.stringify(entry)}\n`, "utf8");
303
+ }
304
+
305
+ export function resumeFromStateFile(stateFile) {
306
+ if (!existsSync(stateFile)) return null;
307
+ const raw = readFileSync(stateFile, "utf8").trim();
308
+ if (!raw) return null;
309
+ const lines = raw.split("\n").filter(Boolean);
310
+ if (lines.length === 0) return null;
311
+ return JSON.parse(lines[lines.length - 1]);
312
+ }
313
+
314
+ // Full-snapshot 기반 stateFile I/O — bridge retry-run 에서 multi-process state 복원용.
315
+ // transition event log (resumeFromStateFile) 와 별도 파일로 관리하는 것을 권장.
316
+ export function loadSnapshot(snapshotFile) {
317
+ if (!existsSync(snapshotFile)) return null;
318
+ const raw = readFileSync(snapshotFile, "utf8").trim();
319
+ if (!raw) return null;
320
+ const parsed = JSON.parse(raw);
321
+ if (!parsed || typeof parsed !== "object") return null;
322
+ if (parsed.version !== 1) {
323
+ throw new Error(
324
+ `unsupported snapshot version: ${parsed.version} (expected 1)`,
325
+ );
326
+ }
327
+ return parsed;
328
+ }
329
+
330
+ export function saveSnapshot(snapshotFile, snapshot) {
331
+ const dir = dirname(snapshotFile);
332
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
333
+ writeFileSync(snapshotFile, JSON.stringify(snapshot), "utf8");
334
+ }
335
+
336
+ export { DEFAULT_ESCALATION_CHAIN, STUCK_THRESHOLD };
@@ -2,83 +2,84 @@
2
2
  // GAP 분석 P2 #7: 심볼 통신 + 약어로 30-50% 토큰 절감
3
3
 
4
4
  /**
5
+ * @experimental 런타임 미연결 — 향후 통합 예정
5
6
  * @experimental
6
7
  * @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>}
7
8
  */
8
9
  export const COMPACT_RULES = [
9
10
  // ── 심볼 치환 (긴 매칭 우선 정렬) ──
10
- { from: ['greater than or equal'], to: '≥', type: 'symbol' },
11
- { from: ['less than or equal'], to: '≤', type: 'symbol' },
12
- { from: ['results in', '결과적으로'], to: '→', type: 'symbol' },
13
- { from: ['therefore', '따라서'], to: '∴', type: 'symbol' },
14
- { from: ['because', '왜냐하면'], to: '∵', type: 'symbol' },
15
- { from: ['approximately', '대략'], to: '≈', type: 'symbol' },
16
- { from: ['not equal', '같지 않'], to: '≠', type: 'symbol' },
17
- { from: ['in progress', '진행 중'], to: '⏳', type: 'symbol' },
18
- { from: ['completed', '완료'], to: '✓', type: 'symbol' },
19
- { from: ['success', '성공'], to: '✓', type: 'symbol' },
20
- { from: ['failure', '실패'], to: '✗', type: 'symbol' },
21
- { from: ['warning', '경고'], to: '⚠', type: 'symbol' },
22
- { from: ['error', '에러'], to: '✗', type: 'symbol' },
23
- { from: ['pending', '대기'], to: '⏸', type: 'symbol' },
11
+ { from: ["greater than or equal"], to: "≥", type: "symbol" },
12
+ { from: ["less than or equal"], to: "≤", type: "symbol" },
13
+ { from: ["results in", "결과적으로"], to: "→", type: "symbol" },
14
+ { from: ["therefore", "따라서"], to: "∴", type: "symbol" },
15
+ { from: ["because", "왜냐하면"], to: "∵", type: "symbol" },
16
+ { from: ["approximately", "대략"], to: "≈", type: "symbol" },
17
+ { from: ["not equal", "같지 않"], to: "≠", type: "symbol" },
18
+ { from: ["in progress", "진행 중"], to: "⏳", type: "symbol" },
19
+ { from: ["completed", "완료"], to: "✓", type: "symbol" },
20
+ { from: ["success", "성공"], to: "✓", type: "symbol" },
21
+ { from: ["failure", "실패"], to: "✗", type: "symbol" },
22
+ { from: ["warning", "경고"], to: "⚠", type: "symbol" },
23
+ { from: ["error", "에러"], to: "✗", type: "symbol" },
24
+ { from: ["pending", "대기"], to: "⏸", type: "symbol" },
24
25
 
25
26
  // ── 약어 (긴 매칭 우선 정렬) ──
26
- { from: ['configuration', '설정'], to: 'cfg', type: 'abbrev' },
27
- { from: ['implementation', '구현'], to: 'impl', type: 'abbrev' },
28
- { from: ['architecture', '아키텍처'], to: 'arch', type: 'abbrev' },
29
- { from: ['dependency', '의존성'], to: 'dep', type: 'abbrev' },
30
- { from: ['function', '함수'], to: 'fn', type: 'abbrev' },
31
- { from: ['parameter', '파라미터'], to: 'param', type: 'abbrev' },
32
- { from: ['repository', '저장소'], to: 'repo', type: 'abbrev' },
33
- { from: ['environment', '환경'], to: 'env', type: 'abbrev' },
34
- { from: ['variable', '변수'], to: 'var', type: 'abbrev' },
35
- { from: ['directory', '디렉토리'], to: 'dir', type: 'abbrev' },
27
+ { from: ["configuration", "설정"], to: "cfg", type: "abbrev" },
28
+ { from: ["implementation", "구현"], to: "impl", type: "abbrev" },
29
+ { from: ["architecture", "아키텍처"], to: "arch", type: "abbrev" },
30
+ { from: ["dependency", "의존성"], to: "dep", type: "abbrev" },
31
+ { from: ["function", "함수"], to: "fn", type: "abbrev" },
32
+ { from: ["parameter", "파라미터"], to: "param", type: "abbrev" },
33
+ { from: ["repository", "저장소"], to: "repo", type: "abbrev" },
34
+ { from: ["environment", "환경"], to: "env", type: "abbrev" },
35
+ { from: ["variable", "변수"], to: "var", type: "abbrev" },
36
+ { from: ["directory", "디렉토리"], to: "dir", type: "abbrev" },
36
37
 
37
38
  // ── 한국어 동사/명령형 약어 ──
38
- { from: ['구현해'], to: 'impl', type: 'abbrev' },
39
- { from: ['확인해'], to: 'check', type: 'abbrev' },
40
- { from: ['수정해'], to: 'fix', type: 'abbrev' },
41
- { from: ['테스트'], to: 'test', type: 'abbrev' },
42
- { from: ['리뷰'], to: 'review', type: 'abbrev' },
43
- { from: ['분석'], to: 'analyze', type: 'abbrev' },
44
- { from: ['설계'], to: 'design', type: 'abbrev' },
45
- { from: ['문서화'], to: 'docs', type: 'abbrev' },
39
+ { from: ["구현해"], to: "impl", type: "abbrev" },
40
+ { from: ["확인해"], to: "check", type: "abbrev" },
41
+ { from: ["수정해"], to: "fix", type: "abbrev" },
42
+ { from: ["테스트"], to: "test", type: "abbrev" },
43
+ { from: ["리뷰"], to: "review", type: "abbrev" },
44
+ { from: ["분석"], to: "analyze", type: "abbrev" },
45
+ { from: ["설계"], to: "design", type: "abbrev" },
46
+ { from: ["문서화"], to: "docs", type: "abbrev" },
46
47
  ];
47
48
 
48
49
  /** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
49
50
  export const REVIEW_RULES = [
50
- { from: ['looks good to me', 'lgtm'], to: '✓lgtm', type: 'abbrev' },
51
- { from: ['needs changes', '수정 필요'], to: '✗chg', type: 'abbrev' },
52
- { from: ['nitpick', '사소한'], to: 'nit', type: 'abbrev' },
53
- { from: ['blocking', '블로킹'], to: 'blk', type: 'abbrev' },
54
- { from: ['suggestion', '제안'], to: 'sug', type: 'abbrev' },
55
- { from: ['question', '질문'], to: 'q', type: 'abbrev' },
56
- { from: ['approved', '승인'], to: '✓apv', type: 'abbrev' },
57
- { from: ['request changes', '변경 요청'], to: '✗req', type: 'abbrev' },
51
+ { from: ["looks good to me", "lgtm"], to: "✓lgtm", type: "abbrev" },
52
+ { from: ["needs changes", "수정 필요"], to: "✗chg", type: "abbrev" },
53
+ { from: ["nitpick", "사소한"], to: "nit", type: "abbrev" },
54
+ { from: ["blocking", "블로킹"], to: "blk", type: "abbrev" },
55
+ { from: ["suggestion", "제안"], to: "sug", type: "abbrev" },
56
+ { from: ["question", "질문"], to: "q", type: "abbrev" },
57
+ { from: ["approved", "승인"], to: "✓apv", type: "abbrev" },
58
+ { from: ["request changes", "변경 요청"], to: "✗req", type: "abbrev" },
58
59
  ];
59
60
 
60
61
  /** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
61
62
  export const DESIGN_RULES = [
62
- { from: ['component', '컴포넌트'], to: 'cmp', type: 'abbrev' },
63
- { from: ['interface', '인터페이스'], to: 'iface', type: 'abbrev' },
64
- { from: ['abstraction', '추상화'], to: 'abs', type: 'abbrev' },
65
- { from: ['pattern', '패턴'], to: 'ptn', type: 'abbrev' },
66
- { from: ['dependency injection', '의존성 주입'], to: 'di', type: 'abbrev' },
67
- { from: ['single responsibility', '단일 책임'], to: 'srp', type: 'abbrev' },
68
- { from: ['open closed', '개방 폐쇄'], to: 'ocp', type: 'abbrev' },
69
- { from: ['inheritance', '상속'], to: 'inh', type: 'abbrev' },
63
+ { from: ["component", "컴포넌트"], to: "cmp", type: "abbrev" },
64
+ { from: ["interface", "인터페이스"], to: "iface", type: "abbrev" },
65
+ { from: ["abstraction", "추상화"], to: "abs", type: "abbrev" },
66
+ { from: ["pattern", "패턴"], to: "ptn", type: "abbrev" },
67
+ { from: ["dependency injection", "의존성 주입"], to: "di", type: "abbrev" },
68
+ { from: ["single responsibility", "단일 책임"], to: "srp", type: "abbrev" },
69
+ { from: ["open closed", "개방 폐쇄"], to: "ocp", type: "abbrev" },
70
+ { from: ["inheritance", "상속"], to: "inh", type: "abbrev" },
70
71
  ];
71
72
 
72
73
  /** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
73
74
  export const DOCS_RULES = [
74
- { from: ['description', '설명'], to: 'desc', type: 'abbrev' },
75
- { from: ['example', '예시'], to: 'ex', type: 'abbrev' },
76
- { from: ['reference', '참조'], to: 'ref', type: 'abbrev' },
77
- { from: ['introduction', '소개'], to: 'intro', type: 'abbrev' },
78
- { from: ['deprecated', '사용 중단'], to: 'dep', type: 'abbrev' },
79
- { from: ['optional', '선택적'], to: 'opt', type: 'abbrev' },
80
- { from: ['required', '필수'], to: 'req', type: 'abbrev' },
81
- { from: ['returns', '반환'], to: 'ret', type: 'abbrev' },
75
+ { from: ["description", "설명"], to: "desc", type: "abbrev" },
76
+ { from: ["example", "예시"], to: "ex", type: "abbrev" },
77
+ { from: ["reference", "참조"], to: "ref", type: "abbrev" },
78
+ { from: ["introduction", "소개"], to: "intro", type: "abbrev" },
79
+ { from: ["deprecated", "사용 중단"], to: "dep", type: "abbrev" },
80
+ { from: ["optional", "선택적"], to: "opt", type: "abbrev" },
81
+ { from: ["required", "필수"], to: "req", type: "abbrev" },
82
+ { from: ["returns", "반환"], to: "ret", type: "abbrev" },
82
83
  ];
83
84
 
84
85
  // ── 프로필 맵 ──
@@ -86,9 +87,9 @@ export const DOCS_RULES = [
86
87
  /** @type {Record<string, Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>>} */
87
88
  const PROFILE_MAP = {
88
89
  default: COMPACT_RULES,
89
- review: [...COMPACT_RULES, ...REVIEW_RULES],
90
- design: [...COMPACT_RULES, ...DESIGN_RULES],
91
- docs: [...COMPACT_RULES, ...DOCS_RULES],
90
+ review: [...COMPACT_RULES, ...REVIEW_RULES],
91
+ design: [...COMPACT_RULES, ...DESIGN_RULES],
92
+ docs: [...COMPACT_RULES, ...DOCS_RULES],
92
93
  };
93
94
 
94
95
  // ── 내부: 정렬된 치환 쌍 빌드 헬퍼 ──
@@ -134,7 +135,7 @@ const CODE_BLOCK_RE = /```[\s\S]*?```/g;
134
135
  */
135
136
  function withCodeProtection(text, transform) {
136
137
  const blocks = [];
137
- const placeholder = '\x00CB';
138
+ const placeholder = "\x00CB";
138
139
  let idx = 0;
139
140
  const masked = text.replace(CODE_BLOCK_RE, (match) => {
140
141
  blocks.push(match);
@@ -145,7 +146,7 @@ function withCodeProtection(text, transform) {
145
146
 
146
147
  // 플레이스홀더 복원
147
148
  return transformed.replace(
148
- new RegExp(`${placeholder}(\\d+)${placeholder}`, 'g'),
149
+ new RegExp(`${placeholder}(\\d+)${placeholder}`, "g"),
149
150
  (_, i) => blocks[Number(i)],
150
151
  );
151
152
  }
@@ -169,14 +170,14 @@ export function isCompactMode() {
169
170
  * @returns {string}
170
171
  */
171
172
  export function compactify(text) {
172
- if (!text || typeof text !== 'string') return text ?? '';
173
+ if (!text || typeof text !== "string") return text ?? "";
173
174
  _compactMode = true;
174
175
 
175
176
  return withCodeProtection(text, (segment) => {
176
177
  let result = segment;
177
178
  for (const { pattern, to } of _compactPairs) {
178
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
179
- const re = new RegExp(escaped, 'gi');
179
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
180
+ const re = new RegExp(escaped, "gi");
180
181
  result = result.replace(re, to);
181
182
  }
182
183
  return result;
@@ -189,8 +190,8 @@ export function compactify(text) {
189
190
  * @param {'default'|'review'|'design'|'docs'} [profile='default'] — 도메인 프로필
190
191
  * @returns {string}
191
192
  */
192
- export function applyCompactRules(text, profile = 'default') {
193
- if (!text || typeof text !== 'string') return text ?? '';
193
+ export function applyCompactRules(text, profile = "default") {
194
+ if (!text || typeof text !== "string") return text ?? "";
194
195
 
195
196
  const rules = PROFILE_MAP[profile] ?? COMPACT_RULES;
196
197
  const pairs = buildCompactPairs(rules);
@@ -198,8 +199,8 @@ export function applyCompactRules(text, profile = 'default') {
198
199
  return withCodeProtection(text, (segment) => {
199
200
  let result = segment;
200
201
  for (const { pattern, to } of pairs) {
201
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
202
- const re = new RegExp(escaped, 'gi');
202
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
203
+ const re = new RegExp(escaped, "gi");
203
204
  result = result.replace(re, to);
204
205
  }
205
206
  return result;
@@ -212,14 +213,14 @@ export function applyCompactRules(text, profile = 'default') {
212
213
  * @returns {string}
213
214
  */
214
215
  export function expand(text) {
215
- if (!text || typeof text !== 'string') return text ?? '';
216
+ if (!text || typeof text !== "string") return text ?? "";
216
217
  _compactMode = false;
217
218
 
218
219
  return withCodeProtection(text, (segment) => {
219
220
  let result = segment;
220
221
  for (const { symbol, restore } of _expandPairs) {
221
- const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
222
- const re = new RegExp(escaped, 'g');
222
+ const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
223
+ const re = new RegExp(escaped, "g");
223
224
  result = result.replace(re, restore);
224
225
  }
225
226
  return result;