@rch4com/agent-setup 1.15.0 → 1.17.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.
package/install.mjs CHANGED
@@ -6,6 +6,7 @@ import { withDeps } from './lib/deps.mjs'
6
6
  import { LocalizedError, createT, isLocaleForced, resolveLocale, toText } from './lib/i18n/index.mjs'
7
7
  import { detectLocale } from './lib/i18n/detect.mjs'
8
8
  import { labelWidth, pad } from './lib/width.mjs'
9
+ import { scopedLabel } from './lib/labels.mjs'
9
10
  import {
10
11
  bootstrapUsage, designUsage, rootUsage, statusUsage, updateUsage,
11
12
  parseBootstrapArgs, parseDesignArgs, parseRootArgs, parseStatusArgs, parseUpdateArgs, preScanLang,
@@ -60,7 +61,7 @@ async function runClassic(root, { dryRun, listOnly, setArg, t }) {
60
61
  if (listOnly) {
61
62
  for (const s of states) {
62
63
  const detail = toText(t, s.detail)
63
- console.log(`${pad(t(`status.${s.status}`), statusWidth)} ${s.item.id} — ${s.item.label}${detail ? ` (${detail})` : ''}`)
64
+ console.log(`${pad(t(`status.${s.status}`), statusWidth)} ${s.item.id} — ${scopedLabel(s.item, t)}${detail ? ` (${detail})` : ''}`)
64
65
  }
65
66
  return
66
67
  }
@@ -97,12 +98,12 @@ async function runClassic(root, { dryRun, listOnly, setArg, t }) {
97
98
  },
98
99
  })
99
100
  for (const r of results) {
100
- console.log(`${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${r.item.label}${r.message ? ` — ${toText(t, r.message)}` : ''}`)
101
+ console.log(`${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${scopedLabel(r.item, t)}${r.message ? ` — ${toText(t, r.message)}` : ''}`)
101
102
  }
102
103
 
103
104
  const after = await scan(root, items)
104
105
  console.log(`\n${t('apply.finalState')}`)
105
- for (const s of after) console.log(` ${pad(t(`status.${s.status}`), statusWidth)} ${s.item.label}`)
106
+ for (const s of after) console.log(` ${pad(t(`status.${s.status}`), statusWidth)} ${scopedLabel(s.item, t)}`)
106
107
  console.log(`\n${t('apply.seeGitDiff')}`)
107
108
  if (results.some((r) => !r.ok)) process.exitCode = 1
108
109
  }
@@ -30,7 +30,105 @@ Document the actual commands used by this repository:
30
30
  - Do not modify files outside this repository.
31
31
  - Do not add production dependencies without explicit approval.
32
32
  - Do not edit generated files directly.
33
- - Never commit credentials, access tokens, or connection strings.`
33
+ - Never commit credentials, access tokens, or connection strings.
34
+
35
+ ## Behavioral guidelines
36
+
37
+ Behavioral guidelines to reduce common LLM coding mistakes.
38
+
39
+ **Scope:** Only what current models still get wrong. If the model or the
40
+ harness already handles something reliably, it doesn't belong here - a rule
41
+ that restates default behavior burns context and buys nothing.
42
+
43
+ **Tradeoff:** These guidelines bias toward caution over speed. For trivial
44
+ tasks, use judgment.
45
+
46
+ ### 1. State Assumptions, Then Proceed
47
+
48
+ **Say what you assumed. Keep going. Default the rest.**
49
+
50
+ Before implementing:
51
+
52
+ - State your assumptions in one line, then start.
53
+ - If multiple interpretations exist, pick the likeliest and say which one you
54
+ picked.
55
+ - If a simpler approach exists, say so while doing the work - not as a question
56
+ that blocks it.
57
+ - Ask only when the answer changes what gets built, not how well, and the wrong
58
+ choice can't be cheaply undone.
59
+
60
+ A stated assumption gets corrected in seconds. A question costs a round-trip
61
+ and hands the work back to the user. If you're about to ask a second question
62
+ in one task, you're doing it wrong.
63
+
64
+ ### 2. Simplicity First
65
+
66
+ **Minimum code that solves the problem. Nothing speculative.**
67
+
68
+ - No features beyond what was asked.
69
+ - No abstractions for single-use code.
70
+ - No "flexibility" or "configurability" that wasn't requested.
71
+ - No error handling for impossible scenarios.
72
+ - If you write 200 lines and it could be 50, rewrite it.
73
+
74
+ Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes,
75
+ simplify.
76
+
77
+ ### 3. Surgical Changes
78
+
79
+ **Touch only what you must. Clean up only your own mess.**
80
+
81
+ When editing existing code:
82
+
83
+ - Don't "improve" adjacent code, comments, or formatting.
84
+ - Don't refactor things that aren't broken.
85
+ - Match existing style, even if you'd do it differently.
86
+ - If you notice unrelated dead code, mention it - don't delete it.
87
+
88
+ When your changes create orphans:
89
+
90
+ - Remove imports/variables/functions that YOUR changes made unused.
91
+ - Don't remove pre-existing dead code unless asked.
92
+
93
+ The test: Every changed line should trace directly to the user's request.
94
+
95
+ ### 4. Verify Before Done
96
+
97
+ **If you touched code, run the check before saying "done" - and report what
98
+ actually ran.**
99
+
100
+ - \`npm test\`, \`pytest\`, \`cargo test\`, whatever the project uses. Smallest
101
+ relevant check first, broader checks when risk is high.
102
+ - No test setup? At minimum, verify the project builds or typechecks.
103
+ - Report the exact command and its result: "passed", "failed with X", or "not
104
+ run because Y".
105
+ - Never write "done", "fixed", or "works" unless a concrete check backs it.
106
+ - Run it proactively, before the user signals "끝", "완료", "다 됐어".
107
+
108
+ This is the step LLMs skip most often. Treat it as non-negotiable.
109
+
110
+ ### 5. Teach One Thing On The Way Out
111
+
112
+ **End with what the user would want to know next time. Two or three
113
+ sentences.**
114
+
115
+ When the work is done:
116
+
117
+ - Name the one concept, tradeoff, or gotcha that actually mattered here.
118
+ - Teach what the code doesn't show: why this way over the obvious one, which
119
+ default you leaned on, what breaks first at scale.
120
+ - If it needs a heading, it's too long. If it restates the diff, delete it.
121
+ - Skip it when the change is trivial, or when the user is the one who taught
122
+ you the thing.
123
+
124
+ Why: an agent that only ships code leaves the user unable to maintain it. They
125
+ should finish each task slightly more able to do it without you.
126
+
127
+ ---
128
+
129
+ **These guidelines are working if:** fewer unnecessary changes in diffs, fewer
130
+ rewrites due to overcomplication, and stated assumptions get corrected early
131
+ instead of surfacing as mistakes late.`
34
132
 
35
133
  export const CLAUDE_BLOCK = `<!-- agent-kit:begin -->
36
134
  @AGENTS.md
package/lib/catalog.mjs CHANGED
@@ -166,11 +166,15 @@ export function definePlugin({ id, label, installId, detectIds, marketplace, not
166
166
  }
167
167
  }
168
168
 
169
- export function defineSkill({ id, label, scope, detect, install, uninstall, note, group = null, unsupported: reasons = {} }) {
169
+ // supports는 이 항목이 실제로 배선하는 CLI다. 기본값이 claude 하나인 것은
170
+ // 대부분의 스킬 상류가 Claude 경로만 주기 때문이고, 상류가 런타임별 설치를
171
+ // 주는 항목(skill.gsd)은 목록을 직접 넘긴다 — 넘긴 CLI는 미배선 사유 대상에서
172
+ // 빠진다.
173
+ export function defineSkill({ id, label, scope, detect, install, uninstall, note, group = null, supports = ['claude'], unsupported: reasons = {} }) {
170
174
  const unsupported = Object.fromEntries(
171
- CLI_IDS.filter((c) => c !== 'claude').map((c) => [c, reasons[c] ?? msg('item.unsupported.claudeSkill')]),
175
+ CLI_IDS.filter((c) => !supports.includes(c)).map((c) => [c, reasons[c] ?? msg('item.unsupported.claudeSkill')]),
172
176
  )
173
- return { id, category: 'skill', label, scope, supports: ['claude'], unsupported, note, group, detect, install, uninstall }
177
+ return { id, category: 'skill', label, scope, supports: [...supports], unsupported, note, group, detect, install, uninstall }
174
178
  }
175
179
 
176
180
  // 이 저장소가 공유 스킬 자리로 쓰는 디렉터리. vercel-labs/skills 레지스트리의
@@ -234,15 +234,17 @@ export default {
234
234
  'item.unsupported.mattpocockRegistry': 'upstream also documents npx skills add (the shared .agents/skills), but this item wires the Claude Code plugin only',
235
235
  'item.unsupported.upstreamNone': 'no official install path upstream',
236
236
  'item.unsupported.superpowersSeparate': 'upstream supports it via a separate per-harness install — this item wires the Claude Code plugin only',
237
- 'item.unsupported.superpowersGlobalClaude': 'covered by the project-scope item superpowers (plugin)',
237
+ 'item.unsupported.superpowersGlobalClaude': 'covered by the repo-scope item superpowers (repo)',
238
238
  'item.unsupported.superpowersGlobalGrok': 'upstream 6.3.0 ships grok plugin install superpowers@xai-official, but the detect/remove paths are unverified — not wired yet',
239
239
  'item.unsupported.superpowersGlobalKimi': 'upstream installs only via the interactive /plugins command — no headless path',
240
- 'item.unsupported.globalProjectItem': 'covered by the project-scope item {item}',
240
+ 'item.unsupported.globalProjectItem': 'covered by the repo-scope item {item}',
241
241
  'item.unsupported.ponytailGlobalGrok': 'upstream ships grok plugin install DietrichGebert/ponytail --trust, but the detect/remove paths are unverified — not wired yet',
242
242
  'item.unsupported.bkitPort': 'upstream ships separate ports (bkit-codex / bkit-gemini) installed apart from this plugin; the Gemini port is still a Gemini CLI extension and has had no update since the 2026-06-18 sunset',
243
243
  'item.unsupported.impeccableJunction': 'upstream supports it, but npx impeccable install breaks the shared .agents/skills link, so only the plugin route is wired',
244
244
  'item.unsupported.gstackHost': 'upstream supports it via ./setup --host — this item installs for Claude Code only',
245
- 'item.unsupported.gsdFlag': 'upstream supports it via a runtime flag — this item installs with --claude only',
245
+ 'item.unsupported.gstackShared': 'the upstream --host route is user-scoped, so it is not used. In a bootstrapped repository the shared .agents/skills junction reaches this CLI instead, and it scans recursively, so it sees the individual skills too (measured 2026-08-15)',
246
+ 'item.unsupported.gstackSharedShallow': 'in a bootstrapped repository the shared .agents/skills junction reaches this CLI, but it scans only one level, so just the router skill shows up (measured 2026-08-15)',
247
+ 'item.unsupported.gsdKimiLocal': 'upstream still blocks project-scope installs — --kimi --local refuses with "Project-level Kimi install semantics remain deferred" (measured 2026-08-15)',
246
248
  'item.unsupported.gsdGemini': 'upstream dropped Gemini CLI (sunset 2026-06) — Antigravity succeeds it via --antigravity',
247
249
  'item.mcp.partial': 'registered: {present} / missing: {missing}',
248
250
  'item.plugin.partial': 'wired: {present} / missing: {missing}',
@@ -280,8 +282,8 @@ export default {
280
282
  'item.skill.mcp-builder.note': 'a guide for building MCP servers (Python FastMCP, Node MCP SDK). Its four reference/ documents and the Apache-2.0 LICENSE.txt are copied along inside the skill directory. Harness-neutral content shared by all 10 CLIs',
281
283
  'item.skill.mattpocock-skills.note': 'copies 35 skills into the shared .agents/skills so all 10 CLIs see them. Upstream documents this route itself, and with no hooks involved nothing is lost against the plugin edition. Only one of the two can be chosen',
282
284
  'item.skill.karpathy.note': 'behavioral guidelines that curb overbuilding and vague success criteria',
283
- 'item.skill.gstack.note': 'repo-local clone + setup (needs bash; Git Bash on Windows). Runtime state (~/.gstack) may be created globally.',
284
- 'item.skill.gsd.note': 'npx @opengsd/gsd-core, installed per project. Upstream supports 18 runtimes (Codex, Antigravity, …); this item wires --claude only',
285
+ 'item.skill.gstack.note': 'repo-local clone + setup (needs bash; Git Bash on Windows). Runtime state (~/.gstack) may be created globally. In a bootstrapped repository .claude/skills is a junction, so the files land in the shared .agents/skills — codex and opencode scan recursively and see the individual skills too',
286
+ 'item.skill.gsd.note': 'npx @opengsd/gsd-core, installed per project — wires five runtimes in one run: claude, codex, opencode, copilot, kilo (.claude/commands, .codex/skills, .opencode/skills, .github/skills, .kilo/skills). Each runtime writes roughly 700 files. gemini was dropped upstream and kimi still blocks project installs',
285
287
  // 커밋 템플릿은 CLI 배선이 아니라 저장소 규약이다 — note가 "무엇이 놓이고
286
288
  // 무엇이 설정되는가"를 대신 말한다(상세 패널에 배선표가 없다).
287
289
  'item.config.gitmessage.en.note': 'commit messages in English. Writes the template and points git commit.template at it, so it opens in every editor and every tool in this repository',
@@ -329,11 +331,13 @@ export default {
329
331
  'tui.review.title': 'Review — {count} change(s)',
330
332
  'tui.review.more': ' …and {count} more',
331
333
  'tui.review.hint': 'Enter apply Esc cancel Ctrl+Q quit',
334
+ 'tui.review.globalWarning': '{count} machine-global change(s) — settings outside this repo (~/.codex etc.) will change',
332
335
  'tui.notItemRow': 'Press Enter to run this row.',
333
336
  'tui.noPreview': 'This item has no preview.',
334
337
  'tui.opened': 'Opened: {target}',
335
338
  'tui.noChanges': 'Nothing to change.',
336
339
  'tui.submitCancelled': 'Submission cancelled.',
340
+ 'tui.selectedGlobal': 'Machine-global item — applies to this whole computer',
337
341
  'tui.toggledAll': 'Selected every visible item in the {tab} tab.',
338
342
  'tui.toggledNone': 'Cleared every visible item in the {tab} tab.',
339
343
  // 배타 묶음에서 형제가 함께 꺼졌을 때. 조용히 뒤집히면 오조작으로 읽힌다.
@@ -371,6 +375,8 @@ export default {
371
375
  'tui.filter.empty': ' Nothing in this tab is wired for {cli}.',
372
376
 
373
377
  'item.location.user': 'installs to: user global',
378
+ 'label.scope.project': '(repo)',
379
+ 'label.scope.user': '(global)',
374
380
  'item.target': 'file: {path}',
375
381
  // 커버리지는 전부 지원할 때도 찍는다 — 표시가 없는 것과 "10/10"은 뜻이 다르다.
376
382
  'item.cliCoverage': 'CLI {covered}/{total}',
@@ -411,6 +417,9 @@ export default {
411
417
  // 대상 파일이 하나뿐인 배타 묶음이라 라디오 표시와 짝이 된다.
412
418
  'category.commit': 'Commit template · .gitmessage.txt · pick one',
413
419
  'category.service': 'External services',
420
+ // PLUGIN 탭 전용 범위 그룹 — 검토 화면(renderReview)의 소제목도 같은 키를 쓴다.
421
+ 'category.scope-project': 'Repo scope — this repository only',
422
+ 'category.scope-user': 'Machine global — applies to this whole computer',
414
423
  'design.localSuffix': '{id} (local)',
415
424
  'design.localTag': 'local',
416
425
  'design.unknownItem': ' unknown item: {token}',
@@ -227,15 +227,17 @@ export default {
227
227
  'item.unsupported.mattpocockRegistry': '상류가 npx skills add(공유 .agents/skills)도 안내하지만 이 항목은 Claude Code 플러그인만 배선합니다',
228
228
  'item.unsupported.upstreamNone': '상류에 공식 설치 경로가 없습니다',
229
229
  'item.unsupported.superpowersSeparate': '상류가 하니스별 별도 설치로 지원합니다 — 이 항목은 Claude Code 플러그인만 배선합니다',
230
- 'item.unsupported.superpowersGlobalClaude': '프로젝트 스코프 항목 superpowers (plugin)이 담당합니다',
230
+ 'item.unsupported.superpowersGlobalClaude': '저장소 범위 항목 superpowers (저장소)가 담당합니다',
231
231
  'item.unsupported.superpowersGlobalGrok': '상류 6.3.0에 grok plugin install superpowers@xai-official 명령이 있지만 감지·제거 경로를 실측하지 못해 아직 배선하지 않습니다',
232
232
  'item.unsupported.superpowersGlobalKimi': '상류 설치가 대화형 /plugins 명령뿐이라 헤드리스 경로가 없습니다',
233
- 'item.unsupported.globalProjectItem': '프로젝트 스코프 항목 {item}이 담당합니다',
233
+ 'item.unsupported.globalProjectItem': '저장소 범위 항목 {item}이 담당합니다',
234
234
  'item.unsupported.ponytailGlobalGrok': '상류 명령(grok plugin install DietrichGebert/ponytail --trust)이 있지만 감지·제거 경로 미실측이라 배선하지 않습니다',
235
235
  'item.unsupported.bkitPort': '상류가 별도 배포판(bkit-codex·bkit-gemini)으로 지원합니다 — 이 플러그인과 별개 설치입니다. Gemini판은 Gemini CLI 확장 그대로이고 2026-06-18 종료 이후 갱신이 없습니다',
236
236
  'item.unsupported.impeccableJunction': '상류는 지원하지만 npx impeccable install이 공유 .agents/skills 연결을 끊어 플러그인 경로만 배선합니다',
237
237
  'item.unsupported.gstackHost': '상류는 ./setup --host로 지원합니다 — 이 항목은 Claude Code에만 설치합니다',
238
- 'item.unsupported.gsdFlag': '상류는 런타임 플래그로 지원합니다 — 이 항목은 --claude로만 설치합니다',
238
+ 'item.unsupported.gstackShared': '상류 --host 경로는 사용자 스코프라 쓰지 않습니다. 대신 부트스트랩 저장소에서는 공유 .agents/skills Junction으로 닿고, 이 CLI는 재귀 스캔이라 개별 스킬까지 봅니다(2026-08-15 실측)',
239
+ 'item.unsupported.gstackSharedShallow': '부트스트랩 저장소에서는 공유 .agents/skills Junction으로 닿지만, 이 CLI는 1단계만 훑어 라우터 스킬 하나만 보입니다(2026-08-15 실측)',
240
+ 'item.unsupported.gsdKimiLocal': '상류가 프로젝트 스코프 설치를 아직 막습니다 — --kimi --local이 "Project-level Kimi install semantics remain deferred"로 거부합니다(2026-08-15 실측)',
239
241
  'item.unsupported.gsdGemini': '상류가 Gemini CLI 지원을 제거했습니다(2026-06 종료) — Antigravity가 --antigravity로 승계합니다',
240
242
  'item.mcp.partial': '등록됨: {present} / 누락: {missing}',
241
243
  'item.plugin.partial': '배선됨: {present} / 누락: {missing}',
@@ -273,8 +275,8 @@ export default {
273
275
  'item.skill.mcp-builder.note': 'MCP 서버 제작 가이드(Python FastMCP·Node MCP SDK). reference/ 문서 4종과 Apache-2.0 LICENSE.txt가 스킬 디렉터리에 함께 복사됩니다. 내용이 하네스 중립이라 10개 CLI가 함께 씁니다',
274
276
  'item.skill.mattpocock-skills.note': '공유 .agents/skills에 스킬 35종을 복사해 10개 CLI가 함께 봅니다. 상류가 직접 안내하는 경로이며 훅이 없어 플러그인 판에 견줘 잃는 것이 없습니다. 둘 중 하나만 고를 수 있습니다',
275
277
  'item.skill.karpathy.note': '과설계와 모호한 완료 기준을 줄이는 행동 지침',
276
- 'item.skill.gstack.note': '저장소 로컬 clone + setup (bash 필요, Windows는 Git Bash). 런타임 상태(~/.gstack)는 전역에 생길 수 있음.',
277
- 'item.skill.gsd.note': 'npx @opengsd/gsd-core 프로젝트 로컬 설치. 상류는 Codex·Antigravity 등 18개 런타임을 지원합니다(이 항목은 --claude)',
278
+ 'item.skill.gstack.note': '저장소 로컬 clone + setup (bash 필요, Windows는 Git Bash). 런타임 상태(~/.gstack)는 전역에 생길 수 있음. 부트스트랩 저장소에서는 .claude/skills가 Junction이라 실물이 공유 .agents/skills에 앉습니다 — codex·opencode는 재귀 스캔이라 개별 스킬까지 함께 봅니다',
279
+ 'item.skill.gsd.note': 'npx @opengsd/gsd-core 프로젝트 로컬 설치 — claude·codex·opencode·copilot·kilo 다섯 런타임을 한 번에 배선합니다(.claude/commands, .codex/skills, .opencode/skills, .github/skills, .kilo/skills). 런타임마다 700개 안팎의 파일이 생깁니다. gemini는 상류가 제거했고 kimi는 프로젝트 설치를 아직 막습니다',
278
280
  // 커밋 템플릿은 CLI 배선이 아니라 저장소 규약이다 — en.mjs 쪽 주석 참고.
279
281
  'item.config.gitmessage.en.note': '커밋 메시지를 영어로 씁니다. 템플릿을 놓고 git commit.template이 그것을 가리키게 하므로, 저장소의 모든 편집기와 도구에서 함께 열립니다',
280
282
  'item.config.gitmessage.ko.note': '커밋 메시지를 한국어로 씁니다. 템플릿을 놓고 git commit.template이 그것을 가리키게 하므로, 저장소의 모든 편집기와 도구에서 함께 열립니다',
@@ -319,11 +321,13 @@ export default {
319
321
  'tui.review.title': '제출 검토 — 변경 {count}건',
320
322
  'tui.review.more': ' …외 {count}건',
321
323
  'tui.review.hint': 'Enter 적용 Esc 취소 Ctrl+Q 종료',
324
+ 'tui.review.globalWarning': '머신 전역 변경 {count}건 — 이 저장소 밖 설정(~/.codex 등)이 바뀝니다',
322
325
  'tui.notItemRow': '이 행은 Enter로 실행합니다.',
323
326
  'tui.noPreview': '이 항목은 미리보기를 제공하지 않습니다.',
324
327
  'tui.opened': '열었습니다: {target}',
325
328
  'tui.noChanges': '변경할 항목이 없습니다.',
326
329
  'tui.submitCancelled': '제출을 취소했습니다.',
330
+ 'tui.selectedGlobal': '머신 전역 항목 — 이 컴퓨터 전체에 적용됩니다',
327
331
  'tui.toggledAll': '{tab} 탭의 보이는 항목을 모두 선택했습니다.',
328
332
  'tui.toggledNone': '{tab} 탭의 보이는 항목을 모두 해제했습니다.',
329
333
  // 배타 묶음에서 형제가 함께 꺼졌을 때. en.mjs 쪽 주석 참고.
@@ -358,6 +362,8 @@ export default {
358
362
  'tui.filter.empty': ' 이 탭에는 {cli}에 배선되는 항목이 없습니다.',
359
363
 
360
364
  'item.location.user': '설치 위치: 사용자 글로벌',
365
+ 'label.scope.project': '(저장소)',
366
+ 'label.scope.user': '(전역)',
361
367
  'item.target': '대상 파일: {path}',
362
368
  // en.mjs 쪽 주석 참고.
363
369
  'item.cliCoverage': 'CLI {covered}/{total}',
@@ -396,6 +402,9 @@ export default {
396
402
  // 헤더 하나가 무엇을 고르는 자리이고 규칙이 무엇인지 함께 말한다. en.mjs 쪽 주석 참고.
397
403
  'category.commit': '커밋 메시지 템플릿 · .gitmessage.txt · 하나만 고릅니다',
398
404
  'category.service': '외부 서비스',
405
+ // PLUGIN 탭 전용 범위 그룹 — 검토 화면(renderReview)의 소제목도 같은 키를 쓴다.
406
+ 'category.scope-project': '저장소 범위 — 이 저장소에만 적용',
407
+ 'category.scope-user': '머신 전역 — 이 컴퓨터 전체에 적용',
399
408
  'design.localSuffix': '{id} (로컬)',
400
409
  'design.localTag': '로컬',
401
410
  'design.unknownItem': ' 알 수 없는 항목: {token}',
@@ -28,7 +28,7 @@ import { msg } from '../i18n/index.mjs'
28
28
  const SUPPORTS = ['codex', 'gemini', 'copilot']
29
29
 
30
30
  const CONFIG = {
31
- id: 'global.ponytail', label: 'Ponytail (global)', group: '__token',
31
+ id: 'global.ponytail', label: 'Ponytail', group: '__token',
32
32
  note: 'item.global.ponytail.note',
33
33
  pluginName: 'ponytail',
34
34
  marketplaceRepo: 'DietrichGebert/ponytail',
@@ -26,7 +26,7 @@ import { msg } from '../i18n/index.mjs'
26
26
  const SUPPORTS = ['codex', 'gemini', 'opencode', 'copilot']
27
27
 
28
28
  const CONFIG = {
29
- id: 'global.superpowers', label: 'superpowers (global)', group: '__flow',
29
+ id: 'global.superpowers', label: 'superpowers', group: '__flow',
30
30
  note: 'item.global.superpowers.note',
31
31
  pluginName: 'superpowers',
32
32
  marketplaceRepo: 'obra/superpowers-marketplace',
@@ -7,7 +7,7 @@ import { msg } from '../i18n/index.mjs'
7
7
  // 아니라 "우리가 플러그인 쪽을 골랐다"이고, 사유가 그렇게 말해야 레지스트리
8
8
  // 항목으로 옮길 수 있다는 것도 화면에서 읽힌다.
9
9
  export default definePlugin({
10
- id: 'plugin.mattpocock-skills', label: 'Matt Pocock (plugin)', group: '__flow',
10
+ id: 'plugin.mattpocock-skills', label: 'Matt Pocock', group: '__flow',
11
11
  exclusive: 'mattpocock',
12
12
  installId: 'mattpocock-skills@mattpocock',
13
13
  detectIds: ['mattpocock-skills@mattpocock'],
@@ -13,7 +13,7 @@ import { msg } from '../i18n/index.mjs'
13
13
  const SEPARATE = ['codex', 'gemini', 'opencode', 'kimi', 'copilot', 'grok']
14
14
 
15
15
  export default definePlugin({
16
- id: 'plugin.superpowers', label: 'superpowers (plugin)', group: '__flow',
16
+ id: 'plugin.superpowers', label: 'superpowers', group: '__flow',
17
17
  exclusive: 'superpowers',
18
18
  installId: 'superpowers@claude-plugins-official',
19
19
  detectIds: ['superpowers@claude-plugins-official', 'superpowers@superpowers-marketplace'],
@@ -4,43 +4,95 @@ import { defineSkill } from '../catalog.mjs'
4
4
  import { CLI_IDS } from '../clis.mjs'
5
5
  import { LocalizedError, msg } from '../i18n/index.mjs'
6
6
 
7
+ // 상류(v1.10.0)는 런타임 플래그 18종을 주는데, 그중 이 저장소의 CLI이면서
8
+ // **프로젝트 스코프(--local)로 설치되고 그 CLI가 실제로 읽는 것**만 배선한다.
9
+ // 다섯 자리 전부 2026-08-15에 실측했다:
10
+ // claude `.claude/commands`에 gsd-* (680파일). skills가 아니라 commands다 —
11
+ // 부트스트랩 저장소의 `.claude/skills` Junction과 무관하다.
12
+ // codex `.codex/skills`에 gsd-* 71종. `codex debug prompt-input`이 이
13
+ // 경로를 스킬 루트로 등록하고 전부 나열하는 것을 확인했다.
14
+ // opencode `.opencode/skills`. `opencode debug skill`이 로드한다.
15
+ // copilot `.github/skills`. `copilot skill list`가 전부 표시한다.
16
+ // kilo `.kilo/skills` 71종을 @kilocode/cli 7.3.45가 읽는다(2026-08-15).
17
+ //
18
+ // 플래그는 한 번에 여러 개를 줄 수 있다 — `--claude --codex --local`이 두
19
+ // 디렉터리를 만드는 것을 실측했다. 그래서 설치·제거 모두 호출 한 번이다.
20
+ // `--uninstall`도 같은 플래그를 받아 그 런타임만 지운다(`--codex --local
21
+ // --uninstall`이 .codex만 비우고 나머지 넷을 보존하는 것을 확인).
22
+ //
23
+ // 미배선 넷의 사유가 전부 다르다:
24
+ // gemini 상류가 명시 제거(2026-06-18 종료). `--gemini --local`은 안내만 찍고
25
+ // 파일을 하나도 만들지 않는다.
26
+ // kimi `--kimi --local`이 "Project-level Kimi install semantics remain
27
+ // deferred"를 찍고 거부한다 — 전역 설치만 가능하다.
28
+ // kiro·grok·vscode 상류 런타임 목록에 없다.
29
+ const RUNTIMES = [
30
+ { cli: 'claude', dirs: ['.claude/commands', '.claude/skills'] },
31
+ { cli: 'codex', dirs: ['.codex/skills'] },
32
+ { cli: 'opencode', dirs: ['.opencode/skills'] },
33
+ { cli: 'copilot', dirs: ['.github/skills'] },
34
+ { cli: 'kilo', dirs: ['.kilo/skills'] },
35
+ ]
36
+ const SUPPORTS = RUNTIMES.map((r) => r.cli)
37
+
38
+ // gsd-* 이름을 기준으로 센다. 상류 제거가 `gsd-install-state.json`과 훅 몇
39
+ // 개를 남기는 것을 실측했는데(디렉터리는 남고 gsd-* 스킬만 사라진다), 파일
40
+ // 존재만 보면 제거한 런타임이 영원히 설치됨으로 읽힌다.
7
41
  function hasGsdFiles(dir) {
8
- return existsSync(dir) && readdirSync(dir).some((f) => f.startsWith('gsd-'))
42
+ try {
43
+ return readdirSync(dir).some((f) => f.startsWith('gsd-'))
44
+ } catch {
45
+ return false
46
+ }
47
+ }
48
+
49
+ function wiredClis(root) {
50
+ return RUNTIMES.filter((r) => r.dirs.some((d) => hasGsdFiles(join(root, ...d.split('/'))))).map((r) => r.cli)
9
51
  }
10
52
 
11
- // 상류(v1.10.0)는 런타임 플래그 19개(--codex·--antigravity 등)를 지원하지만
12
- // 이 항목은 --claude만 배선한다. Gemini CLI는 상류가 명시 제거했다 —
13
- // `--gemini --local`이 2026-06-18 종료 안내를 찍고 파일을 하나도 만들지 않는
14
- // 것을 실측했다(2026-08-15).
15
- // --kilo가 이 저장소의 Kilo Code와 같은 제품인지는 오래 미확인이었는데,
16
- // `--kilo --local`이 만든 .kilo/skills/ 71개를 설치된 Kilo Code
17
- // (@kilocode/cli 7.3.45)가 전부 로드하는 것을 실측해 해소했다(2026-08-15).
18
- const FLAGS = ['codex', 'opencode', 'copilot', 'kimi', 'kilo']
53
+ const flagsFor = (clis) => clis.map((c) => `--${c}`)
19
54
 
20
55
  export default defineSkill({
21
56
  id: 'skill.gsd', label: 'GSD (Get Shit Done)', group: '__flow', scope: 'project',
22
57
  note: 'item.skill.gsd.note',
58
+ supports: SUPPORTS,
23
59
  unsupported: Object.fromEntries(
24
- CLI_IDS.filter((c) => c !== 'claude').map((c) => [
60
+ CLI_IDS.filter((c) => !SUPPORTS.includes(c)).map((c) => [
25
61
  c,
26
- FLAGS.includes(c) ? msg('item.unsupported.gsdFlag')
27
- : c === 'gemini' ? msg('item.unsupported.gsdGemini')
62
+ c === 'gemini' ? msg('item.unsupported.gsdGemini')
63
+ : c === 'kimi' ? msg('item.unsupported.gsdKimiLocal')
28
64
  : msg('item.unsupported.upstreamNone'),
29
65
  ]),
30
66
  ),
67
+
31
68
  async detect({ root }) {
32
- const found = hasGsdFiles(join(root, '.claude', 'commands')) || hasGsdFiles(join(root, '.claude', 'skills'))
33
- return { status: found ? 'installed' : 'absent' }
69
+ const present = wiredClis(root)
70
+ if (present.length === 0) return { status: 'absent' }
71
+ if (present.length === SUPPORTS.length) return { status: 'installed' }
72
+ return {
73
+ status: 'partial',
74
+ detail: msg('item.mcp.partial', {
75
+ present: present.join(', '),
76
+ missing: SUPPORTS.filter((c) => !present.includes(c)).join(', '),
77
+ }),
78
+ }
34
79
  },
80
+
35
81
  async install({ root, exec }) {
36
- const r = await exec('npx', ['-y', '@opengsd/gsd-core@latest', '--claude', '--local'], { cwd: root })
82
+ // 이미 있는 런타임은 빼고 부른다 — 상류가 덮어써도 무해하지만, 부분
83
+ // 설치를 완성할 때 3,500개 파일을 다시 쓰는 것은 낭비다.
84
+ const missing = SUPPORTS.filter((c) => !wiredClis(root).includes(c))
85
+ if (missing.length === 0) return
86
+ const r = await exec('npx', ['-y', '@opengsd/gsd-core@latest', ...flagsFor(missing), '--local'], { cwd: root })
37
87
  if (!r.ok) throw new LocalizedError('error.gsdInstall', { output: r.output })
38
88
  },
89
+
39
90
  async uninstall({ root, exec }) {
40
- // 런타임 플래그가 없으면 상류가 기본값 claude를 지운다 — 지금은 --claude만
41
- // 설치하므로 맞지만, 다른 런타임을 설치하게 되면 여기에도 같은 플래그를
42
- // 넘겨야 엉뚱한 설치본이 지워지지 않는다.
43
- const r = await exec('npx', ['-y', '@opengsd/gsd-core@latest', '--uninstall'], { cwd: root })
91
+ // 런타임 플래그 없는 `--uninstall`은 기본값 claude만 지운다 — 설치된
92
+ // 런타임을 전부 넘겨야 나머지 넷이 남지 않는다.
93
+ const present = wiredClis(root)
94
+ if (present.length === 0) return
95
+ const r = await exec('npx', ['-y', '@opengsd/gsd-core@latest', ...flagsFor(present), '--local', '--uninstall'], { cwd: root })
44
96
  if (!r.ok) throw new LocalizedError('error.gsdUninstall', { output: r.output })
45
97
  },
46
98
  })
@@ -12,8 +12,19 @@ const REL_DIR = '.claude/skills/gstack'
12
12
  // 인자 없이 실행하며 setup의 기본값이 claude 단독이다. README 표에 있는
13
13
  // --host cursor·slate는 setup의 case 문이 거부해 exit 1로 죽는다 —
14
14
  // 확장한다면 안전한 값은 claude·codex·kiro·factory·opencode·auto뿐이다
15
- // (2026-08-02 검증).
16
- const HOSTS = ['codex', 'kiro', 'opencode']
15
+ // (2026-08-02 검증, 2026-08-15 재확인: case 문과 에러 문구 그대로).
16
+ //
17
+ // 그런데 --host를 늘릴 필요가 없다는 것이 2026-08-15 실측으로 드러났다.
18
+ // 상류 --host 경로는 홈(~/.codex/skills 등)에 까는 사용자 스코프인데, 이
19
+ // 항목이 clone하는 `.claude/skills/gstack`은 부트스트랩 저장소에서 Junction
20
+ // 이라 실물이 공유 `.agents/skills/gstack`에 앉는다. 거기서부터는 CLI의 스킬
21
+ // 스캔 깊이가 도달 범위를 정한다 — gstack은 클론 루트에 라우터 SKILL.md가
22
+ // 있고 개별 스킬이 그 아래 한 단계 더 들어간 2단계 구조다.
23
+ // codex·opencode 재귀 스캔이라 개별 스킬까지 전부 인식(중첩 프로브로 실측).
24
+ // copilot 1단계만 훑어 라우터 스킬 하나만 보인다(같은 프로브).
25
+ // 나머지 CLI의 스캔 깊이는 미실측이라 "상류 경로 없음"으로 남긴다 — Junction
26
+ // 도달은 우리 부트스트랩의 부수 효과지 상류 지원이 아니다.
27
+ const SHARED_RECURSIVE = ['codex', 'opencode']
17
28
 
18
29
  export default defineSkill({
19
30
  id: 'skill.gstack', label: 'gstack', group: '__flow', scope: 'project',
@@ -21,7 +32,10 @@ export default defineSkill({
21
32
  unsupported: Object.fromEntries(
22
33
  CLI_IDS.filter((c) => c !== 'claude').map((c) => [
23
34
  c,
24
- HOSTS.includes(c) ? msg('item.unsupported.gstackHost') : msg('item.unsupported.upstreamNone'),
35
+ SHARED_RECURSIVE.includes(c) ? msg('item.unsupported.gstackShared')
36
+ : c === 'copilot' ? msg('item.unsupported.gstackSharedShallow')
37
+ : c === 'kiro' ? msg('item.unsupported.gstackHost')
38
+ : msg('item.unsupported.upstreamNone'),
25
39
  ]),
26
40
  ),
27
41
  async detect({ root }) {
package/lib/labels.mjs ADDED
@@ -0,0 +1,9 @@
1
+ // 플러그인 항목 라벨에 설치 범위 접미사를 붙인다. 목록 밖(검토·진행·결과
2
+ // 화면)에는 범위 그룹 헤더가 없어, 같은 이름의 두 판(superpowers 저장소/전역)을
3
+ // 이 접미사만이 가른다. import가 없는 잎 모듈이어야 한다 — install.mjs가
4
+ // 정적으로 당기므로, 의존성이 들어오면 npm install 없이 도는 부트스트랩
5
+ // (bootstrap.isolation.test.mjs)이 깨진다.
6
+ export function scopedLabel(item, t) {
7
+ if (item?.category !== 'plugin') return item?.label ?? ''
8
+ return `${item.label} ${t(`label.scope.${item.scope ?? 'project'}`)}`
9
+ }
@@ -4,6 +4,7 @@
4
4
  // 실제 시간에 묶여 "14초 경과"를 검증할 방법이 없다.
5
5
  import { createT } from '../i18n/index.mjs'
6
6
  import { cut, labelWidth, pad, width } from '../width.mjs'
7
+ import { scopedLabel } from '../labels.mjs'
7
8
 
8
9
  const ESC = String.fromCharCode(27)
9
10
  const DIM = `${ESC}[2m`
@@ -66,7 +67,7 @@ function entryLines(entry, w, actionWidth, now, t) {
66
67
  tail = ` ${t('progress.skipped')}`
67
68
  }
68
69
  const mark = MARK[entry.state] ?? ' '
69
- const out = [cut(`${mark} ${pad(t(`change.${entry.action}`), actionWidth)} ${entry.item.label}${tail}`, w)]
70
+ const out = [cut(`${mark} ${pad(t(`change.${entry.action}`), actionWidth)} ${scopedLabel(entry.item, t)}${tail}`, w)]
70
71
  // 명령은 실행 중일 때만. 끝난 뒤에도 남기면 화면이 명령 목록이 된다.
71
72
  if (entry.state === 'running' && entry.command) out.push(cut(` ${entry.command}`, w))
72
73
  return out
@@ -150,7 +151,7 @@ export function plainLine(event, t = createT('en')) {
150
151
  index: event.index + 1,
151
152
  total: event.total,
152
153
  action: t(`change.${event.action}`),
153
- label: event.item.label,
154
+ label: scopedLabel(event.item, t),
154
155
  })
155
156
  }
156
157
  if (event.phase === 'done') {
@@ -6,6 +6,7 @@ import { createT } from '../i18n/index.mjs'
6
6
  import { categoryLabel } from '../design-md/flow.mjs'
7
7
  import { cut, pad, width } from '../width.mjs'
8
8
  import { detailLines } from './detail.mjs'
9
+ import { scopedLabel } from '../labels.mjs'
9
10
 
10
11
  // 폭 계산은 화면 전용이 아니다 — 비대화형 목록도 같은 열 맞춤이 필요해
11
12
  // lib/width.mjs에 있다. 여기서 다시 내보내는 것은 호출부(테스트 포함)가
@@ -244,23 +245,61 @@ export function renderReview(changes, opts = {}) {
244
245
  const title = `${t('tui.review.title', { count: changes.length })}${dryRun ? ' (dry-run)' : ''}`
245
246
  const lines = [color ? `${BOLD}${cut(title, w)}${RESET}` : cut(title, w), '']
246
247
 
247
- const body = reviewBodyHeight(height)
248
- const room = Math.max(1, body - 1)
249
- const shown = changes.slice(0, room)
250
- for (const c of shown) {
251
- // 적용 직전 마지막 화면이다. 일부 CLI에만 들어가는 항목은 여기서도 그 사실을
252
- // 밝힌다 — 목록에서 지나쳤더라도 되돌릴 수 있는 마지막 지점이다.
253
- // 전부 지원하는 항목은 조용히 둔다: 경고가 흔해지면 아무도 읽지 않는다.
248
+ // 적용 직전 마지막 화면이다. 일부 CLI에만 들어가는 항목은 여기서도 그 사실을
249
+ // 밝힌다 — 목록에서 지나쳤더라도 되돌릴 수 있는 마지막 지점이다.
250
+ // 전부 지원하는 항목은 조용히 둔다: 경고가 흔해지면 아무도 읽지 않는다.
251
+ const changeLine = (c) => {
254
252
  const partial = c.item.supports && c.item.supports.length < CLI_IDS.length
255
253
  const cov = partial ? ` · ${t('item.cliCoverage', { covered: c.item.supports.length, total: CLI_IDS.length })}` : ''
256
- lines.push(cut(` ${CHANGE_MARK[c.action] ?? '?'} ${pad(t(`change.${c.action}`), 10)} ${c.item.label}${cov}`, w))
254
+ return cut(` ${CHANGE_MARK[c.action] ?? '?'} ${pad(t(`change.${c.action}`), 10)} ${scopedLabel(c.item, t)}${cov}`, w)
255
+ }
256
+
257
+ // 전역 변경이 있을 때만 범위로 가른다 — 전부 저장소 범위인 흔한 경우에
258
+ // 소제목은 잡음이다. 가를 때는 저장소 묶음이 먼저다(목록 화면과 같은 순서).
259
+ const globals = changes.filter((c) => c.item.scope === 'user')
260
+ const split = globals.length > 0
261
+ const groups = split
262
+ ? [
263
+ { header: t('category.scope-project'), list: changes.filter((c) => c.item.scope !== 'user') },
264
+ { header: t('category.scope-user'), list: globals },
265
+ ].filter((g) => g.list.length > 0)
266
+ : [{ header: null, list: changes }]
267
+
268
+ // 소제목·경고 줄이 목록 지면을 갉아먹는다 — 그만큼 떼어 두지 않으면
269
+ // "…외 N건" 판정이 어긋나 화면이 넘친다. 지면이 소제목까지 감당 못 하면
270
+ // 소제목을 버리고 경고만 남긴다 — 경고가 범위 사실을 말하는 최후의 줄이고,
271
+ // 화면이 넘치면 alt 화면 전체가 밀린다.
272
+ const body = reviewBodyHeight(height)
273
+ let overhead = split ? groups.length + 1 : 0
274
+ let showHeaders = split
275
+ if (split && body - 1 - overhead < 1) {
276
+ showHeaders = false
277
+ overhead = 1
278
+ }
279
+ const room = Math.max(1, body - 1 - overhead)
280
+
281
+ let quota = room
282
+ for (const g of groups) {
283
+ // 소제목 지면조차 없으면 위에서 이미 강등돼(showHeaders=false) 이
284
+ // 지점에 오지 않는다 — 여기 온다는 건 소제목 지면은 남았다는 뜻이고,
285
+ // 그때는 목록 지면(quota)이 다해도 소제목만은 낸다: 전역 묶음이
286
+ // 통째로 잘리더라도 그 평면이 있다는 사실은 화면에 남아야 한다
287
+ // (경고 줄이 건수를 마저 말한다).
288
+ if (showHeaders && g.header !== null) lines.push(paint(DIM, cut(` ${g.header}`, w)))
289
+ for (const c of g.list) {
290
+ if (quota <= 0) break
291
+ lines.push(changeLine(c))
292
+ quota--
293
+ }
257
294
  }
258
- if (changes.length > shown.length) {
259
- lines.push(paint(DIM, cut(t('tui.review.more', { count: changes.length - shown.length }), w)))
295
+ const shown = room - quota
296
+ if (changes.length > shown) {
297
+ lines.push(paint(DIM, cut(t('tui.review.more', { count: changes.length - shown }), w)))
260
298
  } else {
261
299
  lines.push('')
262
300
  }
263
- for (let i = shown.length + 1; i < body; i++) lines.push('')
301
+ if (split) lines.push(paint(DIM, cut(t('tui.review.globalWarning', { count: globals.length }), w)))
302
+ while (lines.length < 2 + body) lines.push('')
264
303
 
265
304
  lines.push('')
266
305
  lines.push(paint(DIM, cut(t('tui.review.hint'), w)))
package/lib/tui/rows.mjs CHANGED
@@ -12,6 +12,7 @@ import { loadCatalog, buildItems, netFetch, CATALOG_PATH } from '../design-md/ca
12
12
  import { discoverSources, extraDirsFromEnv } from '../design-md/scan.mjs'
13
13
  import { refreshCatalog, updateInstalled, findStale, categoryLabel } from '../design-md/flow.mjs'
14
14
  import { unsupportedGroups } from './detail.mjs'
15
+ import { scopedLabel } from '../labels.mjs'
15
16
 
16
17
  // 섹션은 탭 이름이자 정렬 키이자 state.tabs의 원소다. 표시 문자열을 그대로
17
18
  // 쓰면 번역하는 순간 정렬이 깨진다 — id를 두고 render.mjs가 표시할 때만
@@ -29,7 +30,7 @@ export const SECTION_ORDER = [ACTION_SECTION, 'plugin', 'mcp', 'skill', 'config'
29
30
  // __commit은 CONFIG 탭에만 나오지만 규칙은 같다 — 헤더가 "무엇을 고르는
30
31
  // 자리인가"를 말한다. 커밋 템플릿은 대상 파일이 하나뿐이라 헤더에 그 파일
31
32
  // 이름과 "하나만"이라는 규칙까지 실어, 라디오 표시와 함께 읽히게 한다.
32
- export const GROUP_ORDER = ['__token', '__context', '__style', '__flow', '__commit', '__service']
33
+ export const GROUP_ORDER = ['__scope-project', '__scope-user', '__token', '__context', '__style', '__flow', '__commit', '__service']
33
34
 
34
35
  function groupRank(group) {
35
36
  const i = GROUP_ORDER.indexOf(group)
@@ -109,7 +110,10 @@ export function designHint(state, multiProvider = false, t) {
109
110
  export function agentShortHint(item, state, t = createT('en')) {
110
111
  const parts = []
111
112
  if (state.status !== 'absent') parts.push(t(`status.${state.status}`))
112
- if (item.supports) parts.push(t('item.cliCoverage', { covered: item.supports.length, total: CLI_IDS.length }))
113
+ // plugin은 수 대신 이름을 나열한다 — 어느 CLI에 설치되는가가 이 탭에서
114
+ // 고르는 기준이고, 가장 긴 나열(codex·gemini·opencode·copilot)도 자리에 든다.
115
+ if (item.category === 'plugin' && item.supports?.length) parts.push(item.supports.join('·'))
116
+ else if (item.supports) parts.push(t('item.cliCoverage', { covered: item.supports.length, total: CLI_IDS.length }))
113
117
  return parts.join(' · ')
114
118
  }
115
119
 
@@ -235,8 +239,13 @@ export function buildRows({ actions = [], agentStates = [], designStates = [], m
235
239
  itemRow({
236
240
  id: s.item.id,
237
241
  section: s.item.category,
238
- group: s.item.group ?? null,
239
- label: s.item.label,
242
+ // plugin 탭은 성격이 아니라 설치 범위로 가른다 — 같은 상류의 두 판
243
+ // (저장소/전역)이 무엇을 건드리는지가 이 탭에서 고르는 기준이다.
244
+ // 표시 계층의 오버라이드다: 항목의 group 필드는 성격의 진실로 남는다.
245
+ group: s.item.category === 'plugin'
246
+ ? (s.item.scope === 'user' ? '__scope-user' : '__scope-project')
247
+ : s.item.group ?? null,
248
+ label: scopedLabel(s.item, t),
240
249
  hint: agentShortHint(s.item, s, t),
241
250
  fullHint: agentHint(s.item, s, t),
242
251
  statusDetail: toText(t, s.detail) ?? null,
package/lib/tui/run.mjs CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  } from './state.mjs'
15
15
  import { render, renderReview, bodyHeight } from './render.mjs'
16
16
  import { createProgress, applyEvent, progressLines } from './progress.mjs'
17
+ import { scopedLabel } from '../labels.mjs'
17
18
 
18
19
  const ESC = String.fromCharCode(27)
19
20
  const HIDE_CURSOR = `${ESC}[?25l`
@@ -183,6 +184,9 @@ export async function runTui(root, opts = {}) {
183
184
  // 화면 밖에 있을 때는 아예 무슨 일이 일어났는지 볼 수조차 없다.
184
185
  const dropped = state.rows.filter((r) => r.id !== row.id && before.has(r.id) && !state.selected.has(r.id))
185
186
  if (dropped.length > 0) return t('tui.exclusiveSwitched', { kept: row.label, dropped: dropped.map((r) => r.label).join(', ') })
187
+ // 전역 항목을 막 켰다면 알린다 — 이 체크 하나가 저장소 밖을 건드린다.
188
+ // 배타 전환 메시지가 우선한다: 상태줄은 한 줄뿐이고 선택이 뒤집힌 쪽이 급하다.
189
+ if (row.item?.scope === 'user' && state.selected.has(row.id) && !before.has(row.id)) return t('tui.selectedGlobal')
186
190
  return ''
187
191
  }
188
192
 
@@ -373,7 +377,7 @@ export async function runTui(root, opts = {}) {
373
377
  log('')
374
378
  for (const r of notable) {
375
379
  const message = toText(t, r.message)
376
- log(` ${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${r.item.label}${message ? ` — ${message}` : ''}`)
380
+ log(` ${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${scopedLabel(r.item, t)}${message ? ` — ${message}` : ''}`)
377
381
  }
378
382
  }
379
383
  log(`\n${t('apply.seeGitDiff')}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rch4com/agent-setup",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "description": "Repository-scoped bootstrap and optional-item installer for using multiple coding-agent CLIs in one repository | 여러 코딩 에이전트 CLI를 한 저장소에서 함께 쓰기 위한 저장소 범위 부트스트랩과 선택 항목 설치기",
5
5
  "keywords": [
6
6
  "agents-md",