triflux 10.30.0 → 10.30.1
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.
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// hub/workers/codex-mcp.mjs — Codex MCP 서버 래퍼
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
2
5
|
import process from "node:process";
|
|
3
6
|
import { fileURLToPath } from "node:url";
|
|
4
7
|
|
|
@@ -94,13 +97,78 @@ function normalizeStructuredContent(structuredContent, fallbackText = "") {
|
|
|
94
97
|
return { threadId, content };
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Read a top-level TOML scalar (`key = "value"` / `key = 'value'` /
|
|
102
|
+
* `key = value`), tolerating an inline `# comment` and surrounding whitespace.
|
|
103
|
+
* Returns the trimmed value, or null when the key is absent/empty. Intentionally
|
|
104
|
+
* tiny — the per-profile files only carry `model` + `model_reasoning_effort`.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} raw
|
|
107
|
+
* @param {string} key
|
|
108
|
+
* @returns {string|null}
|
|
109
|
+
*/
|
|
110
|
+
function readTomlScalar(raw, key) {
|
|
111
|
+
const re = new RegExp(
|
|
112
|
+
`^\\s*${key}\\s*=\\s*(?:"([^"\\n]*)"|'([^'\\n]*)'|([^#\\n]*?))\\s*(?:#.*)?$`,
|
|
113
|
+
"m",
|
|
114
|
+
);
|
|
115
|
+
const match = raw.match(re);
|
|
116
|
+
if (!match) return null;
|
|
117
|
+
const value = (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
118
|
+
return value || null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolve a Codex effort profile name (e.g. "gpt55_high") to its concrete
|
|
123
|
+
* `model` + reasoning effort. SSOT = the per-profile file written by
|
|
124
|
+
* `tfx setup` at `~/.codex/<profile>.config.toml`. Falls back to deriving the
|
|
125
|
+
* effort from the `<...>_<effort>` naming convention when the file is absent.
|
|
126
|
+
*
|
|
127
|
+
* codex 0.137 removed `profile` from the Codex MCP tool schema, so we translate
|
|
128
|
+
* the profile into the still-accepted `model` + `config` fields instead of
|
|
129
|
+
* forwarding `profile` (which now hard-fails with "unknown field `profile`").
|
|
130
|
+
*
|
|
131
|
+
* @param {string} profileName
|
|
132
|
+
* @returns {{ model: string|null, reasoningEffort: string|null }}
|
|
133
|
+
*/
|
|
134
|
+
export function resolveCodexProfileConfig(profileName) {
|
|
135
|
+
const result = { model: null, reasoningEffort: null };
|
|
136
|
+
if (typeof profileName !== "string" || !profileName) return result;
|
|
137
|
+
|
|
138
|
+
const codexHome = process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
139
|
+
try {
|
|
140
|
+
const raw = readFileSync(
|
|
141
|
+
join(codexHome, `${profileName}.config.toml`),
|
|
142
|
+
"utf8",
|
|
143
|
+
);
|
|
144
|
+
result.model = readTomlScalar(raw, "model");
|
|
145
|
+
result.reasoningEffort = readTomlScalar(raw, "model_reasoning_effort");
|
|
146
|
+
} catch {
|
|
147
|
+
// Profile file absent (non-file alias). Derive effort from the naming
|
|
148
|
+
// convention; leave model unset so codex uses its config.toml default.
|
|
149
|
+
const suffix = /_(xhigh|high|med|medium|low)$/.exec(profileName);
|
|
150
|
+
if (suffix) {
|
|
151
|
+
const map = {
|
|
152
|
+
xhigh: "xhigh",
|
|
153
|
+
high: "high",
|
|
154
|
+
med: "medium",
|
|
155
|
+
medium: "medium",
|
|
156
|
+
low: "low",
|
|
157
|
+
};
|
|
158
|
+
result.reasoningEffort = map[suffix[1]] || null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function buildCodexArguments(prompt, opts = {}) {
|
|
98
165
|
const args = { prompt };
|
|
99
166
|
|
|
100
167
|
if (typeof opts.cwd === "string" && opts.cwd) args.cwd = opts.cwd;
|
|
101
168
|
if (typeof opts.model === "string" && opts.model) args.model = opts.model;
|
|
102
|
-
|
|
103
|
-
|
|
169
|
+
// NOTE: `profile` is intentionally NOT forwarded as a Codex tool argument.
|
|
170
|
+
// codex 0.137 dropped it from the tool schema; it is resolved into
|
|
171
|
+
// `model` + `config.model_reasoning_effort` just before return (see below).
|
|
104
172
|
if (typeof opts.approvalPolicy === "string" && opts.approvalPolicy) {
|
|
105
173
|
args["approval-policy"] = opts.approvalPolicy;
|
|
106
174
|
}
|
|
@@ -120,6 +188,22 @@ function buildCodexArguments(prompt, opts = {}) {
|
|
|
120
188
|
args["compact-prompt"] = opts.compactPrompt;
|
|
121
189
|
}
|
|
122
190
|
|
|
191
|
+
// Map the effort profile to model + reasoning effort instead of forwarding the
|
|
192
|
+
// (0.137-rejected) `profile` field. Explicit opts.model wins over the profile;
|
|
193
|
+
// profile effort merges on top of any opts.config.
|
|
194
|
+
if (typeof opts.profile === "string" && opts.profile) {
|
|
195
|
+
const resolved = resolveCodexProfileConfig(opts.profile);
|
|
196
|
+
if (resolved.model && typeof args.model !== "string") {
|
|
197
|
+
args.model = resolved.model;
|
|
198
|
+
}
|
|
199
|
+
if (resolved.reasoningEffort) {
|
|
200
|
+
args.config = {
|
|
201
|
+
...(args.config || {}),
|
|
202
|
+
model_reasoning_effort: resolved.reasoningEffort,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
123
207
|
return args;
|
|
124
208
|
}
|
|
125
209
|
|
package/package.json
CHANGED
package/scripts/tfx-route.sh
CHANGED
|
@@ -123,7 +123,11 @@ _preflight_check_gh_auth
|
|
|
123
123
|
# top-level sandbox/approval_mode와 의미가 다르다. 이 값이 "approve"이면
|
|
124
124
|
# codex exec이 non-TTY subprocess에서 승인 대기로 stall하므로 감지 대상에서 제외.
|
|
125
125
|
# (refs: tellang/triflux#66, Yeachan-Heo/oh-my-codex#1478)
|
|
126
|
-
|
|
126
|
+
# TFX_CODEX_CONFIG override: integration tests that run the full route script
|
|
127
|
+
# point this at an isolated tmpdir config so the MCP config-swap never mutates
|
|
128
|
+
# the real ~/.codex/config.toml (non-hermetic corruption guard). Defaults to the
|
|
129
|
+
# real path for normal runs.
|
|
130
|
+
_CODEX_CONFIG="${TFX_CODEX_CONFIG:-${HOME}/.codex/config.toml}"
|
|
127
131
|
_CODEX_HAS_SANDBOX=""
|
|
128
132
|
if [[ -f "$_CODEX_CONFIG" ]] && awk '
|
|
129
133
|
/^\[{1,2}mcp_servers\..*\.tools\./ { in_mcp_tool=1; next }
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tfx-goal-clarify
|
|
3
|
+
description: "자연어 아이디어를 Claude Code `/goal` 명령어 한 블록으로 변환하는 clarifier. 모호한 작업 설명을 받아 공식 3축(End state / Stated check / Constraints) + stop bound로 압축해 복붙 가능한 /goal 문자열을 출력. '/goal로 만들어', 'goal 프롬프트', 'goal 변환', 'clarify goal', 'goal 양식', '자율 작업 시작' 같은 요청에 사용."
|
|
4
|
+
triggers:
|
|
5
|
+
- goal 프롬프트
|
|
6
|
+
- goal 변환
|
|
7
|
+
- clarify goal
|
|
8
|
+
- goal 양식
|
|
9
|
+
- /goal 만들어
|
|
10
|
+
- goal-clarify
|
|
11
|
+
- tfx-goal-clarify
|
|
12
|
+
- 자율 작업 만들어
|
|
13
|
+
argument-hint: "<목표 자연어> [--tier 1|2|3]"
|
|
14
|
+
allowed-tools:
|
|
15
|
+
- Bash
|
|
16
|
+
- Read
|
|
17
|
+
- Glob
|
|
18
|
+
- Grep
|
|
19
|
+
- AskUserQuestion
|
|
20
|
+
- Write
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# tfx-goal-clarify — 자연어 → `/goal` 블록 변환기
|
|
24
|
+
|
|
25
|
+
> tfx-interview의 슬림 fork. **인터뷰 산출물을 Action Plan 대신 `/goal` 블록 한 덩어리로만** 출력한다.
|
|
26
|
+
> 공식 `/goal` 3축(End state / Stated check / Constraints) + `or stop after N turns`를 강제한다.
|
|
27
|
+
|
|
28
|
+
## 용도
|
|
29
|
+
|
|
30
|
+
Claude Code 2.1.139+의 `/goal` 명령어에 넣을 프롬프트를 자연어 아이디어로부터 구조화한다. `/goal`은 평가자(Haiku)가 transcript만 보고 yes/no 판정하므로, **측정 불가능한 조건은 무한 루프 또는 환각 yes**를 유발한다. 이 스킬은 그 함정을 회피한다.
|
|
31
|
+
|
|
32
|
+
**비교**:
|
|
33
|
+
- `tfx-interview`: 일반 요구사항 인터뷰 → 전체 Action Plan 산출
|
|
34
|
+
- `tfx-goal-clarify`: **/goal 블록 한 덩어리만** 산출 (5분 내 완료 목표)
|
|
35
|
+
- `gstack-office-hours`: 제품 아이디어 단계 (코드 이전)
|
|
36
|
+
|
|
37
|
+
## 입력 처리
|
|
38
|
+
|
|
39
|
+
`ARGUMENTS: <자연어 목표>`가 들어오면 첫 입력으로 사용. 없으면 사용자에게 요청.
|
|
40
|
+
|
|
41
|
+
`--tier N` 플래그:
|
|
42
|
+
- `1` = one-liner (3턴 이내 작업)
|
|
43
|
+
- `2` = standard 3-part (기본값, 10턴 이내)
|
|
44
|
+
- `3` = 9-section (다중 모듈/다일 작업)
|
|
45
|
+
|
|
46
|
+
## 워크플로우
|
|
47
|
+
|
|
48
|
+
### Step 1: 분류 + 초기 ambiguity 측정
|
|
49
|
+
|
|
50
|
+
코드베이스 컨텍스트 살펴보기 (선택적):
|
|
51
|
+
```bash
|
|
52
|
+
# 사용자가 어떤 repo에서 호출했는지 확인
|
|
53
|
+
git rev-parse --show-toplevel 2>/dev/null
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
자연어 입력을 3축으로 분해:
|
|
57
|
+
| 축 | 질문 | 점수 (0~1) |
|
|
58
|
+
|----|------|-----------|
|
|
59
|
+
| End state | 완료를 무엇으로 증명? | goal |
|
|
60
|
+
| Check | Claude가 어떤 명령/출력으로 보여줄 수 있나? | constraints |
|
|
61
|
+
| Constraints | 절대 건드리면 안 될 것은? | criteria |
|
|
62
|
+
|
|
63
|
+
`ambiguity = 1 - (end_state×0.4 + check×0.3 + constraints×0.3)`
|
|
64
|
+
|
|
65
|
+
Gemini 위임 (tfx-interview와 동일):
|
|
66
|
+
```bash
|
|
67
|
+
Bash("bash ~/.claude/scripts/tfx-route.sh gemini '<analysis prompt>'")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Gemini 미사용 환경(m5 등 SSH 원격)에서는 Claude가 직접 채점.
|
|
71
|
+
|
|
72
|
+
### Step 2: 적응형 인터뷰 (AskUserQuestion 사용, Tier별 질문 수 가변)
|
|
73
|
+
|
|
74
|
+
**반드시 Claude Code 네이티브 `AskUserQuestion` 도구를 사용**한다. 자유 텍스트 질의 금지 — 사용자가 옵션 중에서 선택하도록 구조화한다.
|
|
75
|
+
|
|
76
|
+
**Tier별 필수 축**:
|
|
77
|
+
| Tier | 핵심 축 (필수) | 보조 축 (조건부) | 질문 수 |
|
|
78
|
+
|------|---------------|----------------|---------|
|
|
79
|
+
| 1 | End state, Bound | — | 1~2 |
|
|
80
|
+
| 2 | End state, Check, Constraints, Bound | Scope | 3~5 |
|
|
81
|
+
| 3 | End state, Check, Constraints, Scope, Priority, Plan, Bound | Rollback, Output artifact | 5~8 |
|
|
82
|
+
|
|
83
|
+
**ambiguity < 20% 도달 시 즉시 종료** (남은 질문 스킵).
|
|
84
|
+
|
|
85
|
+
#### AskUserQuestion 호출 패턴 (예시)
|
|
86
|
+
|
|
87
|
+
각 축마다 다음 형태로 호출. **options는 사용자 입력 기반으로 동적 생성** + 사용자가 "Other"로 직접 입력 가능.
|
|
88
|
+
|
|
89
|
+
**Q1. End state (모든 Tier 필수)**
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"questions": [{
|
|
93
|
+
"question": "이 작업이 끝났다는 걸 어떤 단일 측정 가능한 결과로 증명하나요?",
|
|
94
|
+
"header": "End state",
|
|
95
|
+
"multiSelect": false,
|
|
96
|
+
"options": [
|
|
97
|
+
{"label": "테스트 exit code", "description": "예: pnpm test <path> exits 0"},
|
|
98
|
+
{"label": "패턴 카운트 0", "description": "예: rg \"legacy\" src returns 0 hits"},
|
|
99
|
+
{"label": "타입체크 통과", "description": "예: pnpm typecheck exits 0"},
|
|
100
|
+
{"label": "빌드 성공", "description": "예: pnpm build exits 0"}
|
|
101
|
+
]
|
|
102
|
+
}]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Q2. Check 명령 (Tier 2/3)**
|
|
107
|
+
사용자 End state 선택 후 그 결과를 어떻게 transcript에 노출시킬지 — 보통 자동 추론(End state가 이미 명령형이면 그대로 사용). 모호하면 명령 후보 3-4개 옵션.
|
|
108
|
+
|
|
109
|
+
**Q3. Scope (Tier 2/3, 코드베이스 컨텍스트 있을 때만)**
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"questions": [{
|
|
113
|
+
"question": "변경 허용 범위는 어디까지인가요?",
|
|
114
|
+
"header": "Scope",
|
|
115
|
+
"multiSelect": true,
|
|
116
|
+
"options": [
|
|
117
|
+
{"label": "src/<module>/만", "description": "단일 모듈 한정"},
|
|
118
|
+
{"label": "src/ 전체", "description": "소스 전체"},
|
|
119
|
+
{"label": "tests/ 포함", "description": "테스트도 수정 가능"},
|
|
120
|
+
{"label": "package.json 가능", "description": "의존성 추가 허용"}
|
|
121
|
+
]
|
|
122
|
+
}]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Q4. Constraints (Tier 2/3 필수)**
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"questions": [{
|
|
130
|
+
"question": "절대 변경하면 안 되는 것은 무엇인가요?",
|
|
131
|
+
"header": "Constraints",
|
|
132
|
+
"multiSelect": true,
|
|
133
|
+
"options": [
|
|
134
|
+
{"label": "공개 API 시그니처", "description": "props/exports 유지"},
|
|
135
|
+
{"label": "기존 테스트", "description": "tests/legacy/ 등 보호"},
|
|
136
|
+
{"label": "DB 스키마", "description": "migration 금지"},
|
|
137
|
+
{"label": "없음 (None)", "description": "제약 없이 진행"}
|
|
138
|
+
]
|
|
139
|
+
}]
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Q5. Priority (Tier 3만)**
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"questions": [{
|
|
147
|
+
"question": "충돌 시 어느 쪽을 우선하나요?",
|
|
148
|
+
"header": "Priority",
|
|
149
|
+
"multiSelect": false,
|
|
150
|
+
"options": [
|
|
151
|
+
{"label": "타입 안전 > 테스트 > 시각", "description": "타입체크 우선"},
|
|
152
|
+
{"label": "테스트 > 타입 > 시각", "description": "기존 동작 보존"},
|
|
153
|
+
{"label": "사용자 정의", "description": "직접 입력"}
|
|
154
|
+
]
|
|
155
|
+
}]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Q6. Bound (모든 Tier 필수, 마지막)**
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"questions": [{
|
|
163
|
+
"question": "최대 얼마나 돌릴까요? (Stop 조건)",
|
|
164
|
+
"header": "Stop bound",
|
|
165
|
+
"multiSelect": false,
|
|
166
|
+
"options": [
|
|
167
|
+
{"label": "10 turns", "description": "단순 수정 작업"},
|
|
168
|
+
{"label": "20 turns", "description": "중간 규모 (기본 권장)"},
|
|
169
|
+
{"label": "30 turns", "description": "마이그레이션"},
|
|
170
|
+
{"label": "60 turns", "description": "다중 모듈 (Tier 3)"}
|
|
171
|
+
]
|
|
172
|
+
}]
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Q7. Rollback (Tier 3 선택)**
|
|
177
|
+
실패 시 어떻게 복구할지 — git revert, checkpoint commit 빈도 등.
|
|
178
|
+
|
|
179
|
+
**Q8. Output artifact (Tier 3 선택)**
|
|
180
|
+
산출물 형태 — PR description, 보고서 파일, 단순 코드 변경 등.
|
|
181
|
+
|
|
182
|
+
#### 적응형 종료 조건
|
|
183
|
+
|
|
184
|
+
각 응답 후 ambiguity 재계산:
|
|
185
|
+
```
|
|
186
|
+
if ambiguity < 0.20:
|
|
187
|
+
→ 즉시 Step 3 (남은 질문 스킵)
|
|
188
|
+
elif 모든 필수 축 답변 완료:
|
|
189
|
+
→ Step 3
|
|
190
|
+
elif 질문 수 >= Tier별 상한:
|
|
191
|
+
→ Step 3 (남은 ambiguity는 사용자가 직접 편집할 영역으로 표시)
|
|
192
|
+
else:
|
|
193
|
+
→ 다음 질문
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Fallback: AskUserQuestion 미사용 환경 (Codex/Gemini CLI 등)
|
|
197
|
+
|
|
198
|
+
자유 텍스트로 한 질문씩 던지되, 위 옵션 리스트를 보기로 제시.
|
|
199
|
+
|
|
200
|
+
### Step 3: `/goal` 블록 조립
|
|
201
|
+
|
|
202
|
+
**Tier 1 (one-liner)** — 단순 명령:
|
|
203
|
+
```
|
|
204
|
+
/goal <end_state> [or stop after N turns]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Tier 2 (standard, 기본)**:
|
|
208
|
+
```
|
|
209
|
+
/goal
|
|
210
|
+
End state: {end_state}
|
|
211
|
+
Check: {check_command}
|
|
212
|
+
Constraints: {constraints}
|
|
213
|
+
Bound: or stop after {N} turns
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Tier 3 (9-section)** — 다중 모듈/긴 작업:
|
|
217
|
+
```
|
|
218
|
+
/goal
|
|
219
|
+
GOAL: {goal}
|
|
220
|
+
CONTEXT: {context}
|
|
221
|
+
CONSTRAINTS: {constraints}
|
|
222
|
+
PRIORITY: {priority}
|
|
223
|
+
PLAN: {plan_steps}
|
|
224
|
+
DONE WHEN: {done_when}
|
|
225
|
+
VERIFY: {verify_commands}
|
|
226
|
+
OUTPUT: {output_artifacts}
|
|
227
|
+
STOP RULES: or stop after {N} turns
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Step 4: 사용자 표시 + 검증
|
|
231
|
+
|
|
232
|
+
출력 형식 (반드시 이 순서):
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
📊 Ambiguity: {initial}% → {final}% (목표 < 20%)
|
|
236
|
+
- End state: {pct}%
|
|
237
|
+
- Check: {pct}%
|
|
238
|
+
- Constraints: {pct}%
|
|
239
|
+
|
|
240
|
+
📋 4000자 한도: {used}/{4000} chars
|
|
241
|
+
|
|
242
|
+
📦 /goal 블록 (Tier {N}):
|
|
243
|
+
|
|
244
|
+
────────── 복붙 영역 ──────────
|
|
245
|
+
{the assembled /goal block}
|
|
246
|
+
─────────────────────────────────
|
|
247
|
+
|
|
248
|
+
✅ 검증:
|
|
249
|
+
- 종료 상태가 측정 가능한가? {Y/N}
|
|
250
|
+
- Check 명령이 transcript에 결과를 남기는가? {Y/N}
|
|
251
|
+
- Stop bound가 있는가? {Y/N}
|
|
252
|
+
- 4000자 이내인가? {Y/N}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Step 5: 저장 (선택)
|
|
256
|
+
|
|
257
|
+
기본은 출력만. 사용자가 원하면 저장:
|
|
258
|
+
- 위치: `.tfx/goals/goal-{timestamp}.txt`
|
|
259
|
+
- 형식: 원본 자연어 + 인터뷰 응답 + 최종 /goal 블록
|
|
260
|
+
|
|
261
|
+
## 출력 후 옵션 (AskUserQuestion)
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
질문: 이 /goal 블록을 어떻게 처리할까요?
|
|
265
|
+
옵션:
|
|
266
|
+
- 클립보드 복사: 블록을 클립보드로 복사 (입력창에 붙여넣기만 하면 됨)
|
|
267
|
+
- 출력만: 화면 출력만 하고 종료
|
|
268
|
+
- 수정: 특정 축(End state/Check/Constraints) 재질문
|
|
269
|
+
- Tier 변경: 1↔2↔3 재조립
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
> `/goal`은 Claude Code 슬래시 명령어라 **에이전트가 세션 입력창에 대신 전송할 수 없다.**
|
|
273
|
+
> 그래서 "바로 실행" 옵션은 두지 않는다. 사용자가 클립보드에서 입력창에 직접 붙여넣어 실행한다.
|
|
274
|
+
|
|
275
|
+
### 클립보드 복사 실행 방법
|
|
276
|
+
|
|
277
|
+
"클립보드 복사" 선택 시 `Bash`로 OS별 클립보드 명령에 블록을 파이프한다.
|
|
278
|
+
블록에 백틱·따옴표가 섞이므로 **임시 파일을 거쳐** 복사한다 (인용 깨짐 방지).
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# 1) 조립된 /goal 블록을 임시 파일로 기록 (Write 도구 또는 heredoc)
|
|
282
|
+
cat > "${TMPDIR:-/tmp}/tfx-goal-block.txt" <<'GOAL_EOF'
|
|
283
|
+
<the assembled /goal block>
|
|
284
|
+
GOAL_EOF
|
|
285
|
+
|
|
286
|
+
# 2) OS별 클립보드로 복사 (darwin 우선, fallback chain)
|
|
287
|
+
f="${TMPDIR:-/tmp}/tfx-goal-block.txt"
|
|
288
|
+
if command -v pbcopy >/dev/null 2>&1; then pbcopy < "$f" # macOS
|
|
289
|
+
elif command -v wl-copy >/dev/null 2>&1; then wl-copy < "$f" # Wayland
|
|
290
|
+
elif command -v xclip >/dev/null 2>&1; then xclip -selection clipboard < "$f" # X11
|
|
291
|
+
elif command -v clip.exe >/dev/null 2>&1; then clip.exe < "$f" # Windows/WSL
|
|
292
|
+
else echo "클립보드 명령 없음 — 위 출력에서 수동 복사"; fi
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
복사 성공 시 `✅ 클립보드에 복사됨 — 입력창에 붙여넣어 실행하세요.` 를 출력한다.
|
|
296
|
+
|
|
297
|
+
## 동작 규칙
|
|
298
|
+
|
|
299
|
+
1. **출력 우선**: 최종 산출물은 항상 `/goal` 블록 한 덩어리. 부수 설명은 위/아래에만.
|
|
300
|
+
2. **4000자 강제**: 한도 초과 시 자동으로 PLAN/CONTEXT를 압축.
|
|
301
|
+
3. **Stop bound 강제**: 사용자가 명시 안 해도 `or stop after 20 turns`를 기본 삽입 (Tier 2/3).
|
|
302
|
+
4. **Check 강제**: 평가자가 transcript만 보므로 Check 없는 블록은 거부.
|
|
303
|
+
5. **Constraints 비어있어도 OK**: 'none' 명시.
|
|
304
|
+
6. **언어**: 사용자가 한국어로 입력하면 인터뷰는 한국어, /goal 블록은 영어 (Claude Code가 영어 명령에 더 안정적).
|
|
305
|
+
|
|
306
|
+
## 안티패턴 (자동 거부)
|
|
307
|
+
|
|
308
|
+
다음 End state는 거부:
|
|
309
|
+
- "production-ready", "better", "modern", "clean" — 주관적
|
|
310
|
+
- "all tests pass" without 명시된 테스트 위치 — 범위 폭발
|
|
311
|
+
- "refactor everything" — 범위 무제한
|
|
312
|
+
|
|
313
|
+
거부 시 사용자에게:
|
|
314
|
+
```
|
|
315
|
+
⚠️ End state "{...}"는 평가자가 측정할 수 없습니다.
|
|
316
|
+
다음 중 어느 형태로 바꿀까요?
|
|
317
|
+
1) 명령어 exit code (예: pnpm test exits 0)
|
|
318
|
+
2) 파일/문자열 카운트 (예: rg X returns 0 hits)
|
|
319
|
+
3) 파일 변경 여부 (예: git diff --stat shows ≤3 files changed)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Quick Examples
|
|
323
|
+
|
|
324
|
+
### Tier 1 입력/출력
|
|
325
|
+
```
|
|
326
|
+
입력: "auth 테스트 다 통과시켜"
|
|
327
|
+
출력: /goal pnpm test test/auth exits 0, or stop after 15 turns
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Tier 2 입력/출력
|
|
331
|
+
```
|
|
332
|
+
입력: "레거시 auth API 호출들을 v2로 마이그레이션"
|
|
333
|
+
출력:
|
|
334
|
+
/goal
|
|
335
|
+
End state: src/legacy/auth/*.ts 의 모든 호출이 src/auth/v2 로 마이그레이션됨
|
|
336
|
+
Check: `pnpm test src/auth` exits 0 AND `rg "legacy/auth" src` returns 0 hits
|
|
337
|
+
Constraints: tests/legacy/는 수정 금지, package.json 의존성 추가 금지
|
|
338
|
+
Bound: or stop after 30 turns
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Tier 3 입력/출력
|
|
342
|
+
```
|
|
343
|
+
입력: "users 모듈 전체를 새 design system으로 갈아엎고 PR 만들어"
|
|
344
|
+
출력:
|
|
345
|
+
/goal
|
|
346
|
+
GOAL: src/users/** 의 모든 React 컴포넌트를 @company/ds-v3로 마이그레이션
|
|
347
|
+
CONTEXT: 기존은 @company/ds-v2, 디자인 토큰은 docs/design/v3.md, 18개 컴포넌트
|
|
348
|
+
CONSTRAINTS:
|
|
349
|
+
- tests/users/ snapshot 갱신 외 변경 금지
|
|
350
|
+
- props 시그니처 유지 (caller 영향 없음)
|
|
351
|
+
- i18n key 변경 금지
|
|
352
|
+
PRIORITY: 시각적 회귀 < 타입 안전성 < 테스트 통과
|
|
353
|
+
PLAN:
|
|
354
|
+
1. 컴포넌트 인벤토리 추출
|
|
355
|
+
2. v2→v3 mapping 표 생성
|
|
356
|
+
3. 각 컴포넌트 변환 + snapshot 갱신
|
|
357
|
+
4. PR description 작성
|
|
358
|
+
DONE WHEN: 18/18 컴포넌트 마이그레이션 + 모든 snapshot 통과 + PR 본문 생성
|
|
359
|
+
VERIFY: `pnpm typecheck` exits 0 AND `pnpm test src/users` exits 0 AND `rg "ds-v2" src/users` returns 0 hits
|
|
360
|
+
OUTPUT: PR description in .claude/scratch/pr-users-ds-v3.md
|
|
361
|
+
STOP RULES: or stop after 60 turns, or if token spend exceeds 500K
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## 토큰 예산
|
|
365
|
+
|
|
366
|
+
| 단계 | Claude | Gemini |
|
|
367
|
+
|------|--------|--------|
|
|
368
|
+
| 초기 분류 | ~0.5K | ~1K |
|
|
369
|
+
| 3축 인터뷰 (Q1~Q3) | ~1K | ~3K |
|
|
370
|
+
| /goal 블록 조립 | ~0.5K | ~1K |
|
|
371
|
+
| **총합** | **~2K** | **~5K** |
|
|
372
|
+
|
|
373
|
+
Gemini 없으면 Claude 단독 ~5K.
|
|
374
|
+
|
|
375
|
+
## 사용 예
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
/tfx-goal-clarify "auth 마이그레이션"
|
|
379
|
+
/tfx-goal-clarify "테스트 다 고쳐" --tier 1
|
|
380
|
+
/tfx-goal-clarify "users 모듈 design system 갈아엎기" --tier 3
|
|
381
|
+
/goal 변환 "스냅샷 갱신해서 CI 그린"
|
|
382
|
+
```
|