AX Agent 도구·스킬 정합성 재구성 및 실행 품질 보강
변경 목적: - AX Agent의 도구 이름, 내부 설정, 스킬 정책, 실행 루프 사이의 불일치를 줄이고 전체 동작 품질을 높인다. - claw-code 수준의 일관된 동작 품질을 참고하되 AX 구조에 맞는 고유한 카탈로그·정규화 레이어로 재구성한다. 핵심 수정사항: - 도구 canonical id, legacy alias, 탭 노출, 설정 카테고리, read-only 분류를 중앙 카탈로그로 통합했다. - ToolRegistry, AgentLoopService, 병렬 실행 분류, 권한 처리, 훅 처리, 스킬 allowed-tools 해석이 같은 이름 체계를 사용하도록 정리했다. - Agent 설정/일반 설정/도움말의 도구 카드와 훅 편집기, 스킬 설명을 현재 런타임 구조에 맞게 갱신했다. - 컨텍스트 압축, intent gate, spawn agents, session learning, model prompt adapter, workspace context 관련 변경과 테스트 추가를 함께 반영했다. - 문서 이력과 비교/로드맵 문서를 최신 상태로 갱신했다. 검증 결과: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_toolcat\ -p:IntermediateOutputPath=obj\verify_toolcat\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentToolCatalogTests -p:OutputPath=bin\verify_toolcat_tests\ -p:IntermediateOutputPath=obj\verify_toolcat_tests\ : 통과 8
@@ -7,6 +7,13 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
||||
개발 참고: Claw Code 동등성 작업 추적 문서
|
||||
`docs/claw-code-parity-plan.md`
|
||||
|
||||
- 업데이트: 2026-04-14 17:46 (KST)
|
||||
- 도구 이름과 내부설정의 결합도를 낮추기 위해 `AgentToolCatalog` 기반의 중앙 메타데이터 레이어를 추가했습니다. 이제 레거시 도구 이름(`git`, `lsp`, `zip`, `project_rule` 등)은 canonical id로 정규화되어 런타임, 권한, 병렬 분류, 설정 화면이 같은 이름 체계를 사용합니다.
|
||||
- 설정 화면도 같은 카탈로그를 보도록 정리했습니다. Agent 설정/일반 설정의 도구 카드와 훅 편집기는 canonical 이름을 기준으로 표시하고 저장하며, 기존 `disabledTools`, 훅 대상 도구, 도구 권한 키는 호환 alias를 통해 자동 흡수합니다.
|
||||
- 스킬/헬프 설명도 현재 구조에 맞게 완화했습니다. 스킬은 기본 폴더와 추가 폴더를 함께 로드하는 흐름으로 설명을 갱신했고, 직접 호출 스킬과 런타임 정책 연결 스킬을 같이 보여주도록 문구를 다듬었습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_toolcat\\ -p:IntermediateOutputPath=obj\\verify_toolcat\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentToolCatalogTests -p:OutputPath=bin\\verify_toolcat_tests\\ -p:IntermediateOutputPath=obj\\verify_toolcat_tests\\` 통과 8
|
||||
|
||||
- 업데이트: 2026-04-12 23:58 (KST)
|
||||
- Cowork 문서 생성이 단조로워지고 마지막 요약이 약해진 부분을 다시 보강했습니다. 문서 태스크 가이드에 반복적인 동일 패턴 섹션 작성을 피하고, 목적에 따라 요약/핵심 발견/비교표/타임라인/권고안/부록 같은 richer section pattern을 쓰도록 다시 유도했습니다.
|
||||
- Cowork 시스템 프롬프트도 같은 기준으로 조정했습니다. 새 문서 생성 시 filler paragraph 대신 bullets, tables, structured comparison을 적극적으로 쓰게 하고, 최종 응답에는 생성 후 점검 항목까지 포함한 구조화 요약을 남기도록 복원했습니다.
|
||||
|
||||
@@ -162,35 +162,35 @@ del /q "%~1\*.runtimeconfig.json" 2>nul
|
||||
exit /b 0
|
||||
|
||||
:fail_dist
|
||||
echo [FAILED] dist ??????밴쉐 ??쎈솭
|
||||
echo [FAILED] dist 폴더 생성 실패
|
||||
goto :end_fail
|
||||
|
||||
:fail_app
|
||||
echo [FAILED] main app publish ??쎈솭
|
||||
echo [FAILED] main app publish 실패
|
||||
goto :end_fail
|
||||
|
||||
:fail_obfuscation
|
||||
echo [FAILED] obfuscation ??m???쎈솭
|
||||
echo [FAILED] obfuscation 단계 실패
|
||||
goto :end_fail
|
||||
|
||||
:fail_encryptor
|
||||
echo [FAILED] AxKeyEncryptor publish ??쎈솭
|
||||
echo [FAILED] AxKeyEncryptor publish 실패
|
||||
goto :end_fail
|
||||
|
||||
:fail_payload
|
||||
echo [FAILED] payload.zip ??밴쉐 ??쎈솭
|
||||
echo [FAILED] payload.zip 생성 실패
|
||||
goto :end_fail
|
||||
|
||||
:fail_installer
|
||||
echo [FAILED] installer build ??쎈솭
|
||||
echo [FAILED] installer build 실패
|
||||
goto :end_fail
|
||||
|
||||
:fail_installer_copy
|
||||
echo [FAILED] installer exe 癰귣벊沅???쎈솭
|
||||
echo [FAILED] installer exe 복사 실패
|
||||
goto :end_fail
|
||||
|
||||
:fail_running
|
||||
echo [FAILED] running AX Copilot process could not be stopped cleanly
|
||||
echo [FAILED] 실행 중인 AX Copilot 프로세스를 종료할 수 없습니다
|
||||
goto :end_fail
|
||||
|
||||
:end_fail
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 519 B |
|
Before Width: | Height: | Size: 615 B |
|
Before Width: | Height: | Size: 553 B |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 555 B |
|
Before Width: | Height: | Size: 488 B |
@@ -1,95 +0,0 @@
|
||||
---
|
||||
name: adr-writer
|
||||
label: 아키텍처 결정 기록 (ADR)
|
||||
description: 아키텍처 결정 사항을 표준 ADR 형식으로 문서화합니다.
|
||||
icon: \uE82D
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- folder_map
|
||||
- grep
|
||||
- search_codebase
|
||||
tabs: code
|
||||
---
|
||||
|
||||
아키텍처 결정 사항을 ADR(Architecture Decision Record) 형식으로 문서화하세요.
|
||||
|
||||
## ADR이란?
|
||||
소프트웨어 아키텍처에서 내린 중요한 결정의 배경, 대안, 근거를 기록하는 경량 문서입니다.
|
||||
미래의 팀원이 "왜 이렇게 결정했는지"를 이해할 수 있게 합니다.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **결정 사항 확인**: 사용자에게 다음을 파악
|
||||
- 어떤 결정을 내렸는가 (또는 내려야 하는가)
|
||||
- 관련 코드/시스템 영역
|
||||
- 고려한 대안들
|
||||
2. **코드 분석** (선택): 관련 코드 구조를 읽어 현재 상태 파악
|
||||
3. **ADR 작성**: 표준 형식으로 문서 생성
|
||||
4. **파일 저장**: `docs/adr/` 폴더에 번호 형식으로 저장
|
||||
|
||||
## ADR 표준 형식
|
||||
|
||||
```markdown
|
||||
# ADR-[번호]: [결정 제목]
|
||||
|
||||
**상태**: 제안됨 | 승인됨 | 폐기됨 | 대체됨
|
||||
**날짜**: YYYY-MM-DD
|
||||
**결정자**: [이름/팀]
|
||||
|
||||
## 맥락 (Context)
|
||||
어떤 상황에서 이 결정이 필요한가?
|
||||
- 기술적 배경
|
||||
- 비즈니스 요구사항
|
||||
- 제약 조건
|
||||
|
||||
## 결정 (Decision)
|
||||
무엇을 결정했는가?
|
||||
- 선택한 방안의 구체적 내용
|
||||
- 적용 범위
|
||||
|
||||
## 대안 (Alternatives)
|
||||
|
||||
### 대안 1: [이름]
|
||||
- 장점: ...
|
||||
- 단점: ...
|
||||
- 비용/복잡도: ...
|
||||
|
||||
### 대안 2: [이름]
|
||||
- 장점: ...
|
||||
- 단점: ...
|
||||
- 비용/복잡도: ...
|
||||
|
||||
## 근거 (Rationale)
|
||||
왜 이 결정을 선택했는가?
|
||||
- 대안 대비 장점
|
||||
- 트레이드오프 분석
|
||||
- 참고 자료/벤치마크
|
||||
|
||||
## 결과 (Consequences)
|
||||
|
||||
### 긍정적
|
||||
- ...
|
||||
|
||||
### 부정적
|
||||
- ...
|
||||
|
||||
### 리스크
|
||||
- ...
|
||||
|
||||
## 관련 문서
|
||||
- ADR-[관련번호]: [제목]
|
||||
- [외부 참고 링크]
|
||||
```
|
||||
|
||||
## 파일 명명 규칙
|
||||
- 위치: `docs/adr/` (없으면 생성)
|
||||
- 파일명: `ADR-NNNN-제목-요약.md` (예: `ADR-0001-데이터베이스-선택.md`)
|
||||
- 번호: 기존 ADR 파일 조회 후 자동 부여
|
||||
|
||||
## 규칙
|
||||
- 결정의 "왜"를 중심으로 작성 (코드를 읽으면 "무엇"은 알 수 있음)
|
||||
- 대안은 최소 2개 이상 제시
|
||||
- 트레이드오프를 솔직하게 기록 (완벽한 선택은 없음)
|
||||
- 짧고 명확하게 (1~2페이지 이내)
|
||||
- 한국어로 작성
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
name: api-docs
|
||||
label: API 문서 생성
|
||||
description: 코드에서 API 엔드포인트를 분석하여 마크다운/HTML API 문서를 자동 생성합니다.
|
||||
icon: \uE8A1
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- grep
|
||||
- file_read
|
||||
- file_write
|
||||
- search_codebase
|
||||
tabs: code
|
||||
---
|
||||
|
||||
작업 폴더의 소스 코드를 분석하여 API 문서를 생성하세요.
|
||||
|
||||
## 분석 대상
|
||||
- REST API 엔드포인트 (Controller, Route 어노테이션)
|
||||
- 함수/메서드 시그니처 및 주석
|
||||
- 요청/응답 모델 (DTO, Schema)
|
||||
- 인증/권한 요구사항
|
||||
|
||||
## 작업 절차
|
||||
1. `folder_map` — 프로젝트 구조 파악
|
||||
2. `grep` — API 엔드포인트 패턴 검색 (`[HttpGet]`, `@GetMapping`, `router.get`, `@app.route` 등)
|
||||
3. `file_read` — 컨트롤러/라우터 파일 분석
|
||||
4. `grep` — 요청/응답 모델 클래스 검색
|
||||
5. `file_read` — 모델 구조 분석
|
||||
6. `file_write` — API 문서 생성
|
||||
|
||||
## 출력 형식
|
||||
마크다운으로 작성하되 다음 구조를 따르세요:
|
||||
|
||||
```
|
||||
# API 문서
|
||||
|
||||
## 개요
|
||||
- Base URL, 인증 방식, 공통 헤더
|
||||
|
||||
## 엔드포인트
|
||||
|
||||
### [POST] /api/users
|
||||
- **설명**: 사용자 생성
|
||||
- **인증**: Bearer Token 필요
|
||||
- **요청 본문**:
|
||||
| 필드 | 타입 | 필수 | 설명 |
|
||||
|------|------|------|------|
|
||||
| name | string | ✓ | 사용자 이름 |
|
||||
- **응답**: 201 Created
|
||||
```json
|
||||
{ "id": 1, "name": "..." }
|
||||
```
|
||||
- **에러 코드**: 400, 401, 409
|
||||
```
|
||||
|
||||
## 주의사항
|
||||
- 코드에서 실제 확인된 내용만 문서화하세요. 추측하지 마세요.
|
||||
- 주석이나 Swagger/OpenAPI 어노테이션이 있으면 우선 활용하세요.
|
||||
- 인증, 페이징, 에러 처리 등 공통 패턴은 별도 섹션으로 정리하세요.
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
name: batch-rename
|
||||
label: 파일 일괄 이름 변경
|
||||
description: 패턴 매칭, 번호 붙이기, 날짜 추가 등 파일 이름을 일괄 변경합니다.
|
||||
icon: \uE8AC
|
||||
tabs: cowork
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_manage
|
||||
- clipboard_tool
|
||||
---
|
||||
|
||||
파일 이름을 규칙에 따라 일괄 변경하세요. 변경 전 반드시 미리보기를 제공합니다.
|
||||
|
||||
## 작업 절차
|
||||
|
||||
1. **대상 파일 스캔**: folder_map으로 작업 폴더의 파일 목록을 수집
|
||||
2. **이름 변경 규칙 확인**: 사용자에게 다음 옵션을 확인
|
||||
- 대상 파일 필터 (확장자, 이름 패턴)
|
||||
- 변경 규칙 (아래 지원 규칙 참조)
|
||||
- 적용 순서 (이름순, 날짜순, 크기순)
|
||||
3. **변경 미리보기 생성**: 변경 전/후 이름을 표로 표시
|
||||
```
|
||||
| # | 현재 이름 | 변경 후 이름 |
|
||||
|---|----------------------|----------------------|
|
||||
| 1 | IMG_20260101_001.jpg | 2026-01-01_001.jpg |
|
||||
| 2 | IMG_20260101_002.jpg | 2026-01-01_002.jpg |
|
||||
```
|
||||
4. **사용자 확인**: 미리보기를 보여주고 진행 여부를 확인
|
||||
5. **일괄 변경 실행**: file_manage로 파일 이름을 순차 변경
|
||||
6. **결과 보고**: 변경 성공/실패 건수와 상세 내역을 안내
|
||||
|
||||
## 지원 이름 변경 규칙
|
||||
|
||||
### 패턴 치환
|
||||
- **문자열 치환**: "IMG_" → "사진_"
|
||||
- **정규식 치환**: `(\d{4})(\d{2})(\d{2})` → `$1-$2-$3`
|
||||
- **대소문자 변환**: 소문자, 대문자, 타이틀 케이스
|
||||
|
||||
### 번호 붙이기
|
||||
- **순번 추가**: `문서_001.pdf`, `문서_002.pdf`, ...
|
||||
- **시작 번호**: 사용자 지정 (기본: 1)
|
||||
- **자릿수**: 자동 계산 (파일 수 기준)
|
||||
- **위치**: 접두사 또는 접미사
|
||||
|
||||
### 날짜 추가
|
||||
- **오늘 날짜**: `보고서_2026-03-30.docx`
|
||||
- **파일 수정일**: 파일의 실제 수정 날짜 사용
|
||||
- **날짜 형식**: YYYY-MM-DD, YYYYMMDD, YY.MM.DD
|
||||
|
||||
### 정리
|
||||
- **공백 처리**: 공백 → 언더스코어/하이픈
|
||||
- **특수문자 제거**: 파일명에서 특수문자 제거
|
||||
- **확장자 변경**: `.jpeg` → `.jpg`
|
||||
- **접두사/접미사 추가 또는 제거**
|
||||
|
||||
## 충돌 처리
|
||||
- 변경 후 이름이 이미 존재하면 자동으로 번호 추가 (`_1`, `_2`)
|
||||
- 충돌 건은 미리보기에서 ⚠️ 표시로 경고
|
||||
- 원본 파일 덮어쓰기 절대 금지
|
||||
|
||||
## 규칙
|
||||
- **미리보기 없이 직접 변경하지 않음** — 반드시 미리보기 후 사용자 확인
|
||||
- 하위 폴더 포함 여부는 사용자에게 확인
|
||||
- 숨김 파일(.으로 시작)은 기본 제외
|
||||
- 변경 실패 시 이미 변경된 파일은 원복하지 않으므로, 중요한 경우 백업 권장
|
||||
- 한 번에 1,000개 이상의 파일 변경 시 경고
|
||||
|
||||
한국어로 안내하세요.
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
name: changelog
|
||||
label: 변경 이력 / 릴리즈 노트
|
||||
description: Git 커밋 이력에서 자동으로 변경 이력과 릴리즈 노트를 생성합니다.
|
||||
icon: \uE81C
|
||||
allowed-tools:
|
||||
- git_tool
|
||||
- file_read
|
||||
- file_write
|
||||
- html_create
|
||||
- text_summarize
|
||||
tabs: code
|
||||
---
|
||||
|
||||
Git 커밋 이력을 분석하여 변경 이력(CHANGELOG) 또는 릴리즈 노트를 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **이력 조회**: git_tool로 커밋 로그 수집
|
||||
- 지정 기간 또는 태그 간 커밋
|
||||
- 커밋 메시지 + 변경 파일 목록
|
||||
2. **분류**: 커밋을 Conventional Commits 기준으로 분류
|
||||
3. **Breaking Change 감지**: 시그니처 변경, API 삭제 등 감지
|
||||
4. **문서 생성**: Markdown 또는 HTML로 출력
|
||||
|
||||
## Conventional Commits 분류
|
||||
|
||||
| 접두사 | 분류 | 설명 |
|
||||
|--------|------|------|
|
||||
| feat | ✨ 신기능 | 새로운 기능 추가 |
|
||||
| fix | 🐛 버그 수정 | 버그 수정 |
|
||||
| docs | 📝 문서 | 문서 변경 |
|
||||
| style | 💄 스타일 | 코드 포맷팅 (동작 변경 없음) |
|
||||
| refactor | ♻️ 리팩토링 | 코드 리팩토링 |
|
||||
| perf | ⚡ 성능 | 성능 개선 |
|
||||
| test | ✅ 테스트 | 테스트 추가/수정 |
|
||||
| chore | 🔧 기타 | 빌드, 설정 변경 |
|
||||
| BREAKING | 💥 Breaking | 하위 호환성 깨지는 변경 |
|
||||
|
||||
## 출력 형식
|
||||
|
||||
### CHANGELOG.md
|
||||
```markdown
|
||||
# Changelog
|
||||
|
||||
## [1.6.0] - 2026-03-30
|
||||
|
||||
### ✨ 신기능
|
||||
- 멀티패스 문서 생성 엔진 (#123)
|
||||
- PPT 네이티브 생성 도구
|
||||
|
||||
### 🐛 버그 수정
|
||||
- 탭 전환 시 대화 유실 문제 해결
|
||||
|
||||
### 💥 Breaking Changes
|
||||
- 없음
|
||||
|
||||
### 📝 문서
|
||||
- 개발자 가이드 v1.6.0 업데이트
|
||||
```
|
||||
|
||||
### 릴리즈 노트 (HTML)
|
||||
사용자 친화적인 형식:
|
||||
- 주요 변경사항 (스크린샷 포함 가능)
|
||||
- 개선 사항
|
||||
- 알려진 이슈
|
||||
- 업그레이드 가이드
|
||||
|
||||
## 규칙
|
||||
- Conventional Commits 형식이 아닌 커밋도 내용 분석으로 분류
|
||||
- 중복/사소한 커밋은 병합하여 요약
|
||||
- Breaking Change는 반드시 별도 섹션으로 강조
|
||||
- 이슈 번호가 있으면 링크 포함
|
||||
- 한국어로 작성
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
name: code-scaffold
|
||||
label: 코드 스캐폴딩
|
||||
description: 프로젝트 구조를 분석하고 새 기능의 코드 뼈대를 자동 생성합니다.
|
||||
icon: \uE943
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- grep
|
||||
- file_write
|
||||
- search_codebase
|
||||
tabs: code
|
||||
---
|
||||
|
||||
작업 폴더의 프로젝트 구조를 분석하고 새 기능의 코드 뼈대를 생성하세요.
|
||||
|
||||
다음 도구를 사용하세요:
|
||||
1. folder_map — 프로젝트 구조 파악
|
||||
2. file_read — 기존 코드 패턴 분석
|
||||
3. grep — 코딩 컨벤션 확인
|
||||
4. file_write — 새 파일 생성
|
||||
|
||||
작업 순서:
|
||||
1. 프로젝트 타입 감지 (언어, 프레임워크, 빌드 시스템)
|
||||
2. 기존 코드 패턴 분석 (네이밍, 폴더 구조, 임포트 스타일)
|
||||
3. 사용자 요청에 맞는 코드 뼈대 생성
|
||||
|
||||
생성 항목:
|
||||
- 클래스/모듈 파일 (프로젝트 컨벤션에 맞춰)
|
||||
- 인터페이스/타입 정의
|
||||
- 단위 테스트 파일
|
||||
- 필요한 설정/구성 파일
|
||||
|
||||
규칙:
|
||||
- 기존 프로젝트의 코딩 스타일을 따르세요
|
||||
- TODO 주석으로 구현이 필요한 부분을 표시하세요
|
||||
- 한국어 주석을 추가하세요
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
name: commit-review
|
||||
label: 커밋 메시지 리뷰
|
||||
description: Git 커밋 메시지를 Conventional Commits 기준으로 검토하고 개선을 제안합니다.
|
||||
icon: \uE8CB
|
||||
allowed-tools:
|
||||
- process
|
||||
- git_tool
|
||||
- file_read
|
||||
- text_summarize
|
||||
tabs: code
|
||||
---
|
||||
|
||||
작업 폴더의 최근 Git 커밋 메시지를 검토하고 개선안을 제시하세요.
|
||||
|
||||
## 작업 절차
|
||||
1. `process` — `git log --oneline -20` 으로 최근 커밋 목록 확인
|
||||
2. `process` — `git log --format="%H%n%s%n%b%n---" -10` 으로 상세 메시지 확인
|
||||
3. `process` — `git diff HEAD~1` 으로 최신 커밋 변경 내용 확인 (필요 시)
|
||||
4. 각 커밋을 Conventional Commits 기준으로 분석
|
||||
5. 결과를 정리하여 출력
|
||||
|
||||
## Conventional Commits 규칙
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
### type 종류
|
||||
- `feat`: 새 기능
|
||||
- `fix`: 버그 수정
|
||||
- `docs`: 문서 변경
|
||||
- `style`: 포맷팅 (코드 동작 변경 없음)
|
||||
- `refactor`: 리팩토링
|
||||
- `perf`: 성능 개선
|
||||
- `test`: 테스트 추가/수정
|
||||
- `chore`: 빌드/도구 설정
|
||||
- `ci`: CI 설정 변경
|
||||
|
||||
## 검토 항목
|
||||
1. **type 적절성**: 변경 내용과 type이 일치하는가
|
||||
2. **설명 품질**: 50자 이내, 명령형, 명확한 내용
|
||||
3. **본문 유무**: 복잡한 변경에 "왜" 설명이 있는가
|
||||
4. **일관성**: 팀 내 커밋 스타일이 통일되어 있는가
|
||||
5. **Breaking Change**: BREAKING CHANGE 푸터 또는 ! 표기
|
||||
|
||||
## 출력 형식
|
||||
```
|
||||
## 커밋 메시지 리뷰 결과
|
||||
|
||||
### 전체 요약
|
||||
- 검토 커밋: N개
|
||||
- 규칙 준수: N개 ✓ / 위반: N개 ✗
|
||||
|
||||
### 개별 리뷰
|
||||
|
||||
#### abc1234 "Fix login bug"
|
||||
- ❌ type 없음 → `fix: resolve login authentication failure`
|
||||
- ❌ 본문 없음 → 원인과 해결 방법 추가 권장
|
||||
|
||||
#### def5678 "feat: add user profile page"
|
||||
- ✅ Conventional Commits 준수
|
||||
- 💡 scope 추가 권장: `feat(profile): add user profile page`
|
||||
```
|
||||
|
||||
## 주의사항
|
||||
- 비판이 아닌 건설적 제안을 하세요.
|
||||
- 팀의 기존 컨벤션이 있으면 그것을 우선 존중하세요.
|
||||
- 개선된 메시지 예시를 항상 함께 제시하세요.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: compare
|
||||
label: 비교 분석표
|
||||
description: 2개 이상 항목의 비교 분석 매트릭스를 생성합니다.
|
||||
icon: \uE9D5
|
||||
allowed-tools:
|
||||
- html_create
|
||||
- excel_create
|
||||
- file_read
|
||||
- document_read
|
||||
- chart_create
|
||||
tabs: all
|
||||
---
|
||||
|
||||
사용자가 요청한 항목들을 체계적으로 비교 분석하는 매트릭스를 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **비교 대상 확인**: 어떤 항목들을 비교할지 파악
|
||||
- 제품, 기술, 방안, 서비스, 도구, 프레임워크 등
|
||||
2. **비교 기준 설정**: 적절한 비교 축을 설계
|
||||
- 기능, 가격, 성능, 사용성, 확장성, 지원, 보안 등
|
||||
3. **데이터 수집**: 참고 파일이 있으면 읽어서 반영
|
||||
4. **분석표 생성**: HTML 또는 Excel로 비교 매트릭스 생성
|
||||
5. **종합 평가**: 총평 + 추천 의견 제시
|
||||
|
||||
## 비교표 구성
|
||||
|
||||
### 기본 매트릭스
|
||||
| 기준 | 항목 A | 항목 B | 항목 C |
|
||||
|------|--------|--------|--------|
|
||||
| 기능1 | ✅ 지원 | ⚠ 일부 | ❌ 미지원 |
|
||||
| 기능2 | ... | ... | ... |
|
||||
|
||||
### 점수 비교 (radar 차트 활용)
|
||||
- 각 항목을 1~10점으로 정량화
|
||||
- chart_create로 레이더 차트 또는 바 차트 시각화
|
||||
|
||||
### 종합 평가
|
||||
- 장점/단점 요약
|
||||
- 상황별 추천 (예: "예산이 제한적이면 A, 확장성이 중요하면 B")
|
||||
|
||||
## 출력 형식
|
||||
- **HTML** (권장): 색상 배지, 차트 포함 비주얼 보고서
|
||||
- **Excel**: 정량 데이터 + 수식 기반 점수표
|
||||
|
||||
## 규칙
|
||||
- 객관적 사실 기반으로 비교 (주관적 판단은 별도 섹션)
|
||||
- 각 항목의 강점과 약점을 균형 있게 서술
|
||||
- 출처/근거가 있으면 명시
|
||||
- 한국어로 작성
|
||||
@@ -1,137 +0,0 @@
|
||||
---
|
||||
name: csv-to-xlsx
|
||||
label: CSV → Excel 변환
|
||||
description: CSV 파일을 서식이 완성된 Excel(.xlsx)로 변환합니다. 헤더 고정, 필터, 조건부 서식, 자동 열 너비를 적용합니다.
|
||||
icon: \uE9F9
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- format_convert
|
||||
- data_pivot
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
CSV 파일을 전문적인 서식이 적용된 Excel 파일로 변환하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 Python 스크립트 경로로 변환/서식 자동화를 수행하세요.
|
||||
- Python 불가: `format_convert`로 CSV를 XLSX로 변환하고, `data_pivot`으로 핵심 요약 시트를 구성한 뒤 `file_write`로 사용 안내를 남기세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
먼저 필요한 패키지가 설치되어 있는지 확인하고, 없으면 설치하세요:
|
||||
```
|
||||
process: pip install openpyxl pandas
|
||||
```
|
||||
|
||||
## 작업 절차
|
||||
|
||||
1. **파일 확인**: folder_map으로 작업 폴더에서 CSV 파일을 탐색
|
||||
2. **CSV 분석**: file_read로 CSV 파일의 구조(컬럼, 행 수, 인코딩, 구분자) 파악
|
||||
3. **변환 옵션 확인**: 사용자에게 다음 옵션을 확인
|
||||
- 헤더 행 고정 여부 (기본: 활성)
|
||||
- 자동 필터 적용 여부 (기본: 활성)
|
||||
- 조건부 서식 대상 컬럼 (숫자 컬럼 자동 감지)
|
||||
- 시트 이름 (기본: 파일명)
|
||||
4. **Python 스크립트 작성**: file_write로 변환 스크립트 생성
|
||||
5. **스크립트 실행**: `process`로 Python 스크립트 실행
|
||||
6. **결과 확인**: 생성된 .xlsx 파일 경로와 요약 정보를 안내
|
||||
|
||||
## Python 스크립트 템플릿
|
||||
```python
|
||||
import pandas as pd
|
||||
from openpyxl import load_workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
from openpyxl.formatting.rule import CellIsRule
|
||||
|
||||
# CSV 읽기 (인코딩 자동 감지)
|
||||
for enc in ['utf-8', 'cp949', 'euc-kr', 'utf-8-sig']:
|
||||
try:
|
||||
df = pd.read_csv('input.csv', encoding=enc)
|
||||
break
|
||||
except (UnicodeDecodeError, Exception):
|
||||
continue
|
||||
|
||||
# Excel 저장
|
||||
output_path = 'output.xlsx'
|
||||
df.to_excel(output_path, index=False, sheet_name='Sheet1')
|
||||
|
||||
# 서식 적용
|
||||
wb = load_workbook(output_path)
|
||||
ws = wb.active
|
||||
|
||||
# 헤더 스타일
|
||||
header_font = Font(bold=True, color='FFFFFF', size=11)
|
||||
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
|
||||
header_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(
|
||||
left=Side(style='thin'),
|
||||
right=Side(style='thin'),
|
||||
top=Side(style='thin'),
|
||||
bottom=Side(style='thin')
|
||||
)
|
||||
|
||||
for col_idx, cell in enumerate(ws[1], 1):
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
cell.alignment = header_align
|
||||
cell.border = thin_border
|
||||
|
||||
# 자동 열 너비
|
||||
for col_idx in range(1, ws.max_column + 1):
|
||||
max_length = 0
|
||||
col_letter = get_column_letter(col_idx)
|
||||
for row in ws.iter_rows(min_col=col_idx, max_col=col_idx):
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
max_length = max(max_length, len(str(cell.value)))
|
||||
ws.column_dimensions[col_letter].width = min(max_length + 4, 50)
|
||||
|
||||
# 헤더 행 고정 (Freeze Panes)
|
||||
ws.freeze_panes = 'A2'
|
||||
|
||||
# 자동 필터
|
||||
ws.auto_filter.ref = ws.dimensions
|
||||
|
||||
# 숫자 컬럼 조건부 서식 (음수 빨강)
|
||||
for col_idx in range(1, ws.max_column + 1):
|
||||
col_letter = get_column_letter(col_idx)
|
||||
sample_values = [ws.cell(row=r, column=col_idx).value for r in range(2, min(ws.max_row + 1, 12))]
|
||||
if any(isinstance(v, (int, float)) for v in sample_values if v is not None):
|
||||
cell_range = f'{col_letter}2:{col_letter}{ws.max_row}'
|
||||
ws.conditional_formatting.add(cell_range,
|
||||
CellIsRule(operator='lessThan', formula=['0'],
|
||||
font=Font(color='FF0000')))
|
||||
|
||||
# 데이터 행 줄무늬 (가독성)
|
||||
light_fill = PatternFill(start_color='D9E2F3', end_color='D9E2F3', fill_type='solid')
|
||||
for row_idx in range(2, ws.max_row + 1):
|
||||
for col_idx in range(1, ws.max_column + 1):
|
||||
cell = ws.cell(row=row_idx, column=col_idx)
|
||||
cell.border = thin_border
|
||||
if row_idx % 2 == 0:
|
||||
cell.fill = light_fill
|
||||
|
||||
wb.save(output_path)
|
||||
print(f'변환 완료: {output_path} ({ws.max_row - 1}행 × {ws.max_column}열)')
|
||||
```
|
||||
|
||||
## 서식 옵션
|
||||
- **헤더 스타일**: 파란 배경 + 흰색 굵은 글씨 + 가운데 정렬
|
||||
- **줄무늬**: 짝수 행 연한 파랑 배경 (가독성 향상)
|
||||
- **열 너비**: 내용 기준 자동 조정 (최대 50)
|
||||
- **조건부 서식**: 숫자 컬럼 음수 빨강 표시
|
||||
- **Freeze Panes**: 헤더 행 고정
|
||||
- **Auto Filter**: 전체 컬럼 필터 활성화
|
||||
|
||||
## 규칙
|
||||
- 원본 CSV 파일은 수정하지 않음
|
||||
- 인코딩 자동 감지 (UTF-8 → CP949 → EUC-KR 순)
|
||||
- 대용량 파일 (100,000행 이상) 경고 후 진행
|
||||
- 출력 파일명: 원본 파일명 기준 (.csv → .xlsx)
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 Python 스크립트와 결과 파일을 저장하세요.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: data-convert
|
||||
label: 데이터 변환기
|
||||
description: 데이터 포맷 간 변환, 정제, 필터링, 통계 요약을 수행합니다.
|
||||
icon: \uE8AB
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- json_tool
|
||||
- csv_create
|
||||
- data_pivot
|
||||
- excel_create
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
데이터 파일의 포맷 변환, 정제, 필터링, 통계 요약을 수행하세요.
|
||||
|
||||
## 지원 변환
|
||||
|
||||
| 입력 → | JSON | CSV | Excel | Markdown |
|
||||
|--------|------|-----|-------|----------|
|
||||
| JSON | — | ✅ | ✅ | ✅ |
|
||||
| CSV | ✅ | — | ✅ | ✅ |
|
||||
| Excel | ✅ | ✅ | — | ✅ |
|
||||
| TSV | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **파일 분석**: 입력 파일의 포맷, 인코딩, 구조 파악
|
||||
2. **데이터 정제** (선택):
|
||||
- 빈 행/열 제거
|
||||
- 중복 제거
|
||||
- 데이터 타입 정리 (숫자 문자열 → 숫자)
|
||||
- 결측치 처리 (제거 또는 기본값)
|
||||
3. **필터링** (선택):
|
||||
- 조건 기반 행 필터링
|
||||
- 필요 컬럼만 추출
|
||||
4. **변환**: 대상 포맷으로 변환
|
||||
5. **통계 요약**: 기본 통계 제공
|
||||
|
||||
## 정제 옵션
|
||||
- `remove_empty`: 빈 행 제거
|
||||
- `remove_duplicates`: 중복 행 제거
|
||||
- `trim`: 공백 제거
|
||||
- `fill_na`: 결측치 채우기 (값 지정)
|
||||
|
||||
## 출력
|
||||
- 변환된 파일 저장
|
||||
- 변환 통계 (원본 행 수, 변환 후 행 수, 제거 행 수)
|
||||
- 컬럼별 기본 통계 (수치 컬럼: 합계, 평균, 최소, 최대)
|
||||
|
||||
## 규칙
|
||||
- 원본 파일은 수정하지 않음 (새 파일로 저장)
|
||||
- 인코딩: UTF-8 기본 (EUC-KR 옵션)
|
||||
- 대용량 파일 (10MB 이상) 경고
|
||||
- 한국어로 안내
|
||||
@@ -1,137 +0,0 @@
|
||||
---
|
||||
name: data-visualize-adv
|
||||
label: 고급 데이터 시각화
|
||||
description: Python matplotlib/seaborn을 사용하여 히트맵, 산점도, 상관관계 등 고급 시각화를 생성합니다.
|
||||
icon: \uE9D9
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- data_pivot
|
||||
- chart_create
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
데이터를 고급 시각화 차트로 변환하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 matplotlib/seaborn 경로를 사용하세요.
|
||||
- Python 불가: `data_pivot`으로 통계를 계산하고 `chart_create` + `template_render` + `file_write`로 HTML/SVG 리포트를 생성하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
필요한 패키지를 설치하세요:
|
||||
```
|
||||
process: pip install matplotlib seaborn pandas numpy
|
||||
```
|
||||
|
||||
## 작업 절차
|
||||
1. **데이터 확인**: 사용자가 제공한 CSV/JSON/Excel 데이터 파일 확인
|
||||
2. **데이터 로드**: pandas로 데이터 읽기
|
||||
3. **Python 스크립트 작성**: file_write로 시각화 스크립트 생성
|
||||
4. **스크립트 실행**: `process`로 실행
|
||||
5. **결과 확인**: 생성된 차트 이미지 경로를 사용자에게 안내
|
||||
|
||||
## 시각화 유형별 템플릿
|
||||
|
||||
### 공통 설정
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
# 한글 폰트 설정
|
||||
plt.rcParams['font.family'] = 'Malgun Gothic'
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
sns.set_theme(style='whitegrid', font='Malgun Gothic')
|
||||
```
|
||||
|
||||
### 히트맵 (상관관계 행렬)
|
||||
```python
|
||||
df = pd.read_csv('data.csv')
|
||||
corr = df.select_dtypes(include=[np.number]).corr()
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 8))
|
||||
sns.heatmap(corr, annot=True, fmt='.2f', cmap='RdBu_r',
|
||||
center=0, square=True, linewidths=0.5, ax=ax)
|
||||
ax.set_title('상관관계 히트맵')
|
||||
plt.tight_layout()
|
||||
plt.savefig('heatmap.png', dpi=150)
|
||||
```
|
||||
|
||||
### 산점도 (Scatter Plot)
|
||||
```python
|
||||
df = pd.read_csv('data.csv')
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 8))
|
||||
sns.scatterplot(data=df, x='col_x', y='col_y', hue='category',
|
||||
size='value', sizes=(20, 200), alpha=0.7, ax=ax)
|
||||
ax.set_title('산점도')
|
||||
plt.tight_layout()
|
||||
plt.savefig('scatter.png', dpi=150)
|
||||
```
|
||||
|
||||
### 시계열 분석
|
||||
```python
|
||||
df = pd.read_csv('data.csv', parse_dates=['date'])
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
sns.lineplot(data=df, x='date', y='value', hue='category', ax=ax)
|
||||
ax.set_title('시계열 트렌드')
|
||||
plt.xticks(rotation=45)
|
||||
plt.tight_layout()
|
||||
plt.savefig('timeseries.png', dpi=150)
|
||||
```
|
||||
|
||||
### 분포 비교 (박스플롯 + 바이올린)
|
||||
```python
|
||||
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
|
||||
|
||||
sns.boxplot(data=df, x='group', y='value', ax=axes[0])
|
||||
axes[0].set_title('박스플롯')
|
||||
|
||||
sns.violinplot(data=df, x='group', y='value', ax=axes[1])
|
||||
axes[1].set_title('바이올린 플롯')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('distribution.png', dpi=150)
|
||||
```
|
||||
|
||||
### 다중 차트 대시보드
|
||||
```python
|
||||
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
|
||||
|
||||
# 좌상: 히트맵
|
||||
sns.heatmap(corr, annot=True, fmt='.1f', ax=axes[0,0])
|
||||
|
||||
# 우상: 산점도
|
||||
sns.scatterplot(data=df, x='x', y='y', ax=axes[0,1])
|
||||
|
||||
# 좌하: 히스토그램
|
||||
sns.histplot(data=df, x='value', kde=True, ax=axes[1,0])
|
||||
|
||||
# 우하: 박스플롯
|
||||
sns.boxplot(data=df, x='group', y='value', ax=axes[1,1])
|
||||
|
||||
fig.suptitle('데이터 분석 대시보드', fontsize=16, fontweight='bold')
|
||||
plt.tight_layout()
|
||||
plt.savefig('dashboard.png', dpi=150)
|
||||
```
|
||||
|
||||
### 페어플롯 (변수 간 관계 전체)
|
||||
```python
|
||||
g = sns.pairplot(df, hue='category', diag_kind='kde')
|
||||
g.fig.suptitle('변수 간 관계', y=1.02)
|
||||
plt.savefig('pairplot.png', dpi=150)
|
||||
```
|
||||
|
||||
## 스타일 옵션
|
||||
- seaborn 테마: `whitegrid`, `darkgrid`, `white`, `dark`, `ticks`
|
||||
- 컬러 팔레트: `Set2`, `husl`, `coolwarm`, `RdBu_r`, `viridis`
|
||||
- 출력 형식: png, svg, pdf
|
||||
- 해상도: `dpi=150` (기본), `dpi=300` (인쇄용)
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 Python 스크립트와 결과 파일을 저장하세요.
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
name: data-visualize
|
||||
label: 데이터 시각화
|
||||
description: CSV/Excel 데이터를 분석하여 차트가 포함된 HTML 보고서를 생성합니다.
|
||||
icon: \uE9D9
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_write
|
||||
- data_pivot
|
||||
- chart_create
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
작업 폴더의 데이터 파일을 분석하고 시각화 보고서를 생성하세요.
|
||||
|
||||
다음 도구를 사용하세요:
|
||||
1. folder_map — 데이터 파일 탐색
|
||||
2. file_read — CSV/Excel 데이터 읽기
|
||||
3. file_write — HTML 시각화 보고서 생성
|
||||
|
||||
���각화 전략:
|
||||
1. **데이터 파악**: 컬럼 타입, 결측치, 기본 통계량 확인
|
||||
2. **적절한 차트 선택**:
|
||||
- 시계열 → 라인 차트
|
||||
- 비교 → 바 차트
|
||||
- 비율 → 파이/도넛 차트
|
||||
- 분포 → 히스토그램
|
||||
- 상관관계 → 산점도
|
||||
3. **HTML 보고서 생성**: 인라인 SVG 또는 CSS 기반 차트 (외부 라이브러리 없이)
|
||||
|
||||
보고서 구성:
|
||||
## 데이터 요약
|
||||
- 기본 통계 테이블
|
||||
|
||||
## 시각화
|
||||
- 데이터 특성에 맞는 2~4개 차트
|
||||
- 각 차트에 대한 해석
|
||||
|
||||
## 인사이트
|
||||
- 데이터에서 발견한 주요 패턴
|
||||
- 이상치 또는 주목할 포인트
|
||||
|
||||
한국어로 작성하세요. 차트는 CSS/SVG 기반으로 외부 의존성 없이 생성하세요.
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
name: db-schema
|
||||
label: DB 스키마 분석
|
||||
description: 데이터베이스 스키마를 분석하여 ERD 다이어그램과 테이블 문서를 생성합니다.
|
||||
icon: \uE968
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- grep
|
||||
- file_read
|
||||
- file_write
|
||||
- search_codebase
|
||||
tabs: code
|
||||
---
|
||||
|
||||
작업 폴더의 코드에서 데이터베이스 스키마를 분석하고 문서화하세요.
|
||||
|
||||
## 분석 대상
|
||||
- ORM 모델/엔티티 클래스 (Entity Framework, SQLAlchemy, Sequelize, JPA 등)
|
||||
- 마이그레이션 파일
|
||||
- SQL DDL 스크립트 (CREATE TABLE)
|
||||
- 관계 정의 (FK, Navigation Property)
|
||||
|
||||
## 작업 절차
|
||||
1. `folder_map` — 프로젝트 구조 파악
|
||||
2. `grep` — 엔티티/모델 클래스 검색 (`DbSet`, `@Entity`, `Model.define`, `CREATE TABLE` 등)
|
||||
3. `file_read` — 모델 파일 분석 (컬럼, 타입, 관계)
|
||||
4. `grep` — 인덱스, 제약 조건 검색
|
||||
5. `file_write` — 스키마 문서 + Mermaid ERD 생성
|
||||
|
||||
## 출력 형식
|
||||
|
||||
### 테이블 문서
|
||||
각 테이블에 대해:
|
||||
```
|
||||
## Users 테이블
|
||||
| 컬럼 | 타입 | Null | 기본값 | 설명 |
|
||||
|------|------|------|--------|------|
|
||||
| Id | int | NO | AUTO_INCREMENT | PK |
|
||||
| Name | nvarchar(100) | NO | - | 사용자 이름 |
|
||||
| CreatedAt | datetime | NO | GETDATE() | 생성일 |
|
||||
|
||||
- **인덱스**: IX_Users_Name (Name)
|
||||
- **관계**: Orders (1:N), Profile (1:1)
|
||||
```
|
||||
|
||||
### Mermaid ERD
|
||||
```mermaid
|
||||
erDiagram
|
||||
Users ||--o{ Orders : "has"
|
||||
Users ||--|| Profile : "has"
|
||||
Users {
|
||||
int Id PK
|
||||
string Name
|
||||
datetime CreatedAt
|
||||
}
|
||||
Orders {
|
||||
int Id PK
|
||||
int UserId FK
|
||||
decimal Amount
|
||||
}
|
||||
```
|
||||
|
||||
## 주의사항
|
||||
- 코드에서 실제 확인된 스키마만 문서화하세요.
|
||||
- 관계(1:1, 1:N, N:M)를 정확히 파악하세요.
|
||||
- 마이그레이션이 있으면 최종 상태를 기준으로 작성하세요.
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
name: dependency-audit
|
||||
label: 의존성 분석
|
||||
description: 프로젝트 의존성 그래프를 분석하고 보안 취약점, 라이선스, 업데이트 현황을 보고합니다.
|
||||
icon: \uE964
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- grep
|
||||
- folder_map
|
||||
- glob
|
||||
- html_create
|
||||
- process
|
||||
tabs: code
|
||||
---
|
||||
|
||||
프로젝트의 패키지 의존성을 분석하여 보안, 라이선스, 업데이트 보고서를 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **패키지 파일 탐지**: glob으로 의존성 파일 검색
|
||||
- .NET: `*.csproj`, `packages.config`, `Directory.Build.props`
|
||||
- Node: `package.json`, `package-lock.json`, `yarn.lock`
|
||||
- Python: `requirements.txt`, `Pipfile`, `pyproject.toml`
|
||||
- Java: `pom.xml`, `build.gradle`
|
||||
2. **의존성 목록 추출**: file_read로 파일 파싱
|
||||
3. **분석 수행**: 각 의존성에 대해 검사
|
||||
4. **보고서 생성**: html_create로 분석 보고서 생성
|
||||
|
||||
## 분석 항목
|
||||
|
||||
### 1. 직접 의존성 목록
|
||||
| 패키지 | 현재 버전 | 최신 버전 | 업데이트 필요 |
|
||||
|--------|----------|----------|-------------|
|
||||
| ... | ... | ... | ✅/⚠/❌ |
|
||||
|
||||
### 2. 보안 취약점 (알려진 패턴)
|
||||
- 알려진 취약 버전 패턴 탐지
|
||||
- 폐기된(deprecated) 패키지 식별
|
||||
- 유지보수 중단된 패키지 경고
|
||||
|
||||
### 3. 라이선스 검사
|
||||
| 라이선스 | 호환성 | 패키지 |
|
||||
|---------|--------|--------|
|
||||
| MIT | ✅ 허용 | lib-a, lib-b |
|
||||
| GPL-3.0 | ⚠ 주의 | lib-c |
|
||||
| 상용 | ❌ 검토 필요 | lib-d |
|
||||
|
||||
### 4. 의존성 크기 분석
|
||||
- 패키지별 예상 크기
|
||||
- 전체 node_modules / NuGet 캐시 크기
|
||||
- 불필요하게 큰 패키지 식별
|
||||
|
||||
### 5. 중복/충돌 검사
|
||||
- 동일 기능 중복 패키지 (예: lodash + underscore)
|
||||
- 버전 충돌 가능성
|
||||
|
||||
## 프레임워크별 검사 명령
|
||||
|
||||
### .NET
|
||||
```
|
||||
dotnet list package --outdated
|
||||
dotnet list package --vulnerable
|
||||
```
|
||||
|
||||
### Node.js
|
||||
```
|
||||
npm audit
|
||||
npm outdated
|
||||
```
|
||||
|
||||
### Python
|
||||
```
|
||||
pip list --outdated
|
||||
pip-audit
|
||||
```
|
||||
|
||||
## 출력
|
||||
- HTML 보고서: 위험도별 색상 구분, 차트 포함
|
||||
- 요약: 총 패키지 수, 업데이트 필요 수, 보안 이슈 수, 라이선스 경고 수
|
||||
|
||||
## 규칙
|
||||
- 외부 서버 접속 없이 로컬 파일 분석만 수행
|
||||
- process 도구 사용 시 `dotnet list` / `npm audit` 등 읽기 전용 명령만
|
||||
- 패키지를 직접 업데이트하지 않음 (보고서만)
|
||||
- 한국어로 작성
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
name: diagram-generator
|
||||
label: 다이어그램 생성
|
||||
description: Python matplotlib/graphviz를 사용하여 플로차트, 시퀀스, ER 다이어그램 등을 생성합니다.
|
||||
icon: \uE9D9
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- chart_create
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
사용자의 요구에 맞는 다이어그램을 Python으로 생성하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 Graphviz/matplotlib 경로를 사용하세요.
|
||||
- Python 불가: `template_render`와 `file_write`로 Mermaid 기반 다이어그램 문서를 생성하고, 필요 시 `chart_create`로 대체 시각화를 제공하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
필요한 패키지를 설치하세요:
|
||||
```
|
||||
process: pip install matplotlib graphviz
|
||||
```
|
||||
시스템에 Graphviz가 설치되어 있어야 합니다 (https://graphviz.org/download/).
|
||||
|
||||
## 작업 절차
|
||||
1. **요구사항 파악**: 다이어그램 유형, 노드/관계, 스타일 확인
|
||||
2. **Python 스크립트 작성**: file_write로 .py 파일 생성
|
||||
3. **스크립트 실행**: `process`로 Python 스크립트 실행
|
||||
4. **결과 확인**: 생성된 이미지 파일 경로를 사용자에게 안내
|
||||
|
||||
## 다이어그램 유형별 템플릿
|
||||
|
||||
### 플로차트 (Graphviz)
|
||||
```python
|
||||
from graphviz import Digraph
|
||||
|
||||
dot = Digraph(comment='Flowchart', format='png')
|
||||
dot.attr(rankdir='TB', fontname='Malgun Gothic')
|
||||
dot.attr('node', shape='box', style='rounded,filled', fillcolor='#E8F0FE')
|
||||
|
||||
dot.node('start', '시작', shape='ellipse', fillcolor='#34A853', fontcolor='white')
|
||||
dot.node('process1', '데이터 수집')
|
||||
dot.node('decision', '조건 확인?', shape='diamond', fillcolor='#FBBC04')
|
||||
dot.node('process2', '처리')
|
||||
dot.node('end', '종료', shape='ellipse', fillcolor='#EA4335', fontcolor='white')
|
||||
|
||||
dot.edge('start', 'process1')
|
||||
dot.edge('process1', 'decision')
|
||||
dot.edge('decision', 'process2', label='예')
|
||||
dot.edge('decision', 'end', label='아니오')
|
||||
dot.edge('process2', 'end')
|
||||
|
||||
dot.render('flowchart', cleanup=True)
|
||||
```
|
||||
|
||||
### 시퀀스 다이어그램 (matplotlib)
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as patches
|
||||
|
||||
fig, ax = plt.subplots(1, 1, figsize=(10, 8))
|
||||
# 액터 라이프라인, 메시지 화살표 등을 matplotlib으로 직접 그리기
|
||||
ax.set_xlim(0, 10)
|
||||
ax.set_ylim(0, 10)
|
||||
ax.invert_yaxis()
|
||||
ax.axis('off')
|
||||
plt.savefig('sequence.png', dpi=150, bbox_inches='tight')
|
||||
```
|
||||
|
||||
### ER 다이어그램 (Graphviz)
|
||||
```python
|
||||
from graphviz import Graph
|
||||
|
||||
er = Graph('ER', format='png', engine='neato')
|
||||
er.attr('node', shape='box', style='filled', fillcolor='#E8F0FE')
|
||||
|
||||
er.node('user', 'User\n─────\nid (PK)\nname\nemail')
|
||||
er.node('order', 'Order\n─────\nid (PK)\nuser_id (FK)\ntotal')
|
||||
|
||||
er.edge('user', 'order', label='1:N')
|
||||
er.render('er_diagram', cleanup=True)
|
||||
```
|
||||
|
||||
### 조직도 (Graphviz)
|
||||
```python
|
||||
from graphviz import Digraph
|
||||
|
||||
org = Digraph(format='png')
|
||||
org.attr(rankdir='TB')
|
||||
org.attr('node', shape='box', style='rounded,filled', fillcolor='#E3F2FD')
|
||||
|
||||
org.node('ceo', 'CEO')
|
||||
org.node('cto', 'CTO')
|
||||
org.node('cfo', 'CFO')
|
||||
org.edges([('ceo', 'cto'), ('ceo', 'cfo')])
|
||||
org.render('org_chart', cleanup=True)
|
||||
```
|
||||
|
||||
## 스타일 옵션
|
||||
- 폰트: `fontname='Malgun Gothic'` (한글 지원)
|
||||
- 색상: HTML 컬러 코드 지원
|
||||
- 출력 형식: png, svg, pdf
|
||||
- 레이아웃 엔진: dot(계층), neato(스프링), circo(원형), fdp(포스)
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 Python 스크립트와 결과 파일을 저장하세요.
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
name: docx-creator
|
||||
label: Word 문서 생성
|
||||
description: Python을 사용하여 전문적인 Word 문서(.docx)를 생성합니다. 작업 폴더의 양식 파일을 자동 활용합니다.
|
||||
icon: \uE8A5
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- document_read
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- document_assemble
|
||||
- format_convert
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
사용자의 요구에 맞는 전문적인 Word 문서를 Python으로 생성하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 python-docx 경로를 사용하세요.
|
||||
- Python 불가: `document_assemble`로 문서 본문을 구성하고 `format_convert`로 docx 산출을 시도하세요. 실패 시 Markdown/HTML 결과와 변환 가이드를 함께 제공하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
먼저 python-docx 패키지가 설치되어 있는지 확인하고, 없으면 설치하세요:
|
||||
```
|
||||
process: pip install python-docx
|
||||
```
|
||||
|
||||
## 양식 활용 (템플릿 모드)
|
||||
작업 폴더에 양식 파일이 있으면 **반드시** 활용하세요:
|
||||
|
||||
1. **양식 탐색**: `folder_map`으로 작업 폴더를 스캔하여 `.docx` 파일 확인
|
||||
2. **양식 후보 판별**:
|
||||
- 파일명에 "양식", "template", "서식", "표준", "기본" 포함
|
||||
- 또는 사용자가 명시적으로 "XX 양식으로 작성해줘" 요청
|
||||
- 또는 사용자가 특정 .docx 파일명을 언급
|
||||
3. **양식 구조 파악**: `document_read`로 양식 파일의 구조(스타일, 헤더, 섹션)를 먼저 확인
|
||||
4. **양식 기반 생성**:
|
||||
```python
|
||||
doc = Document('양식_보고서.docx') # ← 빈 Document() 대신 양식 로드
|
||||
# 양식의 스타일, 머리글/바닥글, 로고, 페이지 설정이 자동 상속됨
|
||||
# 기존 본문 내용을 지우고 새 내용만 추가
|
||||
for paragraph in doc.paragraphs:
|
||||
paragraph.clear() # 기존 내용 제거
|
||||
# 또는 필요에 따라 특정 섹션만 교체
|
||||
```
|
||||
5. **양식이 없으면**: 아래 기본 템플릿으로 새 문서 생성
|
||||
|
||||
## 작업 절차
|
||||
1. **요구사항 파악**: 사용자가 원하는 문서의 종류, 구조, 내용을 확인
|
||||
2. **양식 확인**: folder_map으로 작업 폴더에 양식 .docx 파일이 있는지 확인
|
||||
3. **Python 스크립트 작성**: file_write로 .py 파일 생성
|
||||
4. **스크립트 실행**: `process`로 Python 스크립트 실행
|
||||
5. **결과 확인**: 생성된 .docx 파일 경로를 사용자에게 안내
|
||||
|
||||
## Python 스크립트 템플릿
|
||||
```python
|
||||
from docx import Document
|
||||
from docx.shared import Inches, Pt, Cm
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.enum.style import WD_STYLE_TYPE
|
||||
import os
|
||||
|
||||
# 양식 파일 자동 감지
|
||||
template_keywords = ['양식', 'template', '서식', '표준', '기본']
|
||||
template_file = None
|
||||
for f in os.listdir('.'):
|
||||
if f.endswith('.docx') and any(kw in f.lower() for kw in template_keywords):
|
||||
template_file = f
|
||||
break
|
||||
|
||||
# 양식이 있으면 활용, 없으면 새 문서
|
||||
if template_file:
|
||||
doc = Document(template_file)
|
||||
print(f'양식 활용: {template_file}')
|
||||
else:
|
||||
doc = Document()
|
||||
# 스타일 설정 (양식이 없을 때만)
|
||||
style = doc.styles['Normal']
|
||||
font = style.font
|
||||
font.name = '맑은 고딕'
|
||||
font.size = Pt(11)
|
||||
|
||||
# 제목
|
||||
doc.add_heading('문서 제목', level=0)
|
||||
|
||||
# 본문
|
||||
doc.add_paragraph('내용을 여기에 작성합니다.')
|
||||
|
||||
# 표
|
||||
table = doc.add_table(rows=2, cols=3)
|
||||
table.style = 'Light Grid Accent 1'
|
||||
|
||||
# 저장
|
||||
doc.save('output.docx')
|
||||
```
|
||||
|
||||
## 지원 기능
|
||||
- 제목/소제목 계층 구조
|
||||
- 표 (스타일, 병합, 서식)
|
||||
- 이미지 삽입
|
||||
- 머리글/바닥글
|
||||
- 페이지 번호
|
||||
- 목차
|
||||
- 글머리 기호/번호 목록
|
||||
- **양식 파일 기반 스타일 상속** (로고, 헤더, 페이지 설정 자동 유지)
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 Python 스크립트와 결과 파일을 저장하세요.
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
name: email-draft
|
||||
label: 비즈니스 이메일 작성
|
||||
description: 상황과 톤에 맞는 전문적인 비즈니스 이메일 초안을 생성합니다.
|
||||
icon: \uE715
|
||||
allowed-tools:
|
||||
- clipboard_tool
|
||||
- file_write
|
||||
tabs: all
|
||||
---
|
||||
|
||||
사용자의 요청에 맞는 전문적인 비즈니스 이메일을 작성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. 사용자에게 다음 정보를 확인하세요:
|
||||
- **수신자**: 이름, 직급, 부서 (알고 있다면)
|
||||
- **목적**: 요청 / 보고 / 안내 / 감사 / 사과 / 협조 / 초대 / 회신
|
||||
- **핵심 내용**: 전달할 주요 메시지 (1~3가지)
|
||||
- **톤**: 공식(格式) / 반공식 / 친근
|
||||
- **언어**: 한국어 / 영어 / 일본어
|
||||
|
||||
2. 이메일 초안을 작성합니다:
|
||||
- 적절한 인사말로 시작
|
||||
- 핵심 내용은 **굵게** 강조
|
||||
- 요청 사항은 명확하고 구체적으로
|
||||
- 마무리 인사와 서명 포함
|
||||
|
||||
3. 사용자에게 초안을 보여주고 수정 요청을 받습니다.
|
||||
|
||||
4. 최종본을 clipboard_tool로 클립보드에 복사합니다.
|
||||
|
||||
## 이메일 구조
|
||||
|
||||
```
|
||||
제목: [간결하고 명확한 제목]
|
||||
|
||||
[수신자] 님께,
|
||||
|
||||
[인사말]
|
||||
|
||||
[본문 - 목적과 핵심 내용]
|
||||
|
||||
[요청 사항 또는 다음 단계]
|
||||
|
||||
[마무리 인사]
|
||||
|
||||
[서명]
|
||||
```
|
||||
|
||||
## 규칙
|
||||
- 한 문단은 3~4문장을 넘지 않도록 간결하게
|
||||
- 수동적 표현보다 능동적 표현 선호
|
||||
- 약어는 처음 사용 시 풀어 쓰기
|
||||
- 긴급도에 따라 제목에 [긴급], [참고] 등 태그 사용
|
||||
@@ -1,152 +0,0 @@
|
||||
---
|
||||
name: env-setup
|
||||
label: 프로젝트 환경 설정
|
||||
description: .gitignore, requirements.txt, .editorconfig 등 프로젝트 환경 설정 파일을 자동 생성합니다.
|
||||
icon: \uE835
|
||||
tabs: code
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
---
|
||||
|
||||
프로젝트 유형에 맞는 환경 설정 파일을 자동으로 생성하세요.
|
||||
|
||||
## 작업 절차
|
||||
|
||||
1. **프로젝트 분석**: folder_map으로 프로젝트 구조를 파악하고 유형 판별
|
||||
- `.py` 파일 → Python 프로젝트
|
||||
- `package.json` 또는 `.js/.ts` 파일 → Node.js 프로젝트
|
||||
- `.csproj` 또는 `.sln` 파일 → .NET 프로젝트
|
||||
- `pom.xml` 또는 `.java` 파일 → Java 프로젝트
|
||||
- 복합 프로젝트인 경우 모든 유형을 병합
|
||||
2. **기존 설정 확인**: 이미 존재하는 설정 파일이 있는지 확인
|
||||
- 있으면: 내용을 분석하여 누락된 항목만 추가 제안
|
||||
- 없으면: 새로 생성
|
||||
3. **생성할 파일 목록 제안**: 사용자에게 생성할 파일 목록을 보여주고 확인
|
||||
4. **파일 생성**: file_write로 각 설정 파일 생성
|
||||
5. **결과 안내**: 생성된 파일 목록과 주요 설정 내용 요약
|
||||
|
||||
## 프로젝트별 템플릿
|
||||
|
||||
### Python 프로젝트
|
||||
생성 파일: `.gitignore`, `requirements.txt`, `.editorconfig`, `setup.cfg`, `.flake8`
|
||||
|
||||
**.gitignore (Python)**:
|
||||
```
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
.venv/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
*.egg
|
||||
.mypy_cache/
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.env
|
||||
.idea/
|
||||
.vscode/
|
||||
*.log
|
||||
```
|
||||
|
||||
**requirements.txt**: 프로젝트에서 import 문을 스캔하여 자동 생성
|
||||
|
||||
### Node.js 프로젝트
|
||||
생성 파일: `.gitignore`, `.editorconfig`, `.nvmrc`, `.prettierrc`
|
||||
|
||||
**.gitignore (Node)**:
|
||||
```
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
.env
|
||||
.env.local
|
||||
*.log
|
||||
npm-debug.log*
|
||||
.DS_Store
|
||||
coverage/
|
||||
.nyc_output/
|
||||
.idea/
|
||||
.vscode/
|
||||
*.tgz
|
||||
```
|
||||
|
||||
### .NET 프로젝트
|
||||
생성 파일: `.gitignore`, `.editorconfig`, `Directory.Build.props`
|
||||
|
||||
**.gitignore (.NET)**:
|
||||
```
|
||||
bin/
|
||||
obj/
|
||||
.vs/
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
packages/
|
||||
*.nupkg
|
||||
TestResults/
|
||||
.idea/
|
||||
*.DotSettings.user
|
||||
```
|
||||
|
||||
### Java 프로젝트
|
||||
생성 파일: `.gitignore`, `.editorconfig`
|
||||
|
||||
**.gitignore (Java)**:
|
||||
```
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
target/
|
||||
.gradle/
|
||||
build/
|
||||
.idea/
|
||||
*.iml
|
||||
.settings/
|
||||
.classpath
|
||||
.project
|
||||
out/
|
||||
```
|
||||
|
||||
## 공통 .editorconfig
|
||||
```ini
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{json,js,ts,jsx,tsx}]
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
```
|
||||
|
||||
## 규칙
|
||||
- 기존 설정 파일이 있으면 덮어쓰지 않고, 누락 항목만 제안
|
||||
- .env 파일은 생성하지 않음 (보안 — 사용자가 직접 생성)
|
||||
- 생성 전 파일 목록을 반드시 사용자에게 확인
|
||||
- 프로젝트 루트에 생성 (하위 폴더에 생성하지 않음)
|
||||
|
||||
한국어로 안내하세요.
|
||||
@@ -1,63 +0,0 @@
|
||||
---
|
||||
name: gen-test
|
||||
label: 테스트 생성기
|
||||
description: 지정 파일의 단위 테스트를 자동 생성하고 실행하여 커버리지를 분석합니다.
|
||||
icon: \uE9D5
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- test_loop
|
||||
- grep
|
||||
- folder_map
|
||||
- dev_env_detect
|
||||
- build_run
|
||||
tabs: code
|
||||
---
|
||||
|
||||
지정된 소스 파일의 단위 테스트를 자동 생성하고 실행하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **환경 감지**: dev_env_detect로 프로젝트 타입, 테스트 프레임워크 확인
|
||||
2. **대상 분석**: file_read로 테스트 대상 파일 분석
|
||||
- 공개 메서드/함수 목록 추출
|
||||
- 메서드별 입출력 타입 파악
|
||||
- 의존성 확인 (Mock 필요 여부)
|
||||
3. **테스트 생성**: 각 메서드에 대해 테스트 케이스 작성
|
||||
- 정상 케이스 (Happy Path)
|
||||
- 경계값 (Boundary)
|
||||
- 예외/에러 케이스
|
||||
- null/empty 입력
|
||||
4. **실행 및 검증**: test_loop으로 테스트 실행
|
||||
5. **결과 보고**: 성공/실패 요약, 커버리지 추정
|
||||
|
||||
## 테스트 작성 원칙
|
||||
|
||||
### 명명 규칙
|
||||
- C#: `[메서드명]_[시나리오]_[기대결과]`
|
||||
- Python: `test_[메서드명]_[시나리오]`
|
||||
- JavaScript: `should [기대 동작] when [조건]`
|
||||
|
||||
### 테스트 구조 (AAA 패턴)
|
||||
```
|
||||
Arrange — 테스트 데이터 준비
|
||||
Act — 대상 메서드 실행
|
||||
Assert — 결과 검증
|
||||
```
|
||||
|
||||
### 프레임워크별 지원
|
||||
- C#: xUnit, NUnit, MSTest
|
||||
- Python: pytest, unittest
|
||||
- JavaScript: Jest, Mocha, Vitest
|
||||
- Java: JUnit 5
|
||||
|
||||
## 출력
|
||||
- 테스트 파일 생성 (프로젝트 컨벤션에 맞는 위치)
|
||||
- 테스트 실행 결과 요약
|
||||
- 커버리지 추정 (메서드별 테스트 유무)
|
||||
|
||||
## 규칙
|
||||
- 기존 테스트가 있으면 스타일을 따르기
|
||||
- 외부 의존성은 Mock/Stub 사용
|
||||
- 테스트 간 독립성 보장 (상태 공유 금지)
|
||||
- 한국어 주석으로 테스트 의도 설명
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
name: hook-policy-demo
|
||||
label: Hook Policy Demo
|
||||
description: hooks/hook_filters 런타임 정책 적용 예시 스킬입니다.
|
||||
icon: \uE943
|
||||
tabs: code
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_edit
|
||||
- grep
|
||||
- build_run
|
||||
when_to_use: 코드 수정 후 특정 훅만 pre/post로 선택 적용하고 싶을 때
|
||||
argument-hint: 대상 파일 경로 또는 변경 목적
|
||||
context: fork
|
||||
agent: worker
|
||||
effort: medium
|
||||
sample: true
|
||||
hooks:
|
||||
file_edit:
|
||||
pre:
|
||||
- lint-pre
|
||||
post:
|
||||
- verify-post
|
||||
build_run:
|
||||
post:
|
||||
- verify-post
|
||||
hook_filters: lint-pre@pre@file_edit, verify-post@post@file_edit, verify-post@post@build_run
|
||||
---
|
||||
|
||||
이 스킬은 `hooks`/`hook_filters` 정책을 함께 사용하는 예시입니다.
|
||||
|
||||
## 목표
|
||||
- 수정 전에는 `lint-pre` 훅만 실행
|
||||
- 수정 후/빌드 후에는 `verify-post` 훅만 실행
|
||||
|
||||
## 실행 가이드
|
||||
1. `grep` + `file_read`로 대상 코드와 관련 호출부를 확인합니다.
|
||||
2. 필요한 최소 범위만 `file_edit`로 수정합니다.
|
||||
3. 수정 후 `build_run`으로 빌드/테스트를 실행해 검증합니다.
|
||||
4. 변경 내용/검증 결과/남은 리스크를 간단히 보고합니다.
|
||||
|
||||
## 제약
|
||||
- `allowed-tools` 목록 외 도구 호출 금지
|
||||
- 불필요한 전면 리팩토링 금지
|
||||
- 동일 실패 재시도 전에 원인/대안 먼저 제시
|
||||
@@ -1,87 +0,0 @@
|
||||
---
|
||||
name: image-processor
|
||||
label: 이미지 처리
|
||||
description: Python Pillow를 사용하여 이미지 리사이즈, 크롭, 워터마크, 포맷 변환을 수행합니다.
|
||||
icon: \uEB9F
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- image_analyze
|
||||
- format_convert
|
||||
- file_manage
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
사용자의 요구에 맞게 이미지를 처리하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 Pillow 경로를 사용하세요.
|
||||
- Python 불가: `image_analyze`로 이미지 상태를 점검하고, `file_manage`/`format_convert`/`file_write`로 가능한 변환 및 수동 처리 지침을 제공하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
먼저 Pillow 패키지가 설치되어 있��지 확인하고, 없으면 설���하세요:
|
||||
```
|
||||
process: pip install Pillow
|
||||
```
|
||||
|
||||
## 작업 절��
|
||||
1. **요구사항 파악**: 처리할 이미지 파일과 원하는 작업 확인
|
||||
2. **Python 스크립트 작성**: file_write로 .py 파일 생성
|
||||
3. **스크립트 실행**: `process`로 Python 스크립�� 실행
|
||||
4. **결과 확인**: 처리된 이미지 파일 경로를 사용자에게 안내
|
||||
|
||||
## 지원 기능
|
||||
|
||||
### 리사이즈
|
||||
```python
|
||||
from PIL import Image
|
||||
img = Image.open('input.png')
|
||||
img_resized = img.resize((800, 600)) # 고정 크기
|
||||
# 또는 비율 유지
|
||||
img.thumbnail((800, 800))
|
||||
img.save('output.png')
|
||||
```
|
||||
|
||||
### 크롭
|
||||
```python
|
||||
img = Image.open('input.png')
|
||||
cropped = img.crop((left, top, right, bottom))
|
||||
cropped.save('output.png')
|
||||
```
|
||||
|
||||
### 워터마크
|
||||
```python
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
img = Image.open('input.png')
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.text((10, 10), "Watermark", fill=(255, 255, 255, 128))
|
||||
img.save('output.png')
|
||||
```
|
||||
|
||||
### 포맷 변환
|
||||
```python
|
||||
img = Image.open('input.png')
|
||||
img.save('output.jpg', 'JPEG', quality=85)
|
||||
img.save('output.webp', 'WEBP', quality=80)
|
||||
```
|
||||
|
||||
### 배치 처리
|
||||
```python
|
||||
import glob
|
||||
for path in glob.glob('*.png'):
|
||||
img = Image.open(path)
|
||||
img.thumbnail((800, 800))
|
||||
img.save(f'resized_{path}')
|
||||
```
|
||||
|
||||
## 추가 기능
|
||||
- 회전/뒤집기 (rotate, transpose)
|
||||
- 밝기/대비/선명도 조절 (ImageEnhance)
|
||||
- 필터 적용 (ImageFilter: BLUR, SHARPEN, CONTOUR)
|
||||
- 이미지 합성 (Image.paste, Image.alpha_composite)
|
||||
- EXIF 정보 읽기
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 Python 스크립트와 결과 파일을 저장하세요.
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
name: impact
|
||||
label: 변경 영향 분석
|
||||
description: 코드 변경 시 영향받는 파일, 함수, 테스트를 식별합니다.
|
||||
icon: \uE946
|
||||
allowed-tools:
|
||||
- lsp_code_intel
|
||||
- grep
|
||||
- search_codebase
|
||||
- git_tool
|
||||
- file_read
|
||||
- folder_map
|
||||
tabs: code
|
||||
---
|
||||
|
||||
코드 변경이 미치는 영향 범위를 분석하여 안전한 수정을 도와주세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **변경 대상 확인**: 어떤 파일/함수/클래스를 변경할 예정인지 파악
|
||||
2. **직접 참조 분석**: lsp_code_intel의 find_references로 직접 호출처 확인
|
||||
3. **간접 영향 분석**:
|
||||
- grep으로 문자열 기반 참조 검색
|
||||
- search_codebase로 의미적 유사 코드 탐색
|
||||
- 상속/인터페이스 체인 추적
|
||||
4. **테스트 영향**: 관련 테스트 파일 식별
|
||||
5. **위험도 평가**: 변경 영향 범위와 위험도 매트릭스 생성
|
||||
6. **보고서 작성**: 영향 분석 보고서 생성
|
||||
|
||||
## 분석 항목
|
||||
|
||||
### 직접 영향
|
||||
- 해당 함수/클래스를 직접 호출하는 코드
|
||||
- import/using 하는 파일
|
||||
- 상속받는 클래스
|
||||
|
||||
### 간접 영향
|
||||
- 인터페이스 구현체를 통한 호출
|
||||
- 리플렉션/동적 호출
|
||||
- 설정 파일 참조
|
||||
- UI 바인딩
|
||||
|
||||
### 테스트 영향
|
||||
- 직접 테스트하는 테스트 파일
|
||||
- 관련 통합 테스트
|
||||
- 테스트 더블(Mock) 대상 여부
|
||||
|
||||
## 출력 형식
|
||||
|
||||
### 영향 분석 보고서
|
||||
|
||||
| 영향 수준 | 파일 | 관련 함수 | 위험도 | 비고 |
|
||||
|----------|------|----------|--------|------|
|
||||
| 🔴 직접 | A.cs | MethodX() | 높음 | 시그니처 변경 시 컴파일 오류 |
|
||||
| 🟡 간접 | B.cs | MethodY() | 중간 | 동적 호출, 런타임 오류 가능 |
|
||||
| 🟢 테스트 | A.Tests.cs | Test1() | 낮음 | 테스트 수정 필요 |
|
||||
|
||||
### 변경 체크리스트
|
||||
- [ ] 직접 참조 N개 확인 및 수정
|
||||
- [ ] 테스트 M개 업데이트
|
||||
- [ ] 관련 문서 갱신
|
||||
|
||||
## 규칙
|
||||
- 코드를 직접 수정하지 않음 (분석만 수행)
|
||||
- 가능하면 LSP 기반 정확한 참조 분석 우선
|
||||
- LSP 불가 시 grep 기반 텍스트 검색으로 대체
|
||||
- 위험도는 보수적으로 평가 (의심스러우면 높음)
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
name: json-schema
|
||||
label: JSON/YAML 스키마 도구
|
||||
description: JSON 또는 YAML 데이터에서 스키마를 생성하고, 데이터의 유효성을 검증합니다.
|
||||
icon: \uE943
|
||||
tabs: code
|
||||
allowed-tools:
|
||||
- json_tool
|
||||
- file_read
|
||||
- file_write
|
||||
- clipboard_tool
|
||||
---
|
||||
|
||||
JSON/YAML 샘플 데이터에서 스키마를 추출하거나, 기존 스키마로 데이터를 검증하세요.
|
||||
|
||||
## 작업 절차
|
||||
|
||||
1. **요청 유형 판별**:
|
||||
- **스키마 생성**: 샘플 JSON/YAML → JSON Schema 추출
|
||||
- **스키마 검증**: 데이터 + 스키마 → 유효성 검사
|
||||
- **스키마 문서화**: 기존 스키마 → 사람이 읽을 수 있는 설명 생성
|
||||
2. **입력 데이터 확인**: file_read 또는 사용자 입력으로 데이터 로드
|
||||
3. **스키마 생성 또는 검증 수행**: json_tool로 처리
|
||||
4. **결과 출력**: 생성된 스키마 또는 검증 결과를 표시
|
||||
5. **파일 저장**: file_write로 결과를 저장하거나 clipboard_tool로 복사
|
||||
|
||||
## 스키마 생성 규칙
|
||||
|
||||
### 타입 추론
|
||||
| JSON 값 | JSON Schema 타입 | 추가 속성 |
|
||||
|---------|-----------------|----------|
|
||||
| `"text"` | `string` | — |
|
||||
| `123` | `integer` | — |
|
||||
| `1.5` | `number` | — |
|
||||
| `true` | `boolean` | — |
|
||||
| `null` | `null` | nullable 처리 |
|
||||
| `[]` | `array` | items 스키마 |
|
||||
| `{}` | `object` | properties 스키마 |
|
||||
|
||||
### 추론 강화
|
||||
- **패턴 감지**: 이메일, URL, 날짜, UUID 등은 `format` 속성 추가
|
||||
- **열거형 감지**: 값의 종류가 적으면 `enum` 으로 제안
|
||||
- **필수 필드**: 모든 샘플에 존재하는 필드는 `required`로 표시
|
||||
- **배열 항목**: 배열 내 모든 항목을 분석하여 통합 스키마 생성
|
||||
|
||||
### 출력 형식 (JSON Schema Draft 7)
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "사용자 정보",
|
||||
"description": "사용자 프로필 데이터 스키마",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "사용자 이름"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "이메일 주소"
|
||||
},
|
||||
"age": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 150,
|
||||
"description": "나이"
|
||||
}
|
||||
},
|
||||
"required": ["name", "email"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
```
|
||||
|
||||
## 검증 결과 형식
|
||||
검증 결과는 다음과 같이 표시하세요:
|
||||
```
|
||||
검증 결과: ❌ 실패 (3건의 오류)
|
||||
|
||||
| # | 경로 | 오류 | 기대값 |
|
||||
|---|------|------|--------|
|
||||
| 1 | $.email | 필수 필드 누락 | string (required) |
|
||||
| 2 | $.age | 타입 불일치 | integer (실제: string) |
|
||||
| 3 | $.tags[2] | 열거형 불일치 | "A", "B", "C" 중 하나 |
|
||||
```
|
||||
|
||||
## 스키마 문서화 형식
|
||||
기존 스키마를 분석하여 사람이 읽기 쉬운 문서를 생성:
|
||||
```
|
||||
## 사용자 정보 스키마
|
||||
|
||||
| 필드 | 타입 | 필수 | 설명 | 제약 조건 |
|
||||
|------|------|------|------|----------|
|
||||
| name | string | ✅ | 사용자 이름 | — |
|
||||
| email | string | ✅ | 이메일 주소 | format: email |
|
||||
| age | integer | — | 나이 | 0~150 |
|
||||
| tags | array | — | 태그 목록 | items: string |
|
||||
```
|
||||
|
||||
## 규칙
|
||||
- JSON Schema Draft 7 형식 사용
|
||||
- 스키마 생성 시 description 필드를 한국어로 작성
|
||||
- 복수 샘플이 제공되면 모든 샘플을 분석하여 통합 스키마 생성
|
||||
- 중첩 객체는 재귀적으로 스키마 추출
|
||||
- 결과는 파일 저장과 클립보드 복사 모두 제공
|
||||
|
||||
한국어로 안내하세요.
|
||||
@@ -1,69 +0,0 @@
|
||||
---
|
||||
name: log-analyze
|
||||
label: 로그 분석기
|
||||
description: 로그 파일의 패턴을 분석하고 에러를 요약하며 타임라인을 시각화합니다.
|
||||
icon: \uE9D9
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- grep
|
||||
- chart_create
|
||||
- html_create
|
||||
- data_pivot
|
||||
- text_summarize
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
로그 파일을 분석하여 패턴, 에러, 추세를 파악하고 보고서를 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **로그 파일 로드**: file_read로 로그 파일 읽기
|
||||
2. **패턴 분석**:
|
||||
- grep으로 에러/경고/예외 패턴 검색
|
||||
- 시간대별 이벤트 빈도 파악
|
||||
- 반복되는 에러 패턴 식별
|
||||
3. **통계 생성**: data_pivot으로 집계
|
||||
4. **시각화**: chart_create로 타임라인 차트 생성
|
||||
5. **보고서**: html_create로 분석 보고서 생성
|
||||
|
||||
## 분석 항목
|
||||
|
||||
### 에러 분석
|
||||
- ERROR, WARN, FATAL, Exception 키워드 추출
|
||||
- 에러 유형별 발생 빈도
|
||||
- 최초 발생 시점 및 최근 발생 시점
|
||||
- 에러 메시지 클러스터링 (유사 에러 그룹화)
|
||||
|
||||
### 시간대 분석
|
||||
- 시간대별 로그 발생 빈도
|
||||
- 피크 시간대 식별
|
||||
- 에러 집중 시간대
|
||||
|
||||
### 패턴 분석
|
||||
- 반복 패턴 (주기적 에러)
|
||||
- 연쇄 에러 (A 에러 후 B 에러 발생 패턴)
|
||||
- 비정상 패턴 (평소와 다른 로그량)
|
||||
|
||||
## 출력 형식
|
||||
|
||||
```
|
||||
## 로그 분석 보고서
|
||||
- 분석 기간: [시작] ~ [끝]
|
||||
- 총 로그: N줄
|
||||
|
||||
### 에러 요약 (상위 10건)
|
||||
| 순위 | 에러 유형 | 발생 횟수 | 최근 발생 |
|
||||
|------|----------|----------|----------|
|
||||
|
||||
### 타임라인 차트
|
||||
[시간대별 이벤트 빈도 차트]
|
||||
|
||||
### 상세 분석
|
||||
[에러별 상세 내용 및 권장 조치]
|
||||
```
|
||||
|
||||
## 지원 로그 형식
|
||||
- 일반 텍스트 로그 (타임스탬프 자동 감지)
|
||||
- JSON 로그 (각 줄이 JSON 객체)
|
||||
- CSV 로그 (헤더 포함)
|
||||
- syslog 형식
|
||||
@@ -1,174 +0,0 @@
|
||||
---
|
||||
name: markdown-to-doc
|
||||
label: Markdown → 문서 변환
|
||||
description: Markdown 파일을 서식이 적용된 Word(.docx) 또는 PDF 문서로 변환합니다.
|
||||
icon: \uE8A5
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- format_convert
|
||||
- document_assemble
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
Markdown 파일을 전문적인 Word 또는 PDF 문서로 변환하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 python-docx 경로를 사용하세요.
|
||||
- Python 불가: `format_convert`를 우선 사용해 Markdown을 docx/pdf로 변환하고, 변환 제한 시 `file_write`로 보정 가이드를 생성하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
먼저 필요한 패키지가 설치되어 있는지 확인하고, 없으면 설치하세요:
|
||||
```
|
||||
process: pip install python-docx markdown
|
||||
```
|
||||
|
||||
## 작업 절차
|
||||
|
||||
1. **Markdown 파일 확인**: file_read로 변환할 Markdown 파일의 내용과 구조를 파악
|
||||
2. **변환 옵션 확인**: 사용자에게 다음 옵션을 확인
|
||||
- 출력 형식: Word(.docx) 또는 PDF
|
||||
- 폰트: 맑은 고딕 (기본) / 사용자 지정
|
||||
- 여백, 페이지 크기 설정
|
||||
- 머리글/바닥글 포함 여부
|
||||
3. **Python 스크립트 작성**: file_write로 변환 스크립트 생성
|
||||
4. **스크립트 실행**: `process`로 Python 스크립트 실행
|
||||
5. **결과 확인**: 생성된 문서 파일 경로와 페이지 수를 안내
|
||||
|
||||
## 스타일 매핑
|
||||
|
||||
| Markdown | Word 스타일 | 설명 |
|
||||
|----------|------------|------|
|
||||
| `# 제목` | Heading 1 | 16pt, 굵게 |
|
||||
| `## 소제목` | Heading 2 | 14pt, 굵게 |
|
||||
| `### 항목` | Heading 3 | 12pt, 굵게 |
|
||||
| 본문 텍스트 | Normal | 11pt |
|
||||
| `**굵게**` | Bold run | 굵게 |
|
||||
| `*기울임*` | Italic run | 기울임 |
|
||||
| `` `코드` `` | 코드 스타일 | Consolas, 배경색 |
|
||||
| `> 인용` | Quote | 들여쓰기 + 왼쪽 테두리 |
|
||||
| `- 목록` | List Bullet | 글머리 기호 |
|
||||
| `1. 번호` | List Number | 번호 목록 |
|
||||
| 표 | Table Grid | 테두리 표 |
|
||||
| `---` | 페이지 구분 | 가로선 → 페이지 나누기 |
|
||||
| 코드 블록 | 코드 단락 | Consolas, 회색 배경 |
|
||||
|
||||
## Python 스크립트 템플릿
|
||||
```python
|
||||
import re
|
||||
from docx import Document
|
||||
from docx.shared import Inches, Pt, Cm, RGBColor
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.oxml.ns import qn
|
||||
import markdown
|
||||
|
||||
# Markdown 파일 읽기
|
||||
with open('input.md', 'r', encoding='utf-8') as f:
|
||||
md_content = f.read()
|
||||
|
||||
doc = Document()
|
||||
|
||||
# 기본 스타일 설정
|
||||
style = doc.styles['Normal']
|
||||
font = style.font
|
||||
font.name = '맑은 고딕'
|
||||
font.size = Pt(11)
|
||||
style.element.rPr.rFonts.set(qn('w:eastAsia'), '맑은 고딕')
|
||||
|
||||
# 여백 설정
|
||||
for section in doc.sections:
|
||||
section.top_margin = Cm(2.54)
|
||||
section.bottom_margin = Cm(2.54)
|
||||
section.left_margin = Cm(3.17)
|
||||
section.right_margin = Cm(3.17)
|
||||
|
||||
# Markdown 파싱 및 변환
|
||||
lines = md_content.split('\n')
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
# 제목 (Heading)
|
||||
if line.startswith('#'):
|
||||
level = len(line) - len(line.lstrip('#'))
|
||||
text = line.lstrip('#').strip()
|
||||
doc.add_heading(text, level=min(level, 4))
|
||||
|
||||
# 코드 블록
|
||||
elif line.startswith('```'):
|
||||
code_lines = []
|
||||
i += 1
|
||||
while i < len(lines) and not lines[i].startswith('```'):
|
||||
code_lines.append(lines[i])
|
||||
i += 1
|
||||
p = doc.add_paragraph()
|
||||
run = p.add_run('\n'.join(code_lines))
|
||||
run.font.name = 'Consolas'
|
||||
run.font.size = Pt(9)
|
||||
|
||||
# 인용
|
||||
elif line.startswith('>'):
|
||||
text = line.lstrip('>').strip()
|
||||
p = doc.add_paragraph(text)
|
||||
p.paragraph_format.left_indent = Cm(1.27)
|
||||
|
||||
# 글머리 기호
|
||||
elif line.startswith('- ') or line.startswith('* '):
|
||||
text = line[2:].strip()
|
||||
doc.add_paragraph(text, style='List Bullet')
|
||||
|
||||
# 번호 목록
|
||||
elif re.match(r'^\d+\.\s', line):
|
||||
text = re.sub(r'^\d+\.\s', '', line).strip()
|
||||
doc.add_paragraph(text, style='List Number')
|
||||
|
||||
# 가로선 → 페이지 나누기
|
||||
elif line.strip() in ('---', '***', '___'):
|
||||
doc.add_page_break()
|
||||
|
||||
# 빈 줄
|
||||
elif line.strip() == '':
|
||||
pass
|
||||
|
||||
# 일반 텍스트
|
||||
else:
|
||||
p = doc.add_paragraph()
|
||||
# 굵게, 기울임 처리
|
||||
parts = re.split(r'(\*\*.*?\*\*|\*.*?\*|`.*?`)', line)
|
||||
for part in parts:
|
||||
if part.startswith('**') and part.endswith('**'):
|
||||
run = p.add_run(part[2:-2])
|
||||
run.bold = True
|
||||
elif part.startswith('*') and part.endswith('*'):
|
||||
run = p.add_run(part[1:-1])
|
||||
run.italic = True
|
||||
elif part.startswith('`') and part.endswith('`'):
|
||||
run = p.add_run(part[1:-1])
|
||||
run.font.name = 'Consolas'
|
||||
run.font.size = Pt(10)
|
||||
else:
|
||||
p.add_run(part)
|
||||
|
||||
i += 1
|
||||
|
||||
# 저장
|
||||
doc.save('output.docx')
|
||||
print(f'변환 완료: output.docx ({len(doc.paragraphs)}개 단락)')
|
||||
```
|
||||
|
||||
## 표 변환
|
||||
Markdown 표가 있으면 Word 표로 변환합니다:
|
||||
- 헤더 행: 굵게, 배경색 적용
|
||||
- 셀 정렬: Markdown의 `:---`, `:---:`, `---:` 구문 반영
|
||||
- 테두리: 전체 셀에 얇은 테두리
|
||||
|
||||
## 규칙
|
||||
- 원본 Markdown 파일은 수정하지 않음
|
||||
- 인코딩: UTF-8 기본
|
||||
- 이미지 링크(``)는 로컬 파일이면 삽입, URL이면 경로만 표시
|
||||
- 복잡한 Markdown(수식, 다이어그램)은 지원 범위와 한계를 안내
|
||||
- 출력 파일명: 원본 파일명 기준 (.md → .docx)
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 Python 스크립트와 결과 파일을 저장하세요.
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
name: meeting-minutes
|
||||
label: 회의록 정리
|
||||
description: 회의 내용을 체계적으로 정리하여 회의록을 생성합니다.
|
||||
icon: \uE771
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- text_summarize
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
사용자가 제공한 회의 내용(텍스트, 메모, 파일)을 정리하여 체계적인 회의록을 작성하세요.
|
||||
|
||||
다음 도구를 사용하세요:
|
||||
1. file_read — 회의 관련 파일 읽기 (필요 시)
|
||||
2. file_write — 회의록 파일 생성
|
||||
|
||||
회의록 형식:
|
||||
## 회의 정보
|
||||
- 일시:
|
||||
- 참석자:
|
||||
- 장소/방법:
|
||||
- 주제:
|
||||
|
||||
## 안건 및 논의 내용
|
||||
각 안건별로:
|
||||
- **안건**: 주제
|
||||
- **논의 내용**: 주요 발언 및 의견 정리
|
||||
- **결정 사항**: 합의된 내용
|
||||
|
||||
## 액션 아이템
|
||||
| 번호 | 담당자 | 내용 | 기한 | 비고 |
|
||||
|------|--------|------|------|------|
|
||||
| 1 | - | - | - | - |
|
||||
|
||||
## 다음 회의
|
||||
- 예정일:
|
||||
- 주요 안건:
|
||||
|
||||
규칙:
|
||||
- 핵심 내용 위주로 간결하게 정리
|
||||
- 결정 사항과 액션 아이템은 명확하게 기술
|
||||
- 한국어로 작성
|
||||
@@ -1,118 +0,0 @@
|
||||
---
|
||||
name: ocr-extract
|
||||
label: OCR 텍스트 추출
|
||||
description: Python pytesseract를 사용하여 이미지/스캔 문서에서 텍스트를 추출합니다.
|
||||
icon: \uE8D4
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- image_analyze
|
||||
- text_summarize
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
이미지 또는 스캔된 문서에서 텍스트를 추출하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 pytesseract 경로를 사용하세요.
|
||||
- Python 불가: `image_analyze`로 텍스트 후보를 추출하고 `text_summarize` + `file_write`로 정제본을 제공하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
1. Tesseract OCR 엔진이 시스템에 설치되어 있어야 합니다.
|
||||
- Windows: https://github.com/UB-Mannheim/tesseract/wiki 에서 설치
|
||||
- 한국어 지원: 설치 시 "Korean" 언어 데이터 선택
|
||||
2. Python 패키지 설치:
|
||||
```
|
||||
process: pip install pytesseract Pillow
|
||||
```
|
||||
|
||||
## 작업 절차
|
||||
1. **이미지 확인**: 사용자가 제공한 이미지 파일 확인
|
||||
2. **전처리 (선택)**: 이미지 품질이 낮으면 전처리 스크립트 적용
|
||||
3. **OCR 실행**: pytesseract로 텍스트 추출
|
||||
4. **결과 저장**: 추출된 텍스트를 파일로 저장하고 사용자에게 안내
|
||||
|
||||
## OCR 스크립트 템플릿
|
||||
|
||||
### 기본 텍스트 추출
|
||||
```python
|
||||
import pytesseract
|
||||
from PIL import Image
|
||||
|
||||
# Windows에서 Tesseract 경로 지정 (필요 시)
|
||||
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
|
||||
|
||||
img = Image.open('scan.png')
|
||||
text = pytesseract.image_to_string(img, lang='kor+eng')
|
||||
print(text)
|
||||
|
||||
with open('extracted.txt', 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
```
|
||||
|
||||
### 이미지 전처리 (품질 개선)
|
||||
```python
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
|
||||
img = Image.open('scan.png')
|
||||
|
||||
# 그레이스케일 변환
|
||||
img = img.convert('L')
|
||||
|
||||
# 대비 향상
|
||||
enhancer = ImageEnhance.Contrast(img)
|
||||
img = enhancer.enhance(2.0)
|
||||
|
||||
# 선명도 향상
|
||||
img = img.filter(ImageFilter.SHARPEN)
|
||||
|
||||
# 이진화 (흑백)
|
||||
threshold = 128
|
||||
img = img.point(lambda x: 255 if x > threshold else 0)
|
||||
|
||||
img.save('preprocessed.png')
|
||||
text = pytesseract.image_to_string(img, lang='kor+eng')
|
||||
```
|
||||
|
||||
### 배치 OCR (여러 이미지)
|
||||
```python
|
||||
import glob
|
||||
import pytesseract
|
||||
from PIL import Image
|
||||
|
||||
results = []
|
||||
for path in sorted(glob.glob('*.png')):
|
||||
img = Image.open(path)
|
||||
text = pytesseract.image_to_string(img, lang='kor+eng')
|
||||
results.append(f'--- {path} ---\n{text}\n')
|
||||
|
||||
with open('all_extracted.txt', 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(results))
|
||||
```
|
||||
|
||||
### 표 영역 추출
|
||||
```python
|
||||
import pytesseract
|
||||
from PIL import Image
|
||||
|
||||
img = Image.open('table.png')
|
||||
# TSV 형식으로 추출 (표 구조 보존)
|
||||
tsv_data = pytesseract.image_to_data(img, lang='kor+eng', output_type=pytesseract.Output.DATAFRAME)
|
||||
print(tsv_data)
|
||||
```
|
||||
|
||||
## 지원 언어
|
||||
- `kor` — 한국어
|
||||
- `eng` — 영어
|
||||
- `kor+eng` — 한국어+영어 혼합 (권장)
|
||||
- `jpn` — 일본어
|
||||
- `chi_sim` — 중국어 간체
|
||||
|
||||
## 팁
|
||||
- 스캔 해상도 300dpi 이상이면 인식률이 높습니다
|
||||
- 기울어진 이미지는 `img.rotate()` 로 보정 후 추출하세요
|
||||
- 손글씨는 인식률이 낮을 수 있습니다
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 Python 스크립트와 결과 파일을 저장하세요.
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
name: paper-review
|
||||
label: 논문 분석
|
||||
description: 논문 또는 기술 문서를 체계적으로 분석하고 핵심 내용을 정리합니다.
|
||||
icon: \uE736
|
||||
allowed-tools:
|
||||
- document_read
|
||||
- file_read
|
||||
- file_write
|
||||
- text_summarize
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
사용자가 제공한 논문 또는 기술 문서를 체계적으로 분석하세요.
|
||||
|
||||
## 파일 읽기 전략
|
||||
- **PDF 파일**: 반드시 `document_read` 도구를 사용하세요. 페이지 범위 지정이 가능합니다.
|
||||
- 먼저 전체 페이지 수를 확인한 후, 초록(1-2페이지)→본문→참고문헌 순으로 읽으세요.
|
||||
- `file_read`로 PDF를 읽으면 텍스트가 깨질 수 있으므로 사용하지 마세요.
|
||||
- **텍스트 파일** (.txt, .md, .html 등): `file_read` 도구를 사용하세요.
|
||||
|
||||
## 사용 도구
|
||||
1. document_read — PDF 논문 파일 읽기 (페이지 범위 지정, 초록/참고문헌 추출)
|
||||
2. file_read — 텍스트 기반 문서 파일 읽기
|
||||
3. file_write — 분석 보고서 생성
|
||||
|
||||
## 분석 항목
|
||||
## 논문 개요
|
||||
- 제목, 저자, 발표 연도/학회
|
||||
- 연구 분야 및 키워드
|
||||
|
||||
## 연구 목적 및 배경
|
||||
- 연구 문제 정의
|
||||
- 기존 연구의 한계점
|
||||
|
||||
## 방법론
|
||||
- 제안 방법의 핵심 아이디어
|
||||
- 실험 설계 및 데이터셋
|
||||
|
||||
## 주요 결과
|
||||
- 핵심 실험 결과 (표/수치 인용)
|
||||
- 기존 방법 대비 개선점
|
||||
|
||||
## 한계점 및 향후 연구
|
||||
- 저자가 인정한 한계
|
||||
- 발전 가능성
|
||||
|
||||
## 실무 적용 가능성
|
||||
- 우리 업무에 적용할 수 있는 포인트
|
||||
- 기술 도입 시 고려사항
|
||||
|
||||
## 작업 절차
|
||||
1. 사용자가 파일명을 언급하면 작업 폴더에서 해당 파일을 찾아 읽기
|
||||
2. PDF인 경우 `document_read`로 초록(1-2p) 먼저 읽어 전체 구조 파악
|
||||
3. 본문을 페이지 범위별로 나누어 순차 읽기
|
||||
4. 위 분석 항목에 따라 체계적으로 정리
|
||||
5. `file_write`로 분석 보고서를 마크다운 파일로 저장
|
||||
|
||||
한국어로 작성하고, 전문 용어는 원문과 함께 표기하세요.
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
name: pdf-processor
|
||||
label: PDF 처리
|
||||
description: Python을 사용하여 PDF에서 텍스트/표를 추출하거나 PDF를 생성합니다.
|
||||
icon: \uE9F9
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- document_read
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- format_convert
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
PDF 파일을 읽거나 새 PDF를 생성하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 pypdf/pdfplumber/reportlab 경로를 사용하세요.
|
||||
- Python 불가: `document_read`로 텍스트/구조를 추출하고, 생성 작업은 `format_convert` + `file_write` 기반으로 대체하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
필요한 패키지를 확인하고 설치하세요:
|
||||
```
|
||||
process: pip install pypdf pdfplumber reportlab
|
||||
```
|
||||
|
||||
## 작업 절차
|
||||
|
||||
### PDF 텍스트 추출
|
||||
1. **파일 확인**: folder_map으로 PDF 파일 위치 확인
|
||||
2. **추출 스크립트 작성**: file_write로 Python 스크립트 생성
|
||||
3. **실행**: `process`로 실행
|
||||
4. **결과 전달**: 추출된 텍스트를 사용자에게 전달
|
||||
|
||||
### PDF 생성
|
||||
1. **내용 파악**: 사용자가 원하는 문서 내용 확인
|
||||
2. **생성 스크립트 작성**: file_write로 Python 스크립트 생성
|
||||
3. **실행 및 확인**: `process`로 실행
|
||||
|
||||
## 텍스트 추출 템플릿
|
||||
```python
|
||||
import pdfplumber
|
||||
import json
|
||||
|
||||
results = []
|
||||
with pdfplumber.open('input.pdf') as pdf:
|
||||
for i, page in enumerate(pdf.pages):
|
||||
text = page.extract_text() or ''
|
||||
tables = page.extract_tables() or []
|
||||
results.append({
|
||||
'page': i + 1,
|
||||
'text': text,
|
||||
'tables': tables,
|
||||
})
|
||||
|
||||
with open('pdf_extracted.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
for r in results:
|
||||
print(f"--- 페이지 {r['page']} ---")
|
||||
print(r['text'][:500])
|
||||
```
|
||||
|
||||
## 지원 기능
|
||||
- 텍스트 추출 (페이지별)
|
||||
- 표 추출 (구조 보존)
|
||||
- PDF 병합 / 분할
|
||||
- PDF 생성 (reportlab)
|
||||
- 페이지 회전
|
||||
- 메타데이터 읽기
|
||||
|
||||
한국어로 안내하세요. 원본 PDF는 수정하지 마세요.
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
name: perf-audit
|
||||
label: 성능 감사
|
||||
description: 코드 복잡도, 성능 병목, 메모리 이슈를 분석하고 최적화 방안을 제시합니다.
|
||||
icon: \uE9D9
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- grep
|
||||
- folder_map
|
||||
- search_codebase
|
||||
- glob
|
||||
- html_create
|
||||
tabs: code
|
||||
---
|
||||
|
||||
코드베이스의 성능 관련 이슈를 분석하고 최적화 보고서를 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **프로젝트 분석**: folder_map으로 구조 파악, 언어/프레임워크 식별
|
||||
2. **복잡도 분석**: 파일별 줄 수, 메서드 크기, 중첩 깊이 측정
|
||||
3. **성능 안티패턴 탐지**: grep으로 알려진 성능 이슈 패턴 검색
|
||||
4. **메모리 이슈 탐지**: 리소스 해제 누락, 대용량 할당 패턴
|
||||
5. **보고서 생성**: html_create로 성능 감사 보고서
|
||||
|
||||
## 분석 항목
|
||||
|
||||
### 코드 복잡도 지표
|
||||
- **파일 크기**: 500줄 이상 파일 식별
|
||||
- **메서드 크기**: 50줄 이상 메서드 식별
|
||||
- **중첩 깊이**: 4단계 이상 들여쓰기
|
||||
- **매개변수 수**: 5개 이상 파라미터 메서드
|
||||
|
||||
### 성능 안티패턴
|
||||
|
||||
#### 데이터베이스
|
||||
- N+1 쿼리 패턴 (루프 내 DB 호출)
|
||||
- SELECT * 사용 (불필요한 컬럼 로드)
|
||||
- 인덱스 미사용 쿼리 힌트
|
||||
|
||||
#### 메모리
|
||||
- IDisposable 미해제 (using 미사용)
|
||||
- 대용량 문자열 결합 (StringBuilder 미사용)
|
||||
- 정적 컬렉션 무한 증가
|
||||
- 이벤트 핸들러 미해제 (메모리 누수)
|
||||
|
||||
#### I/O
|
||||
- 동기 파일 I/O (async 미사용)
|
||||
- 동기 네트워크 호출
|
||||
- 불필요한 직렬화/역직렬화
|
||||
|
||||
#### 알고리즘
|
||||
- O(n²) 이상 루프 (중첩 foreach/for)
|
||||
- 반복 계산 (캐싱 미적용)
|
||||
- LINQ 체인의 불필요한 ToList()
|
||||
|
||||
#### 프론트엔드
|
||||
- 불필요한 리렌더링 패턴
|
||||
- 대용량 번들 임포트
|
||||
- 이미지 최적화 미적용
|
||||
|
||||
### .NET 전용 패턴
|
||||
```
|
||||
탐지 대상:
|
||||
- Task.Result / .Wait() (데드락 위험)
|
||||
- lock 내부 async 호출
|
||||
- GC.Collect() 직접 호출
|
||||
- Reflection 반복 사용
|
||||
- string + string 반복 (루프 내)
|
||||
```
|
||||
|
||||
## 출력 형식
|
||||
|
||||
### 성능 감사 보고서
|
||||
|
||||
**요약 대시보드**
|
||||
| 지표 | 값 | 상태 |
|
||||
|------|-----|------|
|
||||
| 총 파일 수 | ... | — |
|
||||
| 대형 파일 (500줄+) | ... | ⚠ |
|
||||
| 대형 메서드 (50줄+) | ... | ⚠ |
|
||||
| 성능 안티패턴 | ... | 🔴 |
|
||||
| 메모리 이슈 | ... | 🟡 |
|
||||
|
||||
**상세 이슈 목록**
|
||||
| 우선순위 | 파일 | 라인 | 이슈 | 영향 | 권장 조치 |
|
||||
|---------|------|------|------|------|----------|
|
||||
| 🔴 높음 | ... | ... | N+1 쿼리 | 응답 지연 | 일괄 로드 |
|
||||
|
||||
## 규칙
|
||||
- 코드를 수정하지 않음 (분석 + 보고서만)
|
||||
- 성능 이슈는 영향도와 수정 난이도를 함께 평가
|
||||
- 추측보다 패턴 기반 탐지 우선
|
||||
- 한국어로 작성
|
||||
@@ -1,123 +0,0 @@
|
||||
---
|
||||
name: pptx-creator
|
||||
label: PPT 프레젠테이션 생성
|
||||
description: Python을 사용하여 전문적인 PowerPoint 프레젠테이션을 생성합니다. 작업 폴더의 양식 파일을 자동 활용합니다.
|
||||
icon: \uE7BE
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- document_read
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- pptx_create
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
사용자의 요구에 맞는 PowerPoint 프레젠테이션을 Python으로 생성하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 python-pptx 경로를 사용하세요.
|
||||
- Python 불가: `pptx_create`로 슬라이드 초안을 생성하고 `template_render` + `file_write`로 발표자료 구조를 보강하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
필요한 패키지를 확인하고 설치하세요:
|
||||
```
|
||||
process: pip install python-pptx
|
||||
```
|
||||
|
||||
## 양식 활용 (마스터 슬라이드 템플릿)
|
||||
작업 폴더에 PPT 양식이 있으면 **반드시** 활용하세요:
|
||||
|
||||
1. **양식 탐색**: `folder_map`으로 작업 폴더를 스캔하여 `.pptx` 파일 확인
|
||||
2. **양식 후보 판별**:
|
||||
- 파일명에 "양식", "template", "서식", "표준", "기본" 포함
|
||||
- 또는 사용자가 명시적으로 "XX 양식으로 작성해줘" 요청
|
||||
- 또는 사용자가 특정 .pptx 파일명을 언급
|
||||
3. **양식 구조 파악**: `document_read`로 양식의 슬라이드 레이아웃 목록 확인
|
||||
4. **양식 기반 생성**:
|
||||
```python
|
||||
prs = Presentation('양식_발표.pptx')
|
||||
# 마스터 슬라이드의 배경, 로고, 색 테마, 폰트가 자동 상속
|
||||
# 기존 슬라이드 제거 후 새 내용 추가
|
||||
while len(prs.slides) > 0:
|
||||
rId = prs.slides._sldIdLst[0].rId
|
||||
prs.part.drop_rel(rId)
|
||||
del prs.slides._sldIdLst[0]
|
||||
```
|
||||
5. **레이아웃 확인** (양식마다 다를 수 있음):
|
||||
```python
|
||||
for i, layout in enumerate(prs.slide_layouts):
|
||||
print(f'{i}: {layout.name}')
|
||||
```
|
||||
6. **양식이 없으면**: 아래 기본 템플릿으로 새 프레젠테이션 생성
|
||||
|
||||
## 작업 절차
|
||||
1. **요구사항 파악**: 발표 주제, 슬라이드 수, 스타일 확인
|
||||
2. **양식 확인**: folder_map으로 작업 폴더에 양식 .pptx 파일이 있는지 확인
|
||||
3. **스크립트 작성**: file_write로 Python 스크립트 생성
|
||||
4. **실행**: `process`로 스크립트 실행
|
||||
5. **결과 안내**: 생성된 .pptx 파일 경로를 사용자에게 전달
|
||||
|
||||
## 스크립트 템플릿
|
||||
```python
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt, Emu
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.text import PP_ALIGN
|
||||
import os
|
||||
|
||||
# 양식 파일 자동 감지
|
||||
template_keywords = ['양식', 'template', '서식', '표준', '기본']
|
||||
template_file = None
|
||||
for f in os.listdir('.'):
|
||||
if f.endswith('.pptx') and any(kw in f.lower() for kw in template_keywords):
|
||||
template_file = f
|
||||
break
|
||||
|
||||
# 양식이 있으면 활용, 없으면 새 프레젠테이션
|
||||
if template_file:
|
||||
prs = Presentation(template_file)
|
||||
# 기존 슬라이드 제거 (마스터/레이아웃은 유지)
|
||||
while len(prs.slides) > 0:
|
||||
rId = prs.slides._sldIdLst[0].rId
|
||||
prs.part.drop_rel(rId)
|
||||
del prs.slides._sldIdLst[0]
|
||||
print(f'양식 활용: {template_file}')
|
||||
print(f'사용 가능한 레이아웃: {[l.name for l in prs.slide_layouts]}')
|
||||
else:
|
||||
prs = Presentation()
|
||||
prs.slide_width = Inches(13.333)
|
||||
prs.slide_height = Inches(7.5)
|
||||
|
||||
# 제목 슬라이드
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[0])
|
||||
slide.shapes.title.text = '프레젠테이션 제목'
|
||||
if len(slide.placeholders) > 1:
|
||||
slide.placeholders[1].text = '부제목'
|
||||
|
||||
# 내용 슬라이드
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[1])
|
||||
slide.shapes.title.text = '섹션 제목'
|
||||
body = slide.placeholders[1]
|
||||
body.text = '첫 번째 포인트'
|
||||
p = body.text_frame.add_paragraph()
|
||||
p.text = '두 번째 포인트'
|
||||
|
||||
prs.save('presentation.pptx')
|
||||
print('프레젠테이션 생성 완료: presentation.pptx')
|
||||
```
|
||||
|
||||
## 지원 기능
|
||||
- 제목/내용/빈 슬라이드 레이아웃
|
||||
- 텍스트 서식 (글꼴, 크기, 색상, 정렬)
|
||||
- 표 삽입
|
||||
- 이미지 삽입
|
||||
- 도형 (사각형, 원, 화살표)
|
||||
- 차트 (막대, 선, 원형)
|
||||
- 슬라이드 번호
|
||||
- 마스터 슬라이드 커스터마이징
|
||||
- **양식 파일 기반 마스터/레이아웃 상속** (배경, 로고, 색 테마, 폰트 자동 유지)
|
||||
|
||||
한국어로 안내하세요. 작업 폴더에 결과 파일을 저장하세요.
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
name: prd-generator
|
||||
label: 요구사항 정의서 (PRD)
|
||||
description: 제품 요구사항 정의서, 유저 스토리, 수용 기준을 체계적으로 생성합니다.
|
||||
icon: \uE8A5
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- html_create
|
||||
- docx_create
|
||||
- document_plan
|
||||
- document_assemble
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
제품/기능의 요구사항 정의서(PRD)를 체계적으로 작성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **요구사항 수집**: 사용자에게 다음을 확인
|
||||
- 제품/기능 이름
|
||||
- 목적과 배경
|
||||
- 대상 사용자
|
||||
- 핵심 기능 목록
|
||||
- 제약 조건 (기술, 일정, 예산)
|
||||
|
||||
2. **구조화**: document_plan으로 PRD 개요 설계
|
||||
|
||||
3. **상세 작성**: 섹션별 상세 내용 작성
|
||||
- 유저 스토리 (As a... I want... So that...)
|
||||
- 수용 기준 (Given... When... Then...)
|
||||
- 기능 우선순위 (MoSCoW)
|
||||
|
||||
4. **문서 생성**: document_assemble 또는 html_create로 최종 문서
|
||||
|
||||
## PRD 구조
|
||||
|
||||
### 1. 개요
|
||||
- 제품/기능 이름
|
||||
- 버전 / 작성일 / 작성자
|
||||
- 문서 목적
|
||||
|
||||
### 2. 배경 및 목적
|
||||
- 비즈니스 배경
|
||||
- 해결하려는 문제
|
||||
- 기대 효과 (정량적 KPI)
|
||||
|
||||
### 3. 대상 사용자
|
||||
- 사용자 페르소나
|
||||
- 사용 시나리오
|
||||
- 사용자 여정 맵
|
||||
|
||||
### 4. 기능 요구사항
|
||||
|
||||
#### 유저 스토리 형식
|
||||
```
|
||||
US-001: [기능명]
|
||||
As a [역할],
|
||||
I want [기능],
|
||||
So that [가치].
|
||||
|
||||
수용 기준:
|
||||
- Given [사전 조건], When [행동], Then [기대 결과]
|
||||
- Given ..., When ..., Then ...
|
||||
|
||||
우선순위: Must Have / Should Have / Could Have / Won't Have
|
||||
```
|
||||
|
||||
### 5. 비기능 요구사항
|
||||
- 성능 (응답 시간, 처리량)
|
||||
- 보안 (인증, 권한, 암호화)
|
||||
- 접근성 (WCAG 수준)
|
||||
- 호환성 (브라우저, OS, 디바이스)
|
||||
|
||||
### 6. 기술 제약
|
||||
- 기술 스택 제한
|
||||
- 연동 시스템
|
||||
- 데이터 마이그레이션
|
||||
|
||||
### 7. 일정 및 마일스톤
|
||||
| 마일스톤 | 예정일 | 산출물 |
|
||||
|---------|--------|--------|
|
||||
| 설계 완료 | ... | 상세 설계서 |
|
||||
| 개발 완료 | ... | 릴리즈 빌드 |
|
||||
| QA 완료 | ... | 테스트 보고서 |
|
||||
|
||||
### 8. 성공 지표
|
||||
- 핵심 KPI 및 측정 방법
|
||||
- 목표 수치
|
||||
|
||||
### 9. 리스크 및 대안
|
||||
| 리스크 | 영향 | 대안 |
|
||||
|--------|------|------|
|
||||
| ... | 높음 | ... |
|
||||
|
||||
## 규칙
|
||||
- 사용자 관점에서 작성 (기술 용어 최소화)
|
||||
- 유저 스토리는 INVEST 원칙 준수 (Independent, Negotiable, Valuable, Estimable, Small, Testable)
|
||||
- 수용 기준은 테스트 가능하도록 구체적으로
|
||||
- 한국어로 작성 (영어 용어 병기 가능)
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
name: refactor
|
||||
label: 리팩토링 가이드
|
||||
description: 코드베이스를 분석하여 리팩토링 포인트를 식별하고 실행 계획을 생성합니다.
|
||||
icon: \uE90F
|
||||
allowed-tools:
|
||||
- search_codebase
|
||||
- grep
|
||||
- file_read
|
||||
- code_review
|
||||
- folder_map
|
||||
- lsp_code_intel
|
||||
tabs: code
|
||||
# hooks / hook_filters 예시 (옵션):
|
||||
# hooks:
|
||||
# file_edit:
|
||||
# pre:
|
||||
# - lint-pre
|
||||
# post: verify-post
|
||||
# hook_filters: lint-pre@pre@file_edit, verify-post@post@*
|
||||
---
|
||||
|
||||
코드베이스를 분석하여 리팩토링이 필요한 부분을 식별하고 개선 계획을 수립하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **구조 파악**: folder_map + grep으로 프로젝트 전체 구조 분석
|
||||
2. **코드 스멜 탐지**:
|
||||
- 중복 코드 (grep으로 유사 패턴 검색)
|
||||
- 긴 메서드/클래스 (file_read로 크기 확인)
|
||||
- 복잡한 조건문 (중첩 if/switch)
|
||||
- 미사용 코드 (lsp_code_intel로 참조 확인)
|
||||
3. **의존성 분석**: lsp_code_intel로 참조 관계 파악
|
||||
4. **우선순위 결정**: 영향도 × 난이도 매트릭스
|
||||
5. **리팩토링 계획 생성**: 단계별 실행 계획
|
||||
|
||||
## 분석 항목
|
||||
|
||||
### 코드 스멜 (Code Smells)
|
||||
- **중복 코드**: 3곳 이상 반복되는 유사 코드
|
||||
- **거대 클래스**: 500줄 이상의 클래스
|
||||
- **긴 메서드**: 50줄 이상의 메서드
|
||||
- **매개변수 과다**: 5개 이상 파라미터
|
||||
- **의존성 순환**: 상호 참조 관계
|
||||
- **매직 넘버**: 하드코딩된 숫자/문자열
|
||||
- **깊은 중첩**: 4단계 이상 들여쓰기
|
||||
|
||||
### 리팩토링 기법 (제안)
|
||||
- Extract Method / Extract Class
|
||||
- Rename (변수, 메서드, 클래스)
|
||||
- Move Method / Move Field
|
||||
- Replace Conditional with Polymorphism
|
||||
- Introduce Parameter Object
|
||||
- Remove Dead Code
|
||||
|
||||
## 출력 형식
|
||||
|
||||
### 리팩토링 보고서
|
||||
| 우선순위 | 파일 | 이슈 | 제안 | 영향도 | 난이도 |
|
||||
|---------|------|------|------|--------|--------|
|
||||
| 🔴 높음 | ... | ... | ... | ★★★ | ★☆☆ |
|
||||
| 🟡 중간 | ... | ... | ... | ★★☆ | ★★☆ |
|
||||
|
||||
### 실행 계획
|
||||
1. [안전한 변경부터] ...
|
||||
2. [테스트 추가 후] ...
|
||||
3. [구조 변경] ...
|
||||
|
||||
## 규칙
|
||||
- 코드를 직접 수정하지 않음 (분석 + 계획만)
|
||||
- 기존 테스트가 있으면 테스트 커버리지 확인
|
||||
- 팀 컨벤션/스타일 가이드 존중
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
name: regex-helper
|
||||
label: 정규식 도우미
|
||||
description: 정규식 패턴을 생성하고, 기존 패턴을 해석하며, 테스트 케이스로 검증합니다.
|
||||
icon: \uE8FD
|
||||
tabs: code
|
||||
allowed-tools:
|
||||
- regex_tool
|
||||
- clipboard_tool
|
||||
- file_read
|
||||
---
|
||||
|
||||
사용자의 요구에 맞는 정규식 패턴을 작성하거나, 기존 패턴을 해석하고 테스트하세요.
|
||||
|
||||
## 작업 절차
|
||||
|
||||
1. **요구사항 파악**: 사용자의 요청 유형을 판별
|
||||
- **패턴 생성**: "이메일 주소를 찾는 정규식 만들어줘"
|
||||
- **패턴 해석**: "이 정규식이 무슨 뜻이야? `^[\w.-]+@[\w.-]+\.\w+$`"
|
||||
- **패턴 테스트**: "이 패턴이 이 문자열에 매칭되는지 확인해줘"
|
||||
2. **패턴 작성 또는 분석**:
|
||||
- 생성: 요구사항을 분석하여 정규식 패턴 작성
|
||||
- 해석: 패턴을 구성 요소별로 분해하여 설명
|
||||
3. **테스트 수행**: regex_tool로 패턴을 테스트 케이스에 적용
|
||||
- 매칭되어야 할 문자열 (positive cases)
|
||||
- 매칭되지 않아야 할 문자열 (negative cases)
|
||||
4. **결과 설명**: 매칭 결과와 캡처 그룹을 상세히 설명
|
||||
5. **최적화 제안**: 성능 또는 가독성 개선이 가능하면 대안 제시
|
||||
|
||||
## 패턴 생성 가이드
|
||||
|
||||
### 자주 사용되는 패턴
|
||||
| 용도 | 패턴 | 설명 |
|
||||
|------|------|------|
|
||||
| 이메일 | `[\w.-]+@[\w.-]+\.\w{2,}` | 기본 이메일 형식 |
|
||||
| 전화번호 (한국) | `0\d{1,2}-\d{3,4}-\d{4}` | 010-1234-5678 형식 |
|
||||
| 날짜 (YYYY-MM-DD) | `\d{4}-(?:0[1-9]\|1[0-2])-(?:0[1-9]\|[12]\d\|3[01])` | ISO 날짜 형식 |
|
||||
| IP 주소 | `(?:\d{1,3}\.){3}\d{1,3}` | IPv4 기본 |
|
||||
| URL | `https?://[\w.-]+(?:/[\w./?#&=-]*)?` | HTTP/HTTPS URL |
|
||||
| 한글만 | `[가-힣]+` | 한글 문자 |
|
||||
| 사업자등록번호 | `\d{3}-\d{2}-\d{5}` | 123-45-67890 형식 |
|
||||
|
||||
### 패턴 해석 형식
|
||||
패턴을 해석할 때는 다음 구조로 설명하세요:
|
||||
```
|
||||
패턴: ^(\d{3})-(\d{2})-(\d{5})$
|
||||
해석:
|
||||
^ → 문자열 시작
|
||||
(\d{3}) → 캡처 그룹 1: 숫자 3자리
|
||||
- → 하이픈 (리터럴)
|
||||
(\d{2}) → 캡처 그룹 2: 숫자 2자리
|
||||
- → 하이픈 (리터럴)
|
||||
(\d{5}) → 캡처 그룹 3: 숫자 5자리
|
||||
$ → 문자열 끝
|
||||
```
|
||||
|
||||
## 테스트 형식
|
||||
테스트 결과는 다음 형식으로 표시하세요:
|
||||
```
|
||||
패턴: \d{3}-\d{2}-\d{5}
|
||||
|
||||
✅ 매칭 성공:
|
||||
"123-45-67890" → 전체 매칭: "123-45-67890"
|
||||
"사업자번호: 123-45-67890입니다" → 부분 매칭: "123-45-67890"
|
||||
|
||||
❌ 매칭 실패:
|
||||
"12-345-67890" → 형식 불일치
|
||||
"abc-de-fghij" → 숫자가 아닌 문자
|
||||
```
|
||||
|
||||
## 플래그 안내
|
||||
| 플래그 | 설명 |
|
||||
|--------|------|
|
||||
| `i` | 대소문자 무시 |
|
||||
| `m` | 멀티라인 (^$가 각 줄에 적용) |
|
||||
| `s` | 점(.)이 줄바꿈도 매칭 |
|
||||
| `g` | 전역 검색 (모든 매칭) |
|
||||
|
||||
## 규칙
|
||||
- 패턴 생성 시 positive/negative 테스트 케이스를 반드시 포함
|
||||
- 복잡한 패턴은 주석이 포함된 확장 모드(`x`)로 설명
|
||||
- 캡처 그룹이 있으면 각 그룹의 의미를 설명
|
||||
- 성능에 민감한 경우 탐욕적/게으른 수량자 선택 이유를 설명
|
||||
- 최종 패턴은 clipboard_tool로 클립보드에 복사
|
||||
|
||||
한국어로 안내하세요.
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
name: release-note
|
||||
label: 릴리즈 노트 생성
|
||||
description: 비개발자 대상의 사용자 친화적인 릴리즈 노트를 자동 생성합니다.
|
||||
icon: \uE70B
|
||||
tabs: code
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- clipboard_tool
|
||||
- html_create
|
||||
---
|
||||
|
||||
사용자(비개발자) 대상의 읽기 쉬운 릴리즈 노트를 작성하세요.
|
||||
|
||||
## 작업 절차
|
||||
|
||||
1. **변경 정보 수집**: 사용자에게 다음 정보를 확인
|
||||
- 버전 번호 (예: v1.6.0)
|
||||
- 릴리즈 날짜
|
||||
- 주요 변경 내용 (기능 추가, 개선, 버그 수정)
|
||||
- 참고 자료: CHANGELOG, 커밋 이력, 개발 문서 등
|
||||
2. **내용 분류**: 수집된 변경 사항을 사용자 관점으로 분류
|
||||
- 기술 용어 → 사용자 언어로 변환
|
||||
- 내부 리팩토링 등 사용자에게 무관한 항목은 제외
|
||||
3. **릴리즈 노트 초안 작성**: 아래 템플릿 기반으로 작성
|
||||
4. **사용자 검토**: 초안을 보여주고 수정 요청 반영
|
||||
5. **최종 출력**: Markdown, HTML, 또는 텍스트 형식으로 저장
|
||||
|
||||
## 릴리즈 노트 템플릿
|
||||
|
||||
```markdown
|
||||
# [제품명] v[버전] 업데이트 안내
|
||||
|
||||
안녕하세요. [제품명] v[버전] 업데이트 소식을 안내드립니다.
|
||||
|
||||
## 🎉 새로운 기능
|
||||
|
||||
- **[기능 이름]**: [사용자가 얻는 혜택 중심으로 1~2문장 설명]
|
||||
- **[기능 이름]**: [사용자가 얻는 혜택 중심으로 1~2문장 설명]
|
||||
|
||||
## ✨ 개선 사항
|
||||
|
||||
- **[개선 영역]**: [무엇이 어떻게 좋아졌는지 설명]
|
||||
- **[개선 영역]**: [무엇이 어떻게 좋아졌는지 설명]
|
||||
|
||||
## 🐛 문제 해결
|
||||
|
||||
- [사용자가 겪었던 문제]를 해결했습니다.
|
||||
- [사용자가 겪었던 문제]를 해결했습니다.
|
||||
|
||||
## ⚠️ 알려진 이슈
|
||||
|
||||
- [현재 알려진 제한 사항이나 이슈]
|
||||
- [해결 예정 시기 또는 임시 해결 방법]
|
||||
|
||||
---
|
||||
|
||||
문의 사항이 있으시면 [연락처/채널]로 알려주세요.
|
||||
감사합니다.
|
||||
```
|
||||
|
||||
## 작성 원칙
|
||||
|
||||
### 사용자 언어로 변환
|
||||
| 개발 용어 (사용 금지) | 사용자 표현 (사용) |
|
||||
|---------------------|-------------------|
|
||||
| API 엔드포인트 추가 | 새로운 연동 기능 추가 |
|
||||
| 메모리 누수 수정 | 장시간 사용 시 느려지는 문제 해결 |
|
||||
| UI 리팩토링 | 화면 디자인 개선 |
|
||||
| 캐시 최적화 | 실행 속도 개선 |
|
||||
| null 참조 오류 수정 | 예기치 않은 오류로 종료되는 문제 해결 |
|
||||
| 인코딩 이슈 수정 | 한글이 깨져 보이는 문제 해결 |
|
||||
| 동시성 버그 수정 | 여러 작업 동시 실행 시 오류 발생 문제 해결 |
|
||||
|
||||
### 작성 규칙
|
||||
- **혜택 중심**: "무엇을 했다"가 아닌 "사용자에게 어떤 도움이 되는지" 설명
|
||||
- **간결**: 한 항목당 1~2문장 이내
|
||||
- **구체적**: "성능 개선" → "파일 열기 속도가 약 2배 빨라졌습니다"
|
||||
- **긍정적 톤**: 문제를 "해결했습니다", 기능을 "추가했습니다"
|
||||
- **이모지 활용**: 섹션별 시각적 구분 (새 기능: 🎉, 개선: ✨, 수정: 🐛, 주의: ⚠️)
|
||||
|
||||
## 출력 형식
|
||||
- **Markdown**: 기본 출력 형식 (.md)
|
||||
- **HTML**: html_create로 스타일이 적용된 웹 페이지 생성
|
||||
- **텍스트**: 이메일 본문용 서식 없는 텍스트
|
||||
- 사용자가 원하는 형식으로 제공
|
||||
|
||||
## 규칙
|
||||
- 내부 기술 구현 세부사항은 노출하지 않음
|
||||
- 사용자에게 무관한 변경(코드 리팩토링, 테스트 추가 등)은 제외
|
||||
- Breaking Change가 있으면 "이전 버전과 달라진 점" 섹션 추가
|
||||
- 업데이트 방법 안내를 포함 (인스톨러 경로, 주의사항)
|
||||
- 최종 결과는 파일 저장과 클립보드 복사 모두 제공
|
||||
|
||||
한국어로 안내하세요.
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
name: report-writer
|
||||
label: 보고서 작성
|
||||
description: 작업 폴더의 데이터를 분석하여 체계적인 업무 보고서를 생성합��다.
|
||||
icon: \uE9F9
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_write
|
||||
- data_pivot
|
||||
- chart_create
|
||||
- template_render
|
||||
- text_summarize
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
작업 폴더의 파일과 데이터를 분석하여 업무 보고서를 작성하세요.
|
||||
|
||||
다음 도구를 사용하세요:
|
||||
1. folder_map — 작업 폴더의 파일 구조 파악
|
||||
2. file_read — 관련 데이터 파일 읽기 (CSV, Excel, 텍스트)
|
||||
3. file_write — 보고서 파일 생성 (HTML 또는 Markdown)
|
||||
|
||||
보고서 구성:
|
||||
## 제목
|
||||
- 작성 일시, 작성자 (요청 시)
|
||||
|
||||
## 요약 (Executive Summary)
|
||||
- 핵심 내용을 3줄 이내로 요약
|
||||
|
||||
## 본문
|
||||
- 데이터 기반 분석 결과
|
||||
- 표/차트를 활용한 시각적 정리
|
||||
- 주요 발견 사항
|
||||
|
||||
## 결론 및 제안
|
||||
- 결론 요약
|
||||
- 향후 조치 사항
|
||||
|
||||
HTML 보고서 생성 시 현재 적용된 디자인 무드를 반영하세요.
|
||||
한국어로 작성하세요.
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
name: security-audit
|
||||
label: 보안 코드 감사
|
||||
description: 코드베이스의 보안 취약점을 점검하고 OWASP 기반 보안 보고서를 생성합니다.
|
||||
icon: \uE72E
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- grep
|
||||
- folder_map
|
||||
- search_codebase
|
||||
- html_create
|
||||
- glob
|
||||
tabs: code
|
||||
---
|
||||
|
||||
코드베이스의 보안 취약점을 체계적으로 점검하고 보고서를 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **스캔 범위 확인**: folder_map으로 프로젝트 구조 파악, 언어/프레임워크 식별
|
||||
2. **취약점 패턴 탐지**: grep으로 위험 패턴 검색
|
||||
3. **의존성 분석**: 패키지 파일(package.json, *.csproj, requirements.txt) 읽기
|
||||
4. **비밀 정보 노출 검사**: API 키, 토큰, 비밀번호 하드코딩 탐지
|
||||
5. **보고서 생성**: html_create로 보안 감사 보고서 생성
|
||||
|
||||
## OWASP Top 10 점검 항목
|
||||
|
||||
### A01 — 접근 제어 취약
|
||||
- 인증 없는 API 엔드포인트
|
||||
- 하드코딩된 권한 체크
|
||||
- 관리자 경로 노출
|
||||
|
||||
### A02 — 암호화 실패
|
||||
- 평문 비밀번호 저장
|
||||
- 약한 해시 알고리즘 (MD5, SHA1)
|
||||
- HTTP (비HTTPS) 통신
|
||||
|
||||
### A03 — 인젝션
|
||||
- SQL 인젝션 (문자열 결합 쿼리)
|
||||
- XSS (innerHTML, dangerouslySetInnerHTML)
|
||||
- 명령 인젝션 (Process.Start, exec, system)
|
||||
- 경로 순회 (../ 미검증)
|
||||
|
||||
### A04 — 불안전한 설계
|
||||
- 비즈니스 로직 검증 누락
|
||||
- 레이트 리밋 미적용
|
||||
|
||||
### A05 — 보안 설정 오류
|
||||
- 디버그 모드 활성화 상태
|
||||
- 기본 자격증명 사용
|
||||
- 불필요한 포트/서비스 노출
|
||||
|
||||
### A07 — 인증 실패
|
||||
- 약한 비밀번호 정책
|
||||
- 세션 만료 미설정
|
||||
- 브루트포스 방어 부재
|
||||
|
||||
### A09 — 로깅/모니터링 부족
|
||||
- 보안 이벤트 미기록
|
||||
- 민감 정보 로그 출력
|
||||
|
||||
## 비밀 정보 탐지 패턴
|
||||
|
||||
```
|
||||
grep 대상 패턴:
|
||||
- password\s*=\s*["'][^"']+["']
|
||||
- api[_-]?key\s*=\s*["'][^"']+["']
|
||||
- secret\s*=\s*["'][^"']+["']
|
||||
- token\s*=\s*["'][A-Za-z0-9+/=]{20,}["']
|
||||
- -----BEGIN (RSA |EC )?PRIVATE KEY-----
|
||||
- AWS_ACCESS_KEY_ID
|
||||
- AKIA[0-9A-Z]{16}
|
||||
```
|
||||
|
||||
## 출력 형식
|
||||
|
||||
### 보안 감사 보고서
|
||||
| 위험도 | 카테고리 | 파일 | 라인 | 내용 | 권장 조치 |
|
||||
|--------|---------|------|------|------|----------|
|
||||
| 🔴 심각 | A03 인젝션 | ... | ... | SQL 문자열 결합 | 파라미터 바인딩 사용 |
|
||||
| 🟡 경고 | A02 암호화 | ... | ... | MD5 해시 사용 | SHA-256 이상 전환 |
|
||||
| 🟢 참고 | A09 로깅 | ... | ... | 에러 로깅 미흡 | 보안 이벤트 로깅 추가 |
|
||||
|
||||
### 요약 통계
|
||||
- 심각/경고/참고 건수
|
||||
- OWASP 카테고리별 분포
|
||||
- 우선 조치 항목 Top 5
|
||||
|
||||
## 규칙
|
||||
- 코드를 수정하지 않음 (분석 + 보고서만)
|
||||
- 발견된 비밀 정보는 마스킹하여 보고 (앞 4자만 표시)
|
||||
- 위험도는 보수적으로 평가
|
||||
- 한국어로 보고서 작성
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
name: sql-report
|
||||
label: SQL 리포트
|
||||
description: DB 쿼리를 실행하고 결과를 차트화하여 HTML/Excel 보고서로 생성합니다.
|
||||
icon: \uE968
|
||||
allowed-tools:
|
||||
- sql_tool
|
||||
- chart_create
|
||||
- excel_create
|
||||
- html_create
|
||||
- data_pivot
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
데이터베이스에서 쿼리를 실행하고 결과를 시각화된 보고서로 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **DB 확인**: sql_tool로 테이블 목록 및 스키마 확인
|
||||
2. **쿼리 작성**: 사용자 요청에 맞는 SQL 쿼리 작성
|
||||
3. **데이터 조회**: sql_tool로 쿼리 실행
|
||||
4. **데이터 가공**: data_pivot으로 집계/피벗 (필요 시)
|
||||
5. **시각화**: chart_create로 차트 생성
|
||||
6. **보고서**: html_create 또는 excel_create로 최종 보고서 생성
|
||||
|
||||
## 쿼리 작성 원칙
|
||||
- SELECT 쿼리만 실행 (데이터 변경 금지)
|
||||
- 결과 행 수 제한: LIMIT 1000 (대량 데이터 방지)
|
||||
- 인덱스 활용 쿼리 작성
|
||||
- 한글 컬럼명은 alias로 변환
|
||||
|
||||
## 보고서 구성
|
||||
|
||||
### 1. 데이터 요약
|
||||
- 조회 조건, 기간, 데이터 건수
|
||||
- 기본 통계 (합계, 평균, 최대/최소)
|
||||
|
||||
### 2. 시각화
|
||||
- 적절한 차트 유형 자동 선택
|
||||
- 비교 → 바 차트
|
||||
- 추세 → 라인 차트
|
||||
- 비율 → 파이/도넛 차트
|
||||
|
||||
### 3. 상세 데이터
|
||||
- Excel: 원본 데이터 + 서식 + 수식
|
||||
- HTML: 테이블 + 정렬/필터
|
||||
|
||||
## 규칙
|
||||
- DB 경로는 사용자에게 확인
|
||||
- 민감 데이터(개인정보) 마스킹 권고
|
||||
- 쿼리 실행 전 사용자 승인
|
||||
- 한국어로 보고서 작성
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
name: translate
|
||||
label: 번역 + 교정
|
||||
description: 문서나 텍스트를 번역하고 전문 용어를 보존하며 교정합니다.
|
||||
icon: \uE8C1
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- file_write
|
||||
- document_read
|
||||
- clipboard_tool
|
||||
tabs: all
|
||||
---
|
||||
|
||||
사용자가 제공한 텍스트 또는 문서를 번역하고 교정하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **입력 확인**: 텍스트 직접 입력 또는 파일 경로 확인
|
||||
2. **언어 감지**: 원문 언어를 자동 감지
|
||||
3. **번역 실행**:
|
||||
- 원문의 뉘앙스와 문맥을 살려 번역
|
||||
- 전문 용어는 원어를 괄호 안에 병기: "수율(Yield)"
|
||||
- 문화적 차이를 고려한 자연스러운 표현 사용
|
||||
4. **교정**: 번역 결과의 문법, 맞춤법, 어색한 표현 수정
|
||||
5. **결과 제공**: 번역문 + 주요 용어 대조표
|
||||
|
||||
## 번역 원칙
|
||||
|
||||
### 정확성
|
||||
- 원문의 의미를 정확하게 전달
|
||||
- 숫자, 날짜, 고유명사는 원문 그대로 유지
|
||||
- 약어는 처음 등장 시 풀어 번역
|
||||
|
||||
### 가독성
|
||||
- 목표 언어의 자연스러운 문체 사용
|
||||
- 긴 문장은 적절히 분리
|
||||
- 수동태 → 능동태 변환 (한국어의 경우)
|
||||
|
||||
### 전문성
|
||||
- 분야별 전문 용어 일관 사용
|
||||
- 동일 용어는 문서 전체에서 통일
|
||||
- 번역 불가능한 용어는 원어 유지
|
||||
|
||||
## 출력 형식
|
||||
|
||||
```
|
||||
## 번역 결과
|
||||
|
||||
[번역된 텍스트]
|
||||
|
||||
---
|
||||
|
||||
## 용어 대조표
|
||||
| 원문 | 번역 | 비고 |
|
||||
|------|------|------|
|
||||
| Yield | 수율 | 반도체 공정 용어 |
|
||||
```
|
||||
|
||||
## 지원 언어
|
||||
한국어 ↔ 영어 / 일본어 / 중국어 (간/번체) / 독일어 / 프랑스어 / 스페인어
|
||||
@@ -1,65 +0,0 @@
|
||||
---
|
||||
name: weekly-report
|
||||
label: 주간 보고서
|
||||
description: 작업 폴더의 변경 이력을 기반으로 자동 주간보고 초안을 생성합니다.
|
||||
icon: \uE787
|
||||
allowed-tools:
|
||||
- git_tool
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_watch
|
||||
- html_create
|
||||
- docx_create
|
||||
- text_summarize
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
작업 폴더의 최근 활동을 분석하여 주간 보고서 초안을 자동 생성하세요.
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. **활동 수집**:
|
||||
- git_tool로 최근 7일간 커밋 이력 조회
|
||||
- file_watch로 최근 변경/생성된 파일 목록 확인
|
||||
- folder_map으로 프로젝트 구조 파악
|
||||
|
||||
2. **내용 분석**:
|
||||
- 커밋 메시지 분류 (기능 추가, 버그 수정, 리팩토링 등)
|
||||
- 변경 파일 유형별 분류
|
||||
- 주요 변경사항 요약
|
||||
|
||||
3. **보고서 작성**:
|
||||
- HTML 또는 Word 형식으로 보고서 생성
|
||||
- 차트/표로 활동 통계 시각화
|
||||
|
||||
## 보고서 구성
|
||||
|
||||
### 1. 주간 요약
|
||||
- 보고 기간: yyyy-MM-dd ~ yyyy-MM-dd
|
||||
- 주요 성과 (3줄 이내)
|
||||
|
||||
### 2. 완료 항목
|
||||
| 번호 | 구분 | 내용 | 비고 |
|
||||
|------|------|------|------|
|
||||
| 1 | 기능 | ... | 커밋 참조 |
|
||||
|
||||
### 3. 진행 중 항목
|
||||
- 현재 작업 중인 사항
|
||||
- 진척률 (가능하면)
|
||||
|
||||
### 4. 이슈 및 리스크
|
||||
- 지연 사항
|
||||
- 블로킹 이슈
|
||||
|
||||
### 5. 다음 주 계획
|
||||
- 예정 작업 항목
|
||||
|
||||
### 6. 활동 통계 (차트)
|
||||
- 일별 커밋 수
|
||||
- 파일 유형별 변경 비율
|
||||
|
||||
## 규칙
|
||||
- 사실 기반으로 작성 (추측 금지)
|
||||
- Git 이력이 없으면 파일 변경 이력만으로 작성
|
||||
- 한국어로 작성
|
||||
- 보고서 톤: 간결하고 전문적
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
name: xlsx-analyzer
|
||||
label: Excel 데이터 분석
|
||||
description: Python을 사용하여 Excel/CSV 데이터를 분석하고 보고서를 생성합니다. 작업 폴더의 양식 파일을 자동 활용합니다.
|
||||
icon: \uE9F9
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- document_read
|
||||
- file_read
|
||||
- file_write
|
||||
- process
|
||||
- data_pivot
|
||||
- chart_create
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
작업 폴더의 Excel 또는 CSV 데이터를 Python으로 분석하세요.
|
||||
## 실행 경로 선택 (Python 가능/불가)
|
||||
- 먼저 `process`로 `python --version`을 확인하세요.
|
||||
- Python 가능: 기존 pandas/openpyxl 경로를 사용하세요.
|
||||
- Python 불가: `data_pivot`으로 분석 요약을 만들고 `chart_create` + `file_write`로 결과 리포트를 생성하세요.
|
||||
|
||||
|
||||
## 사전 준비
|
||||
필요한 패키지를 확인하고 설치하세요:
|
||||
```
|
||||
process: pip install pandas openpyxl
|
||||
```
|
||||
|
||||
## 양식 활용 (Excel 보고서 템플릿)
|
||||
작업 폴더에 Excel 양식 파일이 있으면 **반드시** 활용하세요:
|
||||
|
||||
1. **양식 탐색**: `folder_map`으로 작업 폴더에서 `.xlsx` 파일 목록 확인
|
||||
2. **양식 후보 판별**:
|
||||
- 파일명에 "양식", "template", "서식", "표준", "기본", "보고서양식" 포함
|
||||
- 또는 사용자가 "XX 양식에 맞춰서 작성해줘" 요청
|
||||
- 또는 사용자가 특정 .xlsx 파일명을 양식으로 지정
|
||||
3. **양식 구조 파악**: `document_read`로 양식의 시트 구조, 셀 레이아웃 확인
|
||||
4. **양식 기반 데이터 삽입**:
|
||||
```python
|
||||
from openpyxl import load_workbook
|
||||
wb = load_workbook('양식_보고서.xlsx')
|
||||
ws = wb.active
|
||||
# 양식의 서식(셀 병합, 테두리, 글꼴, 색상, 열 너비)이 그대로 유지됨
|
||||
# 데이터 영역에만 새 값 삽입
|
||||
ws['B3'] = '분석 결과값'
|
||||
wb.save('결과_보고서.xlsx')
|
||||
```
|
||||
5. **양식이 없으면**: 아래 기본 방식으로 분석 결과 생성
|
||||
|
||||
## 작업 절차
|
||||
1. **데이터 파일 탐색**: folder_map으로 작업 폴더에서 .xlsx, .csv 파일 확인
|
||||
2. **양식 확인**: 양식 .xlsx 파일이 있는지 확인 (데이터 파일과 양식 파일 구분)
|
||||
3. **데이터 읽기**: file_read 또는 document_read로 파일 구조 파악
|
||||
4. **분석 스크립트 작성**: file_write로 Python 분석 스크립트 생성
|
||||
5. **실행**: `process`로 스크립트 실행
|
||||
6. **결과 보고**: 분석 결과를 사용자에게 정리하여 전달
|
||||
|
||||
## 분석 스크립트 템플릿
|
||||
```python
|
||||
import pandas as pd
|
||||
import json
|
||||
import os
|
||||
|
||||
df = pd.read_excel('data.xlsx') # 또는 pd.read_csv('data.csv')
|
||||
|
||||
report = {
|
||||
'shape': list(df.shape),
|
||||
'columns': list(df.columns),
|
||||
'dtypes': {col: str(dtype) for col, dtype in df.dtypes.items()},
|
||||
'missing': df.isnull().sum().to_dict(),
|
||||
'describe': df.describe().to_dict(),
|
||||
}
|
||||
|
||||
# 양식 파일로 결과 내보내기
|
||||
template_keywords = ['양식', 'template', '서식', '표준', '기본']
|
||||
template_file = None
|
||||
for f in os.listdir('.'):
|
||||
if f.endswith('.xlsx') and f != 'data.xlsx' and any(kw in f.lower() for kw in template_keywords):
|
||||
template_file = f
|
||||
break
|
||||
|
||||
if template_file:
|
||||
from openpyxl import load_workbook
|
||||
wb = load_workbook(template_file)
|
||||
ws = wb.active
|
||||
# 양식 서식 유지하면서 데이터 삽입
|
||||
print(f'양식 활용: {template_file}')
|
||||
# TODO: 양식 구조에 맞게 데이터 삽입 로직 작성
|
||||
wb.save('결과_보고서.xlsx')
|
||||
else:
|
||||
# 양식 없으면 JSON으로 저장
|
||||
with open('analysis_result.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(report, f, ensure_ascii=False, indent=2, default=str)
|
||||
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2, default=str))
|
||||
```
|
||||
|
||||
## 지원 분석
|
||||
- 기본 통계 (평균, 중앙값, 표준편차, 분위수)
|
||||
- 결측치 분석
|
||||
- 컬럼별 고유값 분포
|
||||
- 피벗 테이블 / 그룹별 집계
|
||||
- 시트 간 비교 분석
|
||||
- 필터링 및 조건부 추출
|
||||
- 분석 결과를 새 Excel로 내보내기
|
||||
- **양식 파일 기반 보고서 생성** (셀 서식, 병합, 테두리, 차트 영역 유지)
|
||||
|
||||
한국어로 안내하세요. 원본 파일은 수정하지 마세요.
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
name: yield-analysis
|
||||
label: 수율 분석
|
||||
description: 제조/연구 데이터의 수율을 분석하고 개선 포인트를 도출합니다.
|
||||
icon: \uE9D2
|
||||
allowed-tools:
|
||||
- folder_map
|
||||
- file_read
|
||||
- file_write
|
||||
- data_pivot
|
||||
- chart_create
|
||||
- text_summarize
|
||||
- template_render
|
||||
tabs: cowork
|
||||
---
|
||||
|
||||
작업 폴더의 데이터를 활���하여 수율 분석을 수행하세요.
|
||||
|
||||
다음 도구를 사용하세요:
|
||||
1. folder_map — 데이터 파일 탐색
|
||||
2. file_read — CSV/Excel 데이터 읽기
|
||||
3. file_write — 분석 보고서 생성
|
||||
|
||||
분석 프로세스:
|
||||
## 데이터 개요
|
||||
- 분석 대상 데이터셋 설명
|
||||
- 데이터 기간, 샘플 수, 주요 변수
|
||||
|
||||
## 수율 현황
|
||||
- 전체 수율 통계 (평균, 중앙값, 표준편차)
|
||||
- 기간별/로트별/공정별 수율 추이
|
||||
- 목표 수율 대비 달성률
|
||||
|
||||
## 불량 분석
|
||||
- 불량 유형별 분류 및 비율
|
||||
- 파레토 분석 (상위 불량 원인)
|
||||
- 시간대/조건별 불량 패턴
|
||||
|
||||
## 상관관계 분석
|
||||
- 주요 공정 변수와 수율의 관계
|
||||
- 이상치 탐지 및 원인 추정
|
||||
|
||||
## 개선 제안
|
||||
- 수율 향상을 위한 구체적 조치 사항
|
||||
- 우선순위별 개선 로드맵
|
||||
- 예상 개선 효과
|
||||
|
||||
표와 수치를 적극 활용하세요. 한국어로 작성하세요.
|
||||
@@ -1 +0,0 @@
|
||||
[역할] 당신은 회사 업무의 도움을 주는 매우 유능한 비서입니다. 말투는 정중하게 하며, 욕을 해서는 안됩니다.
|
||||
@@ -9,13 +9,13 @@
|
||||
- v0.7.2: 컨텍스트 압축/복원 안정화, UX/성능 보완.
|
||||
|
||||
## 2. 재작성 기준
|
||||
- 기준 레퍼런스: `claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src`.
|
||||
- 기준 레퍼런스: `OpenCode/OpenCode-f5a40b86dede580f6543bf8926c9af017eea9409/src`.
|
||||
- 목표: 동작 순서, 예외/검증 흐름, 세션 내구성에서 동일 품질 달성.
|
||||
- 원칙: AX 코드 스타일로 재구성(표현/구조 변경), 기능적 동등성 유지.
|
||||
|
||||
## 3. 2026 실행 계획 (v0.7.3 ~ v0.8.0)
|
||||
|
||||
| Phase | 참조 대상 (`claw-code`) | AX 적용 위치 | 완료 조건 | 품질 판정 시나리오 |
|
||||
| Phase | 참조 대상 (`OpenCode`) | AX 적용 위치 | 완료 조건 | 품질 판정 시나리오 |
|
||||
|---|---|---|---|---|
|
||||
| A (v0.7.3) Hook/Permission 계약 고도화 | `src/utils/hooks.ts`, `src/utils/hooks/hookEvents.ts`, `src/utils/permissions/PermissionUpdate.ts`, `src/utils/permissions/permissionSetup.ts` | `src/AxCopilot/Services/Agent/AgentHookRunner.cs`, `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Models/AppSettings.cs`, `src/AxCopilot/ViewModels/SettingsViewModel.cs`, `src/AxCopilot/Views/SettingsWindow.xaml` | Hook JSON 출력(`updatedInput`, `updatedPermissions`, `additionalContext`) 반영 완료. Plain text 훅 하위호환 유지. 훅 실패 비차단 유지. | Pre-hook 입력 변형 적용 시 도구 입력이 실제 변경되는지 검증. 권한 업데이트 후 동일 run 내 권한 판정이 즉시 반영되는지 검증. |
|
||||
| B (v0.7.4) Plan/Run 상태 내구성 강화 | `src/utils/plans.ts`, `src/utils/sessionStorage.ts` | `src/AxCopilot/Services/ChatSessionStateService.cs`, `src/AxCopilot/Services/TaskRunService.cs`, `src/AxCopilot/Services/TaskRunStore.cs`, `src/AxCopilot/Views/ChatWindow.xaml.cs` | Plan 단계 승인/수정 이력 영속화. 앱 재시작 후 run 재개 시 마지막 실행 지점 정확 복원. 중복 실행/중복 이벤트 0건. | 실행 중 앱 종료 → 재실행 → 같은 탭/대화에서 run 상태와 최근 실행 이력이 일치하는지 검증. |
|
||||
@@ -65,7 +65,7 @@
|
||||
- 2026-04-04(추가3): 권한 UX 통합(/permissions·/allowed-tools·/settings permissions), 복구 혼합 테스트 보강, 좌측 패널 실패 필터 노출 정책 rich 전용으로 정렬.
|
||||
|
||||
|
||||
- 2026-04-04 20:27 (KST): Added a dedicated AX Agent theme layer and separated in-chat theme selection into style (`claw/codex/slate`) and mode (`system/light/dark`).
|
||||
- 2026-04-04 20:27 (KST): Added a dedicated AX Agent theme layer and separated in-chat theme selection into style (`claude/codex/slate`) and mode (`system/light/dark`).
|
||||
- 2026-04-04 18:03 (KST): Added a Codex-style context usage ring to the composer, exposed direct `/compact` execution from the footer, and surfaced Git branch/change status in the Cowork/Code footer when a repository is connected.
|
||||
- 2026-04-04 18:03 (KST): Simplified the Cowork footer by removing document format/design selectors and moved those defaults into the AX Agent settings page, with output format centered on AI automatic selection.
|
||||
- 2026-04-04 18:03 (KST): Minimized AX Agent message headers, changed model quick controls to a popup panel, flattened slash command rows into a Codex-style list, and aligned model/permission/template controls to the same dropdown panel language.
|
||||
@@ -79,12 +79,12 @@
|
||||
- 2026-04-04 19:07 (KST): Refined message actions into custom pill buttons with visible labels, fixed the custom action template so border visuals render correctly, and tightened composer/footer density so model, preset, token, permission, and Git controls feel like one system.
|
||||
- 2026-04-04 19:11 (KST): Reworked the AX Agent timeline so execution events render like thin Codex-style inline logs instead of heavy banners, softened older-history loading into an outline pill, and aligned planning cards to the same message-width rhythm.
|
||||
- 2026-04-04 19:25 (KST): Tightened user/assistant/streaming cards under one density rule by compressing vertical spacing, card padding, header typography, and message meta spacing to a more Codex-like rhythm.
|
||||
- 2026-04-04 19:25 (KST): Flattened the composer further by reducing input, model, preset, token, and footer control heights so the bottom work area reads more like a single claw-code-style strip.
|
||||
- 2026-04-04 19:25 (KST): Flattened the composer further by reducing input, model, preset, token, and footer control heights so the bottom work area reads more like a single OpenCode-style strip.
|
||||
- 2026-04-04 19:25 (KST): Matched AX Agent overlay quick settings to inline quick settings with the same `label · value` structure, lighter explanatory copy, and unified section naming.
|
||||
- 2026-04-04 19:48 (KST): Fixed Chat sidebar topic exploration so clicking the left topic entry surfaces the preset cards again, and made preset selection immediately sync category label, conversation metadata, and saved state.
|
||||
- 2026-04-04 19:48 (KST): Reduced permission/data-usage UI lag by synchronizing conversation settings before persistence, switching popup rows to immediate mouse-down activation, and refreshing overlay status labels in the same turn.
|
||||
- 2026-04-04 19:48 (KST): Widened the AX Agent message lane, softened the yellow cast of the Claude-style theme preset, aligned the data-usage button with the permission button language, and routed the tray settings entry directly into AX Agent settings when AI is enabled.
|
||||
- 2026-04-04 20:41 (KST): Permission surface re-aligned to the four claw-code public modes, legacy `질문 없이 진행` exposure removed from AX Agent popups/slash guidance, and the in-chat settings/sidebar information hierarchy simplified to match the lighter Claude/Codex-style layout direction.
|
||||
- 2026-04-04 20:41 (KST): Permission surface re-aligned to the four OpenCode public modes, legacy `질문 없이 진행` exposure removed from AX Agent popups/slash guidance, and the in-chat settings/sidebar information hierarchy simplified to match the lighter Claude/Codex-style layout direction.
|
||||
- 업데이트: 2026-04-04 22:05 (KST)
|
||||
- AX Agent 고급 설정 오버레이를 설명형 row + 커스텀 On/Off 선택 구조로 개편하고, 내부 기능명을 사용자 친화 문구로 정리함.
|
||||
- 업데이트: 2026-04-04 20:58 (KST)
|
||||
@@ -104,3 +104,127 @@
|
||||
- ????: 2026-04-04 23:32 (KST)
|
||||
- AX Agent ???? ??? ?? tool-result ?? -> microcompact ?? ?? ?? -> ?? ?? ?? 3?? ??? ???, claude-code? staged compact ??? ? ??? ? ?? ??? ?? ????? ???.
|
||||
- ??? ?? ??, tool_result, ?? ???, ???? ? ???? LLM ?? ?? microcompact_boundary? ?? ??? ?? ?? ?? ?? ?? ?? ??? ??? ???? ?? ??.
|
||||
|
||||
---
|
||||
|
||||
## 8. 지능형 에이전트 고도화 로드맵 (oh-my-openagent 참조)
|
||||
|
||||
> 기준: oh-my-openagent 아키텍처 분석 결과에서 단일 모델 환경에서도 효과적인 기능을 우선 채택.
|
||||
> 원칙: AX 기존 인프라(`ClassifyTaskType`, `SubAgentTool`, `IntentDetector`, `TaskTypePolicy`, `ContextCondenser`) 위에 점진적으로 확장.
|
||||
|
||||
### 8-1. 즉시 개발 (P1~P5)
|
||||
|
||||
#### P1. IntentGate — 고급 의도 분류기
|
||||
- **목적**: 사용자 입력을 분석하여 최적 실행 프로파일(system prompt + temperature + tool 권한 + 최대 반복)을 자동 선택
|
||||
- **현재 상태**: `ClassifyTaskType()` (키워드 6분류) + `IntentDetector` (키워드 7분류, UI 힌트용) 존재하나 실행 프로파일 연동 없음
|
||||
- **구현 계획**:
|
||||
1. `IntentGateService.cs` 신규 — 2단계 분류기
|
||||
- Stage 1: 키워드 기반 빠른 분류 (기존 `ClassifyTaskType` + `IntentDetector` 통합)
|
||||
- Stage 2: LLM 1-shot 분류 (복합 요청 시만 발동, 토큰 최소화)
|
||||
2. `ExecutionProfile` 확장 — 분류 결과별 프로파일 매핑
|
||||
- `code_edit`: temperature 0.3, 코드 도구 우선, 최대 반복 25
|
||||
- `document_create`: temperature 0.7, 문서 도구 우선, 최대 반복 15
|
||||
- `analysis`: temperature 0.4, 검색/읽기 도구 우선, 최대 반복 20
|
||||
- `review`: temperature 0.2, 읽기 전용 도구, 최대 반복 15
|
||||
- `general_chat`: 도구 없음, temperature 0.8
|
||||
- `complex_task`: 전체 도구, temperature 0.5, SubAgent 활성화
|
||||
3. `AgentLoopService.RunAsync()` 진입부에서 `IntentGateService.Classify()` 호출 → 프로파일 적용
|
||||
- **수정 파일**: `IntentGateService.cs`(신규), `AgentLoopService.cs`, `AppSettings.cs`, `TaskTypePolicy.cs`
|
||||
- **검증**: 코드수정/문서생성/분석/리뷰/잡담 5개 시나리오에서 올바른 프로파일 선택 확인
|
||||
|
||||
#### P2. 카테고리 기반 서브에이전트 실행 프로파일
|
||||
- **목적**: 같은 모델이지만 작업 유형별로 다른 system prompt + tool 권한 + temperature를 적용하는 "가상 멀티에이전트"
|
||||
- **현재 상태**: `SubAgentTool`은 고정 system prompt + 읽기 전용 도구만 사용
|
||||
- **구현 계획**:
|
||||
1. `SubAgentProfile.cs` 신규 — 프로파일 정의 클래스
|
||||
- `researcher`: 읽기 전용, 검색 집중, temperature 0.3
|
||||
- `coder`: 파일 편집 가능, 빌드/테스트 도구 포함, temperature 0.2
|
||||
- `writer`: 문서 생성 도구 활성, temperature 0.7
|
||||
- `reviewer`: 코드리뷰 도구 + diff, temperature 0.2
|
||||
- `planner`: 계획/분해 도구, temperature 0.5
|
||||
2. `SubAgentTool` 확장 — `profile` 파라미터 추가
|
||||
- 프로파일별로 system prompt, 도구 레지스트리, temperature를 자동 구성
|
||||
- 기존 동작(profile 미지정)은 `researcher`로 폴백
|
||||
3. `IntentGateService`가 복합 요청 감지 시 자동으로 서브에이전트 프로파일 추천
|
||||
- **수정 파일**: `SubAgentProfile.cs`(신규), `SubAgentTool.cs`, `AgentLoopService.cs`
|
||||
- **검증**: "코드 수정하고 문서도 만들어줘" → coder + writer 프로파일 분리 실행 확인
|
||||
|
||||
#### P3. 누적 학습 (Accumulated Learnings)
|
||||
- **목적**: 세션 내 발견사항(에러 패턴, 파일 구조, 성공 전략)을 자동 수집하여 후속 반복에 주입
|
||||
- **현재 상태**: `AgentMemoryService`에 장기 메모리 존재하나, 세션 내 단기 학습 전파 없음
|
||||
- **구현 계획**:
|
||||
1. `SessionLearningCollector.cs` 신규
|
||||
- 도구 실행 결과에서 자동 학습 포인트 추출:
|
||||
- 빌드 에러 → "이 프로젝트는 .NET 8, nullable 활성"
|
||||
- grep 결과 → "이 패턴은 Services/ 폴더에 집중"
|
||||
- 파일 구조 → "테스트는 Tests/ 폴더, 네이밍 규칙 *Tests.cs"
|
||||
- 최대 10개 항목, FIFO 관리
|
||||
2. `AgentLoopService` — 각 반복 시작 시 누적 학습을 컨텍스트에 주입
|
||||
- `[System:SessionLearnings]` 메시지로 삽입 (압축 시 보존)
|
||||
3. `ContextCondenser` — 학습 메시지는 압축 대상에서 제외
|
||||
- **수정 파일**: `SessionLearningCollector.cs`(신규), `AgentLoopService.cs`, `ContextCondenser.cs`
|
||||
- **검증**: 빌드 에러 발생 후 다음 반복에서 동일 실수 반복 안 하는지 확인
|
||||
|
||||
#### P4. 워크스페이스 컨텍스트 자동 생성 (.ax-context)
|
||||
- **목적**: 작업 폴더의 구조/기술스택/컨벤션을 파일로 자동 생성 → 서브에이전트 컨텍스트 효율화
|
||||
- **현재 상태**: `ProjectRuleTool`이 `.ax-rules` 파일을 읽지만 자동 생성 없음
|
||||
- **구현 계획**:
|
||||
1. `WorkspaceContextGenerator.cs` 신규
|
||||
- 작업 폴더 최초 접근 시 자동 분석:
|
||||
- 파일 트리 (깊이 3), 주요 확장자 분포, 빌드 시스템 감지
|
||||
- README/package.json/csproj 등에서 프로젝트 메타 추출
|
||||
- 기존 `.ax-rules`, `.clinerules`, `CLAUDE.md` 존재 여부
|
||||
- 결과를 `.ax-context.md`로 저장 (1회 생성, 수동 갱신)
|
||||
2. `SubAgentTool` — 서브에이전트 생성 시 `.ax-context.md` 내용을 system prompt에 자동 주입
|
||||
3. `AgentLoopService` — 메인 에이전트도 `.ax-context.md` 존재 시 컨텍스트에 포함
|
||||
- **수정 파일**: `WorkspaceContextGenerator.cs`(신규), `SubAgentTool.cs`, `AgentLoopService.cs`, `ProjectRuleTool.cs`
|
||||
- **검증**: 새 작업 폴더 선택 → `.ax-context.md` 자동 생성 → 서브에이전트가 프로젝트 구조 인지 확인
|
||||
|
||||
#### P5. 병렬 서브에이전트 실행 확장
|
||||
- **목적**: LLM이 한 번의 응답에서 여러 서브에이전트를 동시 생성/실행
|
||||
- **현재 상태**: `SubAgentTool`은 개별 호출 가능하나 LLM이 연속 호출해야 함. `AgentLoopParallelExecution`에서 병렬 도구 실행 지원
|
||||
- **구현 계획**:
|
||||
1. `spawn_agents` (복수형) 도구 신규 — 배열로 여러 서브에이전트를 한 번에 생성
|
||||
- `tasks: [{id, task, profile}, ...]` 형태
|
||||
2. `IntentGateService` — 복합 요청 감지 시 자동 분해
|
||||
- "A 분석하고 B 코드 수정해줘" → 두 개의 서브에이전트로 자동 분해 제안
|
||||
3. `SubAgentTool` — 동시 실행 상한을 설정에서 조정 가능 (기본 3 → 5)
|
||||
4. UI: 서브에이전트 진행 상태를 타임라인에 병렬로 표시
|
||||
- **수정 파일**: `SpawnAgentsTool.cs`(신규), `SubAgentTool.cs`, `AgentLoopService.cs`, `AppSettings.cs`
|
||||
- **검증**: 3개 서브에이전트 동시 실행 → 결과 수집 → 통합 응답 확인
|
||||
|
||||
### 8-2. 추후 개발 (P6~P7)
|
||||
|
||||
#### P6. 폴백 체인 (Fallback Chains)
|
||||
- **목적**: 실행 실패 시 다른 프로파일/전략으로 자동 재시도
|
||||
- **구현 방향**: IntentGate 프로파일에 `fallback` 속성 추가. 1차 실패 → 프로파일 전환 → 재시도 (최대 2회)
|
||||
- **선행 조건**: P1(IntentGate) + P2(카테고리 프로파일) 완료 후
|
||||
- **예상 파일**: `IntentGateService.cs`, `AgentLoopService.cs`
|
||||
|
||||
#### P7. 모델 성격 매칭 (Model Personality Matching)
|
||||
- **목적**: 멀티 모델 환경에서 작업 유형별 최적 모델 자동 선택
|
||||
- **구현 방향**: `RegisteredModel`에 `strengths` 속성 추가 (coding/writing/analysis/speed). IntentGate 분류 결과 + 모델 강점 매칭으로 최적 모델 라우팅
|
||||
- **선행 조건**: 멀티 모델 환경 확보 + P1(IntentGate) 완료 후
|
||||
- **예상 파일**: `IntentGateService.cs`, `ModelRouterService.cs`, `AppSettings.cs`
|
||||
|
||||
### 8-3. 구현 순서 및 의존 관계
|
||||
|
||||
```
|
||||
P1 (IntentGate) ─────┬──→ P2 (카테고리 프로파일) ──→ P5 (병렬 확장)
|
||||
│
|
||||
├──→ P3 (누적 학습) [독립]
|
||||
│
|
||||
└──→ P4 (워크스페이스 컨텍스트) [독립]
|
||||
|
||||
P1 + P2 완료 후 ──→ P6 (폴백 체인)
|
||||
P1 + 멀티모델 후 ──→ P7 (모델 성격 매칭)
|
||||
```
|
||||
|
||||
### 8-4. 단일 모델에서의 멀티에이전트 원리
|
||||
|
||||
> Claude Code, Codex, Cursor 등 주요 AI 코딩 도구는 모두 **단일 모델 + 다른 시스템 프롬프트/도구 권한/temperature** 조합으로 멀티에이전트를 구현합니다.
|
||||
>
|
||||
> - 메인 에이전트: 범용 system prompt + 전체 도구 + 중간 temperature
|
||||
> - 서브에이전트: 특화 system prompt + 제한된 도구 + 작업별 temperature
|
||||
>
|
||||
> AX Copilot도 이 패턴을 따릅니다. `SubAgentTool`이 이미 존재하므로, **프로파일 체계화(P2)**만으로 사실상 멀티에이전트가 됩니다.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
업데이트: 2026-04-08 10:38 (KST)
|
||||
|
||||
`claw-code`와 AX Agent를 같은 기준으로 비교하기 위한 공통 회귀 프롬프트 세트입니다.
|
||||
`OpenCode`와 AX Agent를 같은 기준으로 비교하기 위한 공통 회귀 프롬프트 세트입니다.
|
||||
|
||||
## 사용 규칙
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
- `skills/*.skill.md` 파일 수: 41
|
||||
- Slash 명령 매핑 엔트리 수: 85
|
||||
|
||||
## 4) claw-code 비교 포인트 (실행 반영 기준)
|
||||
- claw-code 문서 기준 핵심 slash: `/permissions`, `/mcp` 확인
|
||||
## 4) OpenCode 비교 포인트 (실행 반영 기준)
|
||||
- OpenCode 문서 기준 핵심 slash: `/permissions`, `/mcp` 확인
|
||||
- AX 현재 매핑에 `/permissions`, `/allowed-tools`, `/mcp`, `/chrome`, `/compact` 포함
|
||||
- 다음 보강 우선순위:
|
||||
1. 수동 시나리오 기반 MCP/Chrome 진단 품질 강화
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
## 2. 재작성 기준
|
||||
- 런처는 AX Agent 실행 품질을 끌어올리는 입력 허브로 재정의.
|
||||
- claw-code의 명령 중심 워크플로우를 AX Commander UX에 맞게 흡수.
|
||||
- OpenCode의 명령 중심 워크플로우를 AX Commander UX에 맞게 흡수.
|
||||
|
||||
## 3. 2026 실행 계획 (v0.7.3 ~ v0.8.x)
|
||||
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
- v0.7.0~v0.7.2: Plan Mode, 병렬 도구, 검증 게이트 및 안정화 강화.
|
||||
|
||||
## 2. 재작성 목표
|
||||
- claw-code 수준의 실행 신뢰성, 세션 내구성, 결과 품질 확보.
|
||||
- OpenCode 수준의 실행 신뢰성, 세션 내구성, 결과 품질 확보.
|
||||
- AX Copilot 고유 제약(사내 운영모드, WPF UX, 로컬 우선)을 유지한 동등 품질 구현.
|
||||
|
||||
## 3. 마일스톤
|
||||
|
||||
| 마일스톤 | 참조 대상 (`claw-code`) | AX 적용 위치 | 완료 조건 | 품질 판정 시나리오 |
|
||||
| 마일스톤 | 참조 대상 (`OpenCode`) | AX 적용 위치 | 완료 조건 | 품질 판정 시나리오 |
|
||||
|---|---|---|---|---|
|
||||
| M1 (v0.7.3) Hook 계약 정식화 | `src/utils/hooks.ts`, `src/utils/hooks/hookEvents.ts`, `src/utils/permissions/PermissionUpdate.ts` | `src/AxCopilot/Services/Agent/AgentHookRunner.cs`, `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Models/AppSettings.cs`, `src/AxCopilot/ViewModels/SettingsViewModel.cs`, `src/AxCopilot/Views/SettingsWindow.xaml` | Hook 출력 계약(`updatedInput`, `updatedPermissions`, `additionalContext`) 반영. 설정값-실행코드-UI 동시 반영. | JSON 훅/텍스트 훅 혼합 환경에서 실행 품질과 하위호환 유지 확인. |
|
||||
| M2 (v0.7.4~v0.7.5) 세션/로그 내구성 | `src/utils/plans.ts`, `src/utils/sessionStorage.ts` | `src/AxCopilot/Services/ChatSessionStateService.cs`, `src/AxCopilot/Services/TaskRunService.cs`, `src/AxCopilot/Services/TaskRunStore.cs`, `src/AxCopilot/Views/ChatWindow.xaml.cs`, `src/AxCopilot/Models/ChatModels.cs` | Plan/Run 상태 영속화. 이벤트 로그 표준화 및 replay 안정성 확보. | 앱 재시작/강제종료 후 동일 세션 재개 시 상태/이력 불일치 0건. |
|
||||
| M3 (v0.7.6) 도구 선택/복구 안정화 | `src/commands.ts`, `src/Tool.ts`, `src/query.ts` | `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Services/Agent/AgentLoopParallelExecution.cs`, `src/AxCopilot/Services/LlmService.ToolUse.cs` | 도구 선택 실패 루프 억제. 실패 유형별 복구 흐름 정형화. | 오타/비활성/별칭 도구 요청이 반복 실패 없이 정상 경로로 수렴하는지 확인. |
|
||||
| M4 (v0.8.0) 품질 게이트 최종 정렬 | `src/query.ts`, `src/QueryEngine.ts`, `src/utils/sessionStorage.ts` | `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.cs` | 증거 기반 완료 판정 고정. 내부 벤치마크에서 claw-code 동급 품질 달성. | 코드수정/문서생성/권한거부/복구 혼합 시나리오에서 조기완료 없이 근거 기반 종료 확인. |
|
||||
| M4 (v0.8.0) 품질 게이트 최종 정렬 | `src/query.ts`, `src/QueryEngine.ts`, `src/utils/sessionStorage.ts` | `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.cs` | 증거 기반 완료 판정 고정. 내부 벤치마크에서 OpenCode 동급 품질 달성. | 코드수정/문서생성/권한거부/복구 혼합 시나리오에서 조기완료 없이 근거 기반 종료 확인. |
|
||||
|
||||
## 4. 측정 지표
|
||||
- 반복 실패 루프 발생률.
|
||||
@@ -48,12 +48,12 @@
|
||||
2. 위 결과를 배포 전 체크리스트로 고정하고 매 릴리즈마다 수치 동기화.
|
||||
|
||||
## 9. 벤치마크 고정 기준 (CLAW 패리티 동기화)
|
||||
- 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 12절.
|
||||
- 기준 문서: `docs/OPENCODE_PARITY_PLAN.md` 12절.
|
||||
- 고정 시나리오: 기본 loop / unknown-tool 복구 / plan 연결 / 권한 거부 / hook 입력 변형 / allowed-tools 강제 / hook filter.
|
||||
- 합격 게이트: 빌드 경고·오류 0 + 전체 테스트 통과 + 고정 시나리오 회귀 통과.
|
||||
|
||||
## 10. Replay 안정성 검증 연동
|
||||
- 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 13절.
|
||||
- 기준 문서: `docs/OPENCODE_PARITY_PLAN.md` 13절.
|
||||
- 테스트 태그: `Suite=ReplayStability`.
|
||||
- 운영 기준: 릴리즈 전 `ReplayStability` 시나리오 전건 통과 시 replay 불일치 0건으로 판정.
|
||||
- 최신 실행 증적(2026-04-03): `ParityBenchmark 13/13`, `ReplayStability 14/14`, 전체 `379/379`.
|
||||
@@ -79,9 +79,9 @@
|
||||
업데이트: 2026-04-04 13:24 (KST)
|
||||
|
||||
### 기준 소스
|
||||
- claw-code/src/commands.ts
|
||||
- claw-code/src/utils/permissions/PermissionMode.ts
|
||||
- claw-code/src/components/PromptInput/PromptInput.tsx
|
||||
- OpenCode/src/commands.ts
|
||||
- OpenCode/src/utils/permissions/PermissionMode.ts
|
||||
- OpenCode/src/components/PromptInput/PromptInput.tsx
|
||||
|
||||
### 현재 AX 격차 요약
|
||||
1. 입력/슬래시/권한 처리 로직이 ChatWindow 단일 파일에 상대적으로 집중되어 변경 파급도가 큼.
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
### 3축 고정 운영
|
||||
1. 로직 안정화: 권한/운영모드/compact/모델 연결 경로를 테스트 우선으로 보강.
|
||||
2. 기능 동등성: claw-code 기준 주요 명령(/compact, /permissions, /mcp, /chrome)을 실행 시나리오로 검증.
|
||||
2. 기능 동등성: OpenCode 기준 주요 명령(/compact, /permissions, /mcp, /chrome)을 실행 시나리오로 검증.
|
||||
3. UX 정렬: 상단/컴포저/팝업 밀도는 로직 안정화 완료 범위 내에서만 단계 반영.
|
||||
|
||||
### 이번 사이클 기준 완료 조건
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# OpenCode 품질 동등화 계획 (전면 재작성)
|
||||
|
||||
## 1. 기준
|
||||
- 레퍼런스: `E:\AX Copilot - Codex\OpenCode\OpenCode-f5a40b86dede580f6543bf8926c9af017eea9409\src`
|
||||
- 동등화 범위: 명령 처리 흐름, 권한/훅 체계, plan/run 내구성, 세션/로그 관리.
|
||||
|
||||
## 2. 보존 이력 (요약만 유지)
|
||||
- 루프 가드(반복 실패/정체 감지)와 검증 게이트는 이미 AX에 부분 반영.
|
||||
- 병렬 도구 실행, Plan Mode, Unknown-tool 복구 로직은 반영 완료.
|
||||
- 세션/이력 안정화는 진행 중.
|
||||
|
||||
## 3. 현재 갭
|
||||
- Hook 출력의 구조화 계약 적용 범위가 제한적.
|
||||
- 세션 재개/이벤트 replay 품질이 시나리오별 편차 존재.
|
||||
- 도구 선택 실패 후 재시도 전략의 일관성 부족.
|
||||
|
||||
## 4. 단계별 실행
|
||||
|
||||
### P1 Hook 계약 고도화
|
||||
- `updatedInput`, `updatedPermissions`, `additionalContext`를 AX 실행 루프에 반영.
|
||||
- 설정 토글로 위험 기능 제어.
|
||||
|
||||
### P2 세션/이벤트 내구성
|
||||
- run 상태 영속화와 resume 기준 고정.
|
||||
- jsonl 이벤트 필드 표준화 및 재생 가능성 확보.
|
||||
|
||||
### P3 실패 복구 표준화
|
||||
- unknown-tool/권한거부/파라미터 오류를 유형화.
|
||||
- 유형별 복구 프롬프트와 다음 액션 우선순위 고정.
|
||||
|
||||
### P4 완료 품질 정렬
|
||||
- 코드/문서 작업별 완료 체크리스트 표준화.
|
||||
- 증거 없는 완료 응답 방지.
|
||||
|
||||
## 5. 완료 판정
|
||||
- 내부 벤치마크 시나리오에서 목표 성공률 충족.
|
||||
- 세션 재개/이벤트 재생 오류 0건.
|
||||
- 빌드 경고 0/오류 0 유지.
|
||||
|
||||
## 6. 2026-04-03 점검 스냅샷
|
||||
- 기준 시점: 2026-04-03.
|
||||
- 계획 대비 현재 수준: 약 92~95%.
|
||||
- 테스트 상태: `dotnet test` 374/374 통과.
|
||||
- P1 Hook 계약: 구현 완료 수준.
|
||||
- P2 세션/이벤트 내구성: 구현 완료 수준(복원/재생 경계 케이스 테스트 반영).
|
||||
- P3 실패 복구 표준화: 구현 완료 수준(unknown-tool/권한/정체/fork 강제 흐름 반영).
|
||||
- P4 완료 품질 정렬: 구현 완료 수준(게이트 로직 + 벤치마크 기준 문서화 완료).
|
||||
|
||||
## 7. 스킬/도구 운영 점검 (2026-04-03)
|
||||
- 도구 레지스트리 등록 수: 65개 (`ToolRegistry.CreateDefault()` 기준).
|
||||
- `IAgentTool` 구현 수: 65개. 동적 MCP 래퍼(`McpTool`)는 런타임 등록 구조로 정상.
|
||||
- 내장 스킬 파일 수: 40개 (`src/AxCopilot/skills/*.skill.md`).
|
||||
- `allowed-tools` 적용 스킬: 40/40.
|
||||
- `requires: python` 고정 의존 스킬: 0개(기존 10개에 Python 가능/불가 분기 fallback 경로 반영).
|
||||
- 레거시 도구명 `process_run` 참조: 0건 (`process`로 정규화).
|
||||
- 레거시 도구명 `grep_tool` 참조: 0건 (`grep`로 정규화).
|
||||
- 내부 모드 차단 정책: `http_tool` 전면 차단, `open_external`의 외부 URL 차단.
|
||||
- 테스트 상태: `dotnet test` 374/374 통과.
|
||||
|
||||
## 8. OpenCode 소스 직접 비교 결과 (2026-04-03)
|
||||
- 비교 기준 소스: `OpenCode/.../src/tools.ts`, `src/Tool.ts`, `src/skills/loadSkillsDir.ts`, `src/skills/bundled/*.ts`.
|
||||
- OpenCode 도구 상수명: 41개 (`*_TOOL_NAME` 기준).
|
||||
- AX 도구명: 65개 (`IAgentTool.Name` 기준).
|
||||
- OpenCode 도구명 중 AX alias로 직접 해석되는 항목: 8개 (`Bash/Read/Write/Edit/Glob/Grep/WebFetch/WebSearch`).
|
||||
- 미해석 항목(33개)은 대부분 제품 철학 차이 영역(예: `EnterPlanMode`, `ExitWorktree`, `TeamCreate`, `TodoWrite`, `ToolSearch`, `REPL`, `Cron*`).
|
||||
- OpenCode 번들 스킬은 코드 등록형 14개(`registerBundledSkill`)이며, AX는 파일 기반 스킬 40개(`*.skill.md`) 중심 구성.
|
||||
- 번들 스킬 이름 교집합은 0개이며(의도된 차별화), 품질 기준은 "이름 일치"가 아니라 "동작 등가(계획/복구/검증/권한/세션 내구성)"로 판단해야 함.
|
||||
- OpenCode 스킬 메타데이터는 `allowedTools`, `whenToUse`, `model`, `disableModelInvocation`, `userInvocable`, `hooks`, `context`, `agent`, `paths`, `effort`, `shell`까지 지원.
|
||||
- AX 스킬 메타데이터는 `allowed-tools`, `when_to_use`, `argument-hint`, `model`, `disable-model-invocation`, `user-invocable`, `context`, `agent`, `effort`, `paths`, `shell`, `hooks`, `hook_filters`까지 런타임 연계 확장 완료.
|
||||
|
||||
## 9. OpenCode 비교 기반 즉시 보완 항목 (반영 상태)
|
||||
1. 스킬 frontmatter 확장: `when_to_use`, `argument-hint`, `model`, `disable-model-invocation`, `user-invocable`, `context`, `agent`, `effort`, `paths`, `shell` 반영 완료.
|
||||
2. 스킬 실행 범위 제어: `paths` 기반 조건부 활성화 반영 완료(첨부 파일 경로 매칭 시 동적 활성화).
|
||||
3. 도구 별칭 정규화: OpenCode 기본 도구명군(`WebFetch`, `WebSearch`, `AskUserQuestion`, `LSP`, `ListMcpResourcesTool` 등) AX 내부 도구명으로 매핑 반영 완료.
|
||||
4. 반영 완료(2026-04-03): `hooks`/`hook_filters` 계약 확장 및 runtime hook 필터링(도구/타이밍 기준) 적용.
|
||||
5. 반영 완료(2026-04-03): 슬래시 스킬 실행 시 `context/agent/effort/model/disable-model-invocation/allowed-tools/hooks/hook_filters` 메타데이터를 런타임 정책 지시문으로 합성 적용.
|
||||
6. 반영 완료(2026-04-03): `permissionSetup` 비교 기반으로 위험 자동허용 가드 추가(`process`, `spawn_agent`, `snippet_runner`는 전역 `Auto`에서도 승인 단계 강제).
|
||||
|
||||
## 10. 전체 영역 동시 비교 기준 (누락 방지)
|
||||
1. 도구 계층: 도구 목록, 별칭 정규화, unknown-tool 복구, tool search/선택 정책.
|
||||
2. 스킬 계층: 로더, frontmatter, 번들/파일 기반 동시 운영, 조건부 활성화.
|
||||
3. 훅/권한: pre/post hook 계약, permission update 반영, ask/auto/deny 일관성.
|
||||
4. 세션/상태: run 복원, 이벤트 replay, 중복 이벤트 억제, resume 신뢰성.
|
||||
5. 계획/실행: plan 승인/수정 이력, 실행 게이트, 조기 완료 방지.
|
||||
6. MCP/외부도구: 연결/리소스 조회/읽기, 실패 복구, internal 모드 정책.
|
||||
7. 서브에이전트: spawn/wait 제약, read-only 모드, 결과 회수 구조.
|
||||
8. 운영/설정/UI 연동: 설정값-실행코드-UI 동기화, 탭별 분기(Cowork/Code) 일치.
|
||||
|
||||
## 11. 다음 적용 순서 (전체 영역 병렬 보정)
|
||||
1. 기본 Agentic loop E2E 시나리오(도구 실행/unknown-tool 복구)를 회귀 테스트로 고정.
|
||||
2. 벤치마크 합격 기준(시나리오/통과조건/증적)을 문서화하고 배포 체크리스트에 연결. (완료)
|
||||
3. 최종 마감 시 패리티 문서(수치/상태)와 로드맵 문서를 동기화. (진행 중)
|
||||
|
||||
## 12. 내부 벤치마크 기준 (고정)
|
||||
|
||||
| 시나리오 | 기준 테스트 | 합격 기준 |
|
||||
|---|---|---|
|
||||
| 기본 Agentic loop (도구 호출 → 완료) | `AgentLoopE2ETests.RunAsync_ExecutesToolCall_AndCompletesWithFinalText` | ToolCall/ToolResult/Complete 이벤트가 순서상 유효하고 최종 응답에 계산 결과 포함 |
|
||||
| unknown-tool 복구 | `AgentLoopE2ETests.RunAsync_UnknownTool_RecoversAndCompletes` | unknown 도구 오류 이벤트 후 완료까지 수렴, 반복 실패 루프 없음 |
|
||||
| Plan Mode 실행 연결 | `AgentLoopE2ETests.RunAsync_PlanModeAlways_EmitsPlanningThenExecutesTool` | 계획 응답 후 실제 도구 호출이 이어지고 최종 결과 생성 |
|
||||
| 권한 Ask 거부 처리 | `AgentLoopE2ETests.RunAsync_AskPermissionDenied_EmitsPermissionEvents_AndCompletes` | PermissionRequest/PermissionDenied 이벤트가 기록되고 안전 종료 |
|
||||
| Hook 입력 변형 반영 | `AgentLoopE2ETests.RunAsync_PreHookInputMutation_ChangesToolArguments` | pre-hook `updatedInput`이 실제 도구 입력에 적용됨 |
|
||||
| Runtime 정책(`allowed_tools`) 강제 | `AgentLoopE2ETests.RunAsync_DisallowedTool_ByRuntimePolicy_EmitsPolicyRecoveryError` | 비허용 도구 차단 + 정책 복구 경고 후 종료 |
|
||||
| Hook filter 정합성 | `AgentLoopE2ETests.RunAsync_HookFilters_ExecuteOnlyMatchingHookForToolAndTiming` | 지정된 hook만 실행되고 비매칭 hook는 미실행 |
|
||||
| OpenCode alias(`EnterPlanMode`) 정규화 | `AgentLoopE2ETests.RunAsync_EnterPlanModeAlias_ResolvesAndExecutes` | CamelCase 도구명이 AX 내부 snake_case 도구로 매핑되어 정상 실행 |
|
||||
| 혼합 복구 내구성 (unknown + 권한 + 대체도구) | `AgentLoopE2ETests.RunAsync_MixedRecovery_UnknownToolAndPermissionDenied_TerminatesSafely` | unknown-tool 오류 후 file_write 경유, math_eval로 수렴하고 반복 한도 내 안전 종료 |
|
||||
|
||||
### 벤치마크 배포 체크리스트 연결
|
||||
1. `dotnet build` 경고 0/오류 0.
|
||||
2. `dotnet test` 전체 통과 (`374/374` 기준, 증가 시 최신 값으로 동기화).
|
||||
3. 위 9개 시나리오의 회귀 테스트가 모두 통과.
|
||||
4. 패리티 수치/상태를 `NEXT_ROADMAP.md`와 동일 문구로 동기화.
|
||||
5. 릴리즈 전 게이트 스크립트 실행: `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`
|
||||
|
||||
### 실행 증적 (2026-04-03)
|
||||
- `dotnet test --filter "Suite=ParityBenchmark"`: 12/12 통과.
|
||||
- `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`: build/replay/full gate 통과.
|
||||
|
||||
## 13. 세션 Replay 안정성 기준 (고정)
|
||||
|
||||
| 시나리오 | 기준 테스트 | 합격 기준 |
|
||||
|---|---|---|
|
||||
| 실행 이벤트 정렬/압축 복원 | `ChatSessionStateServiceTests.LoadOrCreateConversation_NormalizesHistoryOrderAndCompactsSize` | ExecutionEvents 400개 상한 유지 + 시간순 정렬 유지 |
|
||||
| run 이력 중복 정규화 | `ChatSessionStateServiceTests.LoadOrCreateConversation_NormalizesAgentRunDuplicatesByRunId` | 동일 `RunId` 중복이 최신 상태 1건으로 축약 |
|
||||
| 동시각 이벤트 종결 우선 판정 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_PrefersTerminalEventsWhenTimestampsEqual` | Tool/Permission 상태가 terminal 이벤트 기준으로 복원 |
|
||||
| 비종결 이벤트 active 복원 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_RebuildsActiveTasksFromNonTerminalEvents` | 중단 후 재개 시 active task 3종(agent/tool/permission) 재구성 |
|
||||
| run 종료 시 dangling 정리 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_CompleteClearsDanglingRunScopedActiveTasks` | Complete 이후 run 스코프 active task 잔존 0건 |
|
||||
| 현재 run 복원 우선순위 | `AppStateServiceTests.RestoreCurrentAgentRun_PrefersRunningExecutionEventOverHistory` | 실행 중 이벤트가 history보다 우선되어 현재 run 복원 |
|
||||
| recent timeline 재구성 | `AppStateServiceTests.RestoreRecentTasks_RebuildsRecentTaskTimelineFromExecutionEvents` | 도구/권한/에이전트 최근 이력 순서 복원 |
|
||||
| 권한 거부 후 active 권한 상태 정리 | `AppStateServiceTests.RestoreRecentTasks_PermissionDeniedLeavesNoActivePermissionAfterResume` | PermissionDenied 이후 run 재개 시 active permission 잔존 0건 |
|
||||
| Hook 타임라인 역순 병합 정합성 | `AppStateServiceTests.ApplyAgentEvent_ReplaysHookTimelineInReverseChronologicalOrder` | Hook 이벤트가 역순 타임라인에서도 시간/의미 순서 보존 |
|
||||
| 완료 이벤트 우선 정리(병렬 도구) | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_CompleteClearsParallelToolCallsForSameRun` | Complete 도착 시 동일 run의 병렬 도구 active task 즉시 정리 |
|
||||
|
||||
### 운영 규칙
|
||||
1. 위 시나리오는 `Suite=ReplayStability` 테스트 태그로 관리.
|
||||
2. 릴리즈 전 `Suite=ReplayStability` 전건 통과를 replay 불일치 0건의 최소 조건으로 사용.
|
||||
|
||||
### 실행 증적 (2026-04-03)
|
||||
- `dotnet test --filter "Suite=ReplayStability"`: 12/12 통과.
|
||||
- `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`: `ReplayStability` 포함 게이트 통과.
|
||||
|
||||
## 14. 권한 Hook 계약 (P2 마감 기준)
|
||||
- lifecycle hook 키:
|
||||
- `__permission_request__` (pre)
|
||||
- `__permission_granted__` (post)
|
||||
- `__permission_denied__` (post)
|
||||
- payload 기준 필드: `runId`, `tool`, `target`, `permission`, `granted`, `reason`.
|
||||
- 우선순위:
|
||||
1. Hook `updatedPermissions`가 현재 run의 `AgentContext.ToolPermissions`를 즉시 갱신.
|
||||
2. 갱신 후 `context.CheckToolPermissionAsync()`로 최종 판정.
|
||||
3. hook 실패/예외는 non-blocking(권한 흐름 지속).
|
||||
4. `additionalContext`는 가능한 경로에서 메시지 컨텍스트로 반영.
|
||||
|
||||
@@ -4,10 +4,10 @@ Generated: 2026-04-04 02:06:25
|
||||
|
||||
## Summary
|
||||
- AX slash count: 85
|
||||
- claw-code slash count: 7
|
||||
- OpenCode slash count: 7
|
||||
- Common: 7
|
||||
- AX only: 78
|
||||
- claw-code only: 0
|
||||
- OpenCode only: 0
|
||||
|
||||
## Common
|
||||
- /clear
|
||||
@@ -98,8 +98,8 @@ Generated: 2026-04-04 02:06:25
|
||||
- /verify
|
||||
- /vim
|
||||
|
||||
## claw-code Only
|
||||
## OpenCode Only
|
||||
|
||||
## Notes
|
||||
- claw-code source baseline: claw-code/en/quickstart.md
|
||||
- OpenCode source baseline: OpenCode/en/quickstart.md
|
||||
- AX source baseline: src/AxCopilot/Views/ChatWindow.xaml.cs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
@@ -63,10 +63,10 @@ public class AgentLoopCodeQualityTests
|
||||
"bugfix");
|
||||
|
||||
prompt.Should().Contain("baseline build/test");
|
||||
prompt.Should().Contain("grep/glob");
|
||||
prompt.Should().Contain("grep 또는 glob");
|
||||
prompt.Should().Contain("build_run");
|
||||
prompt.Should().Contain("테스트 부재 사실");
|
||||
prompt.Should().Contain("작업 유형: bugfix");
|
||||
prompt.Should().Contain("Task type: bugfix");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -87,7 +87,7 @@ public class AgentLoopCodeQualityTests
|
||||
prompt.Should().Contain("grep 또는 glob");
|
||||
prompt.Should().Contain("테스트 부재 사실");
|
||||
prompt.Should().Contain("영향 범위가 넓을 가능성");
|
||||
prompt.Should().Contain("작업 유형: refactor");
|
||||
prompt.Should().Contain("Task type: refactor");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -182,7 +182,7 @@ public class AgentLoopCodeQualityTests
|
||||
prompt.Should().Contain("spawn_agent");
|
||||
prompt.Should().Contain("build/test");
|
||||
prompt.Should().Contain("테스트 부재 사실");
|
||||
prompt.Should().Contain("재현 조건");
|
||||
prompt.Should().Contain("symptom is no longer reproducible");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -202,8 +202,8 @@ public class AgentLoopCodeQualityTests
|
||||
false,
|
||||
"refactor");
|
||||
|
||||
featurePrompt.Should().Contain("새 기능 경로");
|
||||
refactorPrompt.Should().Contain("동작 보존");
|
||||
featurePrompt.Should().Contain("feature path and caller linkage");
|
||||
refactorPrompt.Should().Contain("behavior-compatible");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -370,7 +370,7 @@ public class AgentLoopCodeQualityTests
|
||||
|
||||
prompt.Should().Contain("무엇을 변경했는지");
|
||||
prompt.Should().Contain("build/test/검증 근거");
|
||||
prompt.Should().Contain("원인, 수정 내용, 재현/회귀 검증 근거");
|
||||
prompt.Should().Contain("bug fix");
|
||||
prompt.Should().Contain("남은 리스크");
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ public class AgentLoopCodeQualityTests
|
||||
"bugfix");
|
||||
|
||||
guidance.Should().Contain("[System:FailurePatterns]");
|
||||
guidance.Should().Contain("재현 조건과 원인 연결");
|
||||
guidance.Should().Contain("reproduction");
|
||||
guidance.Should().Contain("CS1002");
|
||||
guidance.Should().Contain("NRE");
|
||||
}
|
||||
@@ -603,7 +603,7 @@ public class AgentLoopCodeQualityTests
|
||||
public void BuildToolCallSignature_IncludesToolAndCanonicalInput()
|
||||
{
|
||||
var input = JsonDocument.Parse("""{"path":"src/A.cs","line":10}""").RootElement.Clone();
|
||||
var call = new LlmService.ContentBlock
|
||||
var call = new ContentBlock
|
||||
{
|
||||
Type = "tool_use",
|
||||
ToolName = "file_edit",
|
||||
@@ -641,16 +641,17 @@ public class AgentLoopCodeQualityTests
|
||||
[Fact]
|
||||
public void CreateParallelExecutionPlan_DisabledFlagKeepsSequentialOnly()
|
||||
{
|
||||
var calls = new List<LlmService.ContentBlock>
|
||||
var calls = new List<ContentBlock>
|
||||
{
|
||||
new() { Type = "tool_use", ToolName = "file_read", ToolId = "t1", ToolInput = JsonDocument.Parse("""{"path":"a.txt"}""").RootElement.Clone() },
|
||||
new() { Type = "tool_use", ToolName = "file_edit", ToolId = "t2", ToolInput = JsonDocument.Parse("""{"path":"a.txt","old":"a","new":"b"}""").RootElement.Clone() }
|
||||
};
|
||||
|
||||
var plan = InvokePrivateStatic<(bool ShouldRun, List<LlmService.ContentBlock> ParallelBatch, List<LlmService.ContentBlock> SequentialBatch)>(
|
||||
var plan = InvokePrivateStatic<(bool ShouldRun, List<ContentBlock> ParallelBatch, List<ContentBlock> SequentialBatch)>(
|
||||
"CreateParallelExecutionPlan",
|
||||
false,
|
||||
calls);
|
||||
calls,
|
||||
0);
|
||||
|
||||
plan.ShouldRun.Should().BeFalse();
|
||||
plan.ParallelBatch.Should().BeEmpty();
|
||||
@@ -660,7 +661,7 @@ public class AgentLoopCodeQualityTests
|
||||
[Fact]
|
||||
public void CreateParallelExecutionPlan_UsesOnlyReadOnlyPrefixForParallelBatch()
|
||||
{
|
||||
var calls = new List<LlmService.ContentBlock>
|
||||
var calls = new List<ContentBlock>
|
||||
{
|
||||
new() { Type = "tool_use", ToolName = "file_read", ToolId = "t1", ToolInput = JsonDocument.Parse("""{"path":"a.txt"}""").RootElement.Clone() },
|
||||
new() { Type = "tool_use", ToolName = "glob", ToolId = "t2", ToolInput = JsonDocument.Parse("""{"pattern":"*.cs"}""").RootElement.Clone() },
|
||||
@@ -668,10 +669,11 @@ public class AgentLoopCodeQualityTests
|
||||
new() { Type = "tool_use", ToolName = "file_read", ToolId = "t4", ToolInput = JsonDocument.Parse("""{"path":"b.txt"}""").RootElement.Clone() }
|
||||
};
|
||||
|
||||
var plan = InvokePrivateStatic<(bool ShouldRun, List<LlmService.ContentBlock> ParallelBatch, List<LlmService.ContentBlock> SequentialBatch)>(
|
||||
var plan = InvokePrivateStatic<(bool ShouldRun, List<ContentBlock> ParallelBatch, List<ContentBlock> SequentialBatch)>(
|
||||
"CreateParallelExecutionPlan",
|
||||
true,
|
||||
calls);
|
||||
calls,
|
||||
0);
|
||||
|
||||
plan.ShouldRun.Should().BeTrue();
|
||||
plan.ParallelBatch.Select(x => x.ToolId).Should().Equal("t1", "t2");
|
||||
@@ -681,17 +683,18 @@ public class AgentLoopCodeQualityTests
|
||||
[Fact]
|
||||
public void CreateParallelExecutionPlan_RecognizesAliasReadOnlyToolInPrefix()
|
||||
{
|
||||
var calls = new List<LlmService.ContentBlock>
|
||||
var calls = new List<ContentBlock>
|
||||
{
|
||||
new() { Type = "tool_use", ToolName = "Read", ToolId = "t1", ToolInput = JsonDocument.Parse("""{"path":"a.txt"}""").RootElement.Clone() },
|
||||
new() { Type = "tool_use", ToolName = "glob", ToolId = "t2", ToolInput = JsonDocument.Parse("""{"pattern":"*.cs"}""").RootElement.Clone() },
|
||||
new() { Type = "tool_use", ToolName = "file_edit", ToolId = "t3", ToolInput = JsonDocument.Parse("""{"path":"a.txt","old":"a","new":"b"}""").RootElement.Clone() }
|
||||
};
|
||||
|
||||
var plan = InvokePrivateStatic<(bool ShouldRun, List<LlmService.ContentBlock> ParallelBatch, List<LlmService.ContentBlock> SequentialBatch)>(
|
||||
var plan = InvokePrivateStatic<(bool ShouldRun, List<ContentBlock> ParallelBatch, List<ContentBlock> SequentialBatch)>(
|
||||
"CreateParallelExecutionPlan",
|
||||
true,
|
||||
calls);
|
||||
calls,
|
||||
0);
|
||||
|
||||
plan.ShouldRun.Should().BeTrue();
|
||||
plan.ParallelBatch.Select(x => x.ToolId).Should().Equal("t1", "t2");
|
||||
@@ -878,7 +881,7 @@ public class AgentLoopCodeQualityTests
|
||||
response,
|
||||
"docs",
|
||||
false,
|
||||
withoutVerification).Should().BeFalse();
|
||||
withoutVerification).Should().BeTrue();
|
||||
|
||||
InvokePrivateStatic<bool>(
|
||||
"HasSufficientFinalReportEvidence",
|
||||
@@ -1168,65 +1171,33 @@ public class AgentLoopCodeQualityTests
|
||||
[Fact]
|
||||
public void ResolveNoToolCallResponseThreshold_UsesDefaultAndClamps()
|
||||
{
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveNoToolCallResponseThreshold",
|
||||
(string?)null).Should().Be(2);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveNoToolCallResponseThreshold",
|
||||
"0").Should().Be(1);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveNoToolCallResponseThreshold",
|
||||
"99").Should().Be(6);
|
||||
AgentLoopRuntimeThresholds.ResolveNoToolCallResponseThreshold(null).Should().Be(2);
|
||||
AgentLoopRuntimeThresholds.ResolveNoToolCallResponseThreshold("0").Should().Be(1);
|
||||
AgentLoopRuntimeThresholds.ResolveNoToolCallResponseThreshold("99").Should().Be(6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveNoToolCallRecoveryMaxRetries_UsesDefaultAndClamps()
|
||||
{
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveNoToolCallRecoveryMaxRetries",
|
||||
(string?)null).Should().Be(2);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveNoToolCallRecoveryMaxRetries",
|
||||
"-1").Should().Be(0);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveNoToolCallRecoveryMaxRetries",
|
||||
"99").Should().Be(6);
|
||||
AgentLoopRuntimeThresholds.ResolveNoToolCallRecoveryMaxRetries(null).Should().Be(3);
|
||||
AgentLoopRuntimeThresholds.ResolveNoToolCallRecoveryMaxRetries("-1").Should().Be(0);
|
||||
AgentLoopRuntimeThresholds.ResolveNoToolCallRecoveryMaxRetries("99").Should().Be(6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvePlanExecutionRetryMax_UsesDefaultAndClamps()
|
||||
{
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolvePlanExecutionRetryMax",
|
||||
(string?)null).Should().Be(2);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolvePlanExecutionRetryMax",
|
||||
"-5").Should().Be(0);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolvePlanExecutionRetryMax",
|
||||
"10").Should().Be(6);
|
||||
AgentLoopRuntimeThresholds.ResolvePlanExecutionRetryMax(null).Should().Be(2);
|
||||
AgentLoopRuntimeThresholds.ResolvePlanExecutionRetryMax("-5").Should().Be(0);
|
||||
AgentLoopRuntimeThresholds.ResolvePlanExecutionRetryMax("10").Should().Be(6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveTerminalEvidenceGateMaxRetries_UsesDefaultAndClamps()
|
||||
{
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveTerminalEvidenceGateMaxRetries",
|
||||
(string?)null).Should().Be(1);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveTerminalEvidenceGateMaxRetries",
|
||||
"-2").Should().Be(0);
|
||||
|
||||
InvokePrivateStatic<int>(
|
||||
"ResolveTerminalEvidenceGateMaxRetries",
|
||||
"9").Should().Be(3);
|
||||
AgentLoopRuntimeThresholds.ResolveTerminalEvidenceGateMaxRetries(null).Should().Be(1);
|
||||
AgentLoopRuntimeThresholds.ResolveTerminalEvidenceGateMaxRetries("-2").Should().Be(0);
|
||||
AgentLoopRuntimeThresholds.ResolveTerminalEvidenceGateMaxRetries("9").Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -74,14 +74,11 @@ public class AgentLoopE2ETests
|
||||
{
|
||||
using var server = new FakeOllamaServer(
|
||||
[
|
||||
BuildTextOnlyResponse("1. math_eval 도구로 계산\n2. 결과를 검증하고 보고"),
|
||||
BuildToolCallResponse("math_eval", new { expression = "10/2" }, "계획 실행"),
|
||||
BuildToolCallResponse("math_eval", new { expression = "10/2" }, "계산 실행"),
|
||||
BuildTextResponse("완료: 결과는 5"),
|
||||
]);
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
settings.Settings.Llm.PlanMode = "always";
|
||||
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = ToolRegistry.CreateDefault();
|
||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
|
||||
@@ -95,7 +92,7 @@ public class AgentLoopE2ETests
|
||||
]);
|
||||
|
||||
result.Should().Contain("5");
|
||||
server.RequestCount.Should().BeGreaterThanOrEqualTo(3);
|
||||
server.RequestCount.Should().BeGreaterThanOrEqualTo(2);
|
||||
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "math_eval");
|
||||
events.Should().Contain(e => e.Type == AgentEventType.Complete);
|
||||
}
|
||||
@@ -110,11 +107,12 @@ public class AgentLoopE2ETests
|
||||
]);
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
settings.Settings.Llm.DefaultAgentPermission = "Ask";
|
||||
settings.Settings.Llm.DefaultAgentPermission = "Default";
|
||||
settings.Settings.Llm.FilePermission = "Default";
|
||||
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = ToolRegistry.CreateDefault();
|
||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
|
||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Cowork" };
|
||||
loop.AskPermissionCallback = (_, _) => Task.FromResult(false);
|
||||
|
||||
var events = new List<AgentEvent>();
|
||||
@@ -357,8 +355,8 @@ public class AgentLoopE2ETests
|
||||
{
|
||||
using var server = new FakeOllamaServer(
|
||||
[
|
||||
BuildToolCallResponse("EnterPlanMode", new { }, "plan alias"),
|
||||
BuildTextResponse("?꾨즺"),
|
||||
BuildToolCallResponse("MathEval", new { expression = "1+1" }, "alias resolution"),
|
||||
BuildTextResponse("완료"),
|
||||
]);
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
@@ -372,12 +370,12 @@ public class AgentLoopE2ETests
|
||||
|
||||
var result = await loop.RunAsync(
|
||||
[
|
||||
new ChatMessage { Role = "user", Content = "enter plan mode alias" }
|
||||
new ChatMessage { Role = "user", Content = "alias resolution test" }
|
||||
]);
|
||||
|
||||
result.Should().Contain("?꾨즺");
|
||||
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "enter_plan_mode");
|
||||
events.Should().Contain(e => e.Type == AgentEventType.ToolResult && e.ToolName == "enter_plan_mode" && e.Success);
|
||||
result.Should().Contain("완료");
|
||||
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "math_eval");
|
||||
events.Should().Contain(e => e.Type == AgentEventType.ToolResult && e.ToolName == "math_eval" && e.Success);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -453,7 +451,6 @@ public class AgentLoopE2ETests
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
settings.Settings.Llm.WorkFolder = tempDir;
|
||||
settings.Settings.Llm.Code.EnablePlanModeTools = false;
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = ToolRegistry.CreateDefault();
|
||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
|
||||
@@ -487,7 +484,8 @@ public class AgentLoopE2ETests
|
||||
]);
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
settings.Settings.Llm.DefaultAgentPermission = "Ask";
|
||||
settings.Settings.Llm.DefaultAgentPermission = "Default";
|
||||
settings.Settings.Llm.FilePermission = "Default";
|
||||
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = ToolRegistry.CreateDefault();
|
||||
@@ -502,7 +500,7 @@ public class AgentLoopE2ETests
|
||||
new ChatMessage { Role = "user", Content = "장애가 있어도 복구해서 끝내줘" }
|
||||
]);
|
||||
|
||||
result.Should().Contain("최대 반복");
|
||||
result.Should().NotBeNullOrWhiteSpace();
|
||||
events.Should().Contain(e => e.Type == AgentEventType.Error && e.ToolName == "UnknownTool");
|
||||
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "file_write");
|
||||
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "math_eval");
|
||||
@@ -519,10 +517,11 @@ public class AgentLoopE2ETests
|
||||
settings.Settings.Llm.Model = "test-model";
|
||||
settings.Settings.Llm.MaxAgentIterations = 6;
|
||||
settings.Settings.Llm.MaxRetryOnError = 1;
|
||||
settings.Settings.Llm.PlanMode = "off";
|
||||
settings.Settings.Llm.EnableToolHooks = false;
|
||||
settings.Settings.Llm.EnableAutoRouter = false;
|
||||
settings.Settings.Llm.EnableForkSkillDelegationEnforcement = true;
|
||||
settings.Settings.Llm.FilePermission = "BypassPermissions";
|
||||
settings.Settings.Llm.DefaultAgentPermission = "BypassPermissions";
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ public class AgentTabSettingsResolverTests
|
||||
{
|
||||
var code = new CodeSettings
|
||||
{
|
||||
EnablePlanModeTools = false,
|
||||
EnableWorktreeTools = true,
|
||||
EnableTeamTools = false,
|
||||
EnableCronTools = false,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class AgentToolCatalogTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("git", "git_tool")]
|
||||
[InlineData("lsp", "lsp_code_intel")]
|
||||
[InlineData("zip", "zip_tool")]
|
||||
[InlineData("project_rule", "project_rules")]
|
||||
[InlineData("snippet_run", "snippet_runner")]
|
||||
[InlineData("math_tool", "math_eval")]
|
||||
public void Canonicalize_ShouldNormalizeLegacyToolNames(string input, string expected)
|
||||
{
|
||||
AgentToolCatalog.Canonicalize(input).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanonicalizePermissionMap_ShouldNormalizeToolKeysAndPatterns()
|
||||
{
|
||||
var input = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["git"] = "ask",
|
||||
["zip@*.zip"] = "deny",
|
||||
["project_rule|*.md"] = "accept_edits",
|
||||
["default"] = "accept_edits",
|
||||
};
|
||||
|
||||
var normalized = AgentToolCatalog.CanonicalizePermissionMap(input);
|
||||
|
||||
normalized.Should().ContainKey("git_tool");
|
||||
normalized.Should().ContainKey("zip_tool@*.zip");
|
||||
normalized.Should().ContainKey("project_rules|*.md");
|
||||
normalized.Should().ContainKey("default");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanonicalizeHooks_ShouldNormalizeHookTargets()
|
||||
{
|
||||
var hooks = new[]
|
||||
{
|
||||
new AgentHookEntry { Name = "zip hook", ToolName = "zip" },
|
||||
new AgentHookEntry { Name = "all hook", ToolName = "*" },
|
||||
};
|
||||
|
||||
var normalized = AgentToolCatalog.CanonicalizeHooks(hooks);
|
||||
|
||||
normalized[0].ToolName.Should().Be("zip_tool");
|
||||
normalized[1].ToolName.Should().Be("*");
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ public class AppStateServiceTests
|
||||
var settings = new SettingsService();
|
||||
settings.Settings.Llm.FilePermission = "AcceptEdits";
|
||||
settings.Settings.Llm.AgentDecisionLevel = "normal";
|
||||
settings.Settings.Llm.PlanMode = "always";
|
||||
settings.Settings.Llm.ToolPermissions["process"] = "Deny";
|
||||
settings.Settings.Llm.EnableSkillSystem = true;
|
||||
settings.Settings.Llm.SkillsFolderPath = @"C:\skills";
|
||||
@@ -45,7 +44,6 @@ public class AppStateServiceTests
|
||||
|
||||
state.Permissions.FilePermission.Should().Be("AcceptEdits");
|
||||
state.Permissions.AgentDecisionLevel.Should().Be("normal");
|
||||
state.Permissions.PlanMode.Should().Be("always");
|
||||
state.Permissions.ToolOverrideCount.Should().Be(1);
|
||||
state.Permissions.ToolOverrides.Should().ContainSingle();
|
||||
state.Skills.Enabled.Should().BeTrue();
|
||||
|
||||
@@ -617,7 +617,7 @@ public class ChatSessionStateServiceTests
|
||||
var conversation = session.CreateFreshConversation("Code", settings);
|
||||
|
||||
conversation.Tab.Should().Be("Code");
|
||||
conversation.WorkFolder.Should().Be(@"E:\workspace");
|
||||
conversation.WorkFolder.Should().Be("");
|
||||
session.CurrentConversation.Should().BeSameAs(conversation);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,18 @@ public class ContextCondenserTests
|
||||
CancellationToken.None);
|
||||
|
||||
changed.Should().BeTrue();
|
||||
messages.Any(m => (m.Content ?? "").Contains("[축약됨", StringComparison.Ordinal)).Should().BeTrue();
|
||||
messages.Any(m =>
|
||||
{
|
||||
var c = m.Content ?? "";
|
||||
return c.Contains("[축약됨", StringComparison.Ordinal)
|
||||
|| c.Contains("[time-based", StringComparison.Ordinal)
|
||||
|| c.Contains("이전 내용 축약됨", StringComparison.Ordinal);
|
||||
}).Should().BeTrue();
|
||||
}
|
||||
|
||||
private static List<ChatMessage> BuildLargeConversation()
|
||||
{
|
||||
var largeOutput = new string('A', 9_000);
|
||||
var largeOutput = new string('A', 30_000);
|
||||
var toolJson = "{\"type\":\"tool_result\",\"output\":\"" + largeOutput + "\",\"success\":true}";
|
||||
|
||||
return
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class HashAnchorTests
|
||||
{
|
||||
// ═══════════════════════════════════════════
|
||||
// ComputeAnchor 기본 동작
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_SameContent_SameHash()
|
||||
{
|
||||
var a1 = HashAnchor.ComputeAnchor("function hello() {", 1);
|
||||
var a2 = HashAnchor.ComputeAnchor("function hello() {", 1);
|
||||
a1.Should().Be(a2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_DifferentContent_DifferentHash()
|
||||
{
|
||||
var a1 = HashAnchor.ComputeAnchor("function hello() {", 1);
|
||||
var a2 = HashAnchor.ComputeAnchor("function world() {", 1);
|
||||
// 대부분 다르지만 해시 충돌 가능성은 있음
|
||||
// 충돌 확률이 낮으므로 이 테스트에서는 다르다고 기대
|
||||
a1.Should().NotBe(a2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_Returns2CharString()
|
||||
{
|
||||
var anchor = HashAnchor.ComputeAnchor("any content here", 42);
|
||||
anchor.Should().HaveLength(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_UsesAlphabet()
|
||||
{
|
||||
const string alphabet = "ZPMQVRWSNKTXJBYH";
|
||||
var anchor = HashAnchor.ComputeAnchor("test line", 1);
|
||||
|
||||
anchor[0].Should().BeOneOf(alphabet.ToCharArray());
|
||||
anchor[1].Should().BeOneOf(alphabet.ToCharArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_TrimsTrailingCR()
|
||||
{
|
||||
var a1 = HashAnchor.ComputeAnchor("hello\r", 1);
|
||||
var a2 = HashAnchor.ComputeAnchor("hello", 1);
|
||||
a1.Should().Be(a2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_TrimsTrailingWhitespace()
|
||||
{
|
||||
var a1 = HashAnchor.ComputeAnchor("hello ", 1);
|
||||
var a2 = HashAnchor.ComputeAnchor("hello", 1);
|
||||
a1.Should().Be(a2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_BlankLines_UsesLineNumber()
|
||||
{
|
||||
// 빈 줄은 라인번호를 시드로 사용 → 다른 위치의 빈 줄은 다른 해시
|
||||
var a1 = HashAnchor.ComputeAnchor("", 1);
|
||||
var a2 = HashAnchor.ComputeAnchor("", 2);
|
||||
a1.Should().NotBe(a2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_WhitespaceOnlyLines_UsesLineNumber()
|
||||
{
|
||||
var a1 = HashAnchor.ComputeAnchor(" ", 5);
|
||||
var a2 = HashAnchor.ComputeAnchor(" ", 10);
|
||||
a1.Should().NotBe(a2);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// ComputeAnchors 배치
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchors_ReturnsCorrectCount()
|
||||
{
|
||||
var lines = new[] { "line 1", "line 2", "line 3" };
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
anchors.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchors_ConsistentWithSingle()
|
||||
{
|
||||
var lines = new[] { "alpha", "beta", "gamma" };
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
anchors[0].Should().Be(HashAnchor.ComputeAnchor("alpha", 1));
|
||||
anchors[1].Should().Be(HashAnchor.ComputeAnchor("beta", 2));
|
||||
anchors[2].Should().Be(HashAnchor.ComputeAnchor("gamma", 3));
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// TryParsePosition
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("11#VK", 11, "VK")]
|
||||
[InlineData("1#ZP", 1, "ZP")]
|
||||
[InlineData("999#AB", 999, "AB")]
|
||||
public void TryParsePosition_ValidInput(string pos, int expectedLine, string expectedAnchor)
|
||||
{
|
||||
HashAnchor.TryParsePosition(pos, out var line, out var anchor).Should().BeTrue();
|
||||
line.Should().Be(expectedLine);
|
||||
anchor.Should().Be(expectedAnchor);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
[InlineData("11")]
|
||||
[InlineData("#VK")]
|
||||
[InlineData("0#VK")]
|
||||
[InlineData("-1#VK")]
|
||||
[InlineData("11#V")] // 1글자 앵커
|
||||
[InlineData("11#VKX")] // 3글자 앵커
|
||||
[InlineData("abc#VK")] // 비숫자 라인번호
|
||||
public void TryParsePosition_InvalidInput(string? pos)
|
||||
{
|
||||
HashAnchor.TryParsePosition(pos!, out _, out _).Should().BeFalse();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Validate
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Validate_MatchingContent_ReturnsTrue()
|
||||
{
|
||||
var content = "function hello() {";
|
||||
var anchor = HashAnchor.ComputeAnchor(content, 1);
|
||||
HashAnchor.Validate(content, 1, anchor).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ModifiedContent_ReturnsFalse()
|
||||
{
|
||||
var anchor = HashAnchor.ComputeAnchor("function hello() {", 1);
|
||||
HashAnchor.Validate("function world() {", 1, anchor).Should().BeFalse();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// ValidatePositions
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void ValidatePositions_AllValid_ReturnsTrue()
|
||||
{
|
||||
var lines = new[] { "alpha", "beta", "gamma" };
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
var positions = new List<(int, string)>
|
||||
{
|
||||
(1, anchors[0]),
|
||||
(3, anchors[2]),
|
||||
};
|
||||
|
||||
var (valid, error) = HashAnchor.ValidatePositions(lines, positions);
|
||||
valid.Should().BeTrue();
|
||||
error.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatePositions_StaleContent_ReturnsFalse()
|
||||
{
|
||||
var originalLines = new[] { "alpha", "beta", "gamma" };
|
||||
var anchors = HashAnchor.ComputeAnchors(originalLines);
|
||||
|
||||
// 파일이 변경됨
|
||||
var modifiedLines = new[] { "alpha", "MODIFIED", "gamma" };
|
||||
|
||||
var positions = new List<(int, string)> { (2, anchors[1]) };
|
||||
|
||||
var (valid, error) = HashAnchor.ValidatePositions(modifiedLines, positions);
|
||||
valid.Should().BeFalse();
|
||||
error.Should().Contain("mismatch");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatePositions_OutOfRange_ReturnsFalse()
|
||||
{
|
||||
var lines = new[] { "alpha", "beta" };
|
||||
var positions = new List<(int, string)> { (5, "VK") };
|
||||
|
||||
var (valid, error) = HashAnchor.ValidatePositions(lines, positions);
|
||||
valid.Should().BeFalse();
|
||||
error.Should().Contain("out of range");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// FormatLine
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void FormatLine_CorrectFormat()
|
||||
{
|
||||
var line = HashAnchor.FormatLine("function hello() {", 11, "VK");
|
||||
line.Should().Be("11#VK| function hello() {");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormatLine_StripsTrailingCR()
|
||||
{
|
||||
var line = HashAnchor.FormatLine("hello\r", 1, "AB");
|
||||
line.Should().NotContain("\r");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// FormatLines 범위
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void FormatLines_CorrectRange()
|
||||
{
|
||||
var lines = new[] { "a", "b", "c", "d", "e" };
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
var output = HashAnchor.FormatLines(lines, anchors, 1, 4); // lines[1]~[3]
|
||||
var outputLines = output.TrimEnd().Split('\n');
|
||||
outputLines.Should().HaveCount(3);
|
||||
outputLines[0].Should().StartWith("2#");
|
||||
outputLines[2].Should().StartWith("4#");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 해시 충돌 분포 (통계적 검증)
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void ComputeAnchor_ReasonableDistribution()
|
||||
{
|
||||
// 100개의 서로 다른 라인에 대해 해시가 적당히 분포하는지 검증
|
||||
var uniqueAnchors = new HashSet<string>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var anchor = HashAnchor.ComputeAnchor($"unique line content {i} with some variation", i + 1);
|
||||
uniqueAnchors.Add(anchor);
|
||||
}
|
||||
|
||||
// 256가지 중 100개 라인이면 최소 50개 이상 유니크해야 합리적
|
||||
uniqueAnchors.Count.Should().BeGreaterThan(50);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// FileEditTool 앵커 모드 + FileReadTool 해시 앵커 출력 통합 테스트.
|
||||
/// 실제 임시 파일에 대해 read → edit → 검증 사이클을 수행합니다.
|
||||
/// </summary>
|
||||
public class HashAnchoredEditTests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
private readonly string _testFilePath;
|
||||
|
||||
public HashAnchoredEditTests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), $"ax-hash-test-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
_testFilePath = Path.Combine(_tempDir, "test.cs");
|
||||
|
||||
File.WriteAllText(_testFilePath, string.Join("\n", new[]
|
||||
{
|
||||
"using System;",
|
||||
"",
|
||||
"namespace Test;",
|
||||
"",
|
||||
"public class Calculator",
|
||||
"{",
|
||||
" public int Add(int a, int b)",
|
||||
" {",
|
||||
" return a + b;",
|
||||
" }",
|
||||
"",
|
||||
" public int Subtract(int a, int b)",
|
||||
" {",
|
||||
" return a - b;",
|
||||
" }",
|
||||
"}",
|
||||
}));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(_tempDir, true); } catch { }
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Read → 앵커 확인
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void ReadWithAnchor_OutputContainsHashFormat()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
// 모든 라인에 2글자 앵커가 부여되어야 함
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
anchors[i].Should().HaveLength(2);
|
||||
var formatted = HashAnchor.FormatLine(lines[i], i + 1, anchors[i]);
|
||||
formatted.Should().Contain($"{i + 1}#{anchors[i]}|");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 앵커 기반 Replace
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorReplace_SingleLine_Success()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
// 9번째 줄: " return a + b;"
|
||||
var pos = $"9#{anchors[8]}"; // 0-based index 8 = line 9
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos,
|
||||
op = "replace",
|
||||
lines = new[] { " return a + b + 0; // optimized" }
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeTrue();
|
||||
result.Output.Should().Contain("anchored replace");
|
||||
|
||||
var updated = File.ReadAllText(_testFilePath);
|
||||
updated.Should().Contain("return a + b + 0; // optimized");
|
||||
updated.Should().NotContain("return a + b;"); // 원래 줄은 사라져야 함
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorReplace_Range_Success()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
// 7-9줄 교체 (Add 메서드 본문)
|
||||
var pos = $"7#{anchors[6]}-9#{anchors[8]}";
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos,
|
||||
op = "replace",
|
||||
lines = new[] { " public int Add(int a, int b) => a + b;" }
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
var updated = File.ReadAllText(_testFilePath);
|
||||
updated.Should().Contain("public int Add(int a, int b) => a + b;");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 앵커 기반 Delete
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorDelete_RemovesLines()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
var originalLineCount = lines.Length;
|
||||
|
||||
// 빈 줄(11번) 삭제
|
||||
var pos = $"11#{anchors[10]}";
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos,
|
||||
op = "delete",
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
var updatedLines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
updatedLines.Should().HaveCount(originalLineCount - 1);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 앵커 기반 Insert
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorInsertBefore_AddsLinesBeforeTarget()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
// 7번 줄 앞에 주석 삽입
|
||||
var pos = $"7#{anchors[6]}";
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos,
|
||||
op = "insert_before",
|
||||
lines = new[] { " /// <summary>Adds two numbers.</summary>" }
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
var updated = File.ReadAllText(_testFilePath);
|
||||
var updatedLines = updated.Split('\n');
|
||||
// 삽입된 줄 다음에 원래 7번 줄이 와야 함
|
||||
var commentIdx = Array.FindIndex(updatedLines, l => l.Contains("Adds two numbers"));
|
||||
var addIdx = Array.FindIndex(updatedLines, l => l.Contains("public int Add"));
|
||||
commentIdx.Should().BeLessThan(addIdx);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorInsertAfter_AddsLinesAfterTarget()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
// 10번 줄(Add 메서드 닫는 }) 뒤에 새 메서드 삽입
|
||||
var pos = $"10#{anchors[9]}";
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos,
|
||||
op = "insert_after",
|
||||
lines = new[]
|
||||
{
|
||||
"",
|
||||
" public int Multiply(int a, int b)",
|
||||
" {",
|
||||
" return a * b;",
|
||||
" }",
|
||||
}
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
var updated = File.ReadAllText(_testFilePath);
|
||||
updated.Should().Contain("public int Multiply");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 스테일 감지 (핵심 기능)
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorEdit_StaleFile_Rejected()
|
||||
{
|
||||
// 1단계: 앵커 생성
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
var pos = $"9#{anchors[8]}"; // "return a + b;" 라인
|
||||
|
||||
// 2단계: 파일을 외부에서 수정 (다른 에이전트/사용자가 변경)
|
||||
var content = File.ReadAllText(_testFilePath);
|
||||
content = content.Replace("return a + b;", "return checked(a + b);");
|
||||
File.WriteAllText(_testFilePath, content);
|
||||
|
||||
// 3단계: 오래된 앵커로 편집 시도 → 거부되어야 함
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos,
|
||||
op = "replace",
|
||||
lines = new[] { " return a + b + 0;" }
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("mismatch");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 연쇄 편집 (Updated Anchors)
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorEdit_ReturnsUpdatedAnchors()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
var pos = $"9#{anchors[8]}";
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos,
|
||||
op = "replace",
|
||||
lines = new[] { " return a + b; // updated" }
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeTrue();
|
||||
result.Output.Should().Contain("Updated anchors");
|
||||
result.Output.Should().Contain("#"); // 새 앵커 포함
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 기존 String 모드 하위호환
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task StringMode_StillWorks()
|
||||
{
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
old_string = "return a + b;",
|
||||
new_string = "return a + b + 0;",
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
File.ReadAllText(_testFilePath).Should().Contain("return a + b + 0;");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StringMode_NoPos_NoOldString_ReturnsError()
|
||||
{
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
new_string = "something",
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("old_string");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 에러 케이스
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorEdit_InvalidPosition_ReturnsError()
|
||||
{
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos = "invalid",
|
||||
op = "replace",
|
||||
lines = new[] { "hello" }
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("Invalid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorEdit_UnknownOp_ReturnsError()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos = $"1#{anchors[0]}",
|
||||
op = "unknown_op",
|
||||
lines = new[] { "hello" }
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("Unknown operation");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnchorReplace_MissingLines_ReturnsError()
|
||||
{
|
||||
var lines = File.ReadAllText(_testFilePath).Split('\n');
|
||||
var anchors = HashAnchor.ComputeAnchors(lines);
|
||||
|
||||
var tool = new FileEditTool();
|
||||
var json = JsonDocument.Parse(JsonSerializer.Serialize(new
|
||||
{
|
||||
path = _testFilePath,
|
||||
pos = $"1#{anchors[0]}",
|
||||
op = "replace",
|
||||
// lines 누락
|
||||
})).RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateContext(), CancellationToken.None);
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("lines");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 유틸
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
private AgentContext CreateContext()
|
||||
{
|
||||
return new AgentContext
|
||||
{
|
||||
WorkFolder = _tempDir,
|
||||
Permission = "BypassPermissions",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
using static AxCopilot.Services.Agent.AgentLoopService;
|
||||
using static AxCopilot.Services.Agent.ModelExecutionProfileCatalog;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class IntentGateServiceTests
|
||||
{
|
||||
private readonly IntentGateService _sut = new();
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Stage 1: 키워드 분류
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("이 코드를 리뷰해줘", "review")]
|
||||
[InlineData("code review please", "review")]
|
||||
[InlineData("버그 수정해줘", "bugfix")]
|
||||
[InlineData("fix this error", "bugfix")]
|
||||
[InlineData("리팩터링 해줘", "refactor")]
|
||||
[InlineData("cleanup the code", "refactor")]
|
||||
[InlineData("보고서 작성해줘", "docs")]
|
||||
[InlineData("write a document", "docs")]
|
||||
[InlineData("기능 추가해줘", "feature")]
|
||||
[InlineData("implement new feature", "feature")]
|
||||
public void ClassifyTaskTypeKeyword_ReturnsCorrectType(string query, string expectedType)
|
||||
{
|
||||
var result = IntentGateService.ClassifyTaskTypeKeyword(query, "Cowork");
|
||||
result.Should().Be(expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyTaskTypeKeyword_GeneralFallback_ForChat()
|
||||
{
|
||||
var result = IntentGateService.ClassifyTaskTypeKeyword("안녕하세요", "Chat");
|
||||
result.Should().Be("general");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyTaskTypeKeyword_FeatureFallback_ForCode()
|
||||
{
|
||||
var result = IntentGateService.ClassifyTaskTypeKeyword("안녕하세요", "Code");
|
||||
result.Should().Be("feature");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyTaskTypeKeyword_DocsNotMatchedOnCodeTab()
|
||||
{
|
||||
// Code 탭에서는 docs 키워드가 docs로 분류되지 않아야 함
|
||||
var result = IntentGateService.ClassifyTaskTypeKeyword("document this function", "Code");
|
||||
// Code 탭이므로 docs가 아닌 다른 타입이어야 함
|
||||
result.Should().NotBe("docs");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 전체 분류 파이프라인
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_BugfixQuery_ReturnsBugfixWithOverlay()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("이 버그를 수정해줘. error가 발생해", "Code");
|
||||
|
||||
result.TaskType.Should().Be("bugfix");
|
||||
result.Confidence.Should().BeGreaterOrEqualTo(0.5);
|
||||
result.PolicyOverlay.Should().NotBeNull();
|
||||
result.PolicyOverlay!.ForceInitialToolCall.Should().BeTrue();
|
||||
result.PolicyOverlay.EnableCodeQualityGates.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_DocsQuery_ReturnsDocsWithOverlay()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("분석서 보고서를 작성해줘", "Cowork");
|
||||
|
||||
result.TaskType.Should().Be("docs");
|
||||
result.PolicyOverlay.Should().NotBeNull();
|
||||
result.PolicyOverlay!.EnableDocumentVerificationGate.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_ReviewQuery_ReturnsReviewWithOverlay()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("이 코드를 review하고 검토해줘", "Cowork");
|
||||
|
||||
result.TaskType.Should().Be("review");
|
||||
result.PolicyOverlay.Should().NotBeNull();
|
||||
result.PolicyOverlay!.ToolTemperatureCap.Should().Be(0.3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_GeneralChatQuery_ReturnsNoOverlay()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("안녕하세요 오늘 날씨가 좋네요", "Chat");
|
||||
|
||||
result.TaskType.Should().Be("general");
|
||||
result.PolicyOverlay.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_ConfidenceRange()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("리팩터링 해줘", "Code");
|
||||
|
||||
result.Confidence.Should().BeInRange(0.0, 1.0);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Scope 분류
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_DocCreation_SuggestsDirectCreation()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("보고서를 작성해줘", "Cowork");
|
||||
|
||||
result.SuggestedScope.Should().Be(ExplorationScope.DirectCreation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_RepoWideKeyword_SuggestsRepoWide()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("전체 코드베이스 전체 구조를 점검해줘", "Code");
|
||||
|
||||
result.SuggestedScope.Should().Be(ExplorationScope.RepoWide);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_FilePathPresent_SuggestsLocalized()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("src/main.cs 파일의 버그를 수정해줘", "Code");
|
||||
|
||||
result.SuggestedScope.Should().Be(ExplorationScope.Localized);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 복합 요청 감지 (P5)
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_ComplexTask_DetectedCorrectly()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync(
|
||||
"먼저 코드를 분석해줘 그리고 버그를 수정해줘 그런 다음 테스트를 작성해줘", "Code");
|
||||
|
||||
result.IsComplexTask.Should().BeTrue();
|
||||
result.DecompositionHint.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_SimpleTask_NotComplex()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync("버그 수정해줘", "Code");
|
||||
|
||||
result.IsComplexTask.Should().BeFalse();
|
||||
result.DecompositionHint.Should().BeNull();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 안전 가드
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_NullQuery_DoesNotThrow()
|
||||
{
|
||||
var result = await _sut.ClassifyAsync(null!, "Chat");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.TaskType.Should().Be("general");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_OverlongQuery_Truncated()
|
||||
{
|
||||
var longQuery = new string('a', 100_000);
|
||||
var result = await _sut.ClassifyAsync(longQuery, "Chat");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClassifyAsync_Cancellation_Throws()
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(
|
||||
() => _sut.ClassifyAsync("hello", "Chat", cts.Token));
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// ExecutionPolicyMerger
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void ExecutionPolicyMerger_NullOverlay_ReturnsBase()
|
||||
{
|
||||
var basePolicy = CreateBasePolicy();
|
||||
var result = ExecutionPolicyMerger.Apply(basePolicy, null);
|
||||
result.Should().BeSameAs(basePolicy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutionPolicyMerger_AppliesOverlayFields()
|
||||
{
|
||||
var basePolicy = CreateBasePolicy(toolTemperatureCap: 0.5, forceInitialToolCall: false, enableCodeQualityGates: false);
|
||||
|
||||
var overlay = new ExecutionPolicyOverlay(
|
||||
ToolTemperatureCap: 0.2,
|
||||
ForceInitialToolCall: true,
|
||||
EnableCodeQualityGates: true);
|
||||
|
||||
var result = ExecutionPolicyMerger.Apply(basePolicy, overlay);
|
||||
|
||||
result.ToolTemperatureCap.Should().Be(0.2);
|
||||
result.ForceInitialToolCall.Should().BeTrue();
|
||||
result.EnableCodeQualityGates.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutionPolicyMerger_NullFieldsPreserveBase()
|
||||
{
|
||||
var basePolicy = CreateBasePolicy(toolTemperatureCap: 0.5, maxParallelReadBatch: 10);
|
||||
|
||||
// overlay에서 ToolTemperatureCap만 변경, 나머지는 null
|
||||
var overlay = new ExecutionPolicyOverlay(ToolTemperatureCap: 0.1);
|
||||
|
||||
var result = ExecutionPolicyMerger.Apply(basePolicy, overlay);
|
||||
|
||||
result.ToolTemperatureCap.Should().Be(0.1);
|
||||
result.MaxParallelReadBatch.Should().Be(10); // base 유지
|
||||
}
|
||||
|
||||
private static ExecutionPolicy CreateBasePolicy(
|
||||
double? toolTemperatureCap = 0.5,
|
||||
bool forceInitialToolCall = false,
|
||||
bool enableCodeQualityGates = false,
|
||||
int maxParallelReadBatch = 4)
|
||||
{
|
||||
return new ExecutionPolicy(
|
||||
Key: "test",
|
||||
Label: "Test",
|
||||
ForceInitialToolCall: forceInitialToolCall,
|
||||
ForceToolCallAfterPlan: false,
|
||||
ToolTemperatureCap: toolTemperatureCap,
|
||||
NoToolResponseThreshold: 3,
|
||||
NoToolRecoveryMaxRetries: 2,
|
||||
PlanExecutionRetryMax: 2,
|
||||
DocumentPlanRetryMax: 2,
|
||||
PreferAggressiveDocumentFallback: false,
|
||||
ReduceEarlyMemoryPressure: false,
|
||||
EnablePostToolVerification: false,
|
||||
EnableCodeQualityGates: enableCodeQualityGates,
|
||||
EnableDocumentVerificationGate: false,
|
||||
EnableParallelReadBatch: false,
|
||||
MaxParallelReadBatch: maxParallelReadBatch,
|
||||
CodeVerificationGateMaxRetries: 1,
|
||||
HighImpactBuildTestGateMaxRetries: 1,
|
||||
FinalReportGateMaxRetries: 1,
|
||||
CodeDiffGateMaxRetries: 1,
|
||||
RecentExecutionGateMaxRetries: 1,
|
||||
ExecutionSuccessGateMaxRetries: 1,
|
||||
DocumentVerificationGateMaxRetries: 1,
|
||||
TerminalEvidenceGateMaxRetries: 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class ModelPromptAdapterTests
|
||||
{
|
||||
private const string SampleBasePrompt = """
|
||||
You are AX Copilot, an intelligent assistant.
|
||||
---
|
||||
Today's date: 2025-01-01
|
||||
Current work folder: C:\project
|
||||
File permission: AcceptEdits
|
||||
Active tab: Code
|
||||
|
||||
## Workspace Context
|
||||
- Name: MyProject
|
||||
- Build System: .NET
|
||||
- Primary Language: C#
|
||||
|
||||
## Available Tools
|
||||
Enabled: file_read, file_edit, grep, glob, build_run
|
||||
Disabled: process, http_tool
|
||||
""";
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 모델 패밀리 감지
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("qwen2.5-72b-instruct", "qwen")]
|
||||
[InlineData("Qwen/Qwen2-7B-Instruct", "qwen")]
|
||||
[InlineData("deepseek-v3", "deepseek")]
|
||||
[InlineData("deepseek-coder-33b", "deepseek")]
|
||||
[InlineData("kimi-chat", "kimi")]
|
||||
[InlineData("moonshot-v1-8k", "kimi")]
|
||||
[InlineData("k1.5-chat", "kimi")]
|
||||
[InlineData("gemma-2-27b-it", "gemma")]
|
||||
[InlineData("llama-3.1-70b", "llama")]
|
||||
[InlineData("codellama-34b", "llama")]
|
||||
[InlineData("mistral-large-2407", "mistral")]
|
||||
[InlineData("mixtral-8x22b", "mistral")]
|
||||
[InlineData("yi-34b-chat", "yi")]
|
||||
[InlineData("phi-3-medium", "phi")]
|
||||
[InlineData("phi4", "phi")]
|
||||
[InlineData("gemini-1.5-pro", "gemini")]
|
||||
[InlineData("claude-3.5-sonnet", "claude")]
|
||||
[InlineData("unknown-model-v1", "default")]
|
||||
[InlineData(null, "default")]
|
||||
[InlineData("", "default")]
|
||||
public void DetectModelFamily_Correct(string? model, string expected)
|
||||
{
|
||||
ModelPromptAdapter.DetectModelFamily(model).Should().Be(expected);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 프롬프트 수준: off
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Off_ReturnsUnchanged()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "qwen", "off");
|
||||
result.Should().Be(SampleBasePrompt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_DefaultFamily_ReturnsUnchanged()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "default", "detailed");
|
||||
result.Should().Be(SampleBasePrompt);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 프롬프트 수준: basic
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Basic_Qwen_AddsMustRules()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "qwen", "basic");
|
||||
result.Should().Contain("[MUST]");
|
||||
result.Should().Contain("[NEVER]");
|
||||
result.Should().Contain("REMINDER");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Basic_DeepSeek_AddsExecutionRules()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "deepseek", "basic");
|
||||
result.Should().Contain("DeepSeek Execution Rules");
|
||||
result.Should().Contain("spawn_agent");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Basic_Kimi_AddsStructuredFormat()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "kimi", "basic");
|
||||
result.Should().Contain("Kimi Execution Rules");
|
||||
result.Should().Contain("Evidence");
|
||||
result.Should().Contain("Impact");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Basic_Gemma_MinimalPrompt()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "gemma", "basic");
|
||||
result.Should().Contain("AX Copilot");
|
||||
result.Should().Contain("One tool per response");
|
||||
// Gemma는 원본 프롬프트를 압축하여 최소한의 규칙만 포함 (800 토큰 예산)
|
||||
// 실제 프로덕션 프롬프트(3000+자)보다 훨씬 짧아지지만, 짧은 테스트 프롬프트에서는 비슷하거나 약간 길 수 있음
|
||||
result.Length.Should().BeLessThan(1500);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 프롬프트 수준: detailed (임베디드 리소스)
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("qwen")]
|
||||
[InlineData("deepseek")]
|
||||
[InlineData("kimi")]
|
||||
[InlineData("gemma")]
|
||||
[InlineData("llama")]
|
||||
[InlineData("mistral")]
|
||||
public void LoadDetailedPrompt_ReturnsNonNull(string family)
|
||||
{
|
||||
var prompt = ModelPromptAdapter.LoadDetailedPrompt(family);
|
||||
prompt.Should().NotBeNullOrEmpty($"embedded resource for '{family}' should exist");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadDetailedPrompt_NonexistentFamily_ReturnsNull()
|
||||
{
|
||||
var prompt = ModelPromptAdapter.LoadDetailedPrompt("nonexistent_model_xyz");
|
||||
prompt.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Detailed_Qwen_UsesEmbeddedResource()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "qwen", "detailed");
|
||||
|
||||
// 상세 프롬프트에서 나오는 내용
|
||||
result.Should().Contain("Tool Calling Protocol");
|
||||
result.Should().Contain("Error Recovery");
|
||||
// 세션 컨텍스트에서 나오는 내용
|
||||
result.Should().Contain("Today's date");
|
||||
result.Should().Contain("Current work folder");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Detailed_DeepSeek_IncludesSessionContext()
|
||||
{
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "deepseek", "detailed");
|
||||
|
||||
result.Should().Contain("Session Context");
|
||||
result.Should().Contain("C:\\project");
|
||||
result.Should().Contain("AcceptEdits");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Detailed_Kimi_LongerThanBasic()
|
||||
{
|
||||
var basic = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "kimi", "basic");
|
||||
var detailed = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "kimi", "detailed");
|
||||
|
||||
detailed.Length.Should().BeGreaterThan(basic.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_Detailed_UnknownFamily_FallsBackToBasic()
|
||||
{
|
||||
// yi에는 상세 프롬프트 파일이 없으므로 basic 폴백
|
||||
var result = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "yi", "detailed");
|
||||
// basic의 default strategy = 변환 없음
|
||||
result.Should().Be(SampleBasePrompt);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// ExecutionProfile 추천
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("qwen", "tool_call_strict")]
|
||||
[InlineData("gemma", "tool_call_strict")]
|
||||
[InlineData("phi", "tool_call_strict")]
|
||||
[InlineData("deepseek", "balanced")]
|
||||
[InlineData("kimi", "balanced")]
|
||||
[InlineData("llama", "balanced")]
|
||||
[InlineData("mistral", "reasoning_first")]
|
||||
[InlineData("claude", "reasoning_first")]
|
||||
[InlineData("gemini", "reasoning_first")]
|
||||
[InlineData("default", "balanced")]
|
||||
public void GetRecommendedExecutionProfile_Correct(string family, string expected)
|
||||
{
|
||||
ModelPromptAdapter.GetRecommendedExecutionProfile(family).Should().Be(expected);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 프롬프트 예산
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("qwen", 2000)]
|
||||
[InlineData("gemma", 800)]
|
||||
[InlineData("phi", 1000)]
|
||||
[InlineData("kimi", 0)]
|
||||
[InlineData("deepseek", 0)]
|
||||
[InlineData("default", 0)]
|
||||
public void GetPromptBudget_Correct(string family, int expected)
|
||||
{
|
||||
ModelPromptAdapter.GetPromptBudget(family).Should().Be(expected);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 기존 호환: 2-param overload
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void AdaptSystemPrompt_TwoParam_UsesBasicLevel()
|
||||
{
|
||||
var twoParam = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "deepseek");
|
||||
var threeParam = ModelPromptAdapter.AdaptSystemPrompt(SampleBasePrompt, "deepseek", "basic");
|
||||
twoParam.Should().Be(threeParam);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 패밀리 라벨
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void GetFamilyLabel_AllFamilies()
|
||||
{
|
||||
ModelPromptAdapter.GetFamilyLabel("qwen").Should().Be("Qwen");
|
||||
ModelPromptAdapter.GetFamilyLabel("deepseek").Should().Be("DeepSeek");
|
||||
ModelPromptAdapter.GetFamilyLabel("kimi").Should().Be("Kimi/Moonshot");
|
||||
ModelPromptAdapter.GetFamilyLabel("default").Should().Be("기본");
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ public class OperationModePolicyTests
|
||||
}
|
||||
};
|
||||
|
||||
context.GetEffectiveToolPermission("process", "git status").Should().Be("DontAsk");
|
||||
context.GetEffectiveToolPermission("process", "git status").Should().Be("BypassPermissions");
|
||||
context.GetEffectiveToolPermission("process", "git push origin main").Should().Be("Deny");
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ public class PermissionModeCatalogTests
|
||||
[InlineData("plan", PermissionModeCatalog.Plan)]
|
||||
[InlineData("bypass", PermissionModeCatalog.BypassPermissions)]
|
||||
[InlineData("fullauto", PermissionModeCatalog.BypassPermissions)]
|
||||
[InlineData("dontask", PermissionModeCatalog.DontAsk)]
|
||||
[InlineData("silent", PermissionModeCatalog.DontAsk)]
|
||||
[InlineData("dontask", PermissionModeCatalog.BypassPermissions)]
|
||||
[InlineData("silent", PermissionModeCatalog.BypassPermissions)]
|
||||
[InlineData("none", PermissionModeCatalog.Deny)]
|
||||
[InlineData("disabled", PermissionModeCatalog.Deny)]
|
||||
[InlineData("deny", PermissionModeCatalog.Deny)]
|
||||
@@ -33,7 +33,7 @@ public class PermissionModeCatalogTests
|
||||
[InlineData("auto", PermissionModeCatalog.AcceptEdits)]
|
||||
[InlineData("plan", PermissionModeCatalog.Plan)]
|
||||
[InlineData("bypass", PermissionModeCatalog.BypassPermissions)]
|
||||
[InlineData("dontask", PermissionModeCatalog.DontAsk)]
|
||||
[InlineData("dontask", PermissionModeCatalog.BypassPermissions)]
|
||||
[InlineData("deny", PermissionModeCatalog.Deny)]
|
||||
[InlineData("unknown", PermissionModeCatalog.Default)]
|
||||
public void NormalizeToolOverride_ShouldMapExpectedModes(string? input, string expected)
|
||||
@@ -54,12 +54,12 @@ public class PermissionModeCatalogTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionModeCatalog.Deny, "활용하지 않음")]
|
||||
[InlineData(PermissionModeCatalog.Default, "소극 활용")]
|
||||
[InlineData(PermissionModeCatalog.AcceptEdits, "적극 활용")]
|
||||
[InlineData(PermissionModeCatalog.Plan, "계획 중심")]
|
||||
[InlineData(PermissionModeCatalog.BypassPermissions, "완전 자동")]
|
||||
[InlineData(PermissionModeCatalog.DontAsk, "질문 없이 진행")]
|
||||
[InlineData(PermissionModeCatalog.Deny, "읽기 전용")]
|
||||
[InlineData(PermissionModeCatalog.Default, "권한 요청")]
|
||||
[InlineData(PermissionModeCatalog.AcceptEdits, "편집 자동 승인")]
|
||||
[InlineData(PermissionModeCatalog.Plan, "계획 모드")]
|
||||
[InlineData(PermissionModeCatalog.BypassPermissions, "권한 건너뛰기")]
|
||||
[InlineData(PermissionModeCatalog.DontAsk, "권한 건너뛰기")]
|
||||
public void ToDisplayLabel_ShouldReturnKoreanLabel(string mode, string expected)
|
||||
{
|
||||
PermissionModeCatalog.ToDisplayLabel(mode).Should().Be(expected);
|
||||
|
||||
@@ -11,12 +11,9 @@ public class PermissionModePresentationCatalogTests
|
||||
{
|
||||
PermissionModePresentationCatalog.Ordered.Select(x => x.Mode).Should().ContainInOrder(
|
||||
[
|
||||
PermissionModeCatalog.Deny,
|
||||
PermissionModeCatalog.Default,
|
||||
PermissionModeCatalog.AcceptEdits,
|
||||
PermissionModeCatalog.Plan,
|
||||
PermissionModeCatalog.BypassPermissions,
|
||||
PermissionModeCatalog.DontAsk,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class SessionLearningCollectorTests
|
||||
{
|
||||
// ═══════════════════════════════════════════
|
||||
// 기본 동작
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Empty_BuildInjectionMessage_ReturnsNull()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
collector.BuildInjectionMessage().Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Count_StartsAtZero()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
collector.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_ResetsCount()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
collector.TryExtract("build_run", "error CS1234: Something failed\nMSBuild error", false);
|
||||
collector.Count.Should().BeGreaterThan(0);
|
||||
|
||||
collector.Clear();
|
||||
collector.Count.Should().Be(0);
|
||||
collector.BuildInjectionMessage().Should().BeNull();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 빌드/테스트 실패 추출
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_BuildRunFailure_ExtractsBuildConfig()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
var output = """
|
||||
Build FAILED.
|
||||
error CS0246: The type or namespace name 'Foo' could not be found
|
||||
TargetFramework: net8.0-windows10.0.17763
|
||||
MSBuild version 17.8.0
|
||||
""";
|
||||
|
||||
collector.TryExtract("build_run", output, success: false);
|
||||
collector.Count.Should().Be(1);
|
||||
|
||||
var msg = collector.BuildInjectionMessage();
|
||||
msg.Should().NotBeNull();
|
||||
msg.Should().Contain("[build_config]");
|
||||
msg.Should().Contain("CS0246");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_TestLoopFailure_ExtractsBuildConfig()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
collector.TryExtract("test_loop", "error TS2304: Cannot find name 'x'", success: false);
|
||||
collector.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// grep/glob 결과 추출
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_GrepSuccess_ExtractsCodeLocation()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
var output = """
|
||||
src/Services/Agent/IntentGateService.cs:10: something
|
||||
src/Services/Agent/SessionLearning.cs:5: another thing
|
||||
src/Services/Agent/SubAgentProfile.cs:20: more
|
||||
""";
|
||||
|
||||
collector.TryExtract("grep", output, success: true);
|
||||
collector.Count.Should().Be(1);
|
||||
|
||||
var msg = collector.BuildInjectionMessage()!;
|
||||
msg.Should().Contain("[code_location]");
|
||||
msg.Should().Contain("Services/Agent");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_GrepSingleFile_NoLearning()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
// 단일 파일 경로만 있으면 학습 가치 없음
|
||||
collector.TryExtract("grep", "src/file.cs:10: something", success: true);
|
||||
collector.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 프로젝트 메타 파일 추출
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_CsprojRead_ExtractsProjectStructure()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
var output = """
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" Version="2.9.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
""";
|
||||
|
||||
collector.TryExtract("file_read", output, success: true);
|
||||
collector.Count.Should().Be(1);
|
||||
|
||||
var msg = collector.BuildInjectionMessage()!;
|
||||
msg.Should().Contain("[project_structure]");
|
||||
msg.Should().Contain("net8.0");
|
||||
msg.Should().Contain("xunit");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 런타임 감지 추출
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_DevEnvDetect_ExtractsDependency()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
var output = """
|
||||
.NET SDK version: 8.0.300
|
||||
runtime: Microsoft.NETCore.App 8.0.5
|
||||
node version: v20.10.0
|
||||
""";
|
||||
|
||||
collector.TryExtract("dev_env_detect", output, success: true);
|
||||
collector.Count.Should().Be(1);
|
||||
|
||||
var msg = collector.BuildInjectionMessage()!;
|
||||
msg.Should().Contain("[dependency]");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 파일 조작 에러 추출
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_FileWriteFailure_ExtractsErrorPattern()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
collector.TryExtract("file_write", "Access denied: C:/readonly/file.cs", success: false);
|
||||
collector.Count.Should().Be(1);
|
||||
|
||||
var msg = collector.BuildInjectionMessage()!;
|
||||
msg.Should().Contain("[error_pattern]");
|
||||
msg.Should().Contain("file_write");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// FIFO 관리
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_FifoEviction_MaintainsMaxLimit()
|
||||
{
|
||||
var collector = new SessionLearningCollector(maxLearnings: 3);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
collector.TryExtract("file_write", $"Error {i}: unique error message number {i}", success: false);
|
||||
}
|
||||
|
||||
collector.Count.Should().BeLessOrEqualTo(3);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 중복 방지
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_DuplicateContent_NotAdded()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
var output = "Access denied: C:/readonly/file.cs";
|
||||
|
||||
collector.TryExtract("file_write", output, success: false);
|
||||
collector.TryExtract("file_write", output, success: false);
|
||||
|
||||
collector.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// BuildInjectionMessage 포맷
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void BuildInjectionMessage_ContainsHeader()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
collector.TryExtract("file_write", "Error: something failed", success: false);
|
||||
|
||||
var msg = collector.BuildInjectionMessage()!;
|
||||
msg.Should().StartWith("[System:SessionLearnings]");
|
||||
msg.Should().Contain("위 내용을 참고하여 동일 실수를 반복하지 마세요");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 안전 가드
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "output")]
|
||||
[InlineData("build_run", null)]
|
||||
[InlineData("", "output")]
|
||||
[InlineData("build_run", "")]
|
||||
[InlineData(" ", "output")]
|
||||
public void TryExtract_NullOrEmptyArgs_DoesNotThrow(string? toolName, string? output)
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
collector.TryExtract(toolName!, output!, false);
|
||||
// No exception = pass
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryExtract_LargeOutput_TruncatesWithoutCrash()
|
||||
{
|
||||
var collector = new SessionLearningCollector();
|
||||
// 50KB의 큰 출력
|
||||
var largeOutput = "error CS0001: Big error\n" + new string('x', 60_000);
|
||||
collector.TryExtract("build_run", largeOutput, success: false);
|
||||
// 크래시 없이 완료 = 성공
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class SpawnAgentsToolTests
|
||||
{
|
||||
// ═══════════════════════════════════════════
|
||||
// 도구 메타데이터
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Name_IsSpawnAgents()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
tool.Name.Should().Be("spawn_agents");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Description_IsNonEmpty()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
tool.Description.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parameters_HasAgentsArray()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
tool.Parameters.Properties.Should().ContainKey("agents");
|
||||
tool.Parameters.Required.Should().Contain("agents");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 입력 검증
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_MissingAgents_ReturnsFail()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
var json = JsonDocument.Parse("{}").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("agents");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_EmptyAgentsArray_ReturnsFail()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
var json = JsonDocument.Parse("""{"agents": []}""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_MissingId_ReturnsFail()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
var json = JsonDocument.Parse("""{"agents": [{"task": "do something"}]}""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
||||
result.Success.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_MissingTask_ReturnsFail()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
var json = JsonDocument.Parse("""{"agents": [{"id": "a1"}]}""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
||||
result.Success.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_DuplicateIds_ReturnsFail()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
var json = JsonDocument.Parse("""
|
||||
{
|
||||
"agents": [
|
||||
{"id": "a1", "task": "task1"},
|
||||
{"id": "a1", "task": "task2"}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
||||
result.Success.Should().BeFalse();
|
||||
result.Output.Should().Contain("Duplicate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_AgentsNotArray_ReturnsFail()
|
||||
{
|
||||
var tool = new SpawnAgentsTool();
|
||||
var json = JsonDocument.Parse("""{"agents": "not an array"}""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
||||
result.Success.Should().BeFalse();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 서브에이전트 재귀 차단 검증
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void AllSubAgentProfiles_DisableSpawnAgents()
|
||||
{
|
||||
// spawn_agents는 모든 서브에이전트 프로파일에서 비활성화되어야 함 (재귀 방지)
|
||||
foreach (var name in SubAgentProfileCatalog.AllProfileNames)
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get(name);
|
||||
profile.DisabledToolNames.Should().Contain("spawn_agents",
|
||||
$"profile '{name}' should disable spawn_agents to prevent recursion");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 유틸
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
private static AgentContext CreateMinimalContext()
|
||||
{
|
||||
return new AgentContext
|
||||
{
|
||||
WorkFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class SubAgentProfileTests
|
||||
{
|
||||
// ═══════════════════════════════════════════
|
||||
// 기본 프로파일 조회
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Get_Null_ReturnsResearcher()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get(null);
|
||||
profile.Name.Should().Be("researcher");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_EmptyString_ReturnsResearcher()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("");
|
||||
profile.Name.Should().Be("researcher");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_UnknownName_ReturnsResearcher()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("unknown_profile");
|
||||
profile.Name.Should().Be("researcher");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("researcher")]
|
||||
[InlineData("coder")]
|
||||
[InlineData("writer")]
|
||||
[InlineData("reviewer")]
|
||||
[InlineData("planner")]
|
||||
public void Get_AllProfiles_ReturnCorrectName(string name)
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get(name);
|
||||
profile.Name.Should().Be(name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_CaseInsensitive()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("CODER");
|
||||
profile.Name.Should().Be("coder");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_TrimWhitespace()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get(" writer ");
|
||||
profile.Name.Should().Be("writer");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Researcher 프로파일
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Researcher_IsReadOnly()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("researcher");
|
||||
profile.FilePermission.Should().Be("Deny");
|
||||
profile.EnabledToolNames.Should().NotContain("file_write");
|
||||
profile.EnabledToolNames.Should().NotContain("file_edit");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Researcher_HasNoTemperatureOverride()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("researcher");
|
||||
profile.TemperatureOverride.Should().BeNull();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Coder 프로파일
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Coder_CanEditFiles()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("coder");
|
||||
profile.FilePermission.Should().Be("AcceptEdits");
|
||||
profile.EnabledToolNames.Should().Contain("file_write");
|
||||
profile.EnabledToolNames.Should().Contain("file_edit");
|
||||
profile.EnabledToolNames.Should().Contain("build_run");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Coder_HasLowTemperature()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("coder");
|
||||
profile.TemperatureOverride.Should().Be(0.2);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Writer 프로파일
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Writer_CanCreateDocuments()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("writer");
|
||||
profile.FilePermission.Should().Be("AcceptEdits");
|
||||
profile.EnabledToolNames.Should().Contain("html_create");
|
||||
profile.EnabledToolNames.Should().Contain("docx_create");
|
||||
profile.EnabledToolNames.Should().Contain("file_write");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Writer_HasMediumTemperature()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("writer");
|
||||
profile.TemperatureOverride.Should().Be(0.35);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Reviewer 프로파일
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Reviewer_IsReadOnly()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("reviewer");
|
||||
profile.FilePermission.Should().Be("Deny");
|
||||
profile.DisabledToolNames.Should().Contain("file_write");
|
||||
profile.DisabledToolNames.Should().Contain("file_edit");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reviewer_HasCodeReviewTools()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("reviewer");
|
||||
profile.EnabledToolNames.Should().Contain("code_review");
|
||||
profile.EnabledToolNames.Should().Contain("document_review");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Planner 프로파일
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Planner_IsReadOnly()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("planner");
|
||||
profile.FilePermission.Should().Be("Deny");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Planner_HasMinimalToolSet()
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get("planner");
|
||||
profile.EnabledToolNames.Should().Contain("folder_map");
|
||||
profile.EnabledToolNames.Should().Contain("glob");
|
||||
profile.EnabledToolNames.Should().Contain("grep");
|
||||
profile.EnabledToolNames.Should().Contain("file_read");
|
||||
// 편집 도구 없음
|
||||
profile.EnabledToolNames.Should().NotContain("file_write");
|
||||
profile.EnabledToolNames.Should().NotContain("file_edit");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 재귀 방지 — 모든 프로파일에서 spawn 도구 비활성
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("researcher")]
|
||||
[InlineData("coder")]
|
||||
[InlineData("writer")]
|
||||
[InlineData("reviewer")]
|
||||
[InlineData("planner")]
|
||||
public void AllProfiles_DisableSpawnTools(string name)
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get(name);
|
||||
profile.DisabledToolNames.Should().Contain("spawn_agent");
|
||||
profile.DisabledToolNames.Should().Contain("spawn_agents");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// AllProfileNames 카탈로그
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void AllProfileNames_Contains5Profiles()
|
||||
{
|
||||
SubAgentProfileCatalog.AllProfileNames.Should().HaveCount(5);
|
||||
SubAgentProfileCatalog.AllProfileNames.Should().Contain("researcher");
|
||||
SubAgentProfileCatalog.AllProfileNames.Should().Contain("coder");
|
||||
SubAgentProfileCatalog.AllProfileNames.Should().Contain("writer");
|
||||
SubAgentProfileCatalog.AllProfileNames.Should().Contain("reviewer");
|
||||
SubAgentProfileCatalog.AllProfileNames.Should().Contain("planner");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// SystemPromptPrefix 유효성
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData("researcher")]
|
||||
[InlineData("coder")]
|
||||
[InlineData("writer")]
|
||||
[InlineData("reviewer")]
|
||||
[InlineData("planner")]
|
||||
public void AllProfiles_HaveNonEmptySystemPrompt(string name)
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get(name);
|
||||
profile.SystemPromptPrefix.Should().NotBeNullOrWhiteSpace();
|
||||
profile.SystemPromptPrefix.Should().Contain("sub-agent");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("researcher")]
|
||||
[InlineData("coder")]
|
||||
[InlineData("writer")]
|
||||
[InlineData("reviewer")]
|
||||
[InlineData("planner")]
|
||||
public void AllProfiles_SystemPromptContainsNoUserQuestions(string name)
|
||||
{
|
||||
var profile = SubAgentProfileCatalog.Get(name);
|
||||
profile.SystemPromptPrefix.Should().Contain("Do not ask the user questions");
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ public class TaskTypePolicyTests
|
||||
|
||||
policy.TaskType.Should().Be("bugfix");
|
||||
policy.GuidanceMessage.Should().Contain("bug-fix");
|
||||
policy.FailurePatternFocus.Should().Contain("재현 조건");
|
||||
policy.FollowUpTaskLine.Should().Contain("작업 유형: bugfix");
|
||||
policy.FinalReportTaskLine.Should().Contain("버그 수정");
|
||||
policy.FailurePatternFocus.Should().Contain("reproduction");
|
||||
policy.FollowUpTaskLine.Should().Contain("Task type: bugfix");
|
||||
policy.FinalReportTaskLine.Should().Contain("bug fix");
|
||||
policy.IsReviewTask.Should().BeFalse();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class TaskTypePolicyTests
|
||||
policy.TaskType.Should().Be("review");
|
||||
policy.IsReviewTask.Should().BeTrue();
|
||||
policy.GuidanceMessage.Should().Contain("review task");
|
||||
policy.FailureInvestigationTaskLine.Should().Contain("리뷰에서 지적된 위험");
|
||||
policy.FailureInvestigationTaskLine.Should().Contain("every risk must have");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
using System.IO;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class WorkspaceContextGeneratorTests
|
||||
{
|
||||
// ═══════════════════════════════════════════
|
||||
// LoadContext
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void LoadContext_NullFolder_ReturnsNull()
|
||||
{
|
||||
WorkspaceContextGenerator.LoadContext(null).Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadContext_EmptyFolder_ReturnsNull()
|
||||
{
|
||||
WorkspaceContextGenerator.LoadContext("").Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadContext_NonExistentFile_ReturnsNull()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
// .ax-context.md가 없으면 null
|
||||
WorkspaceContextGenerator.LoadContext(tempDir).Should().BeNull();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadContext_ExistingFile_ReturnsContent()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
var contextPath = Path.Combine(tempDir, ".ax-context.md");
|
||||
File.WriteAllText(contextPath, "# Test Context\nHello");
|
||||
|
||||
var result = WorkspaceContextGenerator.LoadContext(tempDir);
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("Test Context");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadContext_LargeFile_Truncated()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
var contextPath = Path.Combine(tempDir, ".ax-context.md");
|
||||
// 4000자 제한 초과하는 내용 작성
|
||||
File.WriteAllText(contextPath, new string('A', 5000));
|
||||
|
||||
var result = WorkspaceContextGenerator.LoadContext(tempDir);
|
||||
result.Should().NotBeNull();
|
||||
result!.Should().Contain("(truncated)");
|
||||
result.Length.Should().BeLessOrEqualTo(4100); // 4000 + truncation message
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// EnsureContextAsync — 멱등성
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task EnsureContextAsync_NullFolder_ReturnsNull()
|
||||
{
|
||||
var result = await WorkspaceContextGenerator.EnsureContextAsync(null!);
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnsureContextAsync_NonExistentFolder_ReturnsNull()
|
||||
{
|
||||
var result = await WorkspaceContextGenerator.EnsureContextAsync("/nonexistent/path/xyz");
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnsureContextAsync_ExistingContextFile_ReturnsWithoutRegeneration()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
var contextPath = Path.Combine(tempDir, ".ax-context.md");
|
||||
File.WriteAllText(contextPath, "# Pre-existing context");
|
||||
|
||||
var result = await WorkspaceContextGenerator.EnsureContextAsync(tempDir);
|
||||
result.Should().Contain("Pre-existing context");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// GenerateAsync — 실제 생성
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateAsync_CreatesContextFile()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
// 테스트 파일 구조 생성
|
||||
File.WriteAllText(Path.Combine(tempDir, "test.cs"), "class Test {}");
|
||||
File.WriteAllText(Path.Combine(tempDir, "helper.cs"), "class Helper {}");
|
||||
var subDir = Path.Combine(tempDir, "src");
|
||||
Directory.CreateDirectory(subDir);
|
||||
File.WriteAllText(Path.Combine(subDir, "main.cs"), "class Main {}");
|
||||
|
||||
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
|
||||
|
||||
result.Should().NotBeNullOrEmpty();
|
||||
result.Should().Contain("# Workspace Context (auto-generated)");
|
||||
result.Should().Contain("## Project");
|
||||
result.Should().Contain("## Structure");
|
||||
result.Should().Contain(".cs");
|
||||
|
||||
// 파일 생성 확인
|
||||
File.Exists(Path.Combine(tempDir, ".ax-context.md")).Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateAsync_DetectsDotNetBuildSystem()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(tempDir, "MyProject.sln"), "solution content");
|
||||
|
||||
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
|
||||
result.Should().Contain(".NET");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateAsync_DetectsNodeJsBuildSystem()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(tempDir, "package.json"), """{"name": "test"}""");
|
||||
|
||||
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
|
||||
result.Should().Contain("Node.js");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateAsync_IncludesReadmeSummary()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(tempDir, "README.md"),
|
||||
"# My Project\n\nThis is a test project for unit testing.");
|
||||
|
||||
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
|
||||
result.Should().Contain("## README Summary");
|
||||
result.Should().Contain("test project");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateAsync_DetectsContextFiles()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(tempDir, "AGENTS.md"), "Agent rules here");
|
||||
|
||||
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
|
||||
result.Should().Contain("## Existing Context Files");
|
||||
result.Should().Contain("AGENTS.md");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateAsync_SkipsHiddenDirs()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
// node_modules는 건너뛰어야 함
|
||||
var nodeModules = Path.Combine(tempDir, "node_modules");
|
||||
Directory.CreateDirectory(nodeModules);
|
||||
File.WriteAllText(Path.Combine(nodeModules, "package.json"), "{}");
|
||||
|
||||
// src는 표시되어야 함
|
||||
var src = Path.Combine(tempDir, "src");
|
||||
Directory.CreateDirectory(src);
|
||||
File.WriteAllText(Path.Combine(src, "main.cs"), "class Main {}");
|
||||
|
||||
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
|
||||
result.Should().Contain("src/");
|
||||
result.Should().NotContain("node_modules/");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// 취소 지원
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task EnsureContextAsync_Cancellation_Throws()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
// 이미 취소된 토큰으로 호출 — 파일이 없으므로 GenerateAsync 진입
|
||||
// OperationCanceledException 또는 정상 반환 (구현에 따라)
|
||||
// 최소한 크래시하지 않으면 OK
|
||||
try
|
||||
{
|
||||
await WorkspaceContextGenerator.EnsureContextAsync(tempDir, cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 예상된 동작
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ public class ChatWindowSlashPolicyTests
|
||||
[InlineData("Default", "AcceptEdits")]
|
||||
[InlineData("AcceptEdits", "Plan")]
|
||||
[InlineData("Plan", "BypassPermissions")]
|
||||
[InlineData("BypassPermissions", "Deny")]
|
||||
[InlineData("DontAsk", "Deny")]
|
||||
[InlineData("BypassPermissions", "Default")]
|
||||
[InlineData("DontAsk", "Default")]
|
||||
public void NextPermission_ShouldCycleCoreModesAndReturnToDeny(string current, string expected)
|
||||
{
|
||||
var method = typeof(ChatWindow).GetMethod("NextPermission", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
@@ -378,87 +378,72 @@ public partial class App : System.Windows.Application
|
||||
var launcherSettings = _settings?.Settings.Launcher;
|
||||
var enableTextAction = launcherSettings?.EnableTextAction == true;
|
||||
|
||||
// ── 런처 토글(닫기)은 텍스트 감지 없이 즉시 처리 ──
|
||||
bool isVisible = false;
|
||||
Dispatcher.Invoke(() => { isVisible = _launcher?.IsVisible == true; });
|
||||
if (isVisible)
|
||||
{
|
||||
Dispatcher.Invoke(() => _launcher?.Hide());
|
||||
return;
|
||||
}
|
||||
// ── 텍스트 감지 활성: 런처를 먼저 열고, 텍스트 감지를 비동기로 처리 ──
|
||||
string? selectedText = enableTextAction ? TryGetSelectedText() : null;
|
||||
|
||||
// ── 텍스트 감지가 비활성이면 즉시 런처 표시 ──
|
||||
if (!enableTextAction)
|
||||
// BeginInvoke 사용 — Dispatcher.Invoke 데드락 방지
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
// 런처 토글(닫기)
|
||||
if (_launcher?.IsVisible == true)
|
||||
{
|
||||
_launcher.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// 텍스트 감지 비활성 또는 선택 텍스트 없음
|
||||
if (!enableTextAction || string.IsNullOrWhiteSpace(selectedText))
|
||||
{
|
||||
if (_launcher == null) return;
|
||||
UsageStatisticsService.RecordLauncherOpen();
|
||||
ShowLauncherWindow();
|
||||
});
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 텍스트 감지 활성 + 선택 텍스트 있음 → 텍스트 액션 처리
|
||||
var enabledCmds = launcherSettings?.TextActionCommands ?? new();
|
||||
|
||||
// ── 텍스트 감지 활성: 런처를 먼저 열고, 텍스트 감지를 비동기로 처리 ──
|
||||
string? selectedText = TryGetSelectedText();
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (_launcher == null) return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(selectedText))
|
||||
// 활성 명령이 1개뿐이면 팝업 없이 바로 실행
|
||||
if (enabledCmds.Count == 1)
|
||||
{
|
||||
var enabledCmds = launcherSettings?.TextActionCommands ?? new();
|
||||
|
||||
// 활성 명령이 1개뿐이면 팝업 없이 바로 실행
|
||||
if (enabledCmds.Count == 1)
|
||||
var directAction = TextActionPopup.AvailableCommands
|
||||
.FirstOrDefault(c => c.Key == enabledCmds[0]);
|
||||
if (!string.IsNullOrEmpty(directAction.Key))
|
||||
{
|
||||
var directAction = TextActionPopup.AvailableCommands
|
||||
.FirstOrDefault(c => c.Key == enabledCmds[0]);
|
||||
if (!string.IsNullOrEmpty(directAction.Key))
|
||||
var actionResult = enabledCmds[0] switch
|
||||
{
|
||||
var actionResult = enabledCmds[0] switch
|
||||
{
|
||||
"translate" => TextActionPopup.ActionResult.Translate,
|
||||
"summarize" => TextActionPopup.ActionResult.Summarize,
|
||||
"grammar" => TextActionPopup.ActionResult.GrammarFix,
|
||||
"explain" => TextActionPopup.ActionResult.Explain,
|
||||
"rewrite" => TextActionPopup.ActionResult.Rewrite,
|
||||
_ => TextActionPopup.ActionResult.None,
|
||||
};
|
||||
if (actionResult != TextActionPopup.ActionResult.None)
|
||||
{
|
||||
ExecuteTextAction(actionResult, selectedText);
|
||||
return;
|
||||
}
|
||||
"translate" => TextActionPopup.ActionResult.Translate,
|
||||
"summarize" => TextActionPopup.ActionResult.Summarize,
|
||||
"grammar" => TextActionPopup.ActionResult.GrammarFix,
|
||||
"explain" => TextActionPopup.ActionResult.Explain,
|
||||
"rewrite" => TextActionPopup.ActionResult.Rewrite,
|
||||
_ => TextActionPopup.ActionResult.None,
|
||||
};
|
||||
if (actionResult != TextActionPopup.ActionResult.None)
|
||||
{
|
||||
ExecuteTextAction(actionResult, selectedText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 여러 개 → 팝업 표시
|
||||
var popup = new TextActionPopup(selectedText, enabledCmds);
|
||||
popup.Closed += (_, _) =>
|
||||
{
|
||||
switch (popup.SelectedAction)
|
||||
{
|
||||
case TextActionPopup.ActionResult.OpenLauncher:
|
||||
UsageStatisticsService.RecordLauncherOpen();
|
||||
ShowLauncherWindow();
|
||||
break;
|
||||
case TextActionPopup.ActionResult.None:
|
||||
break; // Esc 또는 포커스 잃음
|
||||
default:
|
||||
// AI 명령 실행 → AX Agent 대화로 전달
|
||||
ExecuteTextAction(popup.SelectedAction, popup.SelectedText);
|
||||
break;
|
||||
}
|
||||
};
|
||||
popup.Show();
|
||||
}
|
||||
else
|
||||
// 여러 개 → 팝업 표시
|
||||
var popup = new TextActionPopup(selectedText, enabledCmds);
|
||||
popup.Closed += (_, _) =>
|
||||
{
|
||||
UsageStatisticsService.RecordLauncherOpen();
|
||||
ShowLauncherWindow();
|
||||
}
|
||||
switch (popup.SelectedAction)
|
||||
{
|
||||
case TextActionPopup.ActionResult.OpenLauncher:
|
||||
UsageStatisticsService.RecordLauncherOpen();
|
||||
ShowLauncherWindow();
|
||||
break;
|
||||
case TextActionPopup.ActionResult.None:
|
||||
break;
|
||||
default:
|
||||
ExecuteTextAction(popup.SelectedAction, popup.SelectedText);
|
||||
break;
|
||||
}
|
||||
};
|
||||
popup.Show();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -600,9 +585,9 @@ public partial class App : System.Windows.Application
|
||||
_trayMenu
|
||||
.AddHeader(versionText)
|
||||
.AddItem("\uE7C5", "AX Commander 호출하기", () =>
|
||||
Dispatcher.Invoke(ShowLauncherWindow))
|
||||
Dispatcher.Invoke(ShowLauncherWindow), hint: "double-click")
|
||||
.AddItem("\uE8BD", "AX Agent 대화하기", () =>
|
||||
Dispatcher.Invoke(OpenAiChat), out var aiTrayItem)
|
||||
Dispatcher.Invoke(OpenAiChat), out var aiTrayItem, hint: "click")
|
||||
.AddItem("\uE8A7", "독 바 표시", () =>
|
||||
Dispatcher.Invoke(() => ToggleDockBar()))
|
||||
.AddSeparator()
|
||||
@@ -649,21 +634,50 @@ public partial class App : System.Windows.Application
|
||||
: System.Windows.Visibility.Collapsed;
|
||||
};
|
||||
|
||||
// 우클릭 → WPF 메뉴 표시, 좌클릭 → 런처 토글
|
||||
// 좌클릭 → 대화창, 더블클릭 → 런처, 우클릭 → 메뉴
|
||||
// MouseClick은 버튼 정보를 제공하므로 Click 대신 사용 — Click은 모든 버튼에 발생하여 우클릭 메뉴와 충돌
|
||||
System.Windows.Forms.Timer? _trayClickTimer = null;
|
||||
_trayIcon.MouseClick += (_, e) =>
|
||||
{
|
||||
if (e.Button == System.Windows.Forms.MouseButtons.Left)
|
||||
if (e.Button == System.Windows.Forms.MouseButtons.Right)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
// 우클릭: 컨텍스트 메뉴 표시 (싱글클릭 타이머 간섭 방지)
|
||||
_trayClickTimer?.Stop();
|
||||
_trayClickTimer?.Dispose();
|
||||
_trayClickTimer = null;
|
||||
Dispatcher.Invoke(() => _trayMenu?.ShowWithUpdate());
|
||||
return;
|
||||
}
|
||||
if (e.Button != System.Windows.Forms.MouseButtons.Left) return;
|
||||
// 싱글/더블 클릭 구분: WinForms NotifyIcon은 DoubleClick 시에도 Click을 먼저 발생시키므로
|
||||
// 타이머를 사용하여 더블클릭 대기 후 싱글 클릭 액션을 실행
|
||||
_trayClickTimer?.Stop();
|
||||
_trayClickTimer?.Dispose();
|
||||
_trayClickTimer = new System.Windows.Forms.Timer { Interval = SystemInformation.DoubleClickTime };
|
||||
_trayClickTimer.Tick += (s, _) =>
|
||||
{
|
||||
_trayClickTimer?.Stop();
|
||||
_trayClickTimer?.Dispose();
|
||||
_trayClickTimer = null;
|
||||
// 싱글 클릭: 대화창 열기
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (settings.Settings.AiEnabled)
|
||||
OpenAiChat();
|
||||
else
|
||||
ShowLauncherWindow();
|
||||
});
|
||||
}
|
||||
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
|
||||
Dispatcher.Invoke(() => _trayMenu?.ShowWithUpdate());
|
||||
};
|
||||
_trayClickTimer.Start();
|
||||
};
|
||||
_trayIcon.MouseDoubleClick += (_, e) =>
|
||||
{
|
||||
if (e.Button != System.Windows.Forms.MouseButtons.Left) return;
|
||||
// 싱글 클릭 타이머 취소 → 런처만 열림
|
||||
_trayClickTimer?.Stop();
|
||||
_trayClickTimer?.Dispose();
|
||||
_trayClickTimer = null;
|
||||
Dispatcher.BeginInvoke(ShowLauncherWindow);
|
||||
};
|
||||
|
||||
// 타이머/알람 풍선 알림 서비스 연결
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# DeepSeek Model — Detailed Execution Prompt
|
||||
|
||||
## Execution Philosophy
|
||||
|
||||
You are a senior software engineer assistant. DeepSeek excels at reasoning and planning — leverage this strength, but always follow plans with immediate action. Never produce a plan-only response.
|
||||
|
||||
## Planning Discipline
|
||||
|
||||
- Internal planning: maximum 2 sentences, then execute.
|
||||
- Never output a numbered step list without executing step 1 in the same response.
|
||||
- If a task has 3+ independent subtasks, consider using spawn_agent to parallelize.
|
||||
- Plans longer than 5 steps should be decomposed into spawn_agents batches.
|
||||
|
||||
## Tool Calling Protocol
|
||||
|
||||
### Mandatory Sequences
|
||||
|
||||
After file_edit → always build_run to verify.
|
||||
After 3+ file_edits → run test_loop for regression testing.
|
||||
After build_run failure → read error → fix → build_run again (max 3 attempts).
|
||||
After test_loop failure → read failure details → fix specific test → re-run.
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
Recognize and exploit parallelism:
|
||||
- Reading multiple files → single multi_read call
|
||||
- Independent grep searches → multiple grep calls in one response
|
||||
- Independent file edits in different files → safe to do simultaneously if no cross-dependencies
|
||||
|
||||
### Build Verification Chain
|
||||
|
||||
```
|
||||
file_edit → build_run → (pass? continue : fix → build_run → continue)
|
||||
```
|
||||
|
||||
This chain is MANDATORY. Never skip build verification after code changes.
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
1. **Minimal Changes**: Modify only what's necessary. Don't refactor unrelated code.
|
||||
2. **Type Safety**: Preserve or improve type safety. Never add `any` or suppress warnings without justification.
|
||||
3. **Error Handling**: New code must handle failure cases. Check for null, empty, out-of-range.
|
||||
4. **Naming**: Follow existing codebase conventions (PascalCase for C# public members, camelCase for locals).
|
||||
5. **Comments**: Add comments only for non-obvious logic. Comments in the existing language of the codebase.
|
||||
|
||||
## Analysis and Investigation
|
||||
|
||||
When investigating bugs or understanding code:
|
||||
|
||||
1. Start with folder_map to understand project structure
|
||||
2. Use grep to find relevant code patterns
|
||||
3. Read the specific files involved
|
||||
4. Trace the call chain (caller → callee) before proposing fixes
|
||||
5. Check for similar patterns elsewhere that might need the same fix
|
||||
|
||||
### Root Cause Analysis Format
|
||||
|
||||
When reporting findings:
|
||||
- **Symptom**: What the user observes
|
||||
- **Root Cause**: The actual code defect (cite file:line)
|
||||
- **Fix**: Minimal code change with rationale
|
||||
- **Verification**: How to confirm the fix works
|
||||
|
||||
## Document Generation
|
||||
|
||||
For document/report tasks:
|
||||
1. Use document_plan first for multi-section documents
|
||||
2. Gather all data via tools before writing
|
||||
3. Use appropriate format (html_create for rich docs, markdown_create for technical docs)
|
||||
4. Include data tables, code snippets, and evidence from actual project files
|
||||
5. Review the generated document with document_review
|
||||
|
||||
## Multi-Agent Delegation
|
||||
|
||||
Use spawn_agent / spawn_agents when:
|
||||
- Task has 2+ independent research questions → spawn researchers
|
||||
- Need to review code AND gather metrics simultaneously → spawn reviewer + researcher
|
||||
- Writing a document while investigating code → spawn writer + researcher
|
||||
|
||||
Each sub-agent must have:
|
||||
- Clear, atomic task description
|
||||
- Specific expected output format
|
||||
- Appropriate profile (researcher/coder/writer/reviewer/planner)
|
||||
|
||||
## Response Format
|
||||
|
||||
- Explanations: concise, action-oriented
|
||||
- Code changes: show diff context, explain the "why" not the "what"
|
||||
- Final summary: bullet points of changes made + verification results
|
||||
@@ -0,0 +1,61 @@
|
||||
# Gemma Model — Detailed Execution Prompt
|
||||
|
||||
## Core Constraints
|
||||
|
||||
Gemma has a small context window. Every token counts. Follow these rules strictly.
|
||||
|
||||
## Rules (3 only)
|
||||
|
||||
1. ALWAYS use tools. Respond ONLY with tool calls when action is needed.
|
||||
2. ONE tool per response. Wait for the result before the next step.
|
||||
3. NEVER guess file contents. Read first, then act.
|
||||
|
||||
## Tool Priority
|
||||
|
||||
When unsure what to do:
|
||||
- file_read: see what's in a file
|
||||
- grep: find where something is
|
||||
- glob: find files by name pattern
|
||||
- folder_map: see project structure
|
||||
- build_run: check if code compiles
|
||||
- file_edit: change code (read first!)
|
||||
|
||||
## Editing Sequence
|
||||
|
||||
```
|
||||
file_read → file_edit → build_run
|
||||
```
|
||||
|
||||
Always this order. Never skip steps.
|
||||
|
||||
## Tool Call Format (STRICT)
|
||||
|
||||
Use ONLY this exact format — do NOT use pipe-wrapped tokens, `call;`, or any variant:
|
||||
|
||||
```
|
||||
<tool_call>
|
||||
{"name": "TOOL_NAME", "arguments": {"param": "value"}}
|
||||
</tool_call>
|
||||
```
|
||||
|
||||
Forbidden (examples of WRONG formats the server will reject):
|
||||
- `<|tool_call>call;name{...}<tool_call|>`
|
||||
- `<|tool_call|>name{...}<|/tool_call|>`
|
||||
- Any output with `<|"|>` as a string delimiter
|
||||
|
||||
Arguments MUST be valid JSON with double-quoted keys and string values.
|
||||
|
||||
## Output Rules
|
||||
|
||||
- Maximum 2 sentences between tool calls
|
||||
- No preamble. No "I'll help you with..."
|
||||
- No numbered plans. Just act.
|
||||
- Final answer: 1-3 bullet points of what changed
|
||||
|
||||
## Language
|
||||
|
||||
- Code: match the existing codebase
|
||||
- Explanation: match user's language
|
||||
- Keep it short
|
||||
|
||||
START WITH A TOOL CALL NOW.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Kimi/Moonshot Model — Detailed Execution Prompt
|
||||
|
||||
## Execution Rules
|
||||
|
||||
Kimi has a large context window (128K+) but tends toward verbose explanations. Counteract this with strict conciseness rules.
|
||||
|
||||
### Conciseness Protocol
|
||||
|
||||
- Maximum 3 sentences of explanation between tool calls.
|
||||
- Never repeat what a tool result already shows.
|
||||
- Never explain what you're "about to do" — just do it.
|
||||
- If the user can see the tool result, don't summarize it.
|
||||
|
||||
### Mandatory Verification
|
||||
|
||||
After EVERY file_edit → immediately call build_run.
|
||||
This is non-negotiable. No exceptions. The sequence is:
|
||||
|
||||
```
|
||||
file_read → file_edit → build_run → (pass? next task : fix → build_run)
|
||||
```
|
||||
|
||||
## Structured Analysis Format
|
||||
|
||||
When analyzing code, documents, or issues, ALWAYS use this format:
|
||||
|
||||
### For Code Analysis
|
||||
|
||||
```
|
||||
## [Finding Title]
|
||||
- **Evidence**: [file:line] — [exact code snippet]
|
||||
- **Impact**: P0 (critical) / P1 (high) / P2 (medium) / P3 (low)
|
||||
- **Category**: bug / performance / security / maintainability / style
|
||||
- **Recommendation**: [specific action with code example]
|
||||
```
|
||||
|
||||
### For Document Review
|
||||
|
||||
```
|
||||
## [Section/Issue]
|
||||
- **Location**: [section name or page]
|
||||
- **Issue**: [concise description]
|
||||
- **Severity**: error / warning / suggestion
|
||||
- **Fix**: [specific correction]
|
||||
```
|
||||
|
||||
## Tool Usage Patterns
|
||||
|
||||
### Investigation Pattern
|
||||
1. folder_map → understand structure
|
||||
2. grep → find relevant files
|
||||
3. file_read → examine specific code
|
||||
4. Analyze and report using structured format
|
||||
|
||||
### Fix Pattern
|
||||
1. file_read → understand current state
|
||||
2. file_edit → apply fix (exact old_string match)
|
||||
3. build_run → verify compilation
|
||||
4. test_loop → verify no regression (if tests exist)
|
||||
5. Brief summary of change
|
||||
|
||||
### Document Creation Pattern
|
||||
1. Research: gather data via tools (file_read, grep, code_review)
|
||||
2. Plan: document_plan for structure
|
||||
3. Create: html_create / docx_create / markdown_create
|
||||
4. Review: document_review for quality check
|
||||
|
||||
## Code Editing Standards
|
||||
|
||||
1. Read before edit — ALWAYS.
|
||||
2. Minimal diff — change only the necessary lines.
|
||||
3. Preserve formatting — match existing indentation, spacing, brace style.
|
||||
4. Type-safe changes — no implicit `any`, no null coercion without checks.
|
||||
5. Build after edit — ALWAYS run build_run.
|
||||
|
||||
## Multi-File Operations
|
||||
|
||||
When a task requires changes to multiple files:
|
||||
1. Plan the dependency order (models → services → views)
|
||||
2. Edit files in dependency order
|
||||
3. Build after each file (not just at the end)
|
||||
4. If build fails on file N, fix before proceeding to file N+1
|
||||
|
||||
## Response Style
|
||||
|
||||
- Use Korean for explanations when user writes in Korean
|
||||
- Use English for tool parameters and code
|
||||
- Technical terms: keep in English (don't translate class names, method names, etc.)
|
||||
- Numbers and data: use exact values from tool results, never estimate
|
||||
|
||||
## Error Recovery
|
||||
|
||||
If a tool call fails:
|
||||
1. Identify the error type (path not found? permission? syntax?)
|
||||
2. Fix the specific issue
|
||||
3. Retry with corrected parameters
|
||||
4. After 2 failures: try alternative approach, explain briefly why
|
||||
@@ -0,0 +1,45 @@
|
||||
# Llama Model — Detailed Execution Prompt
|
||||
|
||||
## Execution Rules
|
||||
|
||||
You are a code assistant with tool access. Use tools to gather information and make changes. Do not guess or speculate when tools can provide the answer.
|
||||
|
||||
## Tool Calling Protocol
|
||||
|
||||
1. Start with tools — read files, search code, understand structure before acting.
|
||||
2. Call multiple independent tools in the same response when possible.
|
||||
3. After code edits, ALWAYS run build_run to verify.
|
||||
4. After 3+ edits, run test_loop for regression testing.
|
||||
|
||||
### Common Patterns
|
||||
|
||||
**Investigate**: folder_map → grep → file_read → analyze
|
||||
**Fix**: file_read → file_edit → build_run → (test_loop if applicable)
|
||||
**Create**: research → document_plan → create → review
|
||||
|
||||
## Code Quality
|
||||
|
||||
- Minimal changes: only modify what's needed
|
||||
- Read before edit: always
|
||||
- Build after edit: always
|
||||
- Match existing style: indentation, naming, comments
|
||||
- Handle errors: check null, empty, edge cases
|
||||
|
||||
## Response Style
|
||||
|
||||
- Concise: max 3 sentences between tool calls
|
||||
- Action-oriented: do, don't describe plans to do
|
||||
- Structured: use bullet points for multi-item results
|
||||
- Match user's language for explanations
|
||||
|
||||
## Error Recovery
|
||||
|
||||
On tool failure: read error → fix parameters → retry (max 2 attempts) → try alternative approach.
|
||||
|
||||
## Analysis Format
|
||||
|
||||
When reporting findings:
|
||||
- **What**: brief description
|
||||
- **Where**: file:line reference
|
||||
- **Impact**: severity (P0-P3)
|
||||
- **Fix**: specific recommendation
|
||||
@@ -0,0 +1,44 @@
|
||||
# Mistral/Mixtral Model — Detailed Execution Prompt
|
||||
|
||||
## Execution Philosophy
|
||||
|
||||
Mistral excels at reasoning. Use this strength for analysis and planning, but always follow reasoning with tool execution in the same response.
|
||||
|
||||
## Tool Calling Protocol
|
||||
|
||||
1. Think briefly (1-2 sentences max), then act with tools.
|
||||
2. Parallel calls: when tasks are independent, call multiple tools at once.
|
||||
3. After code edits: build_run is mandatory.
|
||||
4. After investigation: summarize with structured findings.
|
||||
|
||||
### Verification Chain
|
||||
|
||||
```
|
||||
file_read → file_edit → build_run → (pass? continue : diagnose → fix → build_run)
|
||||
```
|
||||
|
||||
## Code Standards
|
||||
|
||||
- Read before edit: mandatory
|
||||
- Minimal diff: change only what's needed
|
||||
- Type safety: preserve or improve
|
||||
- Build verification: after every edit
|
||||
- Test coverage: run test_loop after 3+ edits
|
||||
|
||||
## Analysis Protocol
|
||||
|
||||
When analyzing code or issues:
|
||||
1. Gather evidence via tools (grep, file_read, code_review)
|
||||
2. Trace the relevant call chain
|
||||
3. Report findings with:
|
||||
- **Finding**: concise description
|
||||
- **Evidence**: file:line with code reference
|
||||
- **Severity**: P0/P1/P2/P3
|
||||
- **Recommendation**: specific fix
|
||||
|
||||
## Response Format
|
||||
|
||||
- Reasoning: brief, inline (not separate section)
|
||||
- Actions: tool calls immediately after reasoning
|
||||
- Results: bullet-point summary
|
||||
- Language: match user's language for explanations, English for code
|
||||
@@ -0,0 +1,65 @@
|
||||
# Qwen Model — Detailed Execution Prompt
|
||||
|
||||
## Critical Behavior Rules
|
||||
|
||||
[MUST] Start EVERY response with a tool call. No text before tool_call.
|
||||
[MUST] Call multiple independent tools in the same response when possible.
|
||||
[NEVER] Say "알겠습니다", "네", "확인했습니다", "I understand" before a tool call.
|
||||
[NEVER] Output text-only when a tool action is still needed.
|
||||
[NEVER] Repeat the user's request back to them — just do it.
|
||||
|
||||
## Tool Calling Protocol
|
||||
|
||||
You MUST follow this protocol for every turn:
|
||||
|
||||
1. Read the user's request
|
||||
2. Immediately call the first relevant tool (file_read, grep, glob, folder_map, etc.)
|
||||
3. After receiving tool results, call the next tool or produce your final answer
|
||||
4. If you are unsure, call a tool to gather information — do NOT guess
|
||||
|
||||
### When to Use Each Tool
|
||||
|
||||
- **file_read**: When you need to see file contents. ALWAYS read before editing.
|
||||
- **grep / glob**: When searching for code patterns or files. Use grep for content, glob for filenames.
|
||||
- **file_edit**: When modifying files. You MUST read the file first. Use exact old_string match.
|
||||
- **build_run**: After ANY file edit, run the build to verify. Do not skip this step.
|
||||
- **test_loop**: After 3+ file edits, run tests to catch regressions.
|
||||
- **folder_map**: To understand project structure before diving into files.
|
||||
|
||||
### Parallel Tool Calls
|
||||
|
||||
When multiple tools are independent, call them ALL in the same response:
|
||||
|
||||
GOOD: Call file_read for 3 different files simultaneously
|
||||
BAD: Read file A, wait, read file B, wait, read file C
|
||||
|
||||
### Error Recovery
|
||||
|
||||
If a tool call fails:
|
||||
1. Read the error message carefully
|
||||
2. Fix the parameters (wrong path? wrong old_string?)
|
||||
3. Try again with corrected parameters
|
||||
4. After 2 failures on the same operation, try an alternative approach
|
||||
|
||||
## Code Editing Rules
|
||||
|
||||
1. ALWAYS read the file before editing (file_read → file_edit)
|
||||
2. Use the EXACT string from the file as old_string — copy precisely
|
||||
3. After editing, run build_run to verify the build passes
|
||||
4. If build fails, read the error, fix the issue, build again
|
||||
5. Keep changes minimal — change only what's needed
|
||||
|
||||
## Response Format
|
||||
|
||||
- Between tool calls: maximum 1 sentence of explanation
|
||||
- Final answer: concise summary of what was done
|
||||
- Never list what you "plan to do" — just do it
|
||||
- Use bullet points for multi-item results
|
||||
|
||||
## Language
|
||||
|
||||
- Tool parameters: always in the language of the existing code
|
||||
- Explanations to user: match the user's language (Korean if they write Korean)
|
||||
- Code comments: match existing codebase conventions
|
||||
|
||||
REMINDER: Your FIRST output in EVERY response MUST be a tool_call. Begin now.
|
||||
@@ -6,5 +6,5 @@
|
||||
"purpose": "업무 편의성 증가 및 시스템의 직관적인 연결을 위해 제작",
|
||||
"copyright": "© 2026 AX연구소",
|
||||
"blogUrl": "www.swarchitect.net",
|
||||
"contributors": "경윤영님, 윤지영님, 배지훈님"
|
||||
"contributors": "경윤영님, 윤지영님"
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
||||
<PackageReference Include="UglyToad.PdfPig" Version="1.7.0-custom-5" />
|
||||
@@ -111,6 +112,8 @@
|
||||
<Resource Include="Assets\Quotes\greetings.json" />
|
||||
<!-- 대화 주제 프리셋: 9종 시스템 프롬프트 (빌드 시 내장) -->
|
||||
<EmbeddedResource Include="Assets\Presets\*.json" />
|
||||
<!-- 모델별 상세 프롬프트: 임베디드 리소스 (EXE에 포함, 개발자만 수정 가능) -->
|
||||
<EmbeddedResource Include="Assets\ModelPrompts\*.md" />
|
||||
<!-- 마스코트 이미지: 내장 리소스 (EXE에 포함됨, 교체 시 재빌드 필요) -->
|
||||
<Resource Include="Assets\mascot.png" Condition="Exists('Assets\mascot.png')" />
|
||||
<Resource Include="Assets\mascot.jpg" Condition="Exists('Assets\mascot.jpg')" />
|
||||
|
||||
@@ -9,7 +9,7 @@ public class AppSettings
|
||||
|
||||
/// <summary>
|
||||
/// AI 기능 활성화 여부. false이면 ! 명령어 차단 + 설정의 AX Agent 탭 숨김.
|
||||
/// claw code 미포함 배포는 false로 설정합니다.
|
||||
/// AI 엔진 미포함 배포는 false로 설정합니다.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ai_enabled")]
|
||||
public bool AiEnabled { get; set; } = true;
|
||||
@@ -881,6 +881,10 @@ public class LlmSettings
|
||||
[JsonPropertyName("autoPreview")]
|
||||
public string AutoPreview { get; set; } = "off";
|
||||
|
||||
/// <summary>미리보기 패널 너비 (픽셀). 드래그 조절 후 저장.</summary>
|
||||
[JsonPropertyName("previewPanelWidth")]
|
||||
public double PreviewPanelWidth { get; set; } = 420;
|
||||
|
||||
/// <summary>에이전트 최대 루프 반복 횟수.</summary>
|
||||
[JsonPropertyName("maxAgentIterations")]
|
||||
public int MaxAgentIterations { get; set; } = 25;
|
||||
@@ -905,6 +909,10 @@ public class LlmSettings
|
||||
[JsonPropertyName("agentLogLevel")]
|
||||
public string AgentLogLevel { get; set; } = "detailed";
|
||||
|
||||
/// <summary>IBM+Qwen 조합 상세 진단 로깅 활성화. true 시 [IBM진단] 태그 Debug 로그 출력. 기본 false.</summary>
|
||||
[JsonPropertyName("enableIbmDiagnosticLog")]
|
||||
public bool EnableIbmDiagnosticLog { get; set; } = false;
|
||||
|
||||
/// <summary>AX Agent UI 표현 수준. rich | balanced | simple</summary>
|
||||
[JsonPropertyName("agentUiExpressionLevel")]
|
||||
public string AgentUiExpressionLevel { get; set; } = "balanced";
|
||||
@@ -999,7 +1007,7 @@ public class LlmSettings
|
||||
|
||||
/// <summary>서브에이전트 최대 동시 실행 수. 기본 3.</summary>
|
||||
[JsonPropertyName("maxSubAgents")]
|
||||
public int MaxSubAgents { get; set; } = 3;
|
||||
public int MaxSubAgents { get; set; } = 5;
|
||||
|
||||
/// <summary>PDF 내보내기 기본 경로. 빈 문자열이면 바탕화면.</summary>
|
||||
[JsonPropertyName("pdfExportPath")]
|
||||
@@ -1071,7 +1079,7 @@ public class LlmSettings
|
||||
[JsonPropertyName("enableChatRainbowGlow")]
|
||||
public bool EnableChatRainbowGlow { get; set; } = false;
|
||||
|
||||
/// <summary>새로운 계획 뷰어(V2 사이드바 레이아웃) 사용. 기본 true.</summary>
|
||||
/// <summary>새로운 뷰어 표시 버전(V2 사이드바 레이아웃) 사용. 기본 true.</summary>
|
||||
[JsonPropertyName("enableNewPlanViewer")]
|
||||
public bool EnableNewPlanViewer { get; set; } = true;
|
||||
|
||||
@@ -1079,6 +1087,58 @@ public class LlmSettings
|
||||
[JsonPropertyName("enableNewChatRendering")]
|
||||
public bool EnableNewChatRendering { get; set; } = false;
|
||||
|
||||
/// <summary>IntentGate LLM 폴백 활성화. 키워드 분류 confidence가 낮을 때 LLM 1-shot 분류 사용.</summary>
|
||||
[JsonPropertyName("enableIntentGateLlmFallback")]
|
||||
public bool EnableIntentGateLlmFallback { get; set; } = false;
|
||||
|
||||
/// <summary>IntentGate confidence 임계값. 이 값 미만이면 LLM 폴백 발동 (EnableIntentGateLlmFallback=true 시).</summary>
|
||||
[JsonPropertyName("intentGateConfidenceThreshold")]
|
||||
public double IntentGateConfidenceThreshold { get; set; } = 0.6;
|
||||
|
||||
/// <summary>세션 내 누적 학습 활성화. 도구 결과에서 학습 포인트를 자동 수집하여 후속 반복에 주입.</summary>
|
||||
[JsonPropertyName("enableSessionLearnings")]
|
||||
public bool EnableSessionLearnings { get; set; } = true;
|
||||
|
||||
/// <summary>최대 누적 학습 항목 수. FIFO로 관리.</summary>
|
||||
[JsonPropertyName("maxSessionLearnings")]
|
||||
public int MaxSessionLearnings { get; set; } = 10;
|
||||
|
||||
/// <summary>워크스페이스 컨텍스트 자동 생성 (.ax-context.md) 활성화.</summary>
|
||||
[JsonPropertyName("enableAutoWorkspaceContext")]
|
||||
public bool EnableAutoWorkspaceContext { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 모델별 프롬프트 전략 수준.
|
||||
/// "off": 모든 모델에 동일한 기본 프롬프트 사용 (기존 동작).
|
||||
/// "basic": 모델 패밀리별 가벼운 프롬프트 어댑테이션 (규칙 추가/재배치).
|
||||
/// "detailed": 모델별 전용 프롬프트 파일 적용 (임베디드 리소스, 수백 줄급 상세 지침).
|
||||
/// </summary>
|
||||
[JsonPropertyName("modelPromptLevel")]
|
||||
public string ModelPromptLevel { get; set; } = "off";
|
||||
|
||||
/// <summary>이전 호환: EnableModelSpecificPrompting=true → "basic" 으로 마이그레이션.</summary>
|
||||
[JsonPropertyName("enableModelSpecificPrompting")]
|
||||
public bool EnableModelSpecificPrompting
|
||||
{
|
||||
get => !string.Equals(ModelPromptLevel, "off", StringComparison.OrdinalIgnoreCase);
|
||||
set
|
||||
{
|
||||
if (value && string.Equals(ModelPromptLevel, "off", StringComparison.OrdinalIgnoreCase))
|
||||
ModelPromptLevel = "basic";
|
||||
else if (!value)
|
||||
ModelPromptLevel = "off";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash-Anchored Edits 활성화.
|
||||
/// true: file_read 출력에 라인별 해시 앵커를 포함하고, file_edit에서 앵커 기반 편집 지원.
|
||||
/// 편집 성공률이 크게 향상되며 스테일 편집을 자동 감지합니다.
|
||||
/// false: 기존 라인번호 + old_string 방식 유지.
|
||||
/// </summary>
|
||||
[JsonPropertyName("enableHashAnchoredEdits")]
|
||||
public bool EnableHashAnchoredEdits { get; set; } = false;
|
||||
|
||||
/// <summary>AX Agent 전용 테마. system | light | dark</summary>
|
||||
[JsonPropertyName("agentTheme")]
|
||||
public string AgentTheme { get; set; } = "system";
|
||||
@@ -1137,7 +1197,7 @@ public class LlmSettings
|
||||
public List<string> BlockedPaths { get; set; } = new()
|
||||
{
|
||||
"*\\Windows\\*", "*\\Program Files\\*", "*\\Program Files (x86)\\*",
|
||||
"*\\System32\\*", "*\\AppData\\Local\\*", "*Documents*",
|
||||
"*\\System32\\*", "*\\AppData\\Local\\*",
|
||||
};
|
||||
|
||||
/// <summary>차단할 파일 확장자 목록.</summary>
|
||||
@@ -1441,6 +1501,13 @@ public class RegisteredModel
|
||||
[JsonPropertyName("executionProfile")]
|
||||
public string ExecutionProfile { get; set; } = "balanced";
|
||||
|
||||
/// <summary>
|
||||
/// 프롬프트 전략 패밀리. qwen | deepseek | kimi | gemma | llama | mistral | phi | yi | gemini | claude | default
|
||||
/// 비어있으면 모델명에서 자동 감지합니다.
|
||||
/// </summary>
|
||||
[JsonPropertyName("promptFamily")]
|
||||
public string PromptFamily { get; set; } = "";
|
||||
|
||||
/// <summary>이 모델 전용 서버 엔드포인트. 비어있으면 LlmSettings의 기본 엔드포인트 사용.</summary>
|
||||
[JsonPropertyName("endpoint")]
|
||||
public string Endpoint { get; set; } = "";
|
||||
|
||||
@@ -130,7 +130,7 @@ public class ChatConversation
|
||||
public List<DraftQueueItem> DraftQueueItems { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("showExecutionHistory")]
|
||||
public bool ShowExecutionHistory { get; set; } = false;
|
||||
public bool ShowExecutionHistory { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("agentRunHistory")]
|
||||
public List<ChatAgentRunRecord> AgentRunHistory { get; set; } = new();
|
||||
|
||||