triflux 10.45.0 → 10.47.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 (37) hide show
  1. package/bin/tfx-live.mjs +576 -53
  2. package/config/routing-policy.json +1 -1
  3. package/hooks/session-start-fast.mjs +6 -0
  4. package/hub/dynamic-routing-engine.mjs +1 -1
  5. package/hub/lib/cto-env.mjs +2 -3
  6. package/hub/server.mjs +2 -51
  7. package/hub/team/recovery-store.mjs +1 -1
  8. package/hub/team/retry-state-machine.mjs +9 -1
  9. package/hub/team/synapse-registry.mjs +14 -11
  10. package/hub/team/uds-orchestrator.mjs +384 -83
  11. package/hub/team/worktree-lifecycle.mjs +8 -48
  12. package/hub/tray-lifecycle.mjs +1 -1
  13. package/hub/workers/codex-mcp.mjs +1 -1
  14. package/hud/providers/codex.mjs +91 -8
  15. package/hud/providers/cto.mjs +3 -0
  16. package/package.json +1 -1
  17. package/scripts/__tests__/release-governance.test.mjs +235 -11
  18. package/scripts/__tests__/release-publish-ancestor.test.mjs +67 -0
  19. package/scripts/__tests__/release-verify-wait.test.mjs +169 -0
  20. package/scripts/__tests__/resolve-auto-release.test.mjs +216 -0
  21. package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +1 -1
  22. package/scripts/__tests__/tfx-route-node-entry.test.mjs +4 -4
  23. package/scripts/lib/agent-route-policy.mjs +18 -17
  24. package/scripts/lib/codex-profile-config.mjs +19 -5
  25. package/scripts/release/publish.mjs +16 -1
  26. package/scripts/release/resolve-auto-release.mjs +136 -0
  27. package/scripts/release/verify.mjs +74 -25
  28. package/scripts/setup.mjs +20 -7
  29. package/scripts/tfx-route.sh +27 -24
  30. package/skills/tfx-auto/SKILL.md +5 -4
  31. package/skills/tfx-live/SKILL.md +112 -7
  32. package/skills/tfx-profile/SKILL.md +9 -7
  33. package/skills/tfx-setup/SKILL.md +1 -1
  34. package/skills/tfx-ship/SKILL.md +43 -36
  35. package/tui/codex-profile.mjs +2 -3
  36. package/tui/setup.mjs +4 -3
  37. package/hub/team/git-preflight.mjs +0 -286
@@ -45,7 +45,7 @@ options:
45
45
  ```
46
46
  | 프로파일 | 모델 | Effort |
47
47
  |----------|------|--------|
48
- | gpt56_terra_high | 프로필 설정의 현재 모델 | high |
48
+ | gpt6_sol_high | 프로필 설정의 현재 모델 | high |
49
49
  | ... | ... | ... |
50
50
  ```
51
51
 
@@ -75,9 +75,10 @@ options:
75
75
  2. AskUserQuestion으로 프로필/alias 선택:
76
76
  ```
77
77
  options:
78
- - label: "gpt56_terra_high" → 기본 고품질 (Recommended)
79
- - label: "gpt56_terra_med" → 균형형
80
- - label: "gpt56_luna_low" → 빠른 경량
78
+ - label: "gpt6_sol_high" → 구현과 리뷰 기본값 (Recommended)
79
+ - label: "gpt6_sol_med" → 정리와 리팩터 균형형
80
+ - label: "gpt6_luna_high" → 명확한 반복 작업
81
+ - label: "gpt6_luna_low" → 지연 우선 경량
81
82
  - label: "gpt6_astra_xhigh" → 고강도 추론
82
83
  - label: "gpt6_astra_max" → 최난도 단일 작업
83
84
  - label: "gpt6_astra_ultra" → 최상위 자동 위임 (비중첩 전용)
@@ -187,12 +188,13 @@ options:
187
188
 
188
189
  | 프로필 | 용도 |
189
190
  |------|------|
190
- | gpt56_terra_high | 기본 고품질 |
191
+ | gpt6_sol_high | 구현과 리뷰 기본값 |
191
192
  | gpt6_astra_xhigh | 고강도 추론 |
192
193
  | gpt6_astra_max | 최난도 단일 작업 |
193
194
  | gpt6_astra_ultra | 최상위 자동 위임 (비중첩 전용) |
194
- | gpt56_terra_med | 균형형 |
195
- | gpt56_luna_low | 빠른 경량 |
195
+ | gpt6_sol_med | 정리와 리팩터 균형형 |
196
+ | gpt6_luna_high | 명확한 반복 작업 |
197
+ | gpt6_luna_low | 지연 우선 경량 |
196
198
  | custom | 사용자가 관리하는 프로필 |
197
199
 
198
200
  ### Codex Effort 레벨
@@ -112,7 +112,7 @@ Bash("triflux setup")
112
112
  #### 단계 3: Codex 프로파일
113
113
 
114
114
  `~/.codex/config.toml`을 Read 도구로 읽어 필수 프로파일 존재 여부 확인.
115
- 필수: `gpt6_astra_ultra`, `gpt6_astra_max`, `gpt6_astra_xhigh`, `gpt56_terra_high`, `gpt56_terra_med`, `gpt56_luna_low`.
115
+ 필수: `gpt6_astra_ultra`, `gpt6_astra_max`, `gpt6_astra_xhigh`, `gpt6_sol_high`, `gpt6_sol_med`, `gpt6_luna_high`, `gpt6_luna_low`.
116
116
 
117
117
  - 모두 존재 → ✅ 표시
118
118
  - 누락 있으면 → AskUserQuestion:
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: tfx-ship
3
3
  description: >
4
- triflux 전용 릴리즈 자동화. **GitHub Actions(release.yml dispatch / npm-publish.yml) 기반 CI 릴리즈가 기본 경로 —
4
+ triflux 전용 릴리즈 자동화. **GitHub Actions(버전 증가 PR 머지 / release.yml dispatch / npm-publish.yml) 기반 CI 릴리즈가 기본 경로 -
5
5
  npm publish 는 OIDC Trusted Publishing 으로 CI 가 수행하므로 로컬 npm login 불필요.** scripts/release/* 래퍼 +
6
6
  AskUserQuestion 기반 버전 선택 + CHANGELOG 편집 게이트 + Co-Authored-By/AI trailer 금지 강제. 'ship', '배포', '릴리즈',
7
7
  'release', 'tfx-ship', 'publish' 같은 요청에 반드시 사용.
8
8
  argument-hint: "[patch|minor|major|<version>] [--skip-tests] [--no-publish] [--dry-run]"
9
9
  ---
10
10
 
11
- # tfx-ship — triflux 릴리즈 자동화
11
+ # tfx-ship - triflux 릴리즈 자동화
12
12
 
13
13
  > **ARGUMENTS 처리**: ARGUMENTS 가 있으면 첫 토큰을 version bump 타입 또는 명시 버전으로 해석.
14
14
  > 플래그는 `--skip-tests`, `--no-publish`, `--dry-run` 지원.
@@ -28,11 +28,11 @@ triflux 는 아래 3채널로 동시 배포. 각 채널의 버전은 반드시
28
28
  | # | 채널 | 명령 | 우선순위 |
29
29
  |---|------|------|---------|
30
30
  | 1 | **GitHub Releases** | `gh release create vX.Y.Z --notes-file <notes>` | 공지 + changelog 공식 소스 |
31
- | 2 | **npm registry** | **CI `npm-publish.yml`** (`v*` 태그 push 자동 / OIDC Trusted Publishing, `NPM_TOKEN` secret) | primary distribution |
31
+ | 2 | **npm registry** | **CI `npm-publish.yml`** (`v*` 태그 push 자동 / OIDC Trusted Publishing) | primary distribution |
32
32
  | 3 | **Claude Code marketplace** | `.claude-plugin/marketplace.json` (`source: npm` 참조) | `claude plugin add triflux` |
33
33
  | 4 | **pypi** (future, 비활성) | 현재 `pyproject.toml` 없음 | 활성화 시 Step 10.5 신설 |
34
34
 
35
- - **npm publish 는 로컬에서 하지 않는다** — `release.yml` dispatch 또는 `v*` 태그 push 시 `npm-publish.yml` 이 OIDC 로 발행한다. 로컬 `publish.mjs --execute` 는 CI 불가 시 폴백 전용.
35
+ - **npm publish 는 로컬에서 하지 않는다** - 버전을 올린 PR 머지 후 ci 성공, `release.yml` dispatch 또는 `v*` 태그 push 시 `npm-publish.yml` 이 OIDC 로 발행한다. 로컬 `publish.mjs --execute` 는 CI 불가 시 폴백 전용.
36
36
  - marketplace 는 자체 publish 명령이 없음. marketplace.json 의 version 만 갱신하면 git push 로 반영됨 (GitHub 호스팅).
37
37
  - `release:check-sync` 가 package.json + marketplace.json + package-lock.json 3곳 version 일치를 강제한다.
38
38
  - pypi 는 triflux 가 Python 모듈을 가지게 되면 활성화. 현 단계는 플레이스홀더.
@@ -42,19 +42,26 @@ triflux 는 아래 3채널로 동시 배포. 각 채널의 버전은 반드시
42
42
  ## 전제 조건
43
43
 
44
44
  - `~/.claude/scripts/tfx-route.sh` 불필요 (CLI 워커 호출 없음)
45
- - `gh` CLI 인증됨 (`gh auth status`) — `release.yml` dispatch / GitHub release 에 필요. 깨졌으면 웹 UI 대체
46
- - **npm 인증 불필요** — npm publish 는 CI(`npm-publish.yml`)가 OIDC/`NPM_TOKEN` 으로 수행 (로컬 `npm login` 필요 없음)
47
- - 릴리즈할 코드가 `origin/main` 에 있을 것 (CI 는 `--ref main` 으로 origin 을 checkout)
45
+ - `gh` CLI 인증됨 (`gh auth status`) - `release.yml` dispatch / GitHub release 에 필요. 깨졌으면 웹 UI 대체
46
+ - **npm 인증 불필요** - npm publish 는 CI(`npm-publish.yml`)가 OIDC 로 수행 (로컬 `npm login` 필요 없음)
47
+ - 릴리즈할 커밋이 `origin/main` 에 포함되어 있을 것. 자동 경로는 ci 의 head_sha, 수동 경로는 dispatch 시점의 github.sha 를 checkout 한다.
48
48
 
49
- ## 기본 경로 — CI 릴리즈 (권장)
49
+ ## 기본 경로 - CI 릴리즈 (권장)
50
+
51
+ PR 에서 `npm run release:bump -- --version X --write` 로 버전을 올려 main 에 머지하면,
52
+ 같은 커밋의 ci 통과 뒤 `release.yml` 이 자동으로 실행된다. 버전을 안 올린 머지는 릴리즈하지 않는다.
53
+ 자동 경로는 해당 커밋과 첫 번째 부모의 package.json 버전을 비교한다. 버전이 같거나 이미 태그가 있으면 건너뛰고, prerelease 는 수동 dispatch 로 진행한다.
54
+ main 이 앞서 있어도 해당 커밋이 origin/main 의 조상이면 릴리즈한다. 강제 푸시로 main 에서 사라진 커밋은 건너뛰고, 최신 안정 태그 이하로 버전이 역행하면 실패한다.
55
+ `resolve` 잡은 동시성 제한 없이 판정하고, 실제 `release` 잡만 `release` concurrency 그룹에서 직렬 실행한다. 자동 경로만 `publish.mjs --allow-ancestor` 를 사용하며 수동 경로는 기존 HEAD 동등성 검사를 유지한다.
56
+ 버전을 올린 커밋 두 개가 거의 동시에 들어오면 앞의 대기 릴리즈가 취소될 수 있다. 해당 커밋의 ci 를 Re-run 하거나 `release.yml` 을 수동 dispatch 해 복구한다.
50
57
 
51
58
  > **npm publish 는 로컬에서 하지 않는다.** GitHub Actions 가 OIDC(Trusted Publishing)로 발행한다.
52
59
  > 워크플로우: `.github/workflows/{release,npm-publish,ci}.yml`
53
60
 
54
- - **`release.yml`** (`workflow_dispatch`, inputs: `version`, `channel`): 한 번의 dispatch 로 prepare(테스트/버전 bump) → 태그 + GitHub release(`publish.mjs --skip-npm`) → `npm-publish.yml` dispatch → npm publish 완료 대기 → `verify.mjs` 까지 전부 CI(ubuntu, node 24)에서 수행.
55
- - **`npm-publish.yml`** (`on: push tags ['v*']` + dispatch): `v*` 태그가 push 되면 자동 npm publish (`--provenance --access public`, `NPM_TOKEN` secret). 루트/core/triflux 3패키지 각각, 이미 게시된 버전은 skip.
61
+ - **`release.yml`** (`workflow_run` + `workflow_dispatch`, inputs: `version`, `channel`): prepare → 태그 + GitHub release(`publish.mjs --skip-npm`) → 태그 ref 와 channel 에 따른 npm_tag(stable=latest, canary=canary)로 `npm-publish.yml` dispatch → 반환 URL의 run ID로 npm publish 완료 대기 → `verify.mjs` 까지 CI(ubuntu, node 24)에서 수행한다. 자동 경로는 같은 커밋의 ci 가 전체 테스트를 통과했으므로 prepare 에서 테스트만 생략한다. 수동 dispatch 는 기존대로 테스트한다. npm 레지스트리 반영은 20초 간격으로 최대 900초 기다리고, 기대 버전이 아직 보이지 않으면 pending 경고를 남기고 통과한다. 2026-09-23 게시에서 반영까지 `@triflux/core` 3분, `triflux` 1시간 25분, `@triflux/remote` 11시간이 걸렸다. 구버전 gh 가 실행 URL을 반환하지 않으면 dispatch 직전 저장한 기존 실행 ID를 제외해 새 실행을 고른다.
62
+ - **`npm-publish.yml`** (`on: push tags ['v*']` + dispatch): `v*` 태그가 push 되면 자동 npm publish (`--provenance --access public --tag "$NPM_TAG"`, OIDC 전용). dispatch 의 선택 입력 `npm_tag` 가 있으면 사용하고, 없으면 버전에 `-` 가 있을 때 canary, 그 밖에는 latest 로 게시한다. latest 와 canary 외의 값은 거부한다. `@triflux/core`, `@triflux/remote`, `triflux` 3패키지 각각, 이미 게시된 버전은 skip. 세 패키지 모두 Trusted Publisher 등록이 필요하며 `npm trust list <패키지>` 로 확인한다.
56
63
 
57
- ### 권장 실행 — release.yml 디스패치
64
+ ### 수동 실행 - release.yml 디스패치
58
65
 
59
66
  ```bash
60
67
  gh workflow run release.yml --ref main -f version=<X.Y.Z> -f channel=stable
@@ -66,7 +73,7 @@ gh run watch "$(gh run list --workflow release.yml -L1 --json databaseId -q '.[0
66
73
  - gh 인증이 없는 환경(예: SSH→m2, hosts.yml PAT 만료)이면 **gh 가 정상인 머신(m5)에서** dispatch 하거나 **GitHub 웹 UI**(Actions → release → Run workflow)로 대체.
67
74
  - CHANGELOG 가 필요하면 dispatch 전에 main 에 별도 커밋으로 반영(아래 로컬 플로우 Step 4 양식 참조).
68
75
 
69
- ### 경량 경로 — 태그만 push
76
+ ### 경량 경로 - 태그만 push
70
77
 
71
78
  이미 검증된 hotfix 등 prepare/verify 없이 npm publish 만 트리거하려면:
72
79
 
@@ -79,12 +86,12 @@ git tag v<X.Y.Z> && git push origin v<X.Y.Z> # → npm-publish.yml 자동 발
79
86
 
80
87
  ---
81
88
 
82
- ## 로컬 수동 플로우 (폴백 — CI 불가 시에만)
89
+ ## 로컬 수동 플로우 (폴백 - CI 불가 시에만)
83
90
 
84
91
  > CI 가 정석이다. 아래는 Actions 가 막혔거나 로컬 디버깅 시의 수동 절차다.
85
92
  > **Step 10 의 로컬 `npm publish` 는 CI 와 중복이므로 평상시 실행 금지.**
86
93
 
87
- ### Step 0 — 환경 확인
94
+ ### Step 0 - 환경 확인
88
95
 
89
96
  ```bash
90
97
  cd "$REPO_ROOT"
@@ -100,7 +107,7 @@ node --version # Node 18+ 확인
100
107
  - uncommitted 있음 → **AskUserQuestion**: "먼저 커밋할까? / ship 에 포함 / 취소"
101
108
  - behind origin → `git pull origin main` 먼저 실행
102
109
 
103
- ### Step 1 — 버전 선택
110
+ ### Step 1 - 버전 선택
104
111
 
105
112
  ARGUMENTS 에 version 있으면 그 값 사용. 없으면 AskUserQuestion:
106
113
 
@@ -108,15 +115,15 @@ ARGUMENTS 에 version 있으면 그 값 사용. 없으면 AskUserQuestion:
108
115
  현재: v{CURRENT}
109
116
  어떤 버전으로 올릴까요?
110
117
 
111
- A) patch (v{CURRENT +0.0.1}) — bug fix / small refactor
112
- B) minor (v{CURRENT +0.1.0}) — 새 기능, backward compatible
113
- C) major (v{CURRENT +1.0.0}) — breaking change
114
- D) custom — 직접 입력
118
+ A) patch (v{CURRENT +0.0.1}) - bug fix / small refactor
119
+ B) minor (v{CURRENT +0.1.0}) - 새 기능, backward compatible
120
+ C) major (v{CURRENT +1.0.0}) - breaking change
121
+ D) custom - 직접 입력
115
122
  ```
116
123
 
117
124
  선택된 버전을 `TARGET_VERSION` 으로 저장.
118
125
 
119
- ### Step 2 — 버전 동기화 사전 체크
126
+ ### Step 2 - 버전 동기화 사전 체크
120
127
 
121
128
  ```bash
122
129
  npm run release:check-sync
@@ -126,7 +133,7 @@ npm run release:check-sync
126
133
  - 자동 수정 제안: `npm run release:check-sync:fix`
127
134
  - 사용자 승인 후 실행
128
135
 
129
- ### Step 3 — 버전 bump
136
+ ### Step 3 - 버전 bump
130
137
 
131
138
  ```bash
132
139
  npm run release:bump -- --version "$TARGET_VERSION" --write
@@ -136,7 +143,7 @@ npm run release:bump -- --version "$TARGET_VERSION" --write
136
143
 
137
144
  > **MANDATORY `--write`**: 플래그 없으면 dry-run 만 동작 (`scripts/release/bump-version.mjs:28` `if (write)` 분기). `--write` 누락 시 후속 Step 이 모두 이전 버전으로 진행되는 silent failure 가 된다.
138
145
 
139
- ### Step 4 — CHANGELOG 초안 생성
146
+ ### Step 4 - CHANGELOG 초안 생성
140
147
 
141
148
  `git log --oneline <previous-tag>..HEAD` 로 범위 커밋 나열 후, Claude 가 아래 섹션 구조로 초안 작성:
142
149
 
@@ -166,7 +173,7 @@ B) 편집하고 계속 (사용자가 에디터에서 수정 후 진행)
166
173
  C) 취소
167
174
  ```
168
175
 
169
- ### Step 5 — 테스트 + 빌드 검증
176
+ ### Step 5 - 테스트 + 빌드 검증
170
177
 
171
178
  ```bash
172
179
  npm run release:prepare -- --execute --version "$TARGET_VERSION" --allow-dirty
@@ -186,7 +193,7 @@ npm run release:prepare -- --execute --version "$TARGET_VERSION" --allow-dirty
186
193
 
187
194
  실패 시 → **STOP**. 사용자에게 에러 보여주고 재시도 옵션 제공.
188
195
 
189
- ### Step 6 — pack.mjs 미러
196
+ ### Step 6 - pack.mjs 미러
190
197
 
191
198
  ```bash
192
199
  node scripts/pack.mjs all
@@ -197,7 +204,7 @@ node scripts/pack.mjs all
197
204
  - `git status` 로 실제 변경 파일만 선별 스테이징
198
205
  - 예: `git add packages/triflux/` (구체 경로 지정)
199
206
 
200
- ### Step 7 — Commit + Tag
207
+ ### Step 7 - Commit + Tag
201
208
 
202
209
  커밋 메시지 (절대 Co-Authored-By 등 포함 금지):
203
210
 
@@ -211,7 +218,7 @@ git tag "v${TARGET_VERSION}"
211
218
 
212
219
  `.gitmessage` 템플릿이 AI trailer 를 자동 주입하는 경우 제거. `--no-edit` 으로 추가 편집 차단.
213
220
 
214
- ### Step 8 — Push
221
+ ### Step 8 - Push
215
222
 
216
223
  **AskUserQuestion** (릴리즈 전 마지막 확인):
217
224
 
@@ -220,7 +227,7 @@ git tag "v${TARGET_VERSION}"
220
227
 
221
228
  A) git push origin main --tags (권장)
222
229
  B) tag 없이 push (수동 tag 후처리)
223
- C) 중단 — 로컬에만 유지
230
+ C) 중단 - 로컬에만 유지
224
231
  ```
225
232
 
226
233
  선택 A 시:
@@ -229,7 +236,7 @@ git push origin main
229
236
  git push origin "v${TARGET_VERSION}"
230
237
  ```
231
238
 
232
- ### Step 9 — GitHub Release
239
+ ### Step 9 - GitHub Release
233
240
 
234
241
  ```bash
235
242
  NOTES_PATH=".omx/plans/release-notes-v${TARGET_VERSION}.md"
@@ -242,7 +249,7 @@ gh release create "v${TARGET_VERSION}" \
242
249
  - 노트 본문 검증: Co-Authored-By / AI trailer 포함됐는지 grep 후 제거
243
250
  - `--draft` 로 초안 생성 후 수동 publish 도 가능 (안전 모드)
244
251
 
245
- ### Step 10 — npm publish (= CI 가 수행, 로컬 금지가 기본)
252
+ ### Step 10 - npm publish (= CI 가 수행, 로컬 금지가 기본)
246
253
 
247
254
  > **평상시 로컬에서 `npm publish` 하지 않는다.** Step 8 에서 `git push origin v<X.Y.Z>` 로 태그가 올라가면 `npm-publish.yml` 이 OIDC 로 자동 발행한다.
248
255
  > CI run 관찰: `gh run list --workflow npm-publish.yml -L1` → `gh run watch <id>`
@@ -250,10 +257,10 @@ gh release create "v${TARGET_VERSION}" \
250
257
  CI 가 완전히 불가능한 비상시에만, npm 인증을 갖춘 환경에서 수동 폴백:
251
258
 
252
259
  ```bash
253
- node scripts/release/publish.mjs --execute # 비상 폴백 전용 — 중복 publish 주의
260
+ node scripts/release/publish.mjs --execute # 비상 폴백 전용 - 중복 publish 주의
254
261
  ```
255
262
 
256
- ### Step 10.5 — pypi publish (future, 현재 비활성)
263
+ ### Step 10.5 - pypi publish (future, 현재 비활성)
257
264
 
258
265
  triflux 가 Python 모듈을 가지게 되면 이 단계를 활성화:
259
266
 
@@ -267,7 +274,7 @@ fi
267
274
 
268
275
  현 단계는 `pyproject.toml` 없음 → skip. 플레이스홀더만 유지.
269
276
 
270
- ### Step 11 — 사후 검증 (3채널 전부)
277
+ ### Step 11 - 사후 검증 (3채널 전부)
271
278
 
272
279
  ```bash
273
280
  npm run release:verify
@@ -280,7 +287,7 @@ npm run release:verify
280
287
  - 릴리즈 노트 공개됨 (`gh release view v${TARGET_VERSION}`)
281
288
  - (future) pypi 는 pyproject.toml 있을 때만 체크
282
289
 
283
- ### Step 12 — 사용자 알림
290
+ ### Step 12 - 사용자 알림
284
291
 
285
292
  ```
286
293
  RELEASE COMPLETE ✓
@@ -300,7 +307,7 @@ github: https://github.com/tellang/triflux/releases/tag/v${TARGET_VERSION}
300
307
  | 단계 | 에러 | 복구 |
301
308
  |------|------|------|
302
309
  | Step 0 | 브랜치가 main 아님 | `git checkout main` 또는 PR 플로우 |
303
- | Step 0 | unpushed 커밋 있음 (ship 전 확인) | 정상 — 릴리즈 대상 |
310
+ | Step 0 | unpushed 커밋 있음 (ship 전 확인) | 정상 - 릴리즈 대상 |
304
311
  | Step 0 | uncommitted 변경 | 먼저 커밋 / ship 에 흡수 / 취소 선택 |
305
312
  | Step 2 | 버전 동기화 실패 | `release:check-sync --fix` 실행 |
306
313
  | Step 5 | 테스트 실패 | 수정 후 재시도. `--skip-tests` 는 위험 |
@@ -308,7 +315,7 @@ github: https://github.com/tellang/triflux/releases/tag/v${TARGET_VERSION}
308
315
  | Step 7 | commit 메시지에 AI trailer 감지 | 하드 차단 + 재작성 요청 |
309
316
  | Step 8 | push 거부 (remote 변경됨) | `git pull --rebase origin main` 후 재시도 |
310
317
  | Step 9 | gh release create 실패 | `gh auth status` 확인, 수동 재시도 |
311
- | Step 10 | npm publish(CI) 실패 | `gh run view <id> --log-failed` 로 `npm-publish.yml` 로그 확인. `NPM_TOKEN` secret / OIDC 설정 점검 |
318
+ | Step 10 | npm publish(CI) 실패 | `gh run view <id> --log-failed` 로 `npm-publish.yml` 로그 확인. 세 패키지의 OIDC Trusted Publisher 설정 점검 |
312
319
 
313
320
  ## 플래그
314
321
 
@@ -345,5 +352,5 @@ github: https://github.com/tellang/triflux/releases/tag/v${TARGET_VERSION}
345
352
  - 버전 불일치: `npm run release:check-sync --fix`
346
353
  - pack CRLF 경고: 실제 변경 파일만 선별 `git add packages/triflux/...`
347
354
  - gh CLI 미인증: `gh auth login` (또는 gh 정상인 머신/웹 UI 로 release.yml dispatch)
348
- - npm publish 는 CI(`npm-publish.yml`, OIDC)가 수행 — 로컬 `npm login` 불필요. CI 실패 시 `gh run view <id> --log-failed`
355
+ - npm publish 는 CI(`npm-publish.yml`, OIDC)가 수행 - 로컬 `npm login` 불필요. CI 실패 시 `gh run view <id> --log-failed`
349
356
  - prepare.mjs stall: `scripts/release/prepare.mjs` 가 `stdio: ["ignore","pipe","pipe"]` + 10분 timeout 적용됨 (v10.9.32 fix 739da2d)
@@ -41,9 +41,8 @@ const CONFIG_PATH = join(CODEX_DIR, "config.toml");
41
41
 
42
42
  const KNOWN_MODELS = [
43
43
  { label: "gpt-6-astra", hint: "프런티어" },
44
- { label: "gpt-5.6-sol", hint: "범용" },
45
- { label: "gpt-5.6-terra", hint: "표준" },
46
- { label: "gpt-5.6-luna", hint: "경량" },
44
+ { label: "gpt-6-sol", hint: "범용" },
45
+ { label: "gpt-6-luna", hint: "경량" },
47
46
  { label: "직접 입력", hint: "" },
48
47
  ];
49
48
 
package/tui/setup.mjs CHANGED
@@ -39,9 +39,10 @@ const REQUIRED_CODEX_PROFILE_NAMES = [
39
39
  "gpt6_astra_ultra",
40
40
  "gpt6_astra_max",
41
41
  "gpt6_astra_xhigh",
42
- "gpt56_terra_high",
43
- "gpt56_terra_med",
44
- "gpt56_luna_low",
42
+ "gpt6_sol_high",
43
+ "gpt6_sol_med",
44
+ "gpt6_luna_high",
45
+ "gpt6_luna_low",
45
46
  ];
46
47
 
47
48
  const STABLE_NODE_COMMAND_CANDIDATES = Object.freeze([
@@ -1,286 +0,0 @@
1
- // hub/team/git-preflight.mjs — Pre-flight safety check for dangerous git ops.
2
- // Blocks checkout/rebase/cherry-pick/reset/stash-pop/worktree-remove when they
3
- // would conflict with other active Synapse sessions' dirty files or claimed
4
- // leases. Fail-open by default: if registry/locks are unavailable, allow the op
5
- // with a warning (worktree isolation is the baseline safety net).
6
-
7
- /**
8
- * Factory for git pre-flight check.
9
- *
10
- * @param {object} opts
11
- * @param {{ getActive: () => Array<object> }} opts.registry — synapse-registry duck type
12
- * @param {{ snapshot: () => Array<object> }} opts.locks — swarm-locks duck type
13
- * @param {boolean} [opts.failOpen=true] — allow op when registry/locks throw or are missing
14
- * @param {(level: string, msg: string, data?: object) => void} [opts.logger] — optional logger
15
- * @returns {{
16
- * check: Function,
17
- * checkRebase: Function,
18
- * checkCheckout: Function,
19
- * checkCherryPick: Function,
20
- * checkReset: Function,
21
- * checkStashPop: Function,
22
- * checkWorktreeRemove: Function,
23
- * }}
24
- */
25
- export function createGitPreflight(opts = {}) {
26
- const { registry, locks, failOpen = true, logger = null } = opts;
27
-
28
- function log(level, msg, data) {
29
- if (logger) {
30
- try {
31
- logger(level, msg, data);
32
- } catch {
33
- /* logger failure is non-fatal */
34
- }
35
- return;
36
- }
37
- if (level === "warn") {
38
- console.warn(`[git-preflight] ${msg}`, data ?? "");
39
- } else if (level === "error") {
40
- console.error(`[git-preflight] ${msg}`, data ?? "");
41
- }
42
- }
43
-
44
- // ── helpers ──────────────────────────────────────────────
45
-
46
- function safeGetActive() {
47
- try {
48
- const value = registry?.getActive?.();
49
- if (!Array.isArray(value)) return null;
50
- return value;
51
- } catch (err) {
52
- log("warn", "registry.getActive threw", { error: err?.message });
53
- return null;
54
- }
55
- }
56
-
57
- function safeSnapshot() {
58
- try {
59
- const value = locks?.snapshot?.();
60
- if (!Array.isArray(value)) return null;
61
- return value;
62
- } catch (err) {
63
- log("warn", "locks.snapshot threw", { error: err?.message });
64
- return null;
65
- }
66
- }
67
-
68
- function failOpenDecision(reason) {
69
- log("warn", "hub unavailable, failing open", { reason });
70
- return { allowed: true, reason: "hub_unavailable_fail_open" };
71
- }
72
-
73
- function normalizeFiles(list) {
74
- if (!Array.isArray(list)) return new Set();
75
- return new Set(
76
- list
77
- .filter((f) => typeof f === "string" && f.length > 0)
78
- .map((f) => f.replace(/\\/g, "/")),
79
- );
80
- }
81
-
82
- function otherActiveSessions(active, sessionId) {
83
- return active.filter((s) => s?.sessionId && s.sessionId !== sessionId);
84
- }
85
-
86
- function otherLeases(snapshot, workerId) {
87
- return snapshot.filter(
88
- (entry) => entry?.workerId && entry.workerId !== workerId,
89
- );
90
- }
91
-
92
- function buildConflict(file, session, holder) {
93
- return {
94
- file,
95
- activeSession: session?.sessionId ?? holder ?? "unknown",
96
- activeTask: session?.taskSummary ?? "",
97
- ...(holder ? { leaseHolder: holder } : {}),
98
- };
99
- }
100
-
101
- function recommend(conflicts) {
102
- if (!conflicts.length) return "";
103
- const first = conflicts[0];
104
- const owner = first.leaseHolder || first.activeSession;
105
- const task = first.activeTask ? ` Active task: '${first.activeTask}'` : "";
106
- return `Wait for '${owner}' to finish, or coordinate via HITL.${task}`;
107
- }
108
-
109
- function blockedDecision(op, conflicts, extraReason) {
110
- return {
111
- allowed: false,
112
- reason: extraReason || "overlap_with_active_session",
113
- conflicts,
114
- recommendation: recommend(conflicts),
115
- op,
116
- };
117
- }
118
-
119
- function allowedDecision() {
120
- return { allowed: true };
121
- }
122
-
123
- // ── shared scanners ─────────────────────────────────────
124
-
125
- function findDirtyFileConflicts(sessionId, workerId) {
126
- const active = safeGetActive();
127
- const snapshot = safeSnapshot();
128
-
129
- if (active == null || snapshot == null) {
130
- return failOpen ? { fallOpen: true } : { fallOpen: false, conflicts: [] };
131
- }
132
-
133
- const conflicts = [];
134
- const seen = new Set();
135
-
136
- for (const session of otherActiveSessions(active, sessionId)) {
137
- const dirty = normalizeFiles(session.dirtyFiles);
138
- for (const file of dirty) {
139
- if (seen.has(file)) continue;
140
- seen.add(file);
141
- conflicts.push(buildConflict(file, session));
142
- }
143
- }
144
-
145
- for (const lease of otherLeases(snapshot, workerId)) {
146
- if (lease.leaseType && lease.leaseType !== "exclusive") continue;
147
- const file = typeof lease.file === "string" ? lease.file : null;
148
- if (!file || seen.has(file)) continue;
149
- seen.add(file);
150
- const sessionMeta = lease.sessionMeta || null;
151
- conflicts.push(
152
- buildConflict(
153
- file,
154
- sessionMeta
155
- ? {
156
- sessionId: sessionMeta.sessionId || lease.workerId,
157
- taskSummary: sessionMeta.taskSummary || "",
158
- }
159
- : null,
160
- lease.workerId,
161
- ),
162
- );
163
- }
164
-
165
- return { conflicts };
166
- }
167
-
168
- function findTargetFileConflicts(targetFiles, sessionId, workerId) {
169
- const active = safeGetActive();
170
- const snapshot = safeSnapshot();
171
-
172
- if (active == null || snapshot == null) {
173
- return failOpen ? { fallOpen: true } : { fallOpen: false, conflicts: [] };
174
- }
175
-
176
- const targets = normalizeFiles(targetFiles);
177
- if (targets.size === 0) return { conflicts: [] };
178
-
179
- const conflicts = [];
180
- const seen = new Set();
181
-
182
- for (const session of otherActiveSessions(active, sessionId)) {
183
- const dirty = normalizeFiles(session.dirtyFiles);
184
- for (const file of dirty) {
185
- if (!targets.has(file) || seen.has(file)) continue;
186
- seen.add(file);
187
- conflicts.push(buildConflict(file, session));
188
- }
189
- }
190
-
191
- for (const lease of otherLeases(snapshot, workerId)) {
192
- if (lease.leaseType && lease.leaseType !== "exclusive") continue;
193
- const file = typeof lease.file === "string" ? lease.file : null;
194
- if (!file || !targets.has(file) || seen.has(file)) continue;
195
- seen.add(file);
196
- const sessionMeta = lease.sessionMeta || null;
197
- conflicts.push(
198
- buildConflict(
199
- file,
200
- sessionMeta
201
- ? {
202
- sessionId: sessionMeta.sessionId || lease.workerId,
203
- taskSummary: sessionMeta.taskSummary || "",
204
- }
205
- : null,
206
- lease.workerId,
207
- ),
208
- );
209
- }
210
-
211
- return { conflicts };
212
- }
213
-
214
- // ── public API ──────────────────────────────────────────
215
-
216
- /**
217
- * Core pre-flight check.
218
- *
219
- * @param {"checkout"|"rebase"|"cherry-pick"|"reset"|"stash-pop"|"worktree-remove"} op
220
- * @param {{ targetFiles?: string[], branch?: string, ref?: string, worktreePath?: string }} args
221
- * @param {{ sessionId: string, workerId?: string }} sessionContext
222
- * @returns {{ allowed: boolean, reason?: string, conflicts?: object[], recommendation?: string, op?: string }}
223
- */
224
- function check(op, args = {}, sessionContext = {}) {
225
- const sessionId = sessionContext.sessionId || "";
226
- const workerId = sessionContext.workerId || sessionId;
227
-
228
- switch (op) {
229
- case "checkout":
230
- case "rebase":
231
- case "reset":
232
- case "stash-pop": {
233
- const res = findDirtyFileConflicts(sessionId, workerId);
234
- if (res.fallOpen) return failOpenDecision(op);
235
- if (res.conflicts.length > 0) return blockedDecision(op, res.conflicts);
236
- return allowedDecision();
237
- }
238
- case "cherry-pick": {
239
- const targets = Array.isArray(args.targetFiles) ? args.targetFiles : [];
240
- const res = findTargetFileConflicts(targets, sessionId, workerId);
241
- if (res.fallOpen) return failOpenDecision(op);
242
- if (res.conflicts.length > 0) return blockedDecision(op, res.conflicts);
243
- return allowedDecision();
244
- }
245
- case "worktree-remove": {
246
- const active = safeGetActive();
247
- if (active == null) return failOpenDecision(op);
248
- const target =
249
- typeof args.worktreePath === "string"
250
- ? args.worktreePath.replace(/\\/g, "/")
251
- : "";
252
- if (!target) return allowedDecision();
253
- const conflicts = [];
254
- for (const session of otherActiveSessions(active, sessionId)) {
255
- const sessionPath =
256
- typeof session.worktreePath === "string"
257
- ? session.worktreePath.replace(/\\/g, "/")
258
- : "";
259
- if (sessionPath && sessionPath === target) {
260
- conflicts.push({
261
- file: target,
262
- activeSession: session.sessionId,
263
- activeTask: session.taskSummary || "",
264
- });
265
- }
266
- }
267
- if (conflicts.length > 0) {
268
- return blockedDecision(op, conflicts, "active_worktree");
269
- }
270
- return allowedDecision();
271
- }
272
- default:
273
- return allowedDecision();
274
- }
275
- }
276
-
277
- return Object.freeze({
278
- check,
279
- checkRebase: (args, ctx) => check("rebase", args, ctx),
280
- checkCheckout: (args, ctx) => check("checkout", args, ctx),
281
- checkCherryPick: (args, ctx) => check("cherry-pick", args, ctx),
282
- checkReset: (args, ctx) => check("reset", args, ctx),
283
- checkStashPop: (args, ctx) => check("stash-pop", args, ctx),
284
- checkWorktreeRemove: (args, ctx) => check("worktree-remove", args, ctx),
285
- });
286
- }