- 홈
- Documentation
- 런타임 도구
- 슬래시 명령어 내부 구조
슬래시 명령어 내부 구조
이 문서는 coding-agent에서 슬래시 명령어가 어떻게 탐지되고, 중복 제거되며, 인터랙티브 모드에서 노출되고, 프롬프트 시점에 확장되는지를 설명합니다.
구현 파일
섹션 제목: “구현 파일”src/extensibility/slash-commands.tssrc/capability/slash-command.tssrc/discovery/builtin.tssrc/discovery/claude.tssrc/discovery/codex.tssrc/discovery/claude-plugins.tssrc/capability/index.tssrc/discovery/helpers.tssrc/session/agent-session.tssrc/modes/interactive-mode.tssrc/modes/controllers/input-controller.tssrc/modes/utils/ui-helpers.tssrc/modes/controllers/command-controller.ts
1) 탐지 모델
섹션 제목: “1) 탐지 모델”슬래시 명령어는 명령어 이름을 키(key: cmd => cmd.name)로 사용하는 기능(id: "slash-commands")입니다.
기능 레지스트리는 등록된 모든 프로바이더를 로드하고, 프로바이더 우선순위 내림차순으로 정렬한 후 첫 번째 항목 우선 방식으로 키 기반 중복 제거를 수행합니다.
프로바이더 우선순위
섹션 제목: “프로바이더 우선순위”현재 슬래시 명령어 프로바이더와 우선순위:
native(OMP) — 우선순위100claude— 우선순위80claude-plugins— 우선순위70codex— 우선순위70
동점 처리: 동일 우선순위의 프로바이더는 등록 순서를 유지합니다. 현재 import 순서상 claude-plugins가 codex보다 먼저 등록되므로, 이름이 충돌할 경우 플러그인 명령어가 codex 명령어보다 우선합니다.
이름 충돌 처리
섹션 제목: “이름 충돌 처리”slash-commands의 경우, 충돌은 기능 중복 제거를 통해 엄격하게 처리됩니다:
- 가장 높은 우선순위의 항목이
result.items에 유지됩니다. - 낮은 우선순위의 중복 항목은
result.all에만 남고_shadowed = true로 표시됩니다.
이는 프로바이더 간뿐만 아니라, 단일 프로바이더가 중복 이름을 반환하는 경우에도 적용됩니다.
파일 스캔 동작
섹션 제목: “파일 스캔 동작”프로바이더는 대부분 loadFilesFromDir(...)를 사용하며, 현재 다음과 같이 동작합니다:
- 기본값은 비재귀 매칭(
*.md) gitignore: true,hidden: false로 네이티브 glob 사용- 매칭된 각 파일을 읽고
SlashCommand로 변환
따라서 숨김 파일/디렉터리는 로드되지 않으며, 무시된 경로는 건너뜁니다.
2) 프로바이더별 소스 경로 및 로컬 우선순위
섹션 제목: “2) 프로바이더별 소스 경로 및 로컬 우선순위”native 프로바이더 (builtin.ts)
섹션 제목: “native 프로바이더 (builtin.ts)”검색 루트는 .xcsh 디렉터리에서 가져옵니다:
- 프로젝트:
<cwd>/.xcsh/commands/*.md - 사용자:
~/.xcsh/agent/commands/*.md
getConfigDirs()는 프로젝트를 먼저, 그다음 사용자 순으로 반환하므로, 이름이 충돌할 경우 프로젝트 네이티브 명령어가 사용자 네이티브 명령어보다 우선합니다.
claude 프로바이더 (claude.ts)
섹션 제목: “claude 프로바이더 (claude.ts)”로드 대상:
- 사용자:
~/.claude/commands/*.md - 프로젝트:
<cwd>/.claude/commands/*.md
프로바이더는 사용자 항목을 프로젝트 항목보다 먼저 추가하므로, 이 프로바이더 내에서 이름이 충돌할 경우 사용자 Claude 명령어가 프로젝트 Claude 명령어보다 우선합니다.
codex 프로바이더 (codex.ts)
섹션 제목: “codex 프로바이더 (codex.ts)”로드 대상:
- 사용자:
~/.codex/commands/*.md - 프로젝트:
<cwd>/.codex/commands/*.md
양쪽을 로드한 후 사용자 우선 순서로 평탄화하므로, 충돌 시 사용자 Codex 명령어가 프로젝트 Codex 명령어보다 우선합니다.
Codex 명령어 내용은 프론트매터 제거(parseFrontmatter)로 파싱되며, 명령어 이름은 프론트매터의 name 필드로 재정의할 수 있습니다. 지정하지 않으면 파일명이 사용됩니다.
claude-plugins 프로바이더 (claude-plugins.ts)
섹션 제목: “claude-plugins 프로바이더 (claude-plugins.ts)”~/.claude/plugins/installed_plugins.json에서 플러그인 명령어 루트를 로드한 후, <pluginRoot>/commands/*.md를 스캔합니다.
순서는 레지스트리 반복 순서와 해당 JSON 데이터의 플러그인별 항목 순서를 따릅니다. 추가적인 정렬 단계는 없습니다.
3) 런타임 FileSlashCommand로의 구체화
섹션 제목: “3) 런타임 FileSlashCommand로의 구체화”src/extensibility/slash-commands.ts의 loadSlashCommands()는 기능 항목을 프롬프트 시점에 사용되는 FileSlashCommand 객체로 변환합니다.
각 명령어에 대해:
- 프론트매터/본문 파싱(
parseFrontmatter) - 설명 소스:
frontmatter.description이 있으면 해당 값 사용- 없으면 본문의 첫 번째 비어있지 않은 줄(트림 후, 최대 60자, 초과 시
...)
- 파싱된 본문을 실행 가능한 템플릿 콘텐츠로 유지
via Claude Code Project와 같은 표시용 소스 문자열 계산
프론트매터 파싱 심각도는 소스에 따라 다릅니다:
native레벨 → 파싱 오류는fataluser/project레벨 → 파싱 오류는warn이며 폴백 파싱 사용
번들 폴백 명령어
섹션 제목: “번들 폴백 명령어”파일시스템/프로바이더 명령어 이후, 내장 명령어 템플릿(EMBEDDED_COMMAND_TEMPLATES)이 이름이 아직 존재하지 않는 경우 추가됩니다.
현재 내장 세트는 src/task/commands.ts에서 가져오며 폴백으로 사용됩니다(source: "bundled").
4) 인터랙티브 모드: 명령어 목록의 출처
섹션 제목: “4) 인터랙티브 모드: 명령어 목록의 출처”인터랙티브 모드는 자동완성과 명령어 라우팅을 위해 여러 명령어 소스를 결합합니다.
생성 시 다음으로부터 대기 중인 명령어 목록을 구성합니다:
- 빌트인 명령어(
BUILTIN_SLASH_COMMANDS, 선택된 명령어에 대한 인자 완성 및 인라인 힌트 포함) - 확장 등록된 슬래시 명령어(
extensionRunner.getRegisteredCommands(...)) - TypeScript 커스텀 명령어(
session.customCommands), 슬래시 명령어 레이블로 매핑 skills.enableSkillCommands가 활성화된 경우 선택적 스킬 명령어(/skill:<name>)
이후 init()이 refreshSlashCommandState(...)를 호출하여 파일 기반 명령어를 로드하고 다음을 포함하는 CombinedAutocompleteProvider 하나를 설치합니다:
- 위의 대기 중인 명령어
- 탐지된 파일 기반 명령어
refreshSlashCommandState(...)는 또한 session.setSlashCommands(...)를 업데이트하여 프롬프트 확장이 동일한 탐지된 파일 명령어 세트를 사용하도록 합니다.
갱신 수명 주기
섹션 제목: “갱신 수명 주기”슬래시 명령어 상태는 다음 시점에 갱신됩니다:
- 인터랙티브 초기화 중
/move로 작업 디렉터리 변경 후(handleMoveCommand가resetCapabilities()를 호출한 다음refreshSlashCommandState(newCwd)호출)
명령어 디렉터리에 대한 지속적인 파일 감시자는 없습니다.
기타 노출 방식
섹션 제목: “기타 노출 방식”확장 대시보드도 slash-commands 기능을 로드하여 _shadowed 중복 항목을 포함한 활성/섀도잉된 명령어 항목을 표시합니다.
5) 프롬프트 파이프라인 위치
섹션 제목: “5) 프롬프트 파이프라인 위치”AgentSession.prompt(...)의 슬래시 처리 순서(expandPromptTemplates !== false인 경우):
- 확장 명령어 (
#tryExecuteExtensionCommand)
/name이 확장 등록 명령어와 일치하면 핸들러가 즉시 실행되고 프롬프트가 반환됩니다. - TypeScript 커스텀 명령어 (
#tryExecuteCustomCommand)
경계 처리만: 일치하면 실행되며 다음을 반환할 수 있습니다:string→ 해당 문자열로 프롬프트 텍스트 교체void/undefined→ 처리된 것으로 간주; LLM 프롬프트 없음
- 파일 기반 슬래시 명령어 (
expandSlashCommand)
텍스트가 여전히/로 시작하면 마크다운 명령어 확장을 시도합니다. - 프롬프트 템플릿 (
expandPromptTemplate)
슬래시/커스텀 처리 이후 적용됩니다. - 전달
- 유휴 상태: 프롬프트가 즉시 에이전트로 전송됨
- 스트리밍 중:
streamingBehavior에 따라 steer/follow-up으로 큐에 추가됨
이것이 슬래시 명령어 확장이 프롬프트 템플릿 확장보다 먼저 수행되는 이유이며, 커스텀 명령어가 파일 명령어 매칭 전에 선행 슬래시를 변환할 수 있는 이유입니다.
6) 파일 기반 슬래시 명령어의 확장 시맨틱
섹션 제목: “6) 파일 기반 슬래시 명령어의 확장 시맨틱”expandSlashCommand(text, fileCommands) 동작:
- 텍스트가
/로 시작할 때만 실행 /이후 첫 번째 토큰에서 명령어 이름 파싱parseCommandArgs를 통해 나머지 텍스트에서 인자 파싱- 로드된
fileCommands에서 정확한 이름 매칭 검색 - 일치하면 다음을 적용:
- 위치 기반 교체:
$1,$2, … - 집계 교체:
$ARGUMENTS및$@ - 이후
{ args, ARGUMENTS, arguments }로prompt.render를 통한 템플릿 렌더링
- 위치 기반 교체:
- 일치하지 않으면 원본 텍스트를 그대로 반환
parseCommandArgs 주의사항
섹션 제목: “parseCommandArgs 주의사항”파서는 단순한 따옴표 인식 분리 방식입니다:
- 공백 유지를 위한
'단일'및"이중"따옴표 지원 - 따옴표 구분자 제거
- 백슬래시 이스케이프 규칙 미구현
- 짝이 맞지 않는 따옴표는 오류가 아님; 파서가 끝까지 소비
7) 알 수 없는 /... 동작
섹션 제목: “7) 알 수 없는 /... 동작”알 수 없는 슬래시 입력은 핵심 슬래시 로직에 의해 거부되지 않습니다.
명령어가 확장/커스텀/파일 레이어에서 처리되지 않으면, expandSlashCommand는 원본 텍스트를 반환하고 리터럴 /... 프롬프트는 일반 프롬프트 템플릿 확장 및 LLM 전달을 통해 진행됩니다.
인터랙티브 모드는 InputController에서 많은 빌트인 명령어를 별도로 직접 처리합니다(예: /settings, /model, /mcp, /move, /exit). 이들은 session.prompt(...)보다 먼저 소비되므로, 해당 경로에서는 파일 명령어 확장에 도달하지 않습니다.
8) 스트리밍 중과 유휴 시의 차이점
섹션 제목: “8) 스트리밍 중과 유휴 시의 차이점”유휴 경로
섹션 제목: “유휴 경로”session.prompt("/x ...")는 명령어 파이프라인을 실행하고 명령어를 즉시 실행하거나 확장된 텍스트를 직접 전송합니다.
스트리밍 경로 (session.isStreaming === true)
섹션 제목: “스트리밍 경로 (session.isStreaming === true)”prompt(...)는 여전히 먼저 확장/커스텀/파일/템플릿 변환을 실행합니다.- 이후
streamingBehavior가 필요합니다:"steer"→ 인터럽트 메시지 큐에 추가(agent.steer)"followUp"→ 턴 후 메시지 큐에 추가(agent.followUp)
streamingBehavior가 생략되면 프롬프트에서 오류가 발생합니다.
명령어별 중요한 스트리밍 동작
섹션 제목: “명령어별 중요한 스트리밍 동작”- 확장 명령어는 스트리밍 중에도 즉시 실행됩니다(텍스트로 큐에 추가되지 않음).
steer(...)/followUp(...)헬퍼 메서드는 확장 명령어를 거부합니다(#throwIfExtensionCommand). 동기적으로 실행해야 하는 핸들러를 위해 명령어 텍스트를 큐에 추가하지 않기 위함입니다.- 압축 큐 재실행은
isKnownSlashCommand(...)를 사용하여 큐에 추가된 항목을session.prompt(...)로 재실행할지(알려진 슬래시 명령어의 경우), 아니면 raw steer/follow-up 메서드로 처리할지를 결정합니다.
9) 오류 처리 및 실패 노출 지점
섹션 제목: “9) 오류 처리 및 실패 노출 지점”- 프로바이더 로드 실패는 격리됩니다. 레지스트리는 경고를 수집하고 다른 프로바이더로 계속 진행합니다.
- 유효하지 않은 슬래시 명령어 항목(이름/경로/콘텐츠 누락 또는 유효하지 않은 레벨)은 기능 유효성 검사에서 제거됩니다.
- 프론트매터 파싱 실패:
- 네이티브 명령어: 치명적 파싱 오류가 전파됨
- 비네이티브 명령어: 경고 + 폴백 키/값 파싱
- 확장/커스텀 명령어 핸들러 예외는 포착되어 확장 오류 채널(또는 확장 러너가 없는 커스텀 명령어의 경우 로거 폴백)을 통해 보고되며, 처리된 것으로 간주됩니다(의도치 않은 폴백 실행 없음).