leerness 1.9.266 → 1.9.268

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/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.268 — 2026-06-02 — grok 정식 EXTERNAL_AGENTS provider 승격 (1.9.266 후속 task)
4
+
5
+ **🤖 grok 을 슬래시 레지스트리 전용에서 정식 provider 로 승격 — provider cycle / setup-agents / dispatch / `--help` probe 가 grok 도 자동 처리.**
6
+
7
+ ### 배경
8
+ 1.9.265~267(UR-0021)에서 grok 은 `AGENT_SLASH_COMMANDS` 슬래시 레지스트리에만 포함됐을 뿐 `EXTERNAL_AGENTS` 정식 provider 는 아니었음(1.9.266 CHANGELOG 명시 후속 task). 그 결과 `slash-commands --refresh` probe 대상에서 제외되고, install 흐름 6선택지·provider cycle·setup-agents 에서도 빠졌음. 1.9.267 의 `--help` probe(3단계)가 완성되면서 grok 을 정식 편입할 자연스러운 시점.
9
+
10
+ ### 구현
11
+ 1. **`EXTERNAL_AGENTS` 에 grok 추가** — `{ id:'grok', bin:'grok', envFlag:'LEERNESS_ENABLE_GROK', versionArgs:['--version'], installCmd:'npm i -g @vibe-kit/grok-cli' }`. 빌트인 5종 → **6종** (claude/codex/agy/**grok**/copilot/ollama).
12
+ 2. **자동 전파** — EXTERNAL_AGENTS 를 순회하는 모든 소비처가 grok 흡수: `_checkAgent`(ready 감지)·`provider list`(6 builtin)·`slash-commands --refresh`(probe 대상)·`setup-agents`·`agents dispatch`·`agents list/quota`.
13
+ 3. **install 흐름 6선택지 확장** — 대화형 `_selectMany` + 비대화형 `1~6` 맵에 grok 추가(`all` 배열 포함). `4) grok (xAI)` 신설로 5→6 선택지.
14
+ 4. **비시크릿 키 + config 템플릿** — `_LEERNESS_NONSECRET_KEYS` 및 `.harness/leerness-config.json` 템플릿에 `LEERNESS_ENABLE_GROK` 추가(AI 가시성 유지, 시크릿 아님).
15
+ 5. **슬래시 레지스트리 note 갱신** — grok `asOf: 1.9.268` + "정식 provider 승격 — --refresh 자동 probe 가능".
16
+ 6. **selftest 19 → 20** — grok EXTERNAL_AGENTS 편입(bin/envFlag/length===6) 검증.
17
+
18
+ ### 검증
19
+ - **selftest 20/20 PASS** · **E2E 217/217 PASS** (회귀 0).
20
+ - `provider list` → 총 6개(빌트인 6), grok=builtin 확인.
21
+ - `slash-commands grok --refresh --dry-run` → 이전 "probe 대상 아님" → 이제 probe 시도(미설치 시 ENOENT graceful fallback) — 3단계 probe 통합 확인.
22
+
23
+ ## 1.9.267 — 2026-06-02 — UR-0021 3단계: CLI `--help` probe 슬래시 레지스트리 자동 refresh
24
+
25
+ **🔄 UR-0021 백로그 완전 소진 — 설치된 CLI 의 `--help` 출력을 probe 해 슬래시 명령 레지스트리를 자동 갱신 (best-effort, offline-first).**
26
+
27
+ ### 배경
28
+ 1단계(1.9.265)는 큐레이션 빌트인 레지스트리 + 사용자 override, 2단계(1.9.266)는 dispatch/handoff 자동 주입을 구축. 3단계는 처음부터 예정됐던 "항상 최신화 3중 경로"의 마지막 — CLI `--help` probe 자동 refresh. 외부 CLI 의 슬래시 명령은 버전마다 변동하므로, 큐레이션(빌트인)·수동(override)에 더해 *설치된 실제 CLI 에서 직접 회수*하는 경로를 완성.
29
+
30
+ ### 구현
31
+ 1. **`_parseSlashFromHelp(text, invoke)` 순수 파서** — `--help` 출력에서 슬래시 명령(`/cmd desc`) 또는 하위명령(들여쓰기 `cmd desc`) best-effort 추출. ANSI 색상 제거 · CLI 플래그(`--foo`)·옵션 의도적 제외 · cmd 길이 24자 가드 · 중복 제거. 부작용 0 → selftest 가능.
32
+ 2. **`_probeAgentSlash(extDef, opts)`** — EXTERNAL_AGENTS 항목의 `bin` 을 `--help` 로 spawn (타임아웃 5s, `windowsHide`). copilot 처럼 subcommand 인 경우 `versionArgs` 의 base(예: `['copilot']`) 재사용. 실패(ENOENT)/0건 검출 시 `ok:false` + 한국어 reason → **호출부가 큐레이션 빌트인 유지(파괴적 덮어쓰기 방지)**.
33
+ 3. **`_refreshAgentSlashCommands(root, targets, opts)`** — probe 성공 agent 만 `.harness/agent-slash-commands.json` 으로 병합 기록(나머지 보존), `note: "probed via …"` + `asOf: VERSION`. grok/ollama 등 EXTERNAL_AGENTS 미포함은 큐레이션/override 만.
34
+ 4. **`slash-commands [agent] --refresh [--dry-run]` CLI** — 전체/단일 agent probe. dry-run 기본 미적용(실 기록은 `--refresh` 단독). `--json` 기계 판독.
35
+ 5. **MCP `leerness_slash_commands` 확장** — `refresh`/`dryRun` 인자 추가(외부 AI 가 sub-agent 호출 전 레지스트리 자동 갱신).
36
+ 6. **selftest 17 → 19** — `_parseSlashFromHelp` 슬래시 검출+플래그 제외 · subcommand 들여쓰기 파싱 2종 추가.
37
+
38
+ ### 검증
39
+ - **selftest 19/19 PASS** · **E2E 217/217 PASS** (회귀 0).
40
+ - probe 성공 경로 단위 검증: 가짜 CLI(`--help` 슬래시 출력) → 2건 검출, `--version` 플래그 정상 제외.
41
+ - graceful fallback 검증: claude/codex/agy 미설치(ENOENT) + grok(대상 외) + copilot(gh ext 미설치, 0건) → 큐레이션 유지, 파일 미기록.
42
+
43
+ ### UR-0021 완료
44
+ 1단계(레지스트리/CLI/hint) → 2단계(dispatch/handoff 주입/MCP 73) → **3단계(--help probe 자동 refresh)** 로 "항상 최신화 3중 경로" 완성.
45
+
3
46
  ## 1.9.266 — 2026-06-01 — UR-0021 2단계: dispatch 슬래시 명령 자동 주입 + handoff 노출 + MCP 73
4
47
 
5
48
  **🤖 서브에이전트 dispatch/handoff 에 각 에이전트 슬래시 명령을 자동 노출 — "알맞은 슬래시 명령으로 작업" (사용자 명시 UR-0021 2단계).**
package/bin/harness.js CHANGED
@@ -7,7 +7,7 @@ const cp = require('child_process');
7
7
  const os = require('os'); // 1.9.178: _publishToNpm 에서 os.tmpdir() 사용 (전역 import)
8
8
  const readline = require('readline');
9
9
 
10
- const VERSION = '1.9.266';
10
+ const VERSION = '1.9.268';
11
11
 
12
12
  // 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
13
13
  // leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
@@ -829,19 +829,20 @@ async function resolveInstallOptions(root, opts = {}) {
829
829
  { label: 'Claude (ANTHROPIC_API_KEY 또는 claude CLI)', description: '추론력 최고 — 코드 작성/리뷰 기본', id: 'claude' },
830
830
  { label: 'Codex (OpenAI codex CLI)', description: 'OpenAI 코드 모델', id: 'codex' },
831
831
  { label: 'Antigravity (agy CLI)', description: 'Google Antigravity 멀티모달 에이전트 (1.9.248~)', id: 'agy' },
832
+ { label: 'Grok (xAI grok-cli)', description: 'xAI Grok CLI — grok-beta 등 (1.9.268~)', id: 'grok' },
832
833
  { label: 'Copilot (gh extension)', description: 'GitHub Copilot CLI', id: 'copilot' },
833
834
  { label: 'Ollama (로컬 LLM — llama3/qwen 등)', description: 'http://localhost:11434 — 무료/오프라인', id: 'ollama' }
834
835
  ], { defaults: [] });
835
836
  agentsOptIn = picked.length ? picked.map(p => p.id) : 'none';
836
837
  } else {
837
838
  log('\nCLI 에이전트 활성화 (복수 선택 — 콤마로 구분, opt-in):');
838
- log(' 1) claude 2) codex 3) agy (Antigravity) 4) copilot 5) ollama (예: 1,5 또는 all 또는 none)');
839
+ log(' 1) claude 2) codex 3) agy (Antigravity) 4) grok (xAI) 5) copilot 6) ollama (예: 1,6 또는 all 또는 none)');
839
840
  const a = (await ask('선택 [none]: ')).trim().toLowerCase();
840
- if (a === 'all') agentsOptIn = ['claude', 'codex', 'agy', 'copilot', 'ollama'];
841
+ if (a === 'all') agentsOptIn = ['claude', 'codex', 'agy', 'grok', 'copilot', 'ollama'];
841
842
  else if (!a || a === 'none' || a === '0') agentsOptIn = 'none';
842
843
  else {
843
- const map = { '1': 'claude', '2': 'codex', '3': 'agy', '4': 'copilot', '5': 'ollama' };
844
- const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','agy','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
844
+ const map = { '1': 'claude', '2': 'codex', '3': 'agy', '4': 'grok', '5': 'copilot', '6': 'ollama' };
845
+ const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','agy','grok','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
845
846
  agentsOptIn = picks.length ? picks : 'none';
846
847
  }
847
848
  }
@@ -1014,6 +1015,7 @@ async function install(root, opts = {}) {
1014
1015
  LEERNESS_ENABLE_CLAUDE: enable('claude') ? '1' : '0',
1015
1016
  LEERNESS_ENABLE_CODEX: enable('codex') ? '1' : '0',
1016
1017
  LEERNESS_ENABLE_AGY: enable('agy') ? '1' : '0',
1018
+ LEERNESS_ENABLE_GROK: enable('grok') ? '1' : '0',
1017
1019
  LEERNESS_ENABLE_COPILOT: enable('copilot') ? '1' : '0',
1018
1020
  LEERNESS_ENABLE_OLLAMA: enable('ollama') ? '1' : '0',
1019
1021
  LEERNESS_SKILL_DISCOVER_URL: '',
@@ -2907,6 +2909,9 @@ function _selfTestCases() {
2907
2909
  { name: '_shellGuardAnalyze: bash && → 문제 없음', run: () => _shellGuardAnalyze('a && b', { shell: 'bash' }).issues.length === 0 },
2908
2910
  { name: 'AGENT_SLASH_COMMANDS: claude/codex/agy/grok/copilot 5종 + 명령 보유', run: () => { const ids = Object.keys(AGENT_SLASH_COMMANDS); return ['claude', 'codex', 'agy', 'grok', 'copilot'].every(id => ids.includes(id) && AGENT_SLASH_COMMANDS[id].commands.length > 0); } },
2909
2911
  { name: '_agentSlashHint: grok 슬래시 요약 + copilot 하위명령 라벨', run: () => { const g = _agentSlashHint('.', 'grok'); const c = _agentSlashHint('.', 'copilot'); return !!g && g.count > 0 && /Grok/.test(g.summary) && !!c && c.invoke === 'subcommand' && /하위명령/.test(c.summary); } },
2912
+ { name: '_parseSlashFromHelp: 슬래시 검출 + 플래그 제외 (1.9.267)', run: () => { const r = _parseSlashFromHelp(' /help show help\n /model switch model\n --version print version\n', 'slash'); return r.length === 2 && r[0].cmd === '/help' && /show help/.test(r[0].desc) && !r.some(c => /version/.test(c.cmd)); } },
2913
+ { name: '_parseSlashFromHelp: subcommand 모드 들여쓰기 파싱 (1.9.267)', run: () => { const r = _parseSlashFromHelp('Commands:\n suggest 제안\n explain 설명\n', 'subcommand'); return r.length >= 2 && r.some(c => c.cmd === 'suggest') && r.some(c => c.cmd === 'explain'); } },
2914
+ { name: 'EXTERNAL_AGENTS: grok 정식 provider 승격 (1.9.268)', run: () => { const g = EXTERNAL_AGENTS.find(a => a.id === 'grok'); return !!g && g.bin === 'grok' && g.envFlag === 'LEERNESS_ENABLE_GROK' && EXTERNAL_AGENTS.length === 6; } },
2910
2915
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
2911
2916
  ];
2912
2917
  }
@@ -3562,7 +3567,7 @@ function commandsCmd(root) {
3562
3567
  { cmd: 'resume [path]', desc: 'auto-resume-plan 적용 (1.9.203)' },
3563
3568
  { cmd: 'route <task-type>', desc: '작업 유형 분류 (11종)' },
3564
3569
  { cmd: 'agents list|check|quota|dispatch|multi', desc: '외부 AI CLI 오케스트레이션' },
3565
- { cmd: 'slash-commands [agent] [--json --record --detect]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 (1.9.265, UR-0021)' },
3570
+ { cmd: 'slash-commands [agent] [--json --record --detect --refresh]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 + --help probe 자동 갱신 (1.9.265~267, UR-0021)' },
3566
3571
  { cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)' },
3567
3572
  { cmd: 'review <file> --persona <ids>', desc: '페르소나 리뷰 (1.9.29)' },
3568
3573
  { cmd: 'brainstorm "<topic>" [--include-code]', desc: '워크스페이스 회수 + 코드 grep' }
@@ -9415,6 +9420,7 @@ const _LEERNESS_NONSECRET_KEYS = new Set([
9415
9420
  'LEERNESS_ENABLE_CLAUDE', // 활성화 플래그
9416
9421
  'LEERNESS_ENABLE_CODEX',
9417
9422
  'LEERNESS_ENABLE_AGY',
9423
+ 'LEERNESS_ENABLE_GROK',
9418
9424
  'LEERNESS_ENABLE_COPILOT',
9419
9425
  'LEERNESS_ENABLE_OLLAMA',
9420
9426
  'LEERNESS_SKILL_DISCOVER_URL', // 공개 URL
@@ -10071,6 +10077,10 @@ const EXTERNAL_AGENTS = [
10071
10077
  // 기존 LEERNESS_ENABLE_AGY 환경변수는 LEERNESS_ENABLE_AGY 로 교체. provider id 도 'agy' 로 통일.
10072
10078
  { id: 'agy', bin: 'agy', envFlag: 'LEERNESS_ENABLE_AGY', versionArgs: ['--version'], desc: 'Google Antigravity CLI (멀티모달 에이전트, --yolo 워크스페이스 수정 가능)',
10073
10079
  installCmd: 'npm i -g @google/antigravity-cli', installHint: 'https://antigravity.google.com (Antigravity IDE/CLI)' },
10080
+ // 1.9.268: grok 정식 provider 승격 (1.9.266 후속 task) — 기존 슬래시 레지스트리(grok)만 보유하던 상태에서 EXTERNAL_AGENTS 로 편입.
10081
+ // → provider cycle / setup-agents / dispatch / slash-commands --refresh probe 가 grok 도 자동 처리.
10082
+ { id: 'grok', bin: 'grok', envFlag: 'LEERNESS_ENABLE_GROK', versionArgs: ['--version'], desc: 'xAI Grok CLI (커뮤니티 grok-cli, /model grok-beta 등)',
10083
+ installCmd: 'npm i -g @vibe-kit/grok-cli', installHint: 'https://github.com/superagent-ai/grok-cli (xAI API 키 필요 — /login 또는 GROK_API_KEY)' },
10074
10084
  { id: 'copilot', bin: 'gh', envFlag: 'LEERNESS_ENABLE_COPILOT', versionArgs: ['copilot', '--version'], desc: 'GitHub Copilot CLI (gh copilot)',
10075
10085
  installCmd: 'gh extension install github/gh-copilot', installHint: 'https://github.com/github/gh-copilot (gh CLI 선행 설치 필요)' },
10076
10086
  // 1.9.146: Ollama 추가 (사용자 명시 요청 #3) — 로컬 LLM, HTTP API 11434
@@ -10081,7 +10091,7 @@ const EXTERNAL_AGENTS = [
10081
10091
  // 1.9.265 (사용자 명시 UR-0021): CLI AI 에이전트별 슬래시 명령어 레지스트리.
10082
10092
  // 목적: 각 CLI (claude/codex/agy/grok/copilot) 의 슬래시 명령어를 큐레이션·기록하고, 서브에이전트 dispatch 시 알맞게 참조.
10083
10093
  // "항상 최신화" 3중 경로: (1) 빌트인은 leerness 릴리스마다 갱신(asOf), (2) 사용자 .harness/agent-slash-commands.json override 병합,
10084
- // (3) [2~3단계 예정] CLI `--help`/`/help` probe 자동 refresh. 0-dep·offline-first 라서 1단계는 (1)+(2) 큐레이션 기반.
10094
+ // (3) [1.9.267 완료] `slash-commands --refresh` — 설치된 CLI `--help` probe 자동 갱신(best-effort, 검출 0건이면 큐레이션 유지). 0-dep·offline-first.
10085
10095
  // 주의: 외부 CLI 의 슬래시 명령은 버전마다 변동 → asOf 표기 + 사용자 override 우선. type='subcommand' 는 슬래시가 아닌 하위명령(예: gh copilot).
10086
10096
  const AGENT_SLASH_COMMANDS = {
10087
10097
  claude: {
@@ -10130,8 +10140,8 @@ const AGENT_SLASH_COMMANDS = {
10130
10140
  ]
10131
10141
  },
10132
10142
  grok: {
10133
- label: 'xAI Grok CLI', asOf: '1.9.265', invoke: 'slash',
10134
- note: '커뮤니티/공식 Grok CLI(예: @vibe-kit/grok-cli) 기준 큐레이션 배포판마다 차이 가능. 사용자 override 권장.',
10143
+ label: 'xAI Grok CLI', asOf: '1.9.268', invoke: 'slash',
10144
+ note: '1.9.268 정식 provider 승격 — slash-commands --refresh 로 자동 probe 가능. 커뮤니티 Grok CLI(@vibe-kit/grok-cli) 기준 큐레이션, 배포판마다 차이 가능.',
10135
10145
  commands: [
10136
10146
  { cmd: '/help', desc: '명령 목록' },
10137
10147
  { cmd: '/clear', desc: '대화 초기화' },
@@ -10219,6 +10229,90 @@ function _agentSlashHint(root, agentId) {
10219
10229
  summary: `${def.label}${invokeNote}: ${top}${def.commands.length > 8 ? ' …' : ''}`
10220
10230
  };
10221
10231
  }
10232
+ // 1.9.267 (UR-0021 3단계): CLI `--help` 출력에서 슬래시 명령/하위명령을 best-effort 파싱.
10233
+ // 순수 함수 (부작용 0, selftest 가능). invoke='slash' → `/cmd desc` 패턴, invoke='subcommand' → 들여쓰기 `cmd desc` 패턴.
10234
+ // CLI 플래그(--foo)·옵션은 의도적으로 제외. 검출 0건이면 큐레이션 빌트인을 그대로 유지(파괴적 덮어쓰기 방지).
10235
+ function _parseSlashFromHelp(text, invoke = 'slash') {
10236
+ const out = [];
10237
+ const seen = new Set();
10238
+ const lines = String(text || '').split(/\r?\n/);
10239
+ for (const raw of lines) {
10240
+ const ln = raw.replace(/\x1b\[[0-9;]*m/g, ''); // ANSI 색상 제거
10241
+ if (invoke === 'subcommand') {
10242
+ // 들여쓰기된 하위명령: " suggest 설명" — 슬래시/대시 시작 제외, 2칸+ 공백 구분
10243
+ const m = ln.match(/^\s{2,}([a-z][a-z0-9][\w-]*)\s{2,}(\S.*)$/);
10244
+ if (m && !/^--/.test(m[1])) {
10245
+ const cmd = m[1];
10246
+ if (!seen.has(cmd) && cmd.length <= 24) { seen.add(cmd); out.push({ cmd, desc: m[2].trim().slice(0, 80) }); }
10247
+ }
10248
+ continue;
10249
+ }
10250
+ // 슬래시 명령: "/help 설명" 또는 " /help - 설명"
10251
+ const m = ln.match(/^\s*(\/[a-zA-Z][\w-]*)(?:\s+[-–:]?\s*(.*))?$/);
10252
+ if (m) {
10253
+ const cmd = m[1];
10254
+ if (!seen.has(cmd) && cmd.length <= 24) { seen.add(cmd); out.push({ cmd, desc: (m[2] || '').trim().slice(0, 80) }); }
10255
+ }
10256
+ }
10257
+ return out;
10258
+ }
10259
+ // 1.9.267 (UR-0021 3단계): 설치된 CLI 의 `--help` 를 spawn 해 슬래시 명령을 probe (best-effort, offline-first).
10260
+ // extDef: EXTERNAL_AGENTS 항목 (bin/versionArgs 보유). copilot 처럼 subcommand 인 경우 versionArgs 의 base(예: ['copilot']) + '--help'.
10261
+ // 실패/0건이면 ok:false + reason — 호출부가 큐레이션 빌트인 유지. 타임아웃 5s (행 방지).
10262
+ function _probeAgentSlash(extDef, opts = {}) {
10263
+ const invoke = opts.invoke || 'slash';
10264
+ const timeout = opts.timeout || 5000;
10265
+ if (!extDef || !extDef.bin) return { ok: false, commands: [], reason: 'probe 대상 정보 없음' };
10266
+ // versionArgs 가 ['copilot','--version'] 처럼 base 를 가지면 그 base 를 help 에 재사용
10267
+ const base = Array.isArray(extDef.versionArgs) ? extDef.versionArgs.filter(a => !/^--?(version|v)$/i.test(a)) : [];
10268
+ const probeArgs = [...base, '--help'];
10269
+ let r;
10270
+ try {
10271
+ r = cp.spawnSync(extDef.bin, probeArgs, { encoding: 'utf8', timeout, windowsHide: true });
10272
+ } catch (e) {
10273
+ return { ok: false, commands: [], reason: `spawn 예외: ${e.message}` };
10274
+ }
10275
+ if (r.error) return { ok: false, commands: [], reason: `spawn 실패: ${r.error.code || r.error.message}` };
10276
+ const text = `${r.stdout || ''}\n${r.stderr || ''}`;
10277
+ const commands = _parseSlashFromHelp(text, invoke);
10278
+ if (!commands.length) return { ok: false, commands: [], reason: `${extDef.bin} --help 출력에서 ${invoke === 'subcommand' ? '하위명령' : '슬래시 명령'} 미검출 (큐레이션 유지)` };
10279
+ return { ok: true, commands, reason: `${commands.length}건 검출`, probeArgs: probeArgs.join(' ') };
10280
+ }
10281
+ // 1.9.267 (UR-0021 3단계): probe 결과를 .harness/agent-slash-commands.json 으로 병합 기록 (성공 agent 만 갱신, 나머지 보존).
10282
+ function _refreshAgentSlashCommands(root, targets, opts = {}) {
10283
+ const merged = _loadAgentSlashCommands(root); // 현재(builtin+user) 상태 보존 기반
10284
+ const results = [];
10285
+ const agents = {};
10286
+ for (const [id, d] of Object.entries(merged)) {
10287
+ agents[id] = { label: d.label, asOf: d.asOf, invoke: d.invoke, note: d.note || undefined, commands: d.commands };
10288
+ }
10289
+ for (const id of targets) {
10290
+ const ext = EXTERNAL_AGENTS.find(a => a.id === id);
10291
+ const def = merged[id] || AGENT_SLASH_COMMANDS[id];
10292
+ if (!ext) { results.push({ id, ok: false, reason: 'probe 대상 아님 (EXTERNAL_AGENTS 미포함 — 사용자 override 전용 agent)' }); continue; }
10293
+ const pr = _probeAgentSlash(ext, { invoke: (def && def.invoke) || 'slash', timeout: opts.timeout });
10294
+ if (pr.ok) {
10295
+ agents[id] = {
10296
+ label: (def && def.label) || id,
10297
+ asOf: VERSION,
10298
+ invoke: (def && def.invoke) || 'slash',
10299
+ note: `probed via "${ext.bin} ${pr.probeArgs}" @ ${new Date().toISOString().slice(0, 10)}`,
10300
+ commands: pr.commands
10301
+ };
10302
+ results.push({ id, ok: true, count: pr.commands.length, reason: pr.reason });
10303
+ } else {
10304
+ results.push({ id, ok: false, reason: pr.reason });
10305
+ }
10306
+ }
10307
+ const anyOk = results.some(r => r.ok);
10308
+ let file = null;
10309
+ if (anyOk && !opts.dryRun) {
10310
+ file = _agentSlashFile(root);
10311
+ mkdirp(path.dirname(file));
10312
+ writeUtf8(file, JSON.stringify({ schemaVersion: 1, recordedAt: new Date().toISOString(), refreshedAt: new Date().toISOString(), agents }, null, 2) + '\n');
10313
+ }
10314
+ return { results, file, anyOk };
10315
+ }
10222
10316
  // 1.9.265 (UR-0021): leerness slash-commands [agent] [--json --record --detect]
10223
10317
  // 인자 없음 → 전체 에이전트 슬래시 명령 목록. <agent> → 단일. --record → 워크스페이스 기록. --detect → 설치된 CLI 만 표시.
10224
10318
  function slashCommandsCmd(root, agentArg, opts = {}) {
@@ -10240,6 +10334,28 @@ function slashCommandsCmd(root, agentArg, opts = {}) {
10240
10334
  log(` → 사용자 편집으로 최신화 가능 (agents.<id>.commands 배열)`);
10241
10335
  return;
10242
10336
  }
10337
+ // 1.9.267 (UR-0021 3단계): --refresh — 설치된 CLI 의 `--help` probe 로 레지스트리 자동 갱신.
10338
+ if (opts.refresh) {
10339
+ const targets = agentArg ? [agentArg] : Object.keys(all);
10340
+ const ref = _refreshAgentSlashCommands(root, targets, { dryRun: opts.dryRun });
10341
+ if (opts.json) {
10342
+ log(JSON.stringify({ refreshed: ref.anyOk && !opts.dryRun, dryRun: !!opts.dryRun, file: ref.file, results: ref.results }, null, 2));
10343
+ return;
10344
+ }
10345
+ const isTty0 = process.stdout && process.stdout.isTTY;
10346
+ const g0 = s => isTty0 ? `\x1b[32m${s}\x1b[0m` : s;
10347
+ const y0 = s => isTty0 ? `\x1b[33m${s}\x1b[0m` : s;
10348
+ const d0 = s => isTty0 ? `\x1b[2m${s}\x1b[0m` : s;
10349
+ log(`# leerness slash-commands --refresh (1.9.267, UR-0021 3단계)${opts.dryRun ? ' [dry-run]' : ''}`);
10350
+ for (const r of ref.results) {
10351
+ if (r.ok) log(` ${g0('✓')} ${r.id} — ${r.count}건 probe 검출`);
10352
+ else log(` ${y0('·')} ${r.id} — ${d0(r.reason)}`);
10353
+ }
10354
+ if (ref.anyOk && !opts.dryRun) log(g0(`\n✓ 갱신 기록: ${ref.file}`));
10355
+ else if (ref.anyOk && opts.dryRun) log(d0(`\n(dry-run — 기록 안 함. --refresh 만 주면 실제 기록)`));
10356
+ else log(y0(`\n⚠ probe 검출 0 — 큐레이션 빌트인 유지 (CLI 미설치 또는 --help 에 슬래시 명령 미노출)`));
10357
+ return;
10358
+ }
10243
10359
  const ids = agentArg ? [agentArg] : Object.keys(all);
10244
10360
  if (agentArg && !all[agentArg]) {
10245
10361
  if (opts.json) { log(JSON.stringify({ error: `unknown agent: ${agentArg}`, known: Object.keys(all) }, null, 2)); return; }
@@ -10273,11 +10389,11 @@ function slashCommandsCmd(root, agentArg, opts = {}) {
10273
10389
  for (const c of d.commands) log(` ${gr((c.cmd || '').padEnd(14))} ${dm(c.desc || '')}`);
10274
10390
  log('');
10275
10391
  }
10276
- log(dm(` 기록: leerness slash-commands --record · 단일: leerness slash-commands claude · 설치 감지: --detect`));
10392
+ log(dm(` 기록: leerness slash-commands --record · 단일: leerness slash-commands claude · 설치 감지: --detect · 자동 갱신: --refresh [--dry-run]`));
10277
10393
  }
10278
10394
 
10279
10395
  // 1.9.157: Provider Registry — 사용자 정의 provider 동적 추가 (.harness/providers.json)
10280
- // 빌트인 5종 (EXTERNAL_AGENTS) + 사용자 정의를 merge. OpenRouter / Bedrock / Groq 등 새 CLI 즉시 흡수 가능.
10396
+ // 빌트인 6종 (EXTERNAL_AGENTS: claude/codex/agy/grok/copilot/ollama) + 사용자 정의를 merge. OpenRouter / Bedrock 등 새 CLI 즉시 흡수 가능.
10281
10397
  // 파일 형식: { "schemaVersion": 1, "providers": [{ id, bin, envFlag, versionArgs, desc, installHint }] }
10282
10398
  function _providersFile(root) { return path.join(absRoot(root), '.harness', 'providers.json'); }
10283
10399
  function _readUserProviders(root) {
@@ -16352,7 +16468,7 @@ function mcpServeCmd(root) {
16352
16468
  { name: 'leerness_api_skill', description: '1.9.245 (사용자 명시 UR-0015) — API 문서·관련링크 자동 캐시. 공식 API 문서 URL을 fetch 하고 1단계 same-domain 관련 링크까지 정리 → .harness/api-skills/<id>.md 저장. 후속 같은 API 관련 작업 시 자동 참조. 응답: list (skills 배열) / show (전체 본문) / match (task 매칭 결과). 인자: { path?, sub ("list"|"show"|"match"|"add"|"drop"), url? (add), id? (show/drop), query? (match), direction? (add: 구현 방향 텍스트) }. 외부 AI가 "이 프로젝트 어떤 API 문서가 정리되어 있나?" / "내 작업과 매칭되는 API skill 있나?" 회수.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, sub: { type: 'string', enum: ['list', 'show', 'match', 'add', 'drop'] }, url: { type: 'string' }, id: { type: 'string' }, query: { type: 'string' }, direction: { type: 'string' } }, required: ['sub'] } },
16353
16469
  { name: 'leerness_selftest', description: '1.9.258/259 — 설치된 leerness 바이너리의 코어 함수(보안 _isSecretKey / 버전 compareVer / 인코딩 _classifyCJK 등) 무결성 자가 검증. 응답: { version, total, pass, fail, ok, results[] }. 외부 AI/CI 가 "이 leerness 설치가 정상인가?(npx 캐시 손상·부분 설치 감지)" 를 1초 내 확인. 인자: 없음.', inputSchema: { type: 'object', properties: {} } },
16354
16470
  { name: 'leerness_shell_guard', description: '1.9.260/261 (사용자 명시 UR-0020) — 터미널 명령 셸 호환성 린터. 외부 AI 가 명령을 실행하기 전에 셸 호환성 문제를 사전 점검. 예: Windows PowerShell 5.1 은 && / || 미지원 → A; if ($?) { B } 제안. 6 규칙(ps5-chain/ps-devnull/ps-inline-env/ps-rm-rf/cmd-semicolon/ps-version-unknown) + 과거 실패 회수(.harness/shell-failures.json). 응답: { shell, psVersion, issues[{rule,severity,detail,suggestion}], pastSame, pastSimilar, ok }. 인자: { command (required), path? }. 현재 셸/PS 버전 자동 감지.', inputSchema: { type: 'object', properties: { command: { type: 'string' }, path: { type: 'string' } }, required: ['command'] } },
16355
- { name: 'leerness_slash_commands', description: '1.9.265/266 (사용자 명시 UR-0021) — CLI AI 에이전트별 슬래시 명령어 레지스트리. 외부 AI(메인)가 sub-agent(codex/agy/claude/grok/copilot)를 호출할 때 각 에이전트에 알맞는 슬래시 명령을 참조. 빌트인 + 사용자 .harness/agent-slash-commands.json override 병합. 응답: { agents: { <id>: { label, asOf, invoke(slash|subcommand), note, source, count, commands[{cmd,desc}] } } }. 인자: { path?, agent? (생략 시 전체) }. agent 지정 시 단일.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, agent: { type: 'string' } } } }
16471
+ { name: 'leerness_slash_commands', description: '1.9.265/266 (사용자 명시 UR-0021) — CLI AI 에이전트별 슬래시 명령어 레지스트리. 외부 AI(메인)가 sub-agent(codex/agy/claude/grok/copilot)를 호출할 때 각 에이전트에 알맞는 슬래시 명령을 참조. 빌트인 + 사용자 .harness/agent-slash-commands.json override 병합. 응답: { agents: { <id>: { label, asOf, invoke(slash|subcommand), note, source, count, commands[{cmd,desc}] } } }. 인자: { path?, agent? (생략 시 전체), refresh? (1.9.267 — 설치된 CLI --help probe 자동 갱신), dryRun? }. agent 지정 시 단일.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, agent: { type: 'string' }, refresh: { type: 'boolean' }, dryRun: { type: 'boolean' } } } }
16356
16472
  ];
16357
16473
 
16358
16474
  function send(obj) {
@@ -16566,6 +16682,8 @@ function mcpServeCmd(root) {
16566
16682
  case 'leerness_slash_commands':
16567
16683
  // 1.9.266 (1.9.265, UR-0021): CLI 에이전트 슬래시 명령 레지스트리
16568
16684
  cliArgs = ['slash-commands', ...(args.agent ? [String(args.agent)] : []), '--path', targetPath, '--json'];
16685
+ if (args.refresh === true) cliArgs.push('--refresh');
16686
+ if (args.dryRun === true) cliArgs.push('--dry-run');
16569
16687
  break;
16570
16688
  default:
16571
16689
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
@@ -20448,7 +20566,7 @@ async function main() {
20448
20566
  // 1.9.265: leerness slash-commands [agent] — CLI 에이전트별 슬래시 명령어 레지스트리 (사용자 명시 UR-0021)
20449
20567
  if (cmd === 'slash-commands' || cmd === 'slash' || cmd === 'agent-slash') {
20450
20568
  const agentArg = args[1] && !args[1].startsWith('-') ? args[1] : null;
20451
- return slashCommandsCmd(arg('--path', process.cwd()), agentArg, { json: has('--json'), record: has('--record'), force: has('--force'), detect: has('--detect') });
20569
+ return slashCommandsCmd(arg('--path', process.cwd()), agentArg, { json: has('--json'), record: has('--record'), force: has('--force'), detect: has('--detect'), refresh: has('--refresh'), dryRun: has('--dry-run') });
20452
20570
  }
20453
20571
  // 1.9.245: API skill cache — 공식 문서·관련링크 자동 정리 (사용자 명시 UR-0015)
20454
20572
  if (cmd === 'api-skill') return apiSkillCmd(arg('--path', process.cwd()), args[1] || 'help');
@@ -20617,5 +20735,6 @@ module.exports = {
20617
20735
  // 1.9.263: shell 실패 메모리 + 환경 버전 변동 (UR-0020 3단계) — handoff 통합 단위 테스트
20618
20736
  _shellFailuresPath, _loadShellFailures, _recordShellFailure, _shellEnvDrift,
20619
20737
  // 1.9.265: CLI 에이전트 슬래시 명령어 레지스트리 (UR-0021 1단계) — 단위 테스트
20620
- AGENT_SLASH_COMMANDS, _agentSlashFile, _loadAgentSlashCommands, _recordAgentSlashCommands, _agentSlashHint, slashCommandsCmd
20738
+ AGENT_SLASH_COMMANDS, _agentSlashFile, _loadAgentSlashCommands, _recordAgentSlashCommands, _agentSlashHint, slashCommandsCmd,
20739
+ _parseSlashFromHelp, _probeAgentSlash, _refreshAgentSlashCommands
20621
20740
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.266",
3
+ "version": "1.9.268",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -797,23 +797,24 @@ total++;
797
797
  total++;
798
798
  {
799
799
  // agents list — claude가 환경변수 + PATH 둘 다 충족 시 ready
800
- const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
800
+ const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_GROK: '0', LEERNESS_ENABLE_COPILOT: '0' };
801
801
  const r1 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list'], { encoding: 'utf8', timeout: 15000, env: env1 });
802
802
  const okList = r1.status === 0
803
803
  && /외부 AI CLI 오케스트레이션 \(1\.9\.30\)/.test(r1.stdout)
804
804
  && /\| claude \|/.test(r1.stdout)
805
805
  && /\| codex \|/.test(r1.stdout)
806
806
  && /\| agy \|/.test(r1.stdout)
807
+ && /\| grok \|/.test(r1.stdout)
807
808
  && /\| copilot \|/.test(r1.stdout);
808
809
  // env 모두 0 → 비활성
809
- // 1.9.146: Ollama 추가 → 5 CLI
810
- const env2 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0', LEERNESS_ENABLE_OLLAMA: '0' };
810
+ // 1.9.146: Ollama 추가 → 5 CLI · 1.9.268: grok 정식 승격 → 6 CLI
811
+ const env2 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_GROK: '0', LEERNESS_ENABLE_COPILOT: '0', LEERNESS_ENABLE_OLLAMA: '0' };
811
812
  const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list', '--json'], { encoding: 'utf8', timeout: 15000, env: env2 });
812
813
  let parsed = null;
813
814
  try { parsed = JSON.parse(r2.stdout); } catch {}
814
- const okJson = parsed && Array.isArray(parsed.agents) && parsed.agents.length === 5 && parsed.agents.every(a => a.status !== 'ready');
815
+ const okJson = parsed && Array.isArray(parsed.agents) && parsed.agents.length === 6 && parsed.agents.every(a => a.status !== 'ready');
815
816
  const ok = okList && okJson;
816
- console.log(ok ? '✓ B(1.9.30+1.9.146) agents list: 5 CLI 정의 (claude/codex/agy/copilot/ollama)' : `✗ agents list 실패 (list=${okList} json=${okJson})`);
817
+ console.log(ok ? '✓ B(1.9.30+1.9.268) agents list: 6 CLI 정의 (claude/codex/agy/grok/copilot/ollama)' : `✗ agents list 실패 (list=${okList} json=${okJson})`);
817
818
  if (!ok) { failed++; console.log(r1.stdout.slice(0, 500)); }
818
819
  }
819
820
 
@@ -838,24 +839,25 @@ total++;
838
839
  total++;
839
840
  {
840
841
  // agents quota — env=0 시 모두 disabled/not-installed, 안내 메시지 포함
841
- const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
842
+ const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_GROK: '0', LEERNESS_ENABLE_COPILOT: '0' };
842
843
  const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'quota'], { encoding: 'utf8', timeout: 15000, env });
843
844
  const okText = r.status === 0
844
845
  && /외부 AI CLI quota 추정 \(1\.9\.31\)/.test(r.stdout)
845
846
  && /\| claude \|/.test(r.stdout)
846
847
  && /\| codex \|/.test(r.stdout)
847
848
  && /\| agy \|/.test(r.stdout)
849
+ && /\| grok \|/.test(r.stdout)
848
850
  && /\| copilot \|/.test(r.stdout)
849
851
  && /provider 대시보드 참조/.test(r.stdout);
850
852
  // JSON 출력
851
853
  const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'quota', '--json'], { encoding: 'utf8', timeout: 15000, env });
852
854
  let parsed = null;
853
855
  try { parsed = JSON.parse(r2.stdout); } catch {}
854
- // 1.9.146: Ollama 추가 → 5 CLI
855
- const okJson = parsed && Array.isArray(parsed.quota) && parsed.quota.length === 5
856
+ // 1.9.146: Ollama 추가 → 5 CLI · 1.9.268: grok 정식 승격 → 6 CLI
857
+ const okJson = parsed && Array.isArray(parsed.quota) && parsed.quota.length === 6
856
858
  && parsed.quota.every(q => typeof q.id === 'string' && typeof q.status === 'string' && (q.hint === null || typeof q.hint === 'string'));
857
859
  const ok = okText && okJson;
858
- console.log(ok ? '✓ B(1.9.31+1.9.146) agents quota: 5 CLI 사용량/안내' : `✗ quota 실패 (text=${okText} json=${okJson})`);
860
+ console.log(ok ? '✓ B(1.9.31+1.9.268) agents quota: 6 CLI 사용량/안내' : `✗ quota 실패 (text=${okText} json=${okJson})`);
859
861
  if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
860
862
  }
861
863