Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
998f0c9fd5 | ||
|
|
d5dbaa6e4a | ||
|
|
e07b6dbed0 | ||
|
|
c6e5abfa50 | ||
|
|
1ec529ed1c | ||
|
|
2e1c7be8c3 | ||
|
|
f0f1f76f48 | ||
|
|
0f64bf3f84 | ||
|
|
eb884e9263 | ||
|
|
e2278eec24 | ||
|
|
db4ccd5df4 | ||
|
|
13061fa3ca | ||
|
|
5161e46ac2 | ||
|
|
6810fb1954 | ||
|
|
4980113b99 | ||
|
|
16e136107c | ||
|
|
e2553a10ae | ||
|
|
82e58bde57 | ||
|
|
2f74780367 | ||
|
|
b9fffbe2b2 | ||
|
|
76afb9d5c8 | ||
|
|
f8067a1f9b | ||
|
|
2c48ca211a | ||
|
|
d6a8ab0ddb | ||
|
|
35e0d0dbbf | ||
|
|
41228ae82e | ||
|
|
96e4f80edf | ||
|
|
3210440767 | ||
|
|
5f4a52929b | ||
|
|
939e594de8 | ||
|
|
9ad587d230 | ||
|
|
bea9335ec0 | ||
|
|
22261579d0 | ||
|
|
f3717cda21 | ||
|
|
8721a0d8c7 | ||
|
|
f173e2a63b | ||
|
|
913b42b2f3 | ||
|
|
5ab04bc53e | ||
|
|
53838a046b | ||
|
|
232d5457d5 | ||
|
|
e823ff83e3 | ||
|
|
9344cf83d6 | ||
|
|
964e40718f | ||
|
|
78b3aa2801 | ||
|
|
f4351aa0eb | ||
|
|
8baeabbb70 | ||
|
|
90f92ccee5 | ||
|
|
b055138b4a | ||
|
|
675bdc9595 | ||
|
|
791d172850 | ||
|
|
99990b9778 | ||
|
|
d3b6b1a936 | ||
|
|
b260008663 | ||
|
|
d58cf2c093 | ||
|
|
4403dc3fc3 | ||
|
|
f3a31e97b1 | ||
|
|
5e40204e80 | ||
|
|
717d0f2143 | ||
|
|
2a49b1da24 | ||
|
|
93c3c647d9 | ||
|
|
f283662d30 | ||
|
|
48e8c57cf3 | ||
|
|
91c4dc74c3 | ||
|
|
2c1926356a | ||
|
|
06540a0e71 | ||
|
|
8c0aa98408 | ||
|
|
bcb3cc4039 | ||
|
|
baafd8280c | ||
|
|
8530ec956a | ||
|
|
ff29a83039 | ||
|
|
918d62b8d5 | ||
|
|
6b3e5e6797 | ||
|
|
7c138f8ed9 | ||
|
|
07fd2267cb | ||
|
|
f33ee7f7db | ||
|
|
59ec4a1371 | ||
|
|
e1f6caf11a | ||
|
|
116c420bf6 | ||
|
|
2e36f2fef1 | ||
|
|
1edeffa206 | ||
|
|
3232db1b12 | ||
|
|
5607f6391e | ||
|
|
1ad5eea32e | ||
|
|
8571a83ed0 | ||
|
|
6c7fba9dff | ||
|
|
d9cb02f3c4 | ||
|
|
0b6d60e959 | ||
|
|
946c31e275 | ||
|
|
3747a92c12 | ||
|
|
4746d2834b | ||
|
|
77495e6ac1 | ||
|
|
ac37311e41 | ||
|
|
8cf025e14d | ||
|
|
1a9b3c4528 | ||
|
|
b17c865c4e | ||
|
|
8cb08576d5 | ||
|
|
fa33b98f7e | ||
|
|
7e774a9387 | ||
|
|
ecff76d9ac |
@@ -208,6 +208,13 @@ if (!enabled) return ToolResult.Ok("비활성 상태입니다. 설정에서 활
|
|||||||
- 모든 변경 후 `dotnet build` 실행 → **경고 0, 오류 0** 필수
|
- 모든 변경 후 `dotnet build` 실행 → **경고 0, 오류 0** 필수
|
||||||
- CS8603 (nullable) 경고 즉시 수정
|
- CS8603 (nullable) 경고 즉시 수정
|
||||||
|
|
||||||
|
### 인코딩 / 주석 기준
|
||||||
|
- **코드 파일 내부 주석은 예외 없이 영어로만 작성**합니다. 새 주석, TODO, XML doc summary, inline comment 모두 동일하게 적용합니다.
|
||||||
|
- 한글 주석 또는 혼합 언어 주석이 필요한 설명이 있더라도, **코드 파일 안에서는 영어 주석으로 변환**하여 작성합니다. 사용자 안내나 설계 설명이 길게 필요하면 `.md` 문서로 분리합니다.
|
||||||
|
- 인코딩 문제가 의심되는 파일을 수정할 때는 **깨진 문자열(mojibake), 깨진 주석, 깨진 프롬프트 조각을 그대로 두지 말고 영어로 정리**합니다.
|
||||||
|
- 특히 `system prompt`, `agent status`, `session memory`, `tool diagnostics`처럼 LLM 컨텍스트에 직접 들어가는 코드 상수 문자열은 깨진 상태로 유지하지 않습니다. 인코딩 오류가 재발하면 해당 코드 파일의 손상된 문자열을 우선 영어로 치환합니다.
|
||||||
|
- 코드 리뷰/수정 시 인코딩 이상 여부를 함께 확인하고, **주석 때문에 인코딩 리스크가 높다고 판단되면 기존 주석도 영어로 정리**합니다.
|
||||||
|
|
||||||
### 성능/실행속도 우선 원칙
|
### 성능/실행속도 우선 원칙
|
||||||
- 기능 구현 시 가능하면 **개발 단계부터 최적화와 실행 속도**를 함께 고려합니다.
|
- 기능 구현 시 가능하면 **개발 단계부터 최적화와 실행 속도**를 함께 고려합니다.
|
||||||
- 동일 품질을 만족하는 구현안이 여러 개라면, **더 가볍고 빠르게 동작하는 구조**를 우선 채택합니다.
|
- 동일 품질을 만족하는 구현안이 여러 개라면, **더 가볍고 빠르게 동작하는 구조**를 우선 채택합니다.
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$Restore,
|
||||||
|
[switch]$Clean,
|
||||||
|
[switch]$StopRunningApp
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$project = Join-Path $root "src\AxCopilot\AxCopilot.csproj"
|
||||||
|
$assetsFile = Join-Path $root "src\AxCopilot\obj\project.assets.json"
|
||||||
|
$configuration = "Release"
|
||||||
|
$runtime = "win-x64"
|
||||||
|
$targetFramework = "net8.0-windows10.0.17763.0"
|
||||||
|
$output = Join-Path $root "src\AxCopilot\bin\$configuration\$targetFramework\$runtime"
|
||||||
|
|
||||||
|
function Invoke-Dotnet {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string[]]$Arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host ("dotnet " + ($Arguments -join " ")) -ForegroundColor DarkGray
|
||||||
|
& dotnet @Arguments
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "dotnet command failed with exit code $LASTEXITCODE."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host " AX Copilot - Quick Build (Windows)" -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
if (-not (Test-Path $project)) {
|
||||||
|
throw "Project file not found: $project"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($StopRunningApp) {
|
||||||
|
$running = Get-Process -Name "AxCopilot" -ErrorAction SilentlyContinue
|
||||||
|
if ($running) {
|
||||||
|
Write-Host "[0/3] Stopping running AxCopilot.exe..." -ForegroundColor Yellow
|
||||||
|
$running | Stop-Process -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Clean) {
|
||||||
|
Write-Host "[1/3] Cleaning previous quick-build output..." -ForegroundColor Yellow
|
||||||
|
Invoke-Dotnet -Arguments @(
|
||||||
|
"clean", $project,
|
||||||
|
"-c", $configuration,
|
||||||
|
"-r", $runtime,
|
||||||
|
"--nologo",
|
||||||
|
"-v", "minimal"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$shouldRestore = $Restore -or -not (Test-Path $assetsFile)
|
||||||
|
if ($shouldRestore) {
|
||||||
|
Write-Host "[2/3] Restoring NuGet packages..." -ForegroundColor Yellow
|
||||||
|
Invoke-Dotnet -Arguments @(
|
||||||
|
"restore", $project,
|
||||||
|
"-r", $runtime,
|
||||||
|
"--nologo",
|
||||||
|
"-v", "minimal"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[3/3] Building AxCopilot only (no installer, no publish)..." -ForegroundColor Yellow
|
||||||
|
$buildArgs = @(
|
||||||
|
"build", $project,
|
||||||
|
"-c", $configuration,
|
||||||
|
"-r", $runtime,
|
||||||
|
"--nologo",
|
||||||
|
"-v", "minimal",
|
||||||
|
"-m",
|
||||||
|
"-p:RunAnalyzersDuringBuild=false"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $shouldRestore) {
|
||||||
|
$buildArgs += "--no-restore"
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-Dotnet -Arguments $buildArgs
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host " Quick Build Complete" -ForegroundColor Green
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host ("Output: " + $output) -ForegroundColor Green
|
||||||
|
Write-Host "Skipped: installer, dist packaging, obfuscation, publish bundle" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Examples:" -ForegroundColor Cyan
|
||||||
|
Write-Host " .\build-quick.ps1"
|
||||||
|
Write-Host " .\build-quick.ps1 -StopRunningApp"
|
||||||
|
Write-Host " .\build-quick.ps1 -Restore -Clean"
|
||||||
@@ -162,35 +162,35 @@ del /q "%~1\*.runtimeconfig.json" 2>nul
|
|||||||
exit /b 0
|
exit /b 0
|
||||||
|
|
||||||
:fail_dist
|
:fail_dist
|
||||||
echo [FAILED] dist ??????밴쉐 ??쎈솭
|
echo [FAILED] dist 폴더 생성 실패
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
:fail_app
|
:fail_app
|
||||||
echo [FAILED] main app publish ??쎈솭
|
echo [FAILED] main app publish 실패
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
:fail_obfuscation
|
:fail_obfuscation
|
||||||
echo [FAILED] obfuscation ??m???쎈솭
|
echo [FAILED] obfuscation 단계 실패
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
:fail_encryptor
|
:fail_encryptor
|
||||||
echo [FAILED] AxKeyEncryptor publish ??쎈솭
|
echo [FAILED] AxKeyEncryptor publish 실패
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
:fail_payload
|
:fail_payload
|
||||||
echo [FAILED] payload.zip ??밴쉐 ??쎈솭
|
echo [FAILED] payload.zip 생성 실패
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
:fail_installer
|
:fail_installer
|
||||||
echo [FAILED] installer build ??쎈솭
|
echo [FAILED] installer build 실패
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
:fail_installer_copy
|
:fail_installer_copy
|
||||||
echo [FAILED] installer exe 癰귣벊沅???쎈솭
|
echo [FAILED] installer exe 복사 실패
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
:fail_running
|
:fail_running
|
||||||
echo [FAILED] running AX Copilot process could not be stopped cleanly
|
echo [FAILED] 실행 중인 AX Copilot 프로세스를 종료할 수 없습니다
|
||||||
goto :end_fail
|
goto :end_fail
|
||||||
|
|
||||||
: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 @@
|
|||||||
[역할] 당신은 회사 업무의 도움을 주는 매우 유능한 비서입니다. 말투는 정중하게 하며, 욕을 해서는 안됩니다.
|
|
||||||
@@ -1,106 +1,46 @@
|
|||||||
# AX Agent 로드맵 (전면 재작성)
|
# AX Agent 로드맵
|
||||||
|
|
||||||
## 1. 보존 이력 (요약만 유지)
|
업데이트: 2026-04-15 18:20 (KST)
|
||||||
- v0.5.0: MCP 연동, 모델 폴백, 대화 분기, 프리뷰/토큰 관리, 에이전트 루프 강화.
|
|
||||||
- v0.5.1: 고급 스킬 확장, 워크플로우 분석기, 런타임 감지.
|
|
||||||
- v0.6.0: 멀티패스 문서 생성, 데이터 피벗/템플릿/이미지 분석 계열 도구 확장.
|
|
||||||
- v0.7.0: Plan Mode, 병렬 도구 실행, 프로젝트 규칙 주입, 테스트 루프 자동화.
|
|
||||||
- v0.7.1: 도구 실행 후 검증 게이트 강화, 피드백 컨텍스트 개선.
|
|
||||||
- v0.7.2: 컨텍스트 압축/복원 안정화, UX/성능 보완.
|
|
||||||
|
|
||||||
## 2. 재작성 기준
|
## 1. 목표
|
||||||
- 기준 레퍼런스: `claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src`.
|
- AX Agent의 실행 신뢰성, 세션 내구성, 결과 품질을 단계적으로 높입니다.
|
||||||
- 목표: 동작 순서, 예외/검증 흐름, 세션 내구성에서 동일 품질 달성.
|
- 비교·모사 표현 대신 실제 참조 모듈 경로와 AX 적용 위치만 문서에 남깁니다.
|
||||||
- 원칙: AX 코드 스타일로 재구성(표현/구조 변경), 기능적 동등성 유지.
|
- 변경 우선순위는 `상태 -> 실행 -> 렌더 -> UX` 순서로 유지합니다.
|
||||||
|
|
||||||
## 3. 2026 실행 계획 (v0.7.3 ~ v0.8.0)
|
## 2. 보존 이력
|
||||||
|
- v0.5.x: MCP, 모델 폴백, 대화 분기, 프리뷰/토큰 관리, 에이전트 루프 기반 구축
|
||||||
|
- v0.6.0: 문서 생성, 데이터 피벗, 템플릿/이미지 분석 도구군 확장
|
||||||
|
- v0.7.0 ~ v0.7.2: Plan Mode, 병렬 도구 실행, 검증 게이트, 컨텍스트 압축 안정화
|
||||||
|
|
||||||
| Phase | 참조 대상 (`claw-code`) | AX 적용 위치 | 완료 조건 | 품질 판정 시나리오 |
|
## 3. 2026 실행 계획
|
||||||
|
|
||||||
|
| Phase | 참조 대상 | 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 내 권한 판정이 즉시 반영되는지 검증. |
|
| 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` | `AgentHookRunner.cs`, `AgentLoopService.cs`, `AppSettings.cs`, `SettingsViewModel.cs`, `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 상태와 최근 실행 이력이 일치하는지 검증. |
|
| B (v0.7.4) Plan/Run 상태 내구성 강화 | `src/utils/plans.ts`, `src/utils/sessionStorage.ts` | `ChatSessionStateService.cs`, `TaskRunService.cs`, `TaskRunStore.cs`, `ChatWindow.xaml.cs` | plan 승인/수정 이력 영속화, 재시작 후 마지막 run 지점 복원, 중복 이벤트 0건 | 실행 중 종료 후 재실행 시 같은 탭/대화에서 run 상태와 최근 이력이 일치하는지 검증 |
|
||||||
| C (v0.7.5) 세션/이벤트 로그 정규화 | `src/utils/sessionStorage.ts`(이벤트 기록/복원 규약), `src/utils/hooks/hookEvents.ts` | `src/AxCopilot/Models/ChatModels.cs`, `src/AxCopilot/Services/ChatSessionStateService.cs`, `src/AxCopilot/Services/AppStateService.cs` | 이벤트 스키마 정리(도구 호출/결과/권한/훅/요약). replay 시 순서 불일치 0건. run 단위 요약 리포트 생성 가능 상태. | 동일 세션을 여러 번 로드해도 실행 이력 순서/상태가 변하지 않는지 검증. |
|
| C (v0.7.5) 세션/이벤트 로그 정규화 | `src/utils/sessionStorage.ts`, `src/utils/hooks/hookEvents.ts` | `ChatModels.cs`, `ChatSessionStateService.cs`, `AppStateService.cs` | 이벤트 스키마 정리, replay 순서 불일치 0건, run 단위 요약 리포트 생성 가능 | 같은 세션을 여러 번 로드해도 실행 이력 순서와 상태가 변하지 않는지 검증 |
|
||||||
| D (v0.7.6) 명령/도구 선택 정확도 개선 | `src/commands.ts`, `src/Tool.ts` | `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Services/Agent/AgentLoopParallelExecution.cs`, `src/AxCopilot/Services/LlmService.ToolUse.cs` | 도구 별칭 정규화 규칙 강화. unknown-tool 재시도 루프 방지. 실패 유형별 복구 흐름 표준화. | 오타/별칭/비활성 도구 요청 시 복구 프롬프트로 정상 도구로 수렴하는지 검증. |
|
| D (v0.7.6) 명령/도구 선택 정확도 개선 | `src/commands.ts`, `src/Tool.ts` | `AgentLoopService.cs`, `AgentLoopParallelExecution.cs`, `LlmService.ToolUse.cs` | 도구 별칭 정규화 강화, unknown-tool 재시도 루프 방지, 실패 유형별 복구 표준화 | 오타/별칭/비활성 도구 요청이 정상 도구로 수렴하는지 검증 |
|
||||||
| E (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` | 코드/문서 작업별 완료 게이트를 증거 기반으로 고정. 내부 벤치마크 목표 성공률 충족. 완료 응답 근거 누락률 최소화. | 코드수정/문서생성/권한거부/실패복구 혼합 시나리오에서 조기 완료 없이 근거 기반 종료하는지 검증. |
|
| E (v0.8.0) 품질 게이트 정렬 | `src/query.ts`, `src/QueryEngine.ts`, `src/utils/sessionStorage.ts` | `AgentLoopService.cs`, `AgentLoopTransitions.Execution.cs`, `AgentLoopTransitions.cs` | 코드/문서 작업 완료 게이트를 증거 기반으로 고정, 조기 완료 방지 | 코드수정/문서생성/권한거부/복구 혼합 시나리오에서 근거 기반 종료를 검증 |
|
||||||
|
|
||||||
## 4. 공통 검증 규칙
|
## 4. 공통 검증 규칙
|
||||||
- 모든 단계: `dotnet build` 경고 0/오류 0.
|
- 모든 단계에서 `dotnet build` 경고 0 / 오류 0
|
||||||
- 고위험 변경: 최소 1개 이상 재현 테스트 추가.
|
- 고위험 변경마다 최소 1개 이상 재현 테스트 추가
|
||||||
- 운영모드(`internal`/`external`) 보안 정책 위반 0건.
|
- `internal` / `external` 운영 모드 정책 위반 0건
|
||||||
|
|
||||||
## 5. 즉시 착수 항목
|
## 5. 즉시 착수 항목
|
||||||
1. 내부 벤치마크(기본 loop/복구/권한/hook) 결과를 릴리즈 체크리스트에 연동 유지.
|
1. 내부 벤치마크 결과를 릴리즈 체크리스트에 계속 연결
|
||||||
2. 세션 replay 안정성(`Suite=ReplayStability`) 전건 통과를 릴리즈 게이트로 운영.
|
2. `Suite=ReplayStability` 전건 통과를 릴리즈 게이트로 유지
|
||||||
3. 패리티 수치(테스트 통과 수/게이트 상태)를 로드맵 문서 간 동일 문구로 유지.
|
3. 계획/실행/검증 문서 간 수치와 상태 문구를 같은 기준으로 동기화
|
||||||
|
|
||||||
## 6. 최신 검증 스냅샷 (2026-04-03)
|
## 6. 최신 검증 스냅샷
|
||||||
- `dotnet test --filter "Suite=ParityBenchmark"`: 13/13 통과.
|
- 기준 시점: 2026-04-03
|
||||||
- `dotnet test --filter "Suite=ReplayStability"`: 14/14 통과.
|
- `dotnet test --filter "Suite=ParityBenchmark"`: 13/13 통과
|
||||||
- `dotnet test`: 379/379 통과.
|
- `dotnet test --filter "Suite=ReplayStability"`: 14/14 통과
|
||||||
|
- `dotnet test`: 379/379 통과
|
||||||
|
|
||||||
## 7. 명령/도구 보강 스냅샷 (2026-04-04)
|
## 7. 최근 보강 메모
|
||||||
- 슬래시 명령 고도화: `/chrome`, `/mcp`, `/verify`, `/commit`, `/settings`, `/permissions` 하위 동작 정리.
|
- 2026-04-04: `/chrome`, `/mcp`, `/verify`, `/commit`, `/settings`, `/permissions` 하위 동작 정리
|
||||||
- `/mcp` 상태 라벨 표준화: `Connected`, `NeedsAuth`, `Configured`, `Disconnected`, `Disabled`.
|
- 2026-04-04: `/mcp` 상태 라벨 표준화 및 `/chrome` 런타임 재시도 경로 보강
|
||||||
- `/chrome` 런타임 재시도: 초기 probe 실패 시 `/mcp reconnect all` 자동 수행 후 1회 재평가.
|
- 2026-04-04: 전용 AX Agent 테마 레이어 추가, 스타일/모드 분리
|
||||||
- Git 정책 정렬: `git_tool`의 `commit` 비활성 문구 제거(로컬 커밋 경로와 정책 일치).
|
- 2026-04-04: 컴포저 컨텍스트 사용량 링, footer 직접 `/compact`, Git 상태 노출 보강
|
||||||
- `/commit` 부분 커밋 지원: `files:path1,path2 :: 메시지` 형식으로 선택 파일만 stage+commit 가능.
|
- 2026-04-04: 권한 UX, popup row 언어, 진행 이력 렌더 밀도를 더 가볍게 정리
|
||||||
- 테스트 보강:
|
|
||||||
- `ChatWindowSlashPolicyTests`: 슬래시 파서/검증 프롬프트/MCP 상태 라벨 단위 검증 추가.
|
|
||||||
- `AgentParityToolsTests`: `git_tool commit` 레거시 비활성 메시지 회귀 방지 테스트 추가.
|
|
||||||
|
|
||||||
## 7. 권한 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`는 가능한 경로에서 메시지 컨텍스트로 반영.
|
|
||||||
|
|
||||||
- 2026-04-04(추가): `/mcp add/remove/reset` 확장, `tool_search` 기반 복구 프롬프트 강화, 슬래시 힌트 밀도(`rich/balanced/simple`) 연동.
|
|
||||||
- 2026-04-04(추가2): /mcp login/logout 세션 인증 토큰 지원, /mcp status·/chrome 진단에 Auth(Session) 반영.
|
|
||||||
- 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 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.
|
|
||||||
- 2026-04-04 18:03 (KST): Expanded the Git branch badge into a dropdown panel, flattened permission/template popups into row-based lists, and made AI message action bars remain visible for the selected message.
|
|
||||||
- 2026-04-04 18:03 (KST): Flattened the data-usage popup, added Git branch switching/creation inside the dropdown, and surfaced quick model chips so the inline model panel relies less on ComboBox controls.
|
|
||||||
- 2026-04-04 18:03 (KST): Replaced the visible inline model/service selectors with custom cards and row lists so the AX Agent quick panel more closely follows the Codex/claude-code control feel.
|
|
||||||
- 2026-04-04 18:03 (KST): Added recent/favorite grouping to the preset popup and enabled right-click pinning so the preset picker better matches Codex-style frequent-use navigation.
|
|
||||||
- 2026-04-04 18:03 (KST): Added preset search, made selected messages visually persistent, and flattened more of the permission detail popup into row-based information to keep the panel lighter.
|
|
||||||
- 2026-04-04 18:03 (KST): Switched recent permission-denied actions to flat row actions, exposed Git upstream state in the branch panel, and made inline quick actions read more like custom stateful cards.
|
|
||||||
- 2026-04-04 19:07 (KST): Unified AX Agent popup panels around the same row-driven language by flattening permission override details, simplifying collapsible section chrome, adding keyboard-friendly row actions, and turning Git change stats into compact pills.
|
|
||||||
- 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): 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 22:05 (KST)
|
|
||||||
- AX Agent 고급 설정 오버레이를 설명형 row + 커스텀 On/Off 선택 구조로 개편하고, 내부 기능명을 사용자 친화 문구로 정리함.
|
|
||||||
- 업데이트: 2026-04-04 20:58 (KST)
|
|
||||||
- AX Agent 서비스 설정의 내부 서비스 모델 선택을 등록 모델 칩 기반으로 정리하고, vLLM SSL 우회는 모델 등록/편집 다이얼로그에서만 노출되도록 설정 표면을 축소함.
|
|
||||||
- 업데이트: 2026-04-04 21:02 (KST)
|
|
||||||
- composer 컨텍스트 카드를 hover 중심의 간단 표기(`%` + `K/M` 사용량)로 정리하고, 최대 컨텍스트 설정 상한을 1M까지 확장함.
|
|
||||||
- 업데이트: 2026-04-04 21:02 (KST)
|
|
||||||
- AX Agent 서비스 설정의 `Gemini/Claude`도 모델 칩 선택 구조로 통일하고, composer 모델 버튼을 `서비스 · 모델` 문법으로 맞춰 설정-실행 흐름의 시각 언어를 정리함.
|
|
||||||
- 업데이트: 2026-04-04 23:14 (KST)
|
|
||||||
- 트레이 좌클릭 기본 진입점을 AX Agent로 전환하고, 우클릭 메뉴 상단에 앱 버전 헤더를 추가해 AX Agent 중심 진입 흐름을 강화함.
|
|
||||||
- 메인 설정 저장 완료 후 열린 AX Agent 창이 즉시 테마/모델/권한/하단 상태줄을 다시 읽어오도록 fan-out 경로를 추가해 설정 반영 지연을 줄임.
|
|
||||||
- DraftQueue kind 분류를 message/command/steering/direct/followup 기준으로 재정리해 큐 타입과 실제 입력 성격이 더 잘 맞도록 보강함.
|
|
||||||
- 업데이트: 2026-04-04 23:23 (KST)
|
|
||||||
- AX Agent가 `SettingsService.SettingsChanged`를 직접 구독하도록 바꿔 메인 설정/AX Agent 설정 어느 경로에서 저장하더라도 테마, 모델, 권한, 데이터 활용, composer, 대기열 UI가 즉시 동일 상태를 반영하도록 fan-out을 통합함.
|
|
||||||
- AX Agent 설정 저장 경로에서 표현 수준을 무조건 `rich`로 덮어쓰던 로직을 제거해 `풍부하게 / 적절하게 / 간단하게`가 다른 설정 저장 경로에서도 유지되도록 보정함.
|
|
||||||
- DraftQueue 패널은 `실행 중 / 다음 작업 / 보류 / 완료 / 실패` 개별 섹션 구조로 다시 나눠 현재 실행 흐름과 재시도 대기, 결과 이력을 더 빠르게 파악할 수 있도록 정리함.
|
|
||||||
- ????: 2026-04-04 23:32 (KST)
|
|
||||||
- AX Agent ???? ??? ?? tool-result ?? -> microcompact ?? ?? ?? -> ?? ?? ?? 3?? ??? ???, claude-code? staged compact ??? ? ??? ? ?? ??? ?? ????? ???.
|
|
||||||
- ??? ?? ??, tool_result, ?? ???, ???? ? ???? LLM ?? ?? microcompact_boundary? ?? ??? ?? ?? ?? ?? ?? ?? ??? ??? ???? ?? ??.
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# AX Agent 구조 리뷰
|
||||||
|
|
||||||
|
업데이트: 2026-04-15 18:20 (KST)
|
||||||
|
|
||||||
|
## 1. 목적
|
||||||
|
- AX Agent의 현재 구조를 실행 엔진, 루프 정책, 세션 저장, transcript 렌더 관점에서 정리합니다.
|
||||||
|
- 외부 제품 비교 대신 AX 내부 구조와 참조 모듈 흐름만 문서화합니다.
|
||||||
|
|
||||||
|
## 2. 현재 구조
|
||||||
|
|
||||||
|
### 실행 계층
|
||||||
|
- `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/StreamingToolExecutionCoordinator.cs`
|
||||||
|
|
||||||
|
### 루프 정책 계층
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopTransitions.Documents.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopCompactionPolicy.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopRuntimeThresholds.cs`
|
||||||
|
|
||||||
|
### 세션/상태 계층
|
||||||
|
- `src/AxCopilot/Services/ChatSessionStateService.cs`
|
||||||
|
- `src/AxCopilot/Services/ChatStorageService.cs`
|
||||||
|
- `src/AxCopilot/Services/AppStateService.cs`
|
||||||
|
- `src/AxCopilot/Models/ChatModels.cs`
|
||||||
|
|
||||||
|
### transcript/UI 계층
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.xaml`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.TranscriptHost.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.TranscriptRenderPlanner.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.TranscriptRenderExecution.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.TranscriptVirtualization.cs`
|
||||||
|
|
||||||
|
## 3. 강점
|
||||||
|
- Chat / Cowork / Code를 하나의 창과 정책 체계 안에서 운영
|
||||||
|
- 등록 모델별 실행 프로파일과 verification gate 지원
|
||||||
|
- 문서 생성 계열 fallback과 품질 리뷰가 루프에 결합되어 있음
|
||||||
|
- 메모리, slash, permissions, queue가 한 세션 상태에 연결되어 있음
|
||||||
|
|
||||||
|
## 4. 구조 리스크
|
||||||
|
1. `ChatWindow.xaml.cs` 책임이 여전히 크고, UI 이벤트와 런타임 상태 조립이 섞여 있습니다.
|
||||||
|
2. transcript와 status line이 더 단일한 런타임 상태를 기준으로 계산되면 유지보수가 쉬워질 여지가 있습니다.
|
||||||
|
3. replay/reopen 경로는 안정화가 많이 진행됐지만, 장기 세션에서는 이벤트 정규화 규칙이 더 중요합니다.
|
||||||
|
4. 설정이 실행 경로를 바꾸는 지점은 사용자용과 개발용 노출 범위를 더 구분할 필요가 있습니다.
|
||||||
|
|
||||||
|
## 5. 개선 우선순위
|
||||||
|
|
||||||
|
### A. 런타임 상태 단일화
|
||||||
|
- 참조 모듈: `src/bootstrap/state.ts`, `src/bridge/initReplBridge.ts`
|
||||||
|
- AX 적용 위치: `ChatWindow.xaml.cs`, `AxAgentExecutionEngine.cs`, `ChatStorageService.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- 현재 턴, queue, retry, 실행 이벤트, 저장 스냅샷이 같은 런타임 상태 모델을 사용
|
||||||
|
- reopen 뒤 저장 상태와 렌더 결과가 어긋나지 않음
|
||||||
|
|
||||||
|
### B. 이벤트 정규화
|
||||||
|
- 참조 모듈: `src/bridge/sessionRunner.ts`, `src/bridge/bridgeMessaging.ts`
|
||||||
|
- AX 적용 위치: `AgentLoopService.cs`, `AgentLoopTransitions.cs`, `AppStateService.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- tool start/result/error/progress, permission, retry, complete 이벤트가 안정적인 스키마를 공유
|
||||||
|
- replay 시 visible banner 중복 0건
|
||||||
|
|
||||||
|
### C. timeline 유도 모델 고정
|
||||||
|
- 참조 모듈: `src/screens/REPL.tsx`, `src/components/Messages.tsx`
|
||||||
|
- AX 적용 위치: `ChatWindow.xaml`, `ChatWindow.xaml.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- assistant/user message, execution log, compact boundary, queue summary를 하나의 derived timeline에서 생성
|
||||||
|
- direct imperative bubble injection 제거
|
||||||
|
|
||||||
|
### D. status/composer 정리
|
||||||
|
- 참조 모듈: `src/components/StatusLine.tsx`
|
||||||
|
- AX 적용 위치: `ChatWindow.xaml`, `ChatWindow.AgentStatusPresentation.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- status strip, queue summary, runtime activity가 debounce된 같은 상태 소스를 사용
|
||||||
|
- composer는 명시적 줄바꿈 중심으로만 성장
|
||||||
|
|
||||||
|
## 6. 검증 기준
|
||||||
|
- reopen 후 최신 assistant state가 저장 상태와 동일
|
||||||
|
- retry/regenerate/queue follow-up이 서로 다른 실행 경로로 분기되지 않음
|
||||||
|
- permission popup, inline ask, plan approval이 transcript 기준으로 닫힘
|
||||||
|
- `dotnet build` 경고 0 / 오류 0
|
||||||
|
|
||||||
|
## 7. 최근 메모
|
||||||
|
- 2026-04-06 09:36 (KST): 상태 strip 표현과 permission/tool-result 설명 카탈로그를 분리
|
||||||
|
- 2026-04-06 09:44 (KST): mixed inline interaction rendering을 presentation 파일로 분리
|
||||||
|
- 2026-04-06 09:58 (KST): Git branch popup과 footer 보조 요약 helper를 분리
|
||||||
|
- 2026-04-06 10:07 (KST): topic preset 렌더와 선택 흐름을 전용 presentation 파일로 이동
|
||||||
|
- 2026-04-06 11:52 (KST): permission, tool-result, skill gallery 설명층을 고도화
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# AX Agent 실행 개선 계획
|
||||||
|
|
||||||
|
업데이트: 2026-04-15 18:20 (KST)
|
||||||
|
|
||||||
|
## 1. 목적
|
||||||
|
- AX Agent의 실행 신뢰성, 권한 처리 일관성, 세션 복원 품질을 높입니다.
|
||||||
|
- 외부 제품명 비교 대신 실제 참조 모듈 경로와 AX 적용 위치만 기준으로 관리합니다.
|
||||||
|
- UI 보정만으로 가리는 수정은 지양하고 `상태 -> 실행 -> 렌더` 순서로 정리합니다.
|
||||||
|
|
||||||
|
## 2. 현재 판단
|
||||||
|
- 실행 엔진과 루프 품질은 안정 구간에 들어왔지만, 장기 세션 복원과 이벤트 정규화는 계속 다듬을 여지가 있습니다.
|
||||||
|
- 설정이 런타임 경로를 바꾸는 지점은 사용자 설정과 개발자용 실험 설정을 더 분리할 필요가 있습니다.
|
||||||
|
- transcript, status line, queue 표시는 충분히 가벼워졌지만 상태 계산 소스는 더 단일화해야 합니다.
|
||||||
|
|
||||||
|
## 3. 참조 흐름
|
||||||
|
|
||||||
|
| 참조 모듈 | AX 적용 위치 | 완료 조건 | 품질 판정 기준 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `src/bootstrap/state.ts` | `src/AxCopilot/Views/ChatWindow.xaml.cs`, `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`, `src/AxCopilot/Services/ChatStorageService.cs` | 현재 턴, queue, retry, 실행 이벤트, 저장 스냅샷이 하나의 런타임 상태 모델로 정리 | reopen/retry/queue 후 중복 assistant 카드나 빈 카드가 생기지 않음 |
|
||||||
|
| `src/bridge/initReplBridge.ts` | `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`, `src/AxCopilot/Services/LlmService.cs` | send/regenerate/retry/queued follow-up/slash가 하나의 실행 준비 경로를 통과 | 같은 입력과 설정에서 진입점이 달라도 실행 경로가 흔들리지 않음 |
|
||||||
|
| `src/bridge/sessionRunner.ts` | `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs` | tool start/result/error/progress가 루프 계층에서 한 번만 정규화 | Cowork/Code 장기 실행 중 상태 문구가 과도하게 흔들리지 않음 |
|
||||||
|
| `src/bridge/bridgeMessaging.ts` | `src/AxCopilot/Views/ChatWindow.xaml.cs`, `src/AxCopilot/Services/Agent/AgentLoopService.cs` | 실행 이벤트와 표시 전용 이벤트를 렌더 이전에 분리 | replay 후 visible banner 중복이 발생하지 않음 |
|
||||||
|
| `src/screens/REPL.tsx`, `src/components/Messages.tsx`, `src/components/StatusLine.tsx` | `src/AxCopilot/Views/ChatWindow.xaml`, `src/AxCopilot/Views/ChatWindow.xaml.cs` | timeline, composer, status strip이 공통 런타임 상태를 사용 | 리사이즈, queue 연속 실행, retry 후에도 UI가 패치식으로 흔들리지 않음 |
|
||||||
|
|
||||||
|
## 4. 실행 단계
|
||||||
|
|
||||||
|
### A. 런타임 상태 단일화
|
||||||
|
- 적용 위치: `ChatWindow.xaml.cs`, `AxAgentExecutionEngine.cs`, `ChatStorageService.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- `Chat`, `Cowork`, `Code`가 같은 런타임 상태 모델을 갱신
|
||||||
|
- queue, retry, compact 이후 상태가 재오픈 후에도 동일하게 복원
|
||||||
|
- 품질 기준:
|
||||||
|
- 같은 대화를 다시 열었을 때 보이는 timeline과 저장 상태가 일치
|
||||||
|
- queue badge와 최근 실행 이력이 어긋나지 않음
|
||||||
|
|
||||||
|
### B. 실행 준비 경로 일원화
|
||||||
|
- 적용 위치: `AxAgentExecutionEngine.cs`, `LlmService.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- prompt stack assembly, execution mode choice, final commit이 엔진 기준으로 통합
|
||||||
|
- send/regenerate/retry/slash가 동일 API를 사용
|
||||||
|
- 품질 기준:
|
||||||
|
- 같은 탭과 같은 설정에서 실행 경로가 예측 가능
|
||||||
|
- UI에서 별도 prompt stack을 만들지 않음
|
||||||
|
|
||||||
|
### C. 루프 이벤트 정규화
|
||||||
|
- 적용 위치: `AgentLoopService.cs`, `AgentLoopTransitions.cs`, `AgentLoopTransitions.Execution.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- 권한 요청, 실패, 재시도, 완료 이벤트가 안정적인 스키마를 사용
|
||||||
|
- bounded activity/event record 기준이 문서화
|
||||||
|
- 품질 기준:
|
||||||
|
- 장기 도구 실행 중 상태 flash가 줄고 debug payload 노출이 최소화
|
||||||
|
|
||||||
|
### D. timeline 유도 모델 고정
|
||||||
|
- 적용 위치: `ChatWindow.xaml`, `ChatWindow.xaml.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- assistant/user message, 실행 로그, compact boundary, queue summary가 하나의 derived timeline에서 생성
|
||||||
|
- 직접 bubble injection 경로 제거
|
||||||
|
- 품질 기준:
|
||||||
|
- blank assistant card 0건
|
||||||
|
- token-only completion 0건
|
||||||
|
- re-render 뒤 duplicate banner 0건
|
||||||
|
|
||||||
|
### E. composer와 status strip 정리
|
||||||
|
- 적용 위치: `ChatWindow.xaml`, `ChatWindow.xaml.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- composer height는 명시적 줄바꿈 중심으로만 증가
|
||||||
|
- status strip, queue summary, runtime activity가 debounce된 업데이트를 사용
|
||||||
|
- 품질 기준:
|
||||||
|
- resize 이후 입력창이 과하게 커지지 않음
|
||||||
|
- metadata가 메시지 본문보다 앞서 보이지 않음
|
||||||
|
|
||||||
|
### F. 복구/재개/검증 마감
|
||||||
|
- 적용 위치: `ChatWindow.xaml.cs`, `AxAgentExecutionEngine.cs`, `ChatStorageService.cs`
|
||||||
|
- 완료 조건:
|
||||||
|
- reopen after interruption, retry-last, regenerate, manual compact, manual stop, manual resume 시나리오를 모두 통과
|
||||||
|
- 최신 assistant state와 저장 conversation이 일치
|
||||||
|
- 품질 기준:
|
||||||
|
- 복원 후 마지막 상태가 실제 종료 시점과 같음
|
||||||
|
- 세션 재개 후 추가 오류 전파가 없음
|
||||||
|
|
||||||
|
## 5. 검증 기준
|
||||||
|
- `dotnet build` 경고 0 / 오류 0
|
||||||
|
- 고위험 변경마다 최소 1개 이상 회귀 테스트 추가
|
||||||
|
- `internal` / `external` 운영 모드 정책 위반 0건
|
||||||
|
- reopen, retry, regenerate, queue follow-up, permission, compact 시나리오 수동 점검 유지
|
||||||
|
|
||||||
|
## 6. 최신 메모
|
||||||
|
- 2026-04-05 15:34 (KST): 실행 개선 계획을 실제 참조 모듈 흐름 기준으로 재정리했습니다.
|
||||||
|
- 2026-04-05 16:55 (KST): 엔진, 채팅 UI, 상태 UX, 설정 연동의 구조적 갭을 분리해 정리했습니다.
|
||||||
|
- 2026-04-06 09:36 (KST): 상태 strip 표현을 전용 카탈로그로 분리하고 permission/tool-result 설명을 정형화했습니다.
|
||||||
|
- 2026-04-06 10:07 (KST): topic preset과 사용자 상호작용 렌더 책임을 presentation 파일로 계속 분리했습니다.
|
||||||
|
- 2026-04-06 11:52 (KST): permission, tool-result, skill gallery 설명층을 더 구체적인 운영 정보 중심으로 보강했습니다.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# AX Agent 품질 계획
|
||||||
|
|
||||||
|
업데이트: 2026-04-15 18:20 (KST)
|
||||||
|
|
||||||
|
## 1. 범위
|
||||||
|
- 명령 처리 흐름
|
||||||
|
- 권한 / hook 계약
|
||||||
|
- plan / run 내구성
|
||||||
|
- 세션 / 로그 관리
|
||||||
|
- 도구 / 스킬 운영 정합성
|
||||||
|
|
||||||
|
## 2. 현재 상태 요약
|
||||||
|
- 루프 가드와 검증 게이트는 주요 경로에 반영됨
|
||||||
|
- 병렬 도구 실행, Plan Mode, unknown-tool 복구는 기본 동작 확보
|
||||||
|
- 세션 재개와 이벤트 replay는 수치 기반 관리 단계로 진입
|
||||||
|
|
||||||
|
## 3. 단계별 품질 게이트
|
||||||
|
|
||||||
|
### P1 Hook 계약 고도화
|
||||||
|
- `updatedInput`, `updatedPermissions`, `additionalContext`를 AX 실행 루프에 반영
|
||||||
|
- 설정 토글로 위험 기능 제어
|
||||||
|
|
||||||
|
### P2 세션 / 이벤트 내구성
|
||||||
|
- run 상태 영속화와 resume 기준 고정
|
||||||
|
- jsonl 이벤트 필드 표준화와 재생 가능성 확보
|
||||||
|
|
||||||
|
### P3 실패 복구 표준화
|
||||||
|
- unknown-tool, 권한거부, 파라미터 오류를 유형화
|
||||||
|
- 유형별 복구 프롬프트와 다음 액션 우선순위 고정
|
||||||
|
|
||||||
|
### P4 완료 품질 정렬
|
||||||
|
- 코드/문서 작업별 완료 체크리스트 표준화
|
||||||
|
- 증거 없는 완료 응답 방지
|
||||||
|
|
||||||
|
## 4. 운영 점검 스냅샷
|
||||||
|
- 도구 레지스트리 등록 수: 65개
|
||||||
|
- `IAgentTool` 구현 수: 65개
|
||||||
|
- 내장 스킬 파일 수: 40개
|
||||||
|
- `allowed-tools` 적용 스킬: 40/40
|
||||||
|
- 레거시 도구명 `process_run`, `grep_tool` 참조: 0건
|
||||||
|
- 내부 모드 차단 정책: `http_tool` 전면 차단, `open_external` 외부 URL 차단
|
||||||
|
|
||||||
|
## 5. 즉시 보완 항목
|
||||||
|
1. 스킬 frontmatter 확장 유지: `when_to_use`, `argument-hint`, `model`, `disable-model-invocation`, `user-invocable`, `context`, `agent`, `effort`, `paths`, `shell`
|
||||||
|
2. `paths` 기반 조건부 활성화 유지
|
||||||
|
3. 도구 별칭 정규화와 runtime hook filter 적용 유지
|
||||||
|
4. 위험 자동허용 가드(`process`, `spawn_agent`, `snippet_runner`) 강제 승인 정책 유지
|
||||||
|
|
||||||
|
## 6. 내부 벤치마크 기준
|
||||||
|
|
||||||
|
| 시나리오 | 기준 테스트 | 합격 기준 |
|
||||||
|
|---|---|---|
|
||||||
|
| 기본 Agentic loop | `AgentLoopE2ETests.RunAsync_ExecutesToolCall_AndCompletesWithFinalText` | ToolCall/ToolResult/Complete 이벤트 순서 유효, 최종 응답에 결과 포함 |
|
||||||
|
| unknown-tool 복구 | `AgentLoopE2ETests.RunAsync_UnknownTool_RecoversAndCompletes` | 오류 후 완료까지 수렴, 반복 실패 루프 없음 |
|
||||||
|
| Plan Mode 실행 연결 | `AgentLoopE2ETests.RunAsync_PlanModeAlways_EmitsPlanningThenExecutesTool` | 계획 응답 뒤 실제 도구 호출과 최종 결과 생성 |
|
||||||
|
| 권한 Ask 거부 처리 | `AgentLoopE2ETests.RunAsync_AskPermissionDenied_EmitsPermissionEvents_AndCompletes` | PermissionRequest/PermissionDenied 이벤트 기록 후 안전 종료 |
|
||||||
|
| Hook 입력 변형 | `AgentLoopE2ETests.RunAsync_PreHookInputMutation_ChangesToolArguments` | pre-hook `updatedInput`이 실제 도구 입력에 반영 |
|
||||||
|
| Runtime 정책 강제 | `AgentLoopE2ETests.RunAsync_DisallowedTool_ByRuntimePolicy_EmitsPolicyRecoveryError` | 비허용 도구 차단과 정책 복구 경고 후 종료 |
|
||||||
|
| Hook filter 정합성 | `AgentLoopE2ETests.RunAsync_HookFilters_ExecuteOnlyMatchingHookForToolAndTiming` | 지정 hook만 실행되고 비매칭 hook는 미실행 |
|
||||||
|
| 별칭 정규화 | `AgentLoopE2ETests.RunAsync_EnterPlanModeAlias_ResolvesAndExecutes` | CamelCase 도구명이 내부 snake_case 도구로 매핑되어 실행 |
|
||||||
|
| 혼합 복구 내구성 | `AgentLoopE2ETests.RunAsync_MixedRecovery_UnknownToolAndPermissionDenied_TerminatesSafely` | 반복 한도 안에서 안전 종료 |
|
||||||
|
|
||||||
|
## 7. replay 안정성 기준
|
||||||
|
|
||||||
|
| 시나리오 | 기준 테스트 | 합격 기준 |
|
||||||
|
|---|---|---|
|
||||||
|
| 실행 이벤트 정렬/압축 복원 | `ChatSessionStateServiceTests.LoadOrCreateConversation_NormalizesHistoryOrderAndCompactsSize` | ExecutionEvents 400개 상한과 시간순 정렬 유지 |
|
||||||
|
| run 이력 중복 정규화 | `ChatSessionStateServiceTests.LoadOrCreateConversation_NormalizesAgentRunDuplicatesByRunId` | 동일 `RunId`는 최신 상태 1건으로 축약 |
|
||||||
|
| terminal 우선 판정 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_PrefersTerminalEventsWhenTimestampsEqual` | 동시각 이벤트는 terminal 상태 기준으로 복원 |
|
||||||
|
| active 복원 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_RebuildsActiveTasksFromNonTerminalEvents` | 중단 후 재개 시 active task 재구성 |
|
||||||
|
| dangling 정리 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_CompleteClearsDanglingRunScopedActiveTasks` | Complete 뒤 run 스코프 dangling active task 0건 |
|
||||||
|
|
||||||
|
## 8. 배포 체크리스트
|
||||||
|
1. `dotnet build` 경고 0 / 오류 0
|
||||||
|
2. `dotnet test` 전체 통과
|
||||||
|
3. 내부 벤치마크 시나리오 전건 통과
|
||||||
|
4. `Suite=ReplayStability` 전건 통과
|
||||||
|
5. `scripts/release-gate.ps1` 실행 결과 기록
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
업데이트: 2026-04-08 10:38 (KST)
|
업데이트: 2026-04-08 10:38 (KST)
|
||||||
|
|
||||||
`claw-code`와 AX Agent를 같은 기준으로 비교하기 위한 공통 회귀 프롬프트 세트입니다.
|
AX Agent 실행 흐름을 같은 기준으로 점검하기 위한 공통 회귀 프롬프트 세트입니다.
|
||||||
|
|
||||||
## 사용 규칙
|
## 사용 규칙
|
||||||
|
|
||||||
- 루프 정책, transcript 렌더, 권한/계획/진행 UX, queue/compact/reopen 흐름에 영향을 주는 변경 뒤에는 이 문서를 기준으로 최소 1회 점검합니다.
|
- 루프 정책, transcript 렌더, 권한/계획/진행 UX, queue/compact/reopen 흐름에 영향을 주는 변경 뒤에는 이 문서를 기준으로 최소 1회 점검합니다.
|
||||||
- 모든 항목을 매번 전부 돌릴 필요는 없지만, 바뀐 영역과 맞닿은 묶음은 반드시 확인합니다.
|
- 모든 항목을 매번 전부 돌릴 필요는 없지만, 바뀐 영역과 맞닿은 묶음은 반드시 확인합니다.
|
||||||
- 결과는 “모양이 똑같은가”보다 “사용자 체감 흐름과 완료 품질이 같은가”를 기준으로 봅니다.
|
- 결과는 “화면이 비슷한가”보다 “사용자 체감 흐름과 완료 품질이 안정적인가”를 기준으로 봅니다.
|
||||||
|
|
||||||
## 실패 분류
|
## 실패 분류
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# AX Agent 런타임 품질 계획
|
||||||
|
|
||||||
|
업데이트: 2026-04-15 18:20 (KST)
|
||||||
|
|
||||||
|
## 1. 기준
|
||||||
|
- 대상 범위: 명령 처리, 권한/훅, plan/run 내구성, 세션/로그 관리
|
||||||
|
- 문서 목적: AX Agent 런타임 경로의 품질 기준과 마감 조건을 명확히 고정
|
||||||
|
|
||||||
|
## 2. 주요 갭
|
||||||
|
- Hook 출력의 구조화 계약 적용 범위가 일부 경로에 아직 제한적입니다.
|
||||||
|
- 세션 재개와 이벤트 replay 품질은 시나리오별 편차가 남아 있습니다.
|
||||||
|
- 도구 선택 실패 뒤 재시도 전략의 메시지와 다음 행동이 완전히 표준화되지는 않았습니다.
|
||||||
|
|
||||||
|
## 3. 실행 순서
|
||||||
|
1. Hook 계약 고도화
|
||||||
|
2. 세션/이벤트 내구성 정리
|
||||||
|
3. 실패 복구 메시지 표준화
|
||||||
|
4. 완료 품질 게이트 고정
|
||||||
|
|
||||||
|
## 4. 완료 판정
|
||||||
|
- 내부 벤치마크 목표 성공률 충족
|
||||||
|
- 세션 재개/이벤트 재생 오류 0건
|
||||||
|
- 빌드 경고 0 / 오류 0 유지
|
||||||
|
|
||||||
|
## 5. 점검 스냅샷
|
||||||
|
- 기준 시점: 2026-04-03
|
||||||
|
- 계획 대비 현재 수준: 약 92~95%
|
||||||
|
- 테스트 상태: `dotnet test` 374/374 통과
|
||||||
|
- Hook 계약: 구현 완료 수준
|
||||||
|
- 세션/이벤트 내구성: 복원/재생 경계 케이스 테스트 반영
|
||||||
|
- 실패 복구 표준화: unknown-tool/권한/정체 경로 반영
|
||||||
|
- 완료 품질 게이트: 문서화와 코드 반영 완료
|
||||||
|
|
||||||
|
## 6. 관리 대상
|
||||||
|
1. 도구 계층: 목록, 별칭 정규화, unknown-tool 복구, 선택 정책
|
||||||
|
2. 스킬 계층: 로더, frontmatter, 파일 기반 운영, 조건부 활성화
|
||||||
|
3. 훅/권한: pre/post hook 계약, permission update 반영, ask/auto/deny 일관성
|
||||||
|
4. 세션/상태: run 복원, 이벤트 replay, 중복 억제, resume 신뢰성
|
||||||
|
5. 계획/실행: plan 승인/수정 이력, 실행 게이트, 조기 완료 방지
|
||||||
|
6. MCP/외부도구: 연결, 리소스 조회, 실패 복구, internal 정책
|
||||||
|
7. 설정/UI 연동: 설정값, 실행 코드, UI 반영 동기화
|
||||||
|
|
||||||
|
## 7. 다음 순서
|
||||||
|
1. Agentic loop E2E 시나리오를 회귀 테스트로 계속 고정
|
||||||
|
2. 벤치마크 합격 기준과 증적을 배포 체크리스트에 연결 유지
|
||||||
|
3. 수치와 상태 문구를 `README.md`, `docs/DEVELOPMENT.md`, 로드맵 문서와 동기화
|
||||||
|
|
||||||
|
## 8. 운영 기준
|
||||||
|
- 내부 벤치마크와 replay 안정성 테스트는 릴리즈 전 최소 조건으로 유지
|
||||||
|
- 문서에는 비교·모사 표현보다 AX 기준 완료 조건과 검증 결과만 기록
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
# claude-code / AX Agent 구조 비교 문서
|
|
||||||
|
|
||||||
업데이트: 2026-04-09 10:20 (KST)
|
|
||||||
|
|
||||||
이 문서는 AX Agent를 `claude-code` 동등 품질 기준으로 유지·검증하기 위한 비교 기준 문서입니다.
|
|
||||||
실제 비교 대상 저장 폴더명은 `claw-code`이지만, 문서 표기는 `claude-code`로 통일합니다.
|
|
||||||
|
|
||||||
## 1. claude-code 구조, 특징 기능, 에이전트 루프
|
|
||||||
|
|
||||||
### 1-1. 구조 핵심
|
|
||||||
|
|
||||||
`claude-code`는 크게 아래 축으로 나뉩니다.
|
|
||||||
|
|
||||||
1. 부트스트랩/세션 상태
|
|
||||||
- `src/bootstrap/state.ts`
|
|
||||||
- `src/bridge/initReplBridge.ts`
|
|
||||||
- `src/bridge/sessionRunner.ts`
|
|
||||||
2. 메인 REPL/대화 화면
|
|
||||||
- `src/screens/REPL.tsx`
|
|
||||||
- `src/components/Messages.tsx`
|
|
||||||
- `src/components/VirtualMessageList.tsx`
|
|
||||||
- `src/components/StatusLine.tsx`
|
|
||||||
3. 도구 실행 계층
|
|
||||||
- `src/services/tools/toolOrchestration.ts`
|
|
||||||
- `src/services/tools/StreamingToolExecutor.ts`
|
|
||||||
4. 메모리/컨텍스트/권한
|
|
||||||
- `04_작동원리.md`
|
|
||||||
- `05_메모리와컨텍스트.md`
|
|
||||||
- `06_권한시스템.md`
|
|
||||||
- `15_스킬.md`
|
|
||||||
|
|
||||||
### 1-2. 특징 기능
|
|
||||||
|
|
||||||
`claude-code`의 강점은 기능 개수보다 실행 품질에 있습니다.
|
|
||||||
|
|
||||||
- 대화, 도구 실행, 권한, 상태줄이 한 흐름으로 이어진다.
|
|
||||||
- transcript가 단순 채팅 로그가 아니라 실행 관측 UI 역할을 한다.
|
|
||||||
- 도구 호출이 응답 후처리가 아니라 루프 자체의 핵심 경로다.
|
|
||||||
- read-only 도구는 병렬화하고, non-concurrent 도구는 직렬화해 체감 반응성을 높인다.
|
|
||||||
- 메모리 구조가 `managed / user / project / local` 계층으로 명확하다.
|
|
||||||
- `CLAUDE.md`, `.claude/rules/*.md`, `paths:` frontmatter, `@include` 같은 규칙 기반 메모리 주입이 강하다.
|
|
||||||
- 권한 시스템이 `default / acceptEdits / plan / bypassPermissions`와 allow/deny rule로 분리되어 있다.
|
|
||||||
- skill 시스템이 단순 프롬프트 스니펫이 아니라 재사용 가능한 워크플로우 레이어다.
|
|
||||||
|
|
||||||
### 1-3. 에이전트 루프 구조
|
|
||||||
|
|
||||||
`claude-code`의 기본 루프는 다음 순서에 가깝습니다.
|
|
||||||
|
|
||||||
1. 사용자 메시지 수신
|
|
||||||
2. 시스템 컨텍스트 조립
|
|
||||||
- Git 상태
|
|
||||||
- 메모리 계층
|
|
||||||
- 현재 날짜/환경
|
|
||||||
- 사용 가능한 도구/권한 상태
|
|
||||||
3. 모델 호출
|
|
||||||
4. tool_use block 감지
|
|
||||||
5. 권한 판단
|
|
||||||
6. 도구 실행
|
|
||||||
7. tool_result를 다시 모델에 투입
|
|
||||||
8. 필요 시 추가 도구 호출
|
|
||||||
9. 최종 텍스트 응답 커밋
|
|
||||||
|
|
||||||
중요한 점은, 이 루프가 단순히 "LLM 호출 후 도구 실행"이 아니라 `도구를 전제로 설계된 루프`라는 점입니다.
|
|
||||||
|
|
||||||
### 1-4. Cowork/Code에서 결과가 잘 나오는 이유
|
|
||||||
|
|
||||||
#### 1. 도구 우선 오케스트레이션
|
|
||||||
|
|
||||||
- `toolOrchestration.ts`가 tool call을 배치로 나누고
|
|
||||||
- `StreamingToolExecutor.ts`가 스트리밍 도중에도 도구 실행 준비를 진행합니다.
|
|
||||||
- read-only/concurrency-safe 도구는 병렬 실행해 첫 반응 시간을 줄입니다.
|
|
||||||
|
|
||||||
#### 2. 실행 중 관측성이 높음
|
|
||||||
|
|
||||||
- `Messages.tsx`
|
|
||||||
- `VirtualMessageList.tsx`
|
|
||||||
- `StatusLine.tsx`
|
|
||||||
|
|
||||||
이 구조 덕분에 사용자는 "지금 무엇을 읽고, 무엇을 실행하고, 어디서 멈췄는지"를 계속 볼 수 있습니다.
|
|
||||||
긴 작업에서도 멈춘 것처럼 느껴지지 않는 이유가 여기 있습니다.
|
|
||||||
|
|
||||||
#### 3. 긴 세션을 버티는 transcript 구조
|
|
||||||
|
|
||||||
- transcript는 전체 렌더를 계속 다시 그리지 않습니다.
|
|
||||||
- `VirtualMessageList.tsx` 기준으로 가시 영역 중심 렌더와 스크롤/탐색 보조 구조가 분리되어 있습니다.
|
|
||||||
- 긴 세션, 많은 도구 호출, 많은 중간 상태에서도 UI 부하가 상대적으로 낮습니다.
|
|
||||||
|
|
||||||
#### 4. 메모리와 규칙 주입이 명시적
|
|
||||||
|
|
||||||
- `CLAUDE.md`
|
|
||||||
- `.claude/rules/*.md`
|
|
||||||
- `paths:`
|
|
||||||
- `@include`
|
|
||||||
|
|
||||||
이 구조 때문에 프로젝트/경로/사용자/관리자 규칙이 프롬프트에 일관되게 들어갑니다.
|
|
||||||
결과가 흔들릴 때도 "왜 그런 성향이 나왔는지" 추적이 쉽습니다.
|
|
||||||
|
|
||||||
### 1-5. 주요 성능과 품질에 영향을 주는 프롬프트 전략
|
|
||||||
|
|
||||||
1. 도구를 먼저 쓰게 만드는 직접적인 지시
|
|
||||||
2. 컨텍스트를 계층화해서 넣음
|
|
||||||
3. 권한과 도구 사용 범위를 명확히 분리
|
|
||||||
4. 실행 중간 상태를 계속 보여줘 재시도/복구가 자연스러움
|
|
||||||
5. read-only tool 병렬화로 초기 탐색 시간을 줄임
|
|
||||||
6. transcript/상태줄이 실제 실행 관측 수단으로 동작
|
|
||||||
|
|
||||||
## 2. AX Agent 구조, 특징 기능, 에이전트 루프
|
|
||||||
|
|
||||||
### 2-1. 구조 핵심
|
|
||||||
|
|
||||||
현재 AX Agent는 다음 축으로 구성됩니다.
|
|
||||||
|
|
||||||
1. 에이전트 실행
|
|
||||||
- `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/StreamingToolExecutionCoordinator.cs`
|
|
||||||
2. 루프 정책 분리 계층
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentLoopTransitions.Documents.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentLoopCompactionPolicy.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentLoopRuntimeThresholds.cs`
|
|
||||||
3. 메모리 구조
|
|
||||||
- `src/AxCopilot/Services/AgentMemoryService.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/MemoryTool.cs`
|
|
||||||
4. transcript/UI 구조
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.TranscriptHost.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.TranscriptRenderPlanner.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.TranscriptRenderExecution.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.TranscriptVirtualization.cs`
|
|
||||||
5. 성능/검증 로그
|
|
||||||
- `src/AxCopilot/Services/AgentPerformanceLogService.cs`
|
|
||||||
|
|
||||||
### 2-2. 특징 기능
|
|
||||||
|
|
||||||
- Chat / Cowork / Code를 한 창에서 운영
|
|
||||||
- 등록 모델별 실행 프로파일 지원
|
|
||||||
- Cowork/Code 전용 agent loop 사용
|
|
||||||
- 문서 생성 계열 fallback 및 verification gate 지원
|
|
||||||
- IBM/vLLM/OpenAI 호환 tool-use 경로 대응
|
|
||||||
- 계층형 메모리 구조 지원
|
|
||||||
- `/memory` 계열 도구와 설정 UI에서 메모리 관리 가능
|
|
||||||
- transcript/진행 표시/UI 구조를 `claude-code` 기준으로 계속 분해해온 상태
|
|
||||||
|
|
||||||
### 2-3. 에이전트 루프 구조
|
|
||||||
|
|
||||||
현재 AX Agent의 루프는 대략 아래 순서입니다.
|
|
||||||
|
|
||||||
1. 실행 컨텍스트 구성
|
|
||||||
2. 사용자 메시지와 시스템 지시 조립
|
|
||||||
3. LLM 호출
|
|
||||||
4. tool call 감지
|
|
||||||
5. `StreamingToolExecutionCoordinator`를 통한 조기 도구 실행/prefetch
|
|
||||||
6. 도구 결과 재투입
|
|
||||||
7. verification/document fallback/compact 정책 수행
|
|
||||||
8. 최종 결과 커밋
|
|
||||||
9. transcript/status/perf 로그 반영
|
|
||||||
|
|
||||||
### 2-4. 현재 구조적 강점
|
|
||||||
|
|
||||||
1. 모델 프로파일 기반 제어
|
|
||||||
2. 계층형 메모리 구조
|
|
||||||
3. Cowork/Code용 별도 실행 정책
|
|
||||||
4. 성능 계측 로그
|
|
||||||
|
|
||||||
### 2-5. 현재 구조적 약점
|
|
||||||
|
|
||||||
1. `ChatWindow.xaml.cs` 책임이 여전히 큼
|
|
||||||
2. transcript가 아직 완전한 데이터 기반 가상화는 아님
|
|
||||||
3. 루프 정책이 여전히 서비스 중심
|
|
||||||
4. tool streaming executor가 독립 계층으로 강화되었지만 결합이 남음
|
|
||||||
|
|
||||||
## 3. claude-code와 AX Agent 비교
|
|
||||||
|
|
||||||
### 3-1. 전체 비교 요약
|
|
||||||
|
|
||||||
| 항목 | claude-code | AX Agent 현재 상태 |
|
|
||||||
|------|-------------|-------------------|
|
|
||||||
| 메모리 계층 | 매우 강함 | 많이 근접 |
|
|
||||||
| tool orchestration | 매우 강함 | 많이 강화됨 |
|
|
||||||
| transcript 가상화 | 강함 | 부분 근접 |
|
|
||||||
| 진행 관측성 | 강함 | 많이 근접 |
|
|
||||||
| 모델 프로파일성 | 단단함 | 프로파일 시스템 도입 완료 |
|
|
||||||
| 문서 fallback | 비교적 단순/직접적 | 더 많은 정책 보유 |
|
|
||||||
| 루프 응집도 | 모듈 분리 우수 | 개선 중, 많이 나아짐 |
|
|
||||||
|
|
||||||
### 3-2. AX Agent가 이미 따라잡은 부분
|
|
||||||
|
|
||||||
1. 메모리 계층 구조
|
|
||||||
2. 진행 표시 UX
|
|
||||||
3. 모델별 실행 성향 제어
|
|
||||||
4. 도구 호출 강화
|
|
||||||
|
|
||||||
### 3-3. AX Agent가 아직 더 개선할 수 있는 부분
|
|
||||||
|
|
||||||
1. transcript의 진짜 가상화
|
|
||||||
2. UI 렌더의 data-template화
|
|
||||||
3. tool executor의 독립성
|
|
||||||
4. Cowork/Code 실사용 로그 기반 검증 체계
|
|
||||||
|
|
||||||
### 3-4. 성능과 결과 품질에 직접 영향이 큰 구조 요소 비교
|
|
||||||
|
|
||||||
#### claude-code 쪽 핵심
|
|
||||||
|
|
||||||
- read-only tool 병렬화
|
|
||||||
- streaming executor
|
|
||||||
- transcript 가상화
|
|
||||||
- 상태줄/메시지/도구 실행 분리
|
|
||||||
- 명시적 메모리 계층
|
|
||||||
- 권한 시스템과 루프의 강한 연결
|
|
||||||
|
|
||||||
#### AX Agent 쪽 핵심
|
|
||||||
|
|
||||||
- 모델 프로파일
|
|
||||||
- document/verification/compact 정책 분리
|
|
||||||
- IBM/vLLM 도구 호출 보강
|
|
||||||
- transcript host/windowing/virtualization 보강
|
|
||||||
- WPF 환경 특화 성능 대응
|
|
||||||
|
|
||||||
### 3-5. 현 시점 결론
|
|
||||||
|
|
||||||
현 시점 AX Agent는 `claude-code` 대비 다음 상태로 보는 것이 적절합니다.
|
|
||||||
|
|
||||||
- 구조적 방향성: 거의 동일한 방향
|
|
||||||
- 메모리/도구/진행 표시: 상당 부분 근접
|
|
||||||
- transcript/렌더 구조: 아직 한 단계 차이 존재
|
|
||||||
- 장기 세션 성능: 실제 로그 기반 추가 검증 필요
|
|
||||||
|
|
||||||
즉 AX Agent는 더 이상 "기본 구조가 부족한 상태"는 아니고, 이제부터는 `실사용 검증 기반으로 남은 구조 차이를 줄이는 단계`입니다.
|
|
||||||
|
|
||||||
## 4. AX 개발 시 이 문서를 어떻게 쓸지
|
|
||||||
|
|
||||||
1. Cowork/Code 품질 이슈가 생기면 먼저 `claude-code` 기준 흐름과 비교
|
|
||||||
2. 구조 리팩터링이 필요할 때 transcript / tool executor / loop policy / memory 중 어디를 건드리는지 먼저 분류
|
|
||||||
3. 실사용 성능 검증 시 `%APPDATA%\\AxCopilot\\perf` 로그와 loop elapsed, transcript render elapsed, no-tool loop, verification/fallback/compact 비율을 함께 본다
|
|
||||||
4. 계획 수립 시 항상 참조 파일, AX 적용 위치, 완료 조건, 품질 판정 기준을 같이 적는다
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
# CLAW-CODE 품질 동등화 계획 (전면 재작성)
|
|
||||||
|
|
||||||
## 1. 기준
|
|
||||||
- 레퍼런스: `E:\AX Copilot - Codex\claw-code\claw-code-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. claw-code 소스 직접 비교 결과 (2026-04-03)
|
|
||||||
- 비교 기준 소스: `claw-code/.../src/tools.ts`, `src/Tool.ts`, `src/skills/loadSkillsDir.ts`, `src/skills/bundled/*.ts`.
|
|
||||||
- claw-code 도구 상수명: 41개 (`*_TOOL_NAME` 기준).
|
|
||||||
- AX 도구명: 65개 (`IAgentTool.Name` 기준).
|
|
||||||
- claw-code 도구명 중 AX alias로 직접 해석되는 항목: 8개 (`Bash/Read/Write/Edit/Glob/Grep/WebFetch/WebSearch`).
|
|
||||||
- 미해석 항목(33개)은 대부분 제품 철학 차이 영역(예: `EnterPlanMode`, `ExitWorktree`, `TeamCreate`, `TodoWrite`, `ToolSearch`, `REPL`, `Cron*`).
|
|
||||||
- claw-code 번들 스킬은 코드 등록형 14개(`registerBundledSkill`)이며, AX는 파일 기반 스킬 40개(`*.skill.md`) 중심 구성.
|
|
||||||
- 번들 스킬 이름 교집합은 0개이며(의도된 차별화), 품질 기준은 "이름 일치"가 아니라 "동작 등가(계획/복구/검증/권한/세션 내구성)"로 판단해야 함.
|
|
||||||
- claw-code 스킬 메타데이터는 `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. claw-code 비교 기반 즉시 보완 항목 (반영 상태)
|
|
||||||
1. 스킬 frontmatter 확장: `when_to_use`, `argument-hint`, `model`, `disable-model-invocation`, `user-invocable`, `context`, `agent`, `effort`, `paths`, `shell` 반영 완료.
|
|
||||||
2. 스킬 실행 범위 제어: `paths` 기반 조건부 활성화 반영 완료(첨부 파일 경로 매칭 시 동적 활성화).
|
|
||||||
3. 도구 별칭 정규화: claw-code 기본 도구명군(`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는 미실행 |
|
|
||||||
| claw-code 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`는 가능한 경로에서 메시지 컨텍스트로 반영.
|
|
||||||
|
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
# Code Context Reliability Plan
|
||||||
|
|
||||||
|
Update: 2026-04-16 09:12 (KST)
|
||||||
|
|
||||||
|
- Implemented the remaining continuity gap-closure items that were still open after the earlier staged loop refactor:
|
||||||
|
- durable conversation-owned `CodeWorkingSetSnapshot`
|
||||||
|
- semantic Code tool-batch continuity summaries
|
||||||
|
- compact-safe `MsgId` and preview metadata preservation
|
||||||
|
- query-context diagnostics for working-set injection, semantic-summary injection, protected diagnostics, compact-boundary use, and legacy-boundary fallback detection
|
||||||
|
- workspace-context bootstrap injection for Code requests when `.ax-context.md` is already available
|
||||||
|
- The active implementation now spans:
|
||||||
|
- `src/AxCopilot/Models/ChatModels.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/CodeTaskWorkingSetService.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopCodeWorkingSetPersistence.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopQueryAssemblyService.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopLlmRequestPreparationService.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentQueryContextBuilder.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopContextReliability.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/ContextCondenser.cs`
|
||||||
|
- `src/AxCopilot/Services/ChatSessionStateService.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
||||||
|
- Remaining follow-up after this wave is now smaller and mainly optional:
|
||||||
|
- broaden the workspace bootstrap path from `LoadContext(...)` reuse into a fuller stale-check refresh only if Code runs show it is still needed
|
||||||
|
- continue cleaning low-traffic legacy mojibake comments and dormant compatibility strings as those files are touched again
|
||||||
|
- evaluate whether the durable Code working set should surface in future debug tooling or exports
|
||||||
|
- keep measuring tool-trace repair frequency in longer real Code sessions to confirm the new durable snapshot and semantic summary blocks reduce repair churn
|
||||||
|
|
||||||
|
Update: 2026-04-16 07:40 (KST)
|
||||||
|
|
||||||
|
- Closed the main gaps that were still open versus the comparison checklist:
|
||||||
|
- durable structured tool transcript persistence back into `conversation.Messages`
|
||||||
|
- `MaxContextTokens = 0` Auto mode with model-aware context budget resolution
|
||||||
|
- snippet-based post-compact tool trace restoration
|
||||||
|
- repeated system prompt deduplication before each prepared turn
|
||||||
|
- The active implementation now spans:
|
||||||
|
- `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentQueryContextBuilder.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/ContextCondenser.cs`
|
||||||
|
- `src/AxCopilot/Models/AppSettings.cs`
|
||||||
|
- `src/AxCopilot/Services/SettingsService.cs`
|
||||||
|
- `src/AxCopilot/ViewModels/SettingsViewModel.cs`
|
||||||
|
- `src/AxCopilot/Views/SettingsWindow.xaml`
|
||||||
|
- `src/AxCopilot/Views/SettingsWindow.xaml.cs`
|
||||||
|
- `src/AxCopilot/Views/AgentSettingsWindow.xaml.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.xaml`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.OverlaySettingsPresentation.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.ContextUsagePresentation.cs`
|
||||||
|
- `src/AxCopilot/Services/LlmService.cs`
|
||||||
|
- `src/AxCopilot/Services/LlmService.ToolUse.cs`
|
||||||
|
- Remaining follow-up is now narrower:
|
||||||
|
- consider splitting context budget and output budget into separate user-facing controls if future provider tuning needs it
|
||||||
|
- continue cleaning legacy mojibake strings in low-traffic diagnostic paths as they are touched
|
||||||
|
|
||||||
|
Update: 2026-04-16 06:41 (KST)
|
||||||
|
|
||||||
|
- Added `src/AxCopilot/Services/Agent/AgentLoopLlmDispatchStageService.cs` so the LLM dispatch path is now split into:
|
||||||
|
- history/query assembly
|
||||||
|
- pre-LLM stage planning
|
||||||
|
- dispatch/stream stage execution
|
||||||
|
- tool execution and recovery
|
||||||
|
- `AgentLoopService` no longer owns the inline stream preview callback or the direct `SendWithToolsWithRecoveryAsync(...)` setup for the primary loop.
|
||||||
|
- `StreamingToolExecutionCoordinator.cs` was also normalized to English-only active-path status strings so the staged dispatch path no longer reintroduces mojibake text during wait/retry handling.
|
||||||
|
- Remaining structural gap versus the target `claw-code` shape:
|
||||||
|
- the `NotSupportedException` / `ToolCallNotSupportedException` fallback branch still lives in `AgentLoopService`
|
||||||
|
- the next extraction target should be a narrower fallback policy stage so the main loop keeps shrinking toward a pure orchestrator
|
||||||
|
|
||||||
|
Update: 2026-04-16 01:37 (KST)
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Recent Code tab runs show that the LLM request payload is still growing over time. In the `2026-04-16 00:46:26` to `00:50:52` run, the request size grew from `messages=7` to `messages=125`. That means the failure mode is not "context does not grow at all." The real problem is context fidelity: detailed evidence that the model still needs is being replaced too quickly by previews, repair notes, and low-signal summaries.
|
||||||
|
|
||||||
|
The same log window repeatedly shows:
|
||||||
|
|
||||||
|
- `tool_calls/tool mismatch detected - flattening assistant message`
|
||||||
|
- `orphan tool message detected - converting to user`
|
||||||
|
- repeated rereads of nearby files after build failures
|
||||||
|
- shifting build failures such as `MC3089` followed by `CS0017` without a stable working set that preserves what was already changed and what remains broken
|
||||||
|
|
||||||
|
In short, the current system grows the raw message count but does not preserve a stable working set for long-running code tasks.
|
||||||
|
|
||||||
|
## Current Findings
|
||||||
|
|
||||||
|
### 1. Workspace context bootstrap is weak on first load
|
||||||
|
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.UtilityPresentation.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs`
|
||||||
|
- Finding:
|
||||||
|
- When `.ax-context.md` is missing, the first Code request can return before background workspace-context generation becomes useful.
|
||||||
|
- Impact:
|
||||||
|
- Empty-workspace and fresh-project tasks start without a reliable folder or project summary in the early loops.
|
||||||
|
|
||||||
|
### 2. Build and file evidence is compacted too aggressively
|
||||||
|
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentToolResultBudget.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/ContextCondenser.cs`
|
||||||
|
- Current values:
|
||||||
|
- `DefaultSoftCharLimit = 900`
|
||||||
|
- `DefaultAggregateBudgetChars = 7_500`
|
||||||
|
- `RecentKeepCount = 6`
|
||||||
|
- Impact:
|
||||||
|
- Code tasks lose detailed build, test, and file-read evidence too early and fall back to previews instead of actionable context.
|
||||||
|
|
||||||
|
### 3. Session learning is not a durable code working set
|
||||||
|
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Services/Agent/SessionLearningCollector.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.SystemPromptBuilder.cs`
|
||||||
|
- Finding:
|
||||||
|
- Session learnings are injected every loop, but they are not structured strongly enough to lock in:
|
||||||
|
- current goal
|
||||||
|
- current architecture
|
||||||
|
- changed files
|
||||||
|
- latest build or test failure
|
||||||
|
- next repair target
|
||||||
|
- Impact:
|
||||||
|
- The model must repeatedly reconstruct project state from noisy history instead of reading a stable code-task memory layer.
|
||||||
|
|
||||||
|
### 4. Tool-trace invariant repairs are too common
|
||||||
|
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Services/LlmService.ToolUse.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentMessageInvariantHelper.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
||||||
|
- Finding:
|
||||||
|
- The recent logs show repeated mismatch and orphan corrections.
|
||||||
|
- Impact:
|
||||||
|
- Even if the total message count grows, the semantic chain between assistant reasoning, tool call, and tool result becomes less reliable.
|
||||||
|
|
||||||
|
### 5. There is no Code-specific working-set layer
|
||||||
|
|
||||||
|
- AX targets:
|
||||||
|
- new service required
|
||||||
|
- injection path should go through:
|
||||||
|
- `AgentLoopLlmRequestPreparationService`
|
||||||
|
- `AgentQueryContextBuilder`
|
||||||
|
- `AgentLoopService`
|
||||||
|
- Finding:
|
||||||
|
- The current request mixes raw chat history, session learnings, project context, and workspace context, but it does not maintain a dedicated code-task state ledger.
|
||||||
|
- Impact:
|
||||||
|
- Long-running runs become increasingly inconsistent because the model keeps rediscovering facts that should already be fixed in memory.
|
||||||
|
|
||||||
|
## External Research Notes
|
||||||
|
|
||||||
|
### Anthropic Claude Code memory docs
|
||||||
|
|
||||||
|
- Claude Code explicitly documents memory files that are auto-loaded at startup and inspectable via `/memory`.
|
||||||
|
- Planning implication:
|
||||||
|
- AX should have a clearly observable memory hierarchy for Code tasks, including what was auto-loaded and why.
|
||||||
|
- Source:
|
||||||
|
- [Anthropic Claude Code memory docs](https://docs.anthropic.com/zh-CN/docs/claude-code/memory)
|
||||||
|
|
||||||
|
### OpenAI practical guide to building agents
|
||||||
|
|
||||||
|
- The guide emphasizes observability, eval baselines, and explicit tool and system design before optimizing agent behavior.
|
||||||
|
- Planning implication:
|
||||||
|
- AX should log the exact context sections that enter each Code request, including what was compacted and why.
|
||||||
|
- Source:
|
||||||
|
- [OpenAI practical guide to building agents](https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf)
|
||||||
|
|
||||||
|
### SWE-Pruner
|
||||||
|
|
||||||
|
- The paper argues that task-aware adaptive pruning outperforms naive fixed truncation for coding agents.
|
||||||
|
- Planning implication:
|
||||||
|
- AX should protect code-task evidence such as latest build failures and changed-file summaries instead of applying mostly size-based pruning.
|
||||||
|
- Source:
|
||||||
|
- [SWE-Pruner: Self-Adaptive Context Pruning for Coding Agents](https://arxiv.org/abs/2601.16746)
|
||||||
|
|
||||||
|
## `claude-code` Reference Points
|
||||||
|
|
||||||
|
Reference targets:
|
||||||
|
|
||||||
|
- `claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/query.ts`
|
||||||
|
- `claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/history.ts`
|
||||||
|
- `claw-code/en/concepts/memory-context.md`
|
||||||
|
|
||||||
|
Observed direction:
|
||||||
|
|
||||||
|
- `claude-code` builds a dedicated `messagesForQuery` window.
|
||||||
|
- It stages compaction through boundary filtering, tool-result budgeting, snip, microcompact, and autocompact.
|
||||||
|
- It treats memory and post-compaction query windows as first-class parts of the request path.
|
||||||
|
|
||||||
|
AX already has similar mechanisms, but the Code flow still lacks stronger working-set preservation and cleaner invariant handling.
|
||||||
|
|
||||||
|
## Remediation Plan
|
||||||
|
|
||||||
|
### Phase 1. Context observability and bootstrap repair
|
||||||
|
|
||||||
|
- Reference targets:
|
||||||
|
- `claw-code/.../src/query.ts`
|
||||||
|
- `claw-code/en/concepts/memory-context.md`
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.UtilityPresentation.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentQueryContextBuilder.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopLlmRequestPreparationService.cs`
|
||||||
|
- Work items:
|
||||||
|
- guarantee workspace-context generation starts even on first miss
|
||||||
|
- log the exact context sections injected into each request
|
||||||
|
- add diagnostics for omitted sections
|
||||||
|
- Done criteria:
|
||||||
|
- empty-workspace runs show workspace context generation by loop 2
|
||||||
|
- logs show section names, sizes, and compaction status
|
||||||
|
- Quality scenario:
|
||||||
|
- a fresh `E:\code` WPF scaffolding run should show folder and project context in the first two request cycles
|
||||||
|
|
||||||
|
### Phase 2. Code working-set memory layer
|
||||||
|
|
||||||
|
- Reference targets:
|
||||||
|
- `claw-code/.../src/query.ts`
|
||||||
|
- `claw-code/.../src/history.ts`
|
||||||
|
- Anthropic memory docs
|
||||||
|
- AX targets:
|
||||||
|
- new `CodeTaskWorkingSetService`
|
||||||
|
- `src/AxCopilot/Services/Agent/SessionLearningCollector.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentQueryContextBuilder.cs`
|
||||||
|
- Work items:
|
||||||
|
- maintain a stable structured ledger with:
|
||||||
|
- current goal
|
||||||
|
- selected architecture
|
||||||
|
- changed files
|
||||||
|
- latest successful writes
|
||||||
|
- open diagnostics
|
||||||
|
- next repair target
|
||||||
|
- inject it only when changed
|
||||||
|
- replace superseded failures with the latest active issue
|
||||||
|
- Done criteria:
|
||||||
|
- long Code runs keep a single coherent working-set block without noisy duplication
|
||||||
|
- build and test failures are preserved as part of the working set
|
||||||
|
- Quality scenario:
|
||||||
|
- after fixing `MC3089`, the run should still remember the earlier structure change while focusing on the new `CS0017` entry-point failure
|
||||||
|
|
||||||
|
### Phase 3. Task-aware pruning and protected evidence
|
||||||
|
|
||||||
|
- Reference targets:
|
||||||
|
- `claw-code/.../src/query.ts`
|
||||||
|
- SWE-Pruner
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentToolResultBudget.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/ContextCondenser.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentQueryContextBuilder.cs`
|
||||||
|
- Work items:
|
||||||
|
- protect:
|
||||||
|
- latest build error block
|
||||||
|
- latest test failure block
|
||||||
|
- current plan or working set
|
||||||
|
- latest folder tree snapshot
|
||||||
|
- last N write diffs
|
||||||
|
- move from pure char-based truncation toward semantic snapshots
|
||||||
|
- tune compaction rules specifically for Code tasks
|
||||||
|
- Done criteria:
|
||||||
|
- active repair evidence survives across loops until superseded
|
||||||
|
- older noise shrinks without losing the current failure context
|
||||||
|
- Quality scenario:
|
||||||
|
- a 30-plus-loop Code run should still preserve the latest failure and target files in the request payload
|
||||||
|
|
||||||
|
### Phase 4. Tool-trace invariant hardening
|
||||||
|
|
||||||
|
- Reference targets:
|
||||||
|
- `claw-code/.../src/query.ts`
|
||||||
|
- `claw-code/.../src/history.ts`
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Services/LlmService.ToolUse.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentMessageInvariantHelper.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
||||||
|
- Work items:
|
||||||
|
- shift from after-the-fact flattening to pre-request validation and normalization
|
||||||
|
- classify mismatch and orphan causes and lock them with regression tests
|
||||||
|
- add a final integrity pass before query submission
|
||||||
|
- Done criteria:
|
||||||
|
- standard Code runs approach zero mismatch or orphan repair logs
|
||||||
|
- assistant, tool, and tool_result chains remain intact end to end
|
||||||
|
- Quality scenario:
|
||||||
|
- a 50-loop Code run should complete without repeated tool-trace repair events
|
||||||
|
|
||||||
|
### Phase 5. Encoding hygiene and prompt cleanup
|
||||||
|
|
||||||
|
- Reference targets:
|
||||||
|
- Anthropic memory docs
|
||||||
|
- OpenAI practical guide eval and observability recommendations
|
||||||
|
- AX targets:
|
||||||
|
- `src/AxCopilot/Views/ChatWindow.SystemPromptBuilder.cs`
|
||||||
|
- `src/AxCopilot/Services/Agent/SessionLearningCollector.cs`
|
||||||
|
- active status, prompt, and catalog files
|
||||||
|
- `AGENTS.md`
|
||||||
|
- Work items:
|
||||||
|
- enforce English-only comments in code files
|
||||||
|
- rewrite mojibake strings in active prompt paths into English
|
||||||
|
- add long-run Code evals to catch prompt and status encoding regressions
|
||||||
|
- Done criteria:
|
||||||
|
- no broken strings remain in active prompt or status paths
|
||||||
|
- touched code files keep English comments only
|
||||||
|
- Quality scenario:
|
||||||
|
- Windows Korean environments should show readable build, test, and status output without mojibake feedback loops
|
||||||
|
|
||||||
|
## Priority
|
||||||
|
|
||||||
|
1. Phase 1: bootstrap and observability
|
||||||
|
2. Phase 2: working-set memory
|
||||||
|
3. Phase 3: task-aware pruning
|
||||||
|
4. Phase 4: tool-trace invariants
|
||||||
|
5. Phase 5: encoding and prompt cleanup
|
||||||
|
|
||||||
|
## Expected Outcome
|
||||||
|
|
||||||
|
- fewer repeated build-failure loops
|
||||||
|
- better structural consistency for project generation and large edits
|
||||||
|
- less drift in long-running Code tasks
|
||||||
|
- fewer quality losses caused by broken strings and low-signal context replacements
|
||||||
|
|
||||||
|
## Latest Delivery
|
||||||
|
|
||||||
|
Updated: 2026-04-16 01:41 (KST)
|
||||||
|
|
||||||
|
- Delivered in this pass:
|
||||||
|
- Phase 1 foundation:
|
||||||
|
- `ChatWindow.UtilityPresentation.cs` now bootstraps workspace context generation on first access and returns language-workflow fallback hints while `.ax-context.md` is still being generated.
|
||||||
|
- `AgentLoopService.cs` now records `query_context` workflow transitions with query-window, budget, supplemental-context, and working-set summaries.
|
||||||
|
- Phase 2 foundation:
|
||||||
|
- `CodeTaskWorkingSetService.cs` adds a Code-only structured ledger for:
|
||||||
|
- goal
|
||||||
|
- selected scaffold/profile
|
||||||
|
- created directories
|
||||||
|
- recent reads/writes
|
||||||
|
- latest diagnostics
|
||||||
|
- next repair focus
|
||||||
|
- the working set is injected into each Code request as a supplemental `code_working_set` system message.
|
||||||
|
- Phase 3 foundation:
|
||||||
|
- `AgentToolResultBudget.cs` and `AgentQueryContextBuilder.cs` now expose a `code` query profile with a larger protected-recent window and larger retained budgets for `build_run`, `test_loop`, `process`, `file_read`, `multi_read`, `lsp_code_intel`, and `git_tool`.
|
||||||
|
- Phase 4 observability step:
|
||||||
|
- `LlmService.ToolUse.cs` now logs sanitization counts for flattened assistant tool traces and converted orphan tool messages, so tool-trace repair frequency can be measured per run.
|
||||||
|
- Remaining follow-up:
|
||||||
|
- extend pre-request tool-trace validation so the flattening/orphan repair count trends toward zero rather than being logged after repair
|
||||||
|
- replace more mojibake prompt/status strings in active Code execution paths with English equivalents
|
||||||
|
|
||||||
|
Updated: 2026-04-16 01:57 (KST)
|
||||||
|
|
||||||
|
- Delivered in this pass:
|
||||||
|
- Phase 4 partial delivery:
|
||||||
|
- `AgentMessageInvariantHelper.cs` now normalizes historical tool traces before the request leaves the agent loop.
|
||||||
|
- structured assistant tool-call messages without matching `tool_result` now flatten into plain assistant transcript text.
|
||||||
|
- orphan `tool_result` messages now flatten into plain user transcript text instead of relying only on late OpenAI payload repair.
|
||||||
|
- `AgentLoopLlmRequestPreparationService.cs` clones the query window first, then applies normalization, so request cleanup does not mutate stored conversation history.
|
||||||
|
- `AgentLoopContextReliability.cs` now logs tool-trace repair counts inside the `query_context` transition for run-by-run observability.
|
||||||
|
- Phase 5 partial delivery:
|
||||||
|
- `SessionLearningCollector.cs` was rewritten with English-only comments and English injection text.
|
||||||
|
- `AgentLoopDiagnosticsFormatter.cs` no longer emits mojibake-prone compaction status text in active Code paths.
|
||||||
|
- Remaining follow-up:
|
||||||
|
- measure whether `tool_trace_repair` counts keep trending down in long Code runs after this preflight normalization
|
||||||
|
- continue replacing older mojibake strings outside the active Code execution path
|
||||||
|
|
||||||
|
Updated: 2026-04-16 02:05 (KST)
|
||||||
|
|
||||||
|
- Delivered in this pass:
|
||||||
|
- structural alignment step:
|
||||||
|
- `AgentLoopQueryAssemblyService.cs` now owns the staged query/history assembly path.
|
||||||
|
- `PrepareHistory(...)` handles session-learning refresh plus queued-command/query-window preparation.
|
||||||
|
- `PrepareRequest(...)` handles Code working-set supplemental context and request-message assembly before dispatch.
|
||||||
|
- `AgentLoopService.cs` now delegates those responsibilities instead of manually stitching them together inline.
|
||||||
|
- test and encoding hygiene step:
|
||||||
|
- `AgentLoopQueryAssemblyServiceTests.cs` locks the new staged assembly behavior.
|
||||||
|
- `SessionLearningCollectorTests.cs` was rewritten to English-only comments and assertions to match the new repository rule.
|
||||||
|
- Remaining follow-up:
|
||||||
|
- keep extracting more inline AgentLoop responsibilities into smaller staged services where it improves observability or retry correctness
|
||||||
|
- continue measuring long Code runs against claw-code-style continuity scenarios
|
||||||
|
|
||||||
|
Updated: 2026-04-16 02:13 (KST)
|
||||||
|
|
||||||
|
- Delivered in this pass:
|
||||||
|
- structural alignment step:
|
||||||
|
- `AgentLoopPreLlmStageService.cs` now owns the iteration decisions immediately before the LLM call.
|
||||||
|
- the service centralizes:
|
||||||
|
- thinking-summary selection
|
||||||
|
- Gemini free-tier delay planning
|
||||||
|
- user-prompt submit hook fingerprint/payload planning
|
||||||
|
- missing-tool guard shaping
|
||||||
|
- request assembly handoff
|
||||||
|
- `AgentLoopService.cs` now consumes that stage result instead of computing those branches inline.
|
||||||
|
- test coverage step:
|
||||||
|
- `AgentLoopPreLlmStageServiceTests.cs` now locks the new pre-LLM decision layer.
|
||||||
|
- Remaining follow-up:
|
||||||
|
- continue extracting the actual LLM dispatch / streaming callback branch into a narrower execution service
|
||||||
|
- compare long-running Code traces against claw-code-style staged transitions and keep reducing inline loop logic
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
- `skills/*.skill.md` 파일 수: 41
|
- `skills/*.skill.md` 파일 수: 41
|
||||||
- Slash 명령 매핑 엔트리 수: 85
|
- Slash 명령 매핑 엔트리 수: 85
|
||||||
|
|
||||||
## 4) claw-code 비교 포인트 (실행 반영 기준)
|
## 4) 핵심 명령 점검 포인트 (실행 반영 기준)
|
||||||
- claw-code 문서 기준 핵심 slash: `/permissions`, `/mcp` 확인
|
- 공통 운영 명령 기준: `/permissions`, `/mcp` 확인
|
||||||
- AX 현재 매핑에 `/permissions`, `/allowed-tools`, `/mcp`, `/chrome`, `/compact` 포함
|
- AX 현재 매핑에 `/permissions`, `/allowed-tools`, `/mcp`, `/chrome`, `/compact` 포함
|
||||||
- 다음 보강 우선순위:
|
- 다음 보강 우선순위:
|
||||||
1. 수동 시나리오 기반 MCP/Chrome 진단 품질 강화
|
1. 수동 시나리오 기반 MCP/Chrome 진단 품질 강화
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## 2. 재작성 기준
|
## 2. 재작성 기준
|
||||||
- 런처는 AX Agent 실행 품질을 끌어올리는 입력 허브로 재정의.
|
- 런처는 AX Agent 실행 품질을 끌어올리는 입력 허브로 재정의.
|
||||||
- claw-code의 명령 중심 워크플로우를 AX Commander UX에 맞게 흡수.
|
- 명령 중심 워크플로우를 AX Commander UX에 맞게 재구성.
|
||||||
|
|
||||||
## 3. 2026 실행 계획 (v0.7.3 ~ v0.8.x)
|
## 3. 2026 실행 계획 (v0.7.3 ~ v0.8.x)
|
||||||
|
|
||||||
|
|||||||
@@ -1,133 +1,80 @@
|
|||||||
# AX Copilot 통합 로드맵 (전면 재작성)
|
# AX Copilot 통합 로드맵
|
||||||
|
|
||||||
## 1. 보존 이력 (요약만 유지)
|
업데이트: 2026-04-15 18:20 (KST)
|
||||||
- v0.5.x: 에이전트/런처 핵심 기능 확장, MCP/스킬/검증 체계 도입.
|
|
||||||
- v0.6.0: 문서 생성 및 업무 자동화 도구군 확장.
|
|
||||||
- v0.7.0~v0.7.2: Plan Mode, 병렬 도구, 검증 게이트 및 안정화 강화.
|
|
||||||
|
|
||||||
## 2. 재작성 목표
|
## 1. 방향
|
||||||
- claw-code 수준의 실행 신뢰성, 세션 내구성, 결과 품질 확보.
|
- AX Copilot 전반의 실행 신뢰성, 세션 내구성, 결과 품질을 함께 끌어올립니다.
|
||||||
- AX Copilot 고유 제약(사내 운영모드, WPF UX, 로컬 우선)을 유지한 동등 품질 구현.
|
- 개발 문서는 비교 보고서가 아니라 AX 기준 계획과 검증 기록으로 유지합니다.
|
||||||
|
|
||||||
## 3. 마일스톤
|
## 2. 마일스톤
|
||||||
|
|
||||||
| 마일스톤 | 참조 대상 (`claw-code`) | AX 적용 위치 | 완료 조건 | 품질 판정 시나리오 |
|
| 마일스톤 | 참조 대상 | 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 훅/텍스트 훅 혼합 환경에서 실행 품질과 하위호환 유지 확인. |
|
| M1 (v0.7.3) Hook 계약 정식화 | `src/utils/hooks.ts`, `src/utils/hooks/hookEvents.ts`, `src/utils/permissions/PermissionUpdate.ts` | `AgentHookRunner.cs`, `AgentLoopService.cs`, `AppSettings.cs`, `SettingsViewModel.cs`, `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건. |
|
| M2 (v0.7.4 ~ v0.7.5) 세션/로그 내구성 | `src/utils/plans.ts`, `src/utils/sessionStorage.ts` | `ChatSessionStateService.cs`, `TaskRunService.cs`, `TaskRunStore.cs`, `ChatWindow.xaml.cs`, `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` | 도구 선택 실패 루프 억제. 실패 유형별 복구 흐름 정형화. | 오타/비활성/별칭 도구 요청이 반복 실패 없이 정상 경로로 수렴하는지 확인. |
|
| M3 (v0.7.6) 도구 선택/복구 안정화 | `src/commands.ts`, `src/Tool.ts`, `src/query.ts` | `AgentLoopService.cs`, `AgentLoopParallelExecution.cs`, `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` | `AgentLoopService.cs`, `AgentLoopTransitions.Execution.cs`, `AgentLoopTransitions.cs` | 증거 기반 완료 판정 고정, 릴리즈 게이트 자동화 강화 | 코드수정/문서생성/권한거부/복구 혼합 시나리오에서 조기 완료 없이 종료 |
|
||||||
|
|
||||||
## 4. 측정 지표
|
## 3. 측정 지표
|
||||||
- 반복 실패 루프 발생률.
|
- 반복 실패 루프 발생률
|
||||||
- 세션 재개 성공률.
|
- 세션 재개 성공률
|
||||||
- 완료 응답 근거 누락률.
|
- 완료 응답 근거 누락률
|
||||||
- 빌드/테스트 통과율.
|
- 빌드/테스트 통과율
|
||||||
|
|
||||||
## 5. 운영 원칙
|
## 4. 운영 원칙
|
||||||
- `internal` 모드: 외부 유출 경로 차단 우선.
|
- `internal` 모드에서는 외부 유출 경로 차단 우선
|
||||||
- 신규 기능은 반드시 설정값 연결 + 실제 동작 반영.
|
- 신규 기능은 설정값 연결과 실제 동작 반영을 함께 완료
|
||||||
- 문서 마스터는 `.md`, HTML은 요청 시만 갱신.
|
- 문서 마스터는 `.md`, HTML은 요청 시만 갱신
|
||||||
|
|
||||||
## 6. 즉시 실행 백로그
|
## 5. 즉시 실행 백로그
|
||||||
1. Hook 계약 확장 마무리 및 회귀 테스트.
|
1. Hook 계약 확장 마무리 및 회귀 테스트
|
||||||
2. 세션 복원 경계조건 테스트 세트 추가.
|
2. 세션 복원 경계조건 테스트 세트 추가
|
||||||
3. 이벤트 로그 스키마 고정 및 분석 리포트 템플릿 정리.
|
3. 이벤트 로그 스키마 고정 및 분석 리포트 템플릿 정리
|
||||||
|
|
||||||
## 7. 2026-04-03 실행 증적 동기화 (M4 포함)
|
## 6. 실행 증적 기준
|
||||||
- 기준 시점: 2026-04-03.
|
- 기준 시점: 2026-04-03
|
||||||
- 테스트: `dotnet test` 379/379 통과.
|
- 전체 테스트: `379/379` 통과
|
||||||
- M1 증적: Hook 계약 필드(`updatedInput`, `updatedPermissions`, `additionalContext`) 반영 경로 구현 완료.
|
- Hook 계약 필드 반영 경로 구현 완료
|
||||||
- M2 증적: run 복원/이력 재구성(`RestoreRecentFromExecutionEvents`, `RestoreCurrentAgentRun`, plan 이력 조회) 구현 및 테스트 존재.
|
- run 복원/이력 재구성 경로 구현 및 테스트 존재
|
||||||
- M3 증적: unknown-tool 복구 루프/결정 이벤트 처리 경로 구현 및 테스트 존재.
|
- unknown-tool 복구 루프와 완료 게이트 경로 구현 및 테스트 존재
|
||||||
- M4 증적: 실행 종료 품질 게이트(`CodeQualityGate`, `ExecutionSuccessGate`, `TerminalEvidenceGate`) 반영 및 관련 테스트 존재.
|
- 스킬/도구 운영 정렬: `allowed-tools` 전건 명시, Python 의존 경로 fallback 반영, 레거시 도구명 참조 제거
|
||||||
- 스킬/도구 운영 정렬: 40개 스킬 모두 `allowed-tools` 명시 완료, Python 의존 10개는 fallback 분기 적용, 레거시 도구명(`process_run`, `grep_tool`) 참조 제거.
|
|
||||||
|
|
||||||
## 8. 남은 마감 항목 (M4 종료 조건)
|
## 7. 벤치마크 기준
|
||||||
1. 세션 replay 불일치 0건을 재현 시나리오별 수치로 기록.
|
- 기준 문서: `docs/AX_AGENT_QUALITY_PLAN.md`
|
||||||
2. 위 결과를 배포 전 체크리스트로 고정하고 매 릴리즈마다 수치 동기화.
|
- 고정 시나리오:
|
||||||
|
- 기본 loop
|
||||||
|
- unknown-tool 복구
|
||||||
|
- plan 연결
|
||||||
|
- 권한 거부
|
||||||
|
- hook 입력 변형
|
||||||
|
- allowed-tools 강제
|
||||||
|
- hook filter
|
||||||
|
- 합격 게이트:
|
||||||
|
- 빌드 경고·오류 0
|
||||||
|
- 전체 테스트 통과
|
||||||
|
- 고정 시나리오 회귀 통과
|
||||||
|
|
||||||
## 9. 벤치마크 고정 기준 (CLAW 패리티 동기화)
|
## 8. 실행 프레임
|
||||||
- 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 12절.
|
|
||||||
- 고정 시나리오: 기본 loop / unknown-tool 복구 / plan 연결 / 권한 거부 / hook 입력 변형 / allowed-tools 강제 / hook filter.
|
|
||||||
- 합격 게이트: 빌드 경고·오류 0 + 전체 테스트 통과 + 고정 시나리오 회귀 통과.
|
|
||||||
|
|
||||||
## 10. Replay 안정성 검증 연동
|
### 입력 계층
|
||||||
- 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 13절.
|
- slash 명령 카탈로그와 매칭 상태를 별도 파일로 분리
|
||||||
- 테스트 태그: `Suite=ReplayStability`.
|
- 입력 파싱 변경은 회귀 테스트와 함께 반영
|
||||||
- 운영 기준: 릴리즈 전 `ReplayStability` 시나리오 전건 통과 시 replay 불일치 0건으로 판정.
|
|
||||||
- 최신 실행 증적(2026-04-03): `ParityBenchmark 13/13`, `ReplayStability 14/14`, 전체 `379/379`.
|
|
||||||
- 실행 자동화: `scripts/release-gate.ps1`로 빌드/벤치마크/리플레이/전체 테스트를 일괄 점검.
|
|
||||||
|
|
||||||
## 11. 권한 Hook 계약 고정 (M1 완료 기준)
|
### 권한 UX
|
||||||
- lifecycle hook 키:
|
- 권한 모드 명칭, 설명, 색, 정렬 순서를 단일 소스에서 관리
|
||||||
- `__permission_request__` (pre)
|
- 팝업, 상태 배너, 슬래시 출력이 같은 용어를 사용
|
||||||
- `__permission_granted__` (post)
|
|
||||||
- `__permission_denied__` (post)
|
|
||||||
- payload 기준 필드: `runId`, `tool`, `target`, `permission`, `granted`, `reason`.
|
|
||||||
- 실행 우선순위:
|
|
||||||
1. Hook의 `updatedPermissions`를 현재 run 권한 컨텍스트에 즉시 반영.
|
|
||||||
2. 반영 후 `CheckToolPermissionAsync()`로 최종 권한 판정 수행.
|
|
||||||
3. hook 예외/실패는 non-blocking으로 처리하고 권한 흐름은 지속.
|
|
||||||
4. `additionalContext`는 가능한 경로에서 실행 메시지 컨텍스트에 병합.
|
|
||||||
|
|
||||||
|
### 설정 동기화
|
||||||
|
- Cowork/Code 설정 분기를 실행 코드까지 일치
|
||||||
|
- 설정 변경 즉시 반영 시나리오를 체크리스트화
|
||||||
|
|
||||||
|
### 통합 회귀
|
||||||
|
- `internal/external`, permission deny/recover, mcp reconnect, compact 수동/자동 흐름을 함께 검증
|
||||||
|
|
||||||
|
### 업무형 UI 마감
|
||||||
|
- 좌측 패널, 컴포저, 권한 팝업 밀도와 탐색 동작을 단순하고 일관된 패턴으로 정리
|
||||||
|
|
||||||
## 2026-04-04 추가 계획 (Codex/Claude 추격 로드맵 재정렬)
|
## 9. 최근 진행 메모
|
||||||
|
- 2026-04-15 08:32 (KST): 루프 품질은 장기 상태 고정과 세분화 중심 과제로 축소
|
||||||
업데이트: 2026-04-04 13:24 (KST)
|
- 2026-04-15 09:49 (KST): 언어별 workflow 주입과 문서 포맷 critic/repair 루프를 다음 우선순위로 정리
|
||||||
|
- 2026-04-15 10:05 (KST): queued command projector, run lifecycle helper, quality formatter 확장을 다음 배치로 고정
|
||||||
### 기준 소스
|
|
||||||
- claw-code/src/commands.ts
|
|
||||||
- claw-code/src/utils/permissions/PermissionMode.ts
|
|
||||||
- claw-code/src/components/PromptInput/PromptInput.tsx
|
|
||||||
|
|
||||||
### 현재 AX 격차 요약
|
|
||||||
1. 입력/슬래시/권한 처리 로직이 ChatWindow 단일 파일에 상대적으로 집중되어 변경 파급도가 큼.
|
|
||||||
2. 설정-런타임-UI 동기화는 기능은 많지만 구조적 분리가 부족해 회귀 리스크가 높음.
|
|
||||||
3. Agent loop 품질 게이트는 확보됐으나, UI 상호작용 회귀 시나리오와 결합된 자동 검증이 더 필요함.
|
|
||||||
|
|
||||||
### 연속 실행 목표 (L1~L5)
|
|
||||||
- **L1. 입력 계층 모듈화**
|
|
||||||
- 목표: slash 명령 카탈로그/매칭/선택 상태를 분리해 ChatWindow 결합도 축소
|
|
||||||
- 완료 기준: slash 관련 핵심 로직을 독립 파일로 분리하고 기존 slash 테스트 통과
|
|
||||||
- **L2. 권한 UX/로직 단일 카탈로그화**
|
|
||||||
- 목표: 권한 모드 명칭/설명/색/정렬 순서를 단일 소스에서 제공
|
|
||||||
- 완료 기준: 팝업/상태 배너/슬래시 출력에서 동일 용어·동일 순서 유지
|
|
||||||
- **L3. 설정 동기화 강화**
|
|
||||||
- 목표: Cowork/Code 설정 분기를 실행 코드 경로까지 일치시켜 회귀 감소
|
|
||||||
- 완료 기준: 설정 변경 즉시 반영 시나리오 체크리스트 전건 통과
|
|
||||||
- **L4. Agentic loop + UI 결합 회귀 테스트 보강**
|
|
||||||
- 목표: internal/external, permission deny/recover, mcp reconnect, compact 수동/자동 흐름을 통합 검증
|
|
||||||
- 완료 기준: 릴리즈 게이트용 통합 테스트 세트 추가 및 통과
|
|
||||||
- **L5. Codex/Claude형 UI 마감**
|
|
||||||
- 목표: 좌측 패널/컴포저/권한 팝업 밀도와 탐색 동작을 단순·일관 패턴으로 정리
|
|
||||||
- 완료 기준: UI 체크리스트 주요 항목 100% 충족 + 빌드 경고/오류 0
|
|
||||||
|
|
||||||
### 이번 턴 즉시 반영 (L1 시작)
|
|
||||||
- ChatWindow의 대형 slash 명령 사전을 SlashCommandCatalog로 분리.
|
|
||||||
- ChatWindow는 카탈로그 API(MatchBuiltinCommands, TryGetEntry)를 통해 조회하도록 전환.
|
|
||||||
- 결과: 입력 계층 결합도 감소 + 향후 slash 확장/정리 시 변경 범위 축소.
|
|
||||||
|
|
||||||
## 2026-04-04 실행 프레임 고정 (중기 계획)
|
|
||||||
|
|
||||||
업데이트: 2026-04-04 14:47 (KST)
|
|
||||||
|
|
||||||
### 3축 고정 운영
|
|
||||||
1. 로직 안정화: 권한/운영모드/compact/모델 연결 경로를 테스트 우선으로 보강.
|
|
||||||
2. 기능 동등성: claw-code 기준 주요 명령(/compact, /permissions, /mcp, /chrome)을 실행 시나리오로 검증.
|
|
||||||
3. UX 정렬: 상단/컴포저/팝업 밀도는 로직 안정화 완료 범위 내에서만 단계 반영.
|
|
||||||
|
|
||||||
### 이번 사이클 기준 완료 조건
|
|
||||||
- 빌드: `dotnet build` 경고 0 / 오류 0.
|
|
||||||
- 핵심 회귀: 운영모드, 권한, slash, 모델연결, compact 관련 필터 테스트 통과.
|
|
||||||
- 문서: `README.md`, `docs/DEVELOPMENT.md`에 시간 포함 이력 동기화 후 커밋/푸시.
|
|
||||||
|
|
||||||
### 이번 사이클 실제 보강 항목
|
|
||||||
- `ContextCondenserTests` 추가:
|
|
||||||
- proactive 비활성 시 무변경 확인
|
|
||||||
- 대용량 tool_result 축약 동작 확인
|
|
||||||
- `LlmRuntimeOverrideTests` 보강:
|
|
||||||
- vLLM 암호화 API키 런타임 복호화 검증
|
|
||||||
- 등록모델/전역 TLS 우회 플래그 합성 규칙 검증
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# 도구 / 슬래시 커버리지 현황
|
||||||
|
|
||||||
|
업데이트: 2026-04-15 18:20 (KST)
|
||||||
|
|
||||||
|
## 1. 요약
|
||||||
|
- AX slash count: 85
|
||||||
|
- 공통 핵심 명령 수: 7
|
||||||
|
- AX 확장 명령 수: 78
|
||||||
|
|
||||||
|
## 2. 공통 핵심 명령
|
||||||
|
- `/clear`
|
||||||
|
- `/exit`
|
||||||
|
- `/help`
|
||||||
|
- `/init`
|
||||||
|
- `/mcp`
|
||||||
|
- `/memory`
|
||||||
|
- `/permissions`
|
||||||
|
|
||||||
|
## 3. AX 확장 명령
|
||||||
|
- `/add-dir`
|
||||||
|
- `/advisor`
|
||||||
|
- `/agents`
|
||||||
|
- `/allowed-tools`
|
||||||
|
- `/branch`
|
||||||
|
- `/btw`
|
||||||
|
- `/build`
|
||||||
|
- `/chrome`
|
||||||
|
- `/color`
|
||||||
|
- `/commit`
|
||||||
|
- `/compact`
|
||||||
|
- `/config`
|
||||||
|
- `/context`
|
||||||
|
- `/copy`
|
||||||
|
- `/cost`
|
||||||
|
- `/desktop`
|
||||||
|
- `/diff`
|
||||||
|
- `/doctor`
|
||||||
|
- `/effort`
|
||||||
|
- `/explain`
|
||||||
|
- `/export`
|
||||||
|
- `/fast`
|
||||||
|
- `/feedback`
|
||||||
|
- `/files`
|
||||||
|
- `/fix`
|
||||||
|
- `/heapdump`
|
||||||
|
- `/hooks`
|
||||||
|
- `/ide`
|
||||||
|
- `/init-verifiers`
|
||||||
|
- `/install-github-app`
|
||||||
|
- `/install-slack-app`
|
||||||
|
- `/keybindings`
|
||||||
|
- `/login`
|
||||||
|
- `/logout`
|
||||||
|
- `/mobile`
|
||||||
|
- `/model`
|
||||||
|
- `/new`
|
||||||
|
- `/output-style`
|
||||||
|
- `/passes`
|
||||||
|
- `/plan`
|
||||||
|
- `/plugin`
|
||||||
|
- `/pr`
|
||||||
|
- `/pr-comments`
|
||||||
|
- `/privacy-settings`
|
||||||
|
- `/rate-limit-options`
|
||||||
|
- `/release-notes`
|
||||||
|
- `/reload-plugins`
|
||||||
|
- `/remote-env`
|
||||||
|
- `/rename`
|
||||||
|
- `/reset`
|
||||||
|
- `/resume`
|
||||||
|
- `/review`
|
||||||
|
- `/rewind`
|
||||||
|
- `/sandbox-toggle`
|
||||||
|
- `/search`
|
||||||
|
- `/security-review`
|
||||||
|
- `/session`
|
||||||
|
- `/settings`
|
||||||
|
- `/skills`
|
||||||
|
- `/stats`
|
||||||
|
- `/status`
|
||||||
|
- `/statusline`
|
||||||
|
- `/stickers`
|
||||||
|
- `/structure`
|
||||||
|
- `/summary`
|
||||||
|
- `/tag`
|
||||||
|
- `/tasks`
|
||||||
|
- `/terminal-setup`
|
||||||
|
- `/test`
|
||||||
|
- `/theme`
|
||||||
|
- `/thinkback`
|
||||||
|
- `/thinkback-play`
|
||||||
|
- `/translate`
|
||||||
|
- `/ultrareview`
|
||||||
|
- `/upgrade`
|
||||||
|
- `/usage`
|
||||||
|
- `/verify`
|
||||||
|
- `/vim`
|
||||||
|
|
||||||
|
## 4. 메모
|
||||||
|
- AX source baseline: `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
||||||
|
- 이 문서는 비교 보고서가 아니라 현재 AX가 제공하는 명령 범위와 공통 핵심 명령을 확인하기 위한 운영 문서입니다.
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
# Tool/Slash Parity Report
|
|
||||||
|
|
||||||
Generated: 2026-04-04 02:06:25
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
- AX slash count: 85
|
|
||||||
- claw-code slash count: 7
|
|
||||||
- Common: 7
|
|
||||||
- AX only: 78
|
|
||||||
- claw-code only: 0
|
|
||||||
|
|
||||||
## Common
|
|
||||||
- /clear
|
|
||||||
- /exit
|
|
||||||
- /help
|
|
||||||
- /init
|
|
||||||
- /mcp
|
|
||||||
- /memory
|
|
||||||
- /permissions
|
|
||||||
|
|
||||||
## AX Only
|
|
||||||
- /add-dir
|
|
||||||
- /advisor
|
|
||||||
- /agents
|
|
||||||
- /allowed-tools
|
|
||||||
- /branch
|
|
||||||
- /btw
|
|
||||||
- /build
|
|
||||||
- /chrome
|
|
||||||
- /color
|
|
||||||
- /commit
|
|
||||||
- /compact
|
|
||||||
- /config
|
|
||||||
- /context
|
|
||||||
- /copy
|
|
||||||
- /cost
|
|
||||||
- /desktop
|
|
||||||
- /diff
|
|
||||||
- /doctor
|
|
||||||
- /effort
|
|
||||||
- /explain
|
|
||||||
- /export
|
|
||||||
- /fast
|
|
||||||
- /feedback
|
|
||||||
- /files
|
|
||||||
- /fix
|
|
||||||
- /heapdump
|
|
||||||
- /hooks
|
|
||||||
- /ide
|
|
||||||
- /init-verifiers
|
|
||||||
- /install-github-app
|
|
||||||
- /install-slack-app
|
|
||||||
- /keybindings
|
|
||||||
- /login
|
|
||||||
- /logout
|
|
||||||
- /mobile
|
|
||||||
- /model
|
|
||||||
- /new
|
|
||||||
- /output-style
|
|
||||||
- /passes
|
|
||||||
- /plan
|
|
||||||
- /plugin
|
|
||||||
- /pr
|
|
||||||
- /pr-comments
|
|
||||||
- /privacy-settings
|
|
||||||
- /rate-limit-options
|
|
||||||
- /release-notes
|
|
||||||
- /reload-plugins
|
|
||||||
- /remote-env
|
|
||||||
- /rename
|
|
||||||
- /reset
|
|
||||||
- /resume
|
|
||||||
- /review
|
|
||||||
- /rewind
|
|
||||||
- /sandbox-toggle
|
|
||||||
- /search
|
|
||||||
- /security-review
|
|
||||||
- /session
|
|
||||||
- /settings
|
|
||||||
- /skills
|
|
||||||
- /stats
|
|
||||||
- /status
|
|
||||||
- /statusline
|
|
||||||
- /stickers
|
|
||||||
- /structure
|
|
||||||
- /summary
|
|
||||||
- /tag
|
|
||||||
- /tasks
|
|
||||||
- /terminal-setup
|
|
||||||
- /test
|
|
||||||
- /theme
|
|
||||||
- /thinkback
|
|
||||||
- /thinkback-play
|
|
||||||
- /translate
|
|
||||||
- /ultrareview
|
|
||||||
- /upgrade
|
|
||||||
- /usage
|
|
||||||
- /verify
|
|
||||||
- /vim
|
|
||||||
|
|
||||||
## claw-code Only
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
- claw-code source baseline: claw-code/en/quickstart.md
|
|
||||||
- AX source baseline: src/AxCopilot/Views/ChatWindow.xaml.cs
|
|
||||||
@@ -1,523 +0,0 @@
|
|||||||
# Claw Code Parity Plan (Rewritten)
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
- Align AX Copilot with claw-code quality for loop reliability, permission/hook behavior, and session durability.
|
|
||||||
|
|
||||||
## Update
|
|
||||||
- Updated: 2026-04-05 15:34 (KST)
|
|
||||||
- Rebased the AX Agent improvement plan on actual `claw-code` runtime files instead of earlier AX snapshots. The reference spine is now `src/bootstrap/state.ts -> src/bridge/initReplBridge.ts -> src/bridge/sessionRunner.ts -> src/screens/REPL.tsx -> src/components/Messages.tsx -> src/components/StatusLine.tsx`.
|
|
||||||
- AX Agent work should follow that same quality order: state first, execution second, render last. UI-only fixes that bypass state/execution should be treated as temporary.
|
|
||||||
- Updated: 2026-04-05 16:55 (KST)
|
|
||||||
- Current estimated parity vs `claw-code`: core execution engine `82%`, main chat UI `68%`, Cowork/Code status UX `63%`, internal settings linkage `88%`, overall AX Agent `74%`.
|
|
||||||
- Engine-affecting settings should be handled conservatively during parity work. If a setting changes the main execution route, approval flow, or recovery behavior without representing a stable real-world user choice, it should be moved to developer-only UI or removed from user-facing surfaces.
|
|
||||||
- Updated: 2026-04-06 09:36 (KST)
|
|
||||||
- Progressed the maintainability track by moving runtime strip styling into `OperationalStatusPresentationCatalog.cs`, expanding permission/tool-result transcript catalogs with typed descriptions, and removing the stale plan-mode presentation branch from permission UI surfaces. The next structural focus remains footer/status/composer presentation slimming and regression ritual enforcement.
|
|
||||||
- Updated: 2026-04-06 09:44 (KST)
|
|
||||||
- Continued the maintainability track by splitting mixed inline interaction rendering into `ChatWindow.UserAskPresentation.cs` and `ChatWindow.PlanApprovalPresentation.cs`. This reduces message-type coupling inside the main window and keeps the next focus on footer/composer presentation and regression-routine formalization.
|
|
||||||
- Updated: 2026-04-06 09:58 (KST)
|
|
||||||
- Continued the maintainability track by splitting Git branch popup and footer-adjacent summary helpers into `ChatWindow.GitBranchPresentation.cs`, leaving `ChatWindow.FooterPresentation.cs` focused on folder bar state and preset-guide sync only.
|
|
||||||
- Formalized the regression ritual in `docs/AX_AGENT_REGRESSION_PROMPTS.md` by adding failure classes (`blank-reply`, `duplicate-banner`, `bad-approval-flow`, `queue-drift`, `restore-drift`, `status-noise`) and required prompt bundles per change area.
|
|
||||||
- Updated: 2026-04-06 10:07 (KST)
|
|
||||||
- Continued the maintainability track by moving topic preset rendering, custom preset context menus, and topic-selection application flow into `ChatWindow.TopicPresetPresentation.cs`. This reduces mixed preset UI logic inside `ChatWindow.xaml.cs` and keeps the main window closer to orchestration-only responsibility.
|
|
||||||
- Updated: 2026-04-06 11:52 (KST)
|
|
||||||
- Continued the tool/permission/skill sophistication track by expanding AX presentation catalogs toward `claw-code` specificity. `PermissionRequestPresentationCatalog.cs` now models action-level permission kinds plus severity/action hints, `ToolResultPresentationCatalog.cs` now models `approval_required`/`partial` result states plus follow-up guidance, and the AX skill gallery now exposes runtime-policy metadata that was previously hidden.
|
|
||||||
|
|
||||||
## Preserved History (Summary)
|
|
||||||
- Core loop guards and post-tool verification gates are already partially implemented.
|
|
||||||
- Plan Mode, parallel tool execution, and unknown-tool recovery are in place.
|
|
||||||
- Session restore hardening is ongoing.
|
|
||||||
|
|
||||||
## Reference Map
|
|
||||||
|
|
||||||
| claw-code reference | AX apply target | completion criteria | quality criteria |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `src/bootstrap/state.ts` | `src/AxCopilot/Views/ChatWindow.xaml.cs`, `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`, `src/AxCopilot/Services/ChatStorageService.cs` | one canonical runtime/session state for current turn, queue, retry, execution events, and persisted snapshot | reopen/retry/queue flows do not create duplicate or blank assistant messages |
|
|
||||||
| `src/bridge/initReplBridge.ts` | `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`, `src/AxCopilot/Services/LlmService.cs` | send/regenerate/retry/queued follow-up/slash all enter through one prepared-execution path | same input under same settings takes same execution route regardless of entry point |
|
|
||||||
| `src/bridge/sessionRunner.ts` | `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs` | tool start/result/error/progress normalized once inside loop layer | Cowork/Code no longer flash repeated status strings or overshare debug payloads |
|
|
||||||
| `src/bridge/bridgeMessaging.ts` | `src/AxCopilot/Views/ChatWindow.xaml.cs`, `src/AxCopilot/Services/Agent/AgentLoopService.cs` | inbound execution events separated from display-only events before UI render | execution event replay does not duplicate visible timeline banners |
|
|
||||||
| `src/screens/REPL.tsx` | `src/AxCopilot/Views/ChatWindow.xaml`, `src/AxCopilot/Views/ChatWindow.xaml.cs` | screen state transitions, queue flow, retry flow, and composer state use shared runtime helpers | window resize, queue chaining, and retry feel stable instead of UI-patched |
|
|
||||||
| `src/components/Messages.tsx` | `src/AxCopilot/Views/ChatWindow.xaml.cs` | timeline derives from normalized conversation/session state only | no token-only completions, blank cards, or direct injected duplicates |
|
|
||||||
| `src/components/StatusLine.tsx` | `src/AxCopilot/Views/ChatWindow.xaml`, `src/AxCopilot/Views/ChatWindow.xaml.cs` | status strip computed from debounced runtime state, not multiple imperative refresh calls | metadata stays lightweight and does not overpower message timeline |
|
|
||||||
|
|
||||||
## AX Agent Improvement Phases
|
|
||||||
|
|
||||||
### Phase A. Runtime State Canonicalization
|
|
||||||
- Reference: `src/bootstrap/state.ts`
|
|
||||||
- AX apply location: `src/AxCopilot/Views/ChatWindow.xaml.cs`, `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`, `src/AxCopilot/Services/ChatStorageService.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- `Chat`, `Cowork`, `Code` all update one shared runtime/session state model.
|
|
||||||
- queue, retry, post-compaction, and execution-event state can be restored after reopen.
|
|
||||||
- Quality criteria:
|
|
||||||
- reopening a conversation reproduces the same visible timeline without extra assistant cards.
|
|
||||||
- queue and execution badges remain in sync with the stored conversation.
|
|
||||||
|
|
||||||
### Phase B. Prepared Execution Unification
|
|
||||||
- Reference: `src/bridge/initReplBridge.ts`
|
|
||||||
- AX apply location: `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`, `src/AxCopilot/Services/LlmService.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- prompt stack assembly, execution mode choice, and final assistant commit are engine-owned.
|
|
||||||
- send/regenerate/retry/queued follow-up/slash flows all call the same preparation API.
|
|
||||||
- Quality criteria:
|
|
||||||
- behavior is deterministic per tab/settings combination.
|
|
||||||
- UI stops building different prompt stacks for the same conversation state.
|
|
||||||
|
|
||||||
### Phase C. AgentLoop Event Normalization
|
|
||||||
- Reference: `src/bridge/sessionRunner.ts`, `src/bridge/bridgeMessaging.ts`
|
|
||||||
- AX apply location: `src/AxCopilot/Services/Agent/AgentLoopService.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.cs`, `src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- loop events are normalized into bounded activity/event records before UI consumption.
|
|
||||||
- permission requests, failure states, retries, and completion states use a stable event shape.
|
|
||||||
- Quality criteria:
|
|
||||||
- Cowork/Code no longer flash rapidly during long-running tool sequences.
|
|
||||||
- file path/debug detail remains collapsed by default.
|
|
||||||
|
|
||||||
### Phase D. Timeline Render Parity
|
|
||||||
- Reference: `src/screens/REPL.tsx`, `src/components/Messages.tsx`
|
|
||||||
- AX apply location: `src/AxCopilot/Views/ChatWindow.xaml`, `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- assistant/user messages, execution logs, compact boundaries, and queue summaries are rendered from one derived timeline model.
|
|
||||||
- direct imperative bubble injection is removed from normal send/regenerate/retry flows.
|
|
||||||
- Quality criteria:
|
|
||||||
- no blank assistant cards.
|
|
||||||
- no token-only completion without visible content.
|
|
||||||
- no duplicate event banners after re-render.
|
|
||||||
|
|
||||||
### Phase E. Composer and Status Strip Simplification
|
|
||||||
- Reference: `src/screens/REPL.tsx`, `src/components/StatusLine.tsx`
|
|
||||||
- AX apply location: `src/AxCopilot/Views/ChatWindow.xaml`, `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- composer height grows only on explicit line breaks.
|
|
||||||
- status strip, queue summary, and runtime activity all use debounced runtime updates.
|
|
||||||
- Chat/Cowork/Code share one responsive width calculation policy.
|
|
||||||
- Quality criteria:
|
|
||||||
- resizing feels natural.
|
|
||||||
- composer does not keep growing after send.
|
|
||||||
- metadata remains subordinate to the message timeline.
|
|
||||||
|
|
||||||
### Phase F. Recovery, Resume, and Verification
|
|
||||||
- Reference: `src/bootstrap/state.ts`, `src/bridge/sessionRunner.ts`, `src/screens/REPL.tsx`
|
|
||||||
- AX apply location: `src/AxCopilot/Views/ChatWindow.xaml.cs`, `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`, `src/AxCopilot/Services/ChatStorageService.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- reopen after interruption keeps queue, runtime summary, and latest visible assistant state consistent.
|
|
||||||
- retry-last and regenerate do not depend on mutating `InputBox.Text`.
|
|
||||||
- all three tabs pass reopen/retry/manual compact/manual stop/manual resume scenarios.
|
|
||||||
- Quality criteria:
|
|
||||||
- stored conversation and rendered conversation stay identical after restore.
|
|
||||||
- final reopened state matches the last completed runtime state.
|
|
||||||
|
|
||||||
## Execution Tracks
|
|
||||||
1. Hook contract parity
|
|
||||||
- Structured hook output support (`updatedInput`, `updatedPermissions`, `additionalContext`).
|
|
||||||
- Runtime gating through settings toggles.
|
|
||||||
|
|
||||||
2. Session/state parity
|
|
||||||
- Deterministic run resume rules.
|
|
||||||
- Stable jsonl event schema + replay compatibility.
|
|
||||||
|
|
||||||
3. Recovery parity
|
|
||||||
- Failure-type classification and standardized retry guidance.
|
|
||||||
- Reduced repeated wrong-tool loops.
|
|
||||||
|
|
||||||
4. Completion parity
|
|
||||||
- Evidence-based finalization criteria for code/document tasks.
|
|
||||||
|
|
||||||
## Done Criteria
|
|
||||||
- Internal parity scenarios pass target threshold.
|
|
||||||
- Resume/replay failures: zero.
|
|
||||||
- `dotnet build` warnings/errors: zero.
|
|
||||||
|
|
||||||
## Validation Matrix
|
|
||||||
- Build: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\`
|
|
||||||
- Manual scenario 1: Chat send -> answer visible -> retry -> regenerate -> reopen conversation
|
|
||||||
- Manual scenario 2: Cowork tool run -> progress summary -> completion -> queue next request -> reopen
|
|
||||||
- Manual scenario 3: Code task with execution log noise -> completion -> compact -> next turn -> reopen
|
|
||||||
- Manual scenario 4: AX Agent internal settings change -> immediate runtime reflection without layout regression
|
|
||||||
|
|
||||||
## Canonical Prompt Set
|
|
||||||
- Updated: 2026-04-05 22:04 (KST)
|
|
||||||
- The following prompt set should be used for AX vs `claw-code` parity checks. The goal is not byte-identical output, but equivalent execution route, approval behavior, and artifact/result quality.
|
|
||||||
- Operational checklist copy: `docs/AX_AGENT_REGRESSION_PROMPTS.md`
|
|
||||||
|
|
||||||
1. Chat basic answer
|
|
||||||
- Prompt: `회의 일정 조정 메일을 정중한 한국어로 써줘`
|
|
||||||
- Apply to: `Chat`
|
|
||||||
- Verify: normal reply render, retry/regenerate stability, reopen durability
|
|
||||||
|
|
||||||
2. Chat long-form explanation
|
|
||||||
- Prompt: `RAG와 fine-tuning 차이를 실무 관점으로 7가지로 설명해줘`
|
|
||||||
- Apply to: `Chat`
|
|
||||||
- Verify: long response rendering, compaction follow-up continuity
|
|
||||||
|
|
||||||
3. Cowork document task
|
|
||||||
- Prompt: `신규 ERP 도입 제안서 초안을 작성해줘. 목적, 범위, 기대효과, 추진일정 포함`
|
|
||||||
- Apply to: `Cowork`
|
|
||||||
- Verify: topic/task preset routing, plan-first execution, actual document-oriented output path
|
|
||||||
|
|
||||||
4. Cowork data task
|
|
||||||
- Prompt: `매출 CSV를 분석해서 월별 추세와 이상치를 요약해줘`
|
|
||||||
- Apply to: `Cowork`
|
|
||||||
- Verify: data-analysis tool choice, reduced runtime noise, final summary quality
|
|
||||||
|
|
||||||
5. Code bug-fix task
|
|
||||||
- Prompt: `현재 프로젝트에서 설정 저장 버그 원인 찾고 수정해줘`
|
|
||||||
- Apply to: `Code`
|
|
||||||
- Verify: read/search/edit path, diff persistence, reopen consistency
|
|
||||||
|
|
||||||
6. Code build/test task
|
|
||||||
- Prompt: `빌드 오류를 재현하고 수정한 뒤 다시 빌드해줘`
|
|
||||||
- Apply to: `Code`
|
|
||||||
- Verify: build/test loop, failure retry, final completion message
|
|
||||||
|
|
||||||
7. Queued follow-up
|
|
||||||
- Prompt sequence:
|
|
||||||
- `이 창 레이아웃 문제 원인 찾아줘`
|
|
||||||
- `끝나면 README도 같이 갱신해줘`
|
|
||||||
- Apply to: `Cowork`, `Code`
|
|
||||||
- Verify: queue chaining, next-turn pickup without UI mutation
|
|
||||||
|
|
||||||
8. Post-compaction continuity
|
|
||||||
- Prompt: `지금까지 논의한 내용을 5줄로 이어서 정리하고 다음 작업 제안해줘`
|
|
||||||
- Apply to: `Chat`, `Cowork`, `Code`
|
|
||||||
- Verify: compact-after-next-turn continuity, no token-only completion
|
|
||||||
|
|
||||||
9. Permission approval
|
|
||||||
- Prompt: `이 파일을 수정해서 저장해줘`
|
|
||||||
- Apply to: `Code`
|
|
||||||
- Verify: permission request, approve/reject rendering, final transcript consistency
|
|
||||||
|
|
||||||
10. Slash / skill entry
|
|
||||||
- Prompt: `/bug-hunt src 폴더 잠재 버그 찾아줘`
|
|
||||||
- Apply to: `Code`
|
|
||||||
- Verify: slash entry uses the same prepared-execution route as normal send
|
|
||||||
|
|
||||||
## Tool / Skill Delta Snapshot
|
|
||||||
- Updated: 2026-04-05 22:04 (KST)
|
|
||||||
- AX tool registry count is larger than `claw-code`, but the shape is different.
|
|
||||||
- AX reference: `src/AxCopilot/Services/Agent/ToolRegistry.cs`
|
|
||||||
- `claw-code` reference: `src/tools/*`, `src/skills/bundledSkills.ts`
|
|
||||||
|
|
||||||
### AX stronger areas
|
|
||||||
- Document/office generation and conversion (`ExcelSkill`, `DocxSkill`, `PptxSkill`, `DocumentPlannerTool`, `DocumentAssemblerTool`)
|
|
||||||
- Data/business utilities (`DataPivotTool`, `SqlTool`, `FormatConvertTool`, `TextSummarizeTool`)
|
|
||||||
- WPF-integrated enterprise UX and Korean workflow presets
|
|
||||||
|
|
||||||
### claw-code stronger areas
|
|
||||||
- Transcript-native tool use / rejection / approval message taxonomy
|
|
||||||
- Plan approval request/response rendering in the message stream
|
|
||||||
- Permission and tool-result message consistency
|
|
||||||
- Bundled skill registry and skill message integration
|
|
||||||
|
|
||||||
### Remaining parity target
|
|
||||||
- Keep AX's richer business/document tool set
|
|
||||||
- Bring transcript rendering and approval/status UX closer to `claw-code`
|
|
||||||
|
|
||||||
## Transcript-First Approval / Ask UX
|
|
||||||
- Updated: 2026-04-05 18:58 (KST)
|
|
||||||
- `plan approval` and `user ask` should both resolve inside the transcript first.
|
|
||||||
- Secondary windows are allowed only as detail surfaces, not as the primary decision flow.
|
|
||||||
- AX implementation status:
|
|
||||||
- `plan approval`: transcript-first, detail view via `PlanViewerWindow`
|
|
||||||
- `user ask`: transcript-first inline question card with choices / direct input / submit
|
|
||||||
|
|
||||||
## Tool / Skill UX Parity Follow-up
|
|
||||||
- Updated: 2026-04-05 19:04 (KST)
|
|
||||||
- Default transcript should prefer role-oriented badges and readable labels over raw internal tool names.
|
|
||||||
- AX implementation status:
|
|
||||||
- tool event badges: simplified to role-first labels
|
|
||||||
- item naming: normalized into readable Korean labels or `/skill-name` style
|
|
||||||
- observability panels: permission/background diagnostics reduced outside debug mode
|
|
||||||
- Remaining quality target:
|
|
||||||
- move more tool-result and permission-result presentation into smaller message-type-specific helpers, closer to `claw-code` component separation
|
|
||||||
|
|
||||||
## Focused Quality Tracks
|
|
||||||
- Updated: 2026-04-06 09:27 (KST)
|
|
||||||
- The remaining improvement work should now be managed in three parallel tracks so UX polish, runtime quality, and maintainability do not get mixed together.
|
|
||||||
|
|
||||||
### Track 1. User-Facing UI/UX Quality
|
|
||||||
- Reference:
|
|
||||||
- `src/components/Messages.tsx`
|
|
||||||
- `src/components/MessageRow.tsx`
|
|
||||||
- `src/components/StatusLine.tsx`
|
|
||||||
- `src/components/PromptInput/PromptInput.tsx`
|
|
||||||
- `src/components/PromptInput/PromptInputFooter.tsx`
|
|
||||||
- `src/components/SessionPreview.tsx`
|
|
||||||
- AX apply target:
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.MessageInteractions.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.StatusPresentation.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.FooterPresentation.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.PreviewPresentation.cs`
|
|
||||||
- Focus:
|
|
||||||
- keep transcript text dominant and metadata secondary
|
|
||||||
- make footer controls read as a task bar, not a settings strip
|
|
||||||
- unify preview surfaces, chooser popups, and approval cards under one visual language
|
|
||||||
- reduce visual noise from queue/status/diagnostic surfaces unless the state is actionable
|
|
||||||
- Completion criteria:
|
|
||||||
- message rows, footer, preview, and inline approval/question cards feel visually coherent
|
|
||||||
- chooser popups share the same spacing, hover behavior, and summary-row structure
|
|
||||||
- footer/status elements appear only when they convey useful state
|
|
||||||
- Quality criteria:
|
|
||||||
- a user can understand “what is happening now” from the transcript and footer without opening extra panels
|
|
||||||
- the interface remains readable under narrow widths without text clipping or layout jitter
|
|
||||||
|
|
||||||
### Track 2. LLM / Task Handling Quality
|
|
||||||
- Reference:
|
|
||||||
- `src/bootstrap/state.ts`
|
|
||||||
- `src/bridge/initReplBridge.ts`
|
|
||||||
- `src/bridge/sessionRunner.ts`
|
|
||||||
- `src/components/messages/AssistantToolUseMessage.tsx`
|
|
||||||
- `src/components/messages/PlanApprovalMessage.tsx`
|
|
||||||
- `src/components/permissions/*`
|
|
||||||
- AX apply target:
|
|
||||||
- `src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentLoopService.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.InlineInteractions.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.TranscriptPolicy.cs`
|
|
||||||
- Focus:
|
|
||||||
- keep all entry routes (`send`, `retry`, `regenerate`, `queue`, `slash`) on the same prepared execution path
|
|
||||||
- distinguish `success / error / reject / cancel / needs-approval` tool results more clearly
|
|
||||||
- keep plan approval and user-question flows transcript-native by default
|
|
||||||
- minimize mismatches between execution state and what the user sees in the timeline
|
|
||||||
- Completion criteria:
|
|
||||||
- plan/permission/tool-result/question events all have consistent transcript-native lifecycles
|
|
||||||
- reopen/retry/queue/compact flows preserve the same visible runtime state
|
|
||||||
- tool failures and permission rejections are clearly distinguishable in transcript rendering
|
|
||||||
- Quality criteria:
|
|
||||||
- the same prompt under the same tab/settings uses the same execution route
|
|
||||||
- users can tell whether the agent succeeded, failed, was blocked, or is waiting for approval without reading raw diagnostics
|
|
||||||
|
|
||||||
### Track 3. Maintainability / Extensibility Structure
|
|
||||||
- Reference:
|
|
||||||
- `src/components/*` split by role in `claw-code`
|
|
||||||
- `src/components/messages/*`
|
|
||||||
- `src/components/permissions/*`
|
|
||||||
- `src/components/PromptInput/*`
|
|
||||||
- AX apply target:
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.*.cs`
|
|
||||||
- `src/AxCopilot/Services/AppStateService.cs`
|
|
||||||
- `src/AxCopilot/Services/Agent/*.cs`
|
|
||||||
- Focus:
|
|
||||||
- continue shrinking `ChatWindow.xaml.cs` toward orchestration-only responsibility
|
|
||||||
- keep renderer, popup, footer, message-interaction, status, and preview logic in dedicated partials
|
|
||||||
- centralize presentation rules in catalogs/models instead of scattered UI string/visibility branches
|
|
||||||
- prepare the codebase for new permission types, new tool classes, and new transcript card types without re-bloating the main window file
|
|
||||||
- Completion criteria:
|
|
||||||
- `ChatWindow.xaml.cs` owns orchestration and runtime coordination more than direct UI element construction
|
|
||||||
- new message/permission/tool card types can be added via presentation catalogs or dedicated partials
|
|
||||||
- runtime summary and footer/status visibility derive from presentation models rather than ad-hoc branching
|
|
||||||
- Quality criteria:
|
|
||||||
- adding a new tool-result or approval type should mostly affect one catalog/renderer area
|
|
||||||
- future UI polish work should land in dedicated presentation files rather than expanding the main window file again
|
|
||||||
|
|
||||||
## Recommended Execution Order
|
|
||||||
- Updated: 2026-04-06 09:27 (KST)
|
|
||||||
1. Finish Track 2 consistency first whenever a UX issue is caused by runtime truth mismatch.
|
|
||||||
2. Apply Track 1 visual cleanup only after the state/message lifecycle is stable for that surface.
|
|
||||||
3. Fold each stable surface into Track 3 structure immediately so later changes do not reintroduce `ChatWindow.xaml.cs` sprawl.
|
|
||||||
4. Keep validating against `docs/AX_AGENT_REGRESSION_PROMPTS.md` after each change set, especially for `plan / permission / queue / compact / reopen`.
|
|
||||||
|
|
||||||
## Current Snapshot
|
|
||||||
- Updated: 2026-04-05 19:42 (KST)
|
|
||||||
- Estimated parity:
|
|
||||||
- Core engine: `89%`
|
|
||||||
- Main transcript UI: `96%`
|
|
||||||
- Cowork/Code runtime UX: `92%`
|
|
||||||
- Internal settings linkage: `88%`
|
|
||||||
- Overall AX Agent parity: `93%`
|
|
||||||
|
|
||||||
## Remaining Gaps
|
|
||||||
1. Prompt lifecycle parity
|
|
||||||
- `claw-code` reference: `src/utils/handlePromptSubmit.ts`, `src/utils/processUserInput/processTextPrompt.ts`
|
|
||||||
- AX gap:
|
|
||||||
- `send / retry / regenerate` are mostly unified, but `slash / compact 후 다음 턴 / 일부 queue 후처리`는 아직 `ChatWindow.xaml.cs`에서 UI 상태를 먼저 만지는 구간이 남아 있습니다.
|
|
||||||
- 목표는 모든 입력 진입점이 `AxAgentExecutionEngine`의 동일한 prepare/execute/finalize 축만 타게 만드는 것입니다.
|
|
||||||
|
|
||||||
2. Plan / approval rendering parity
|
|
||||||
- `claw-code` reference: `src/components/messages/PlanApprovalMessage.tsx`
|
|
||||||
- AX gap:
|
|
||||||
- 기본 transcript에서는 compact pill 위주로 줄였지만, 승인/계획 결과 표현이 아직 `Popup/Window + WPF 카드`와 섞여 있습니다.
|
|
||||||
|
|
||||||
## Quality Uplift Plan
|
|
||||||
- Updated: 2026-04-06 00:22 (KST)
|
|
||||||
- Goal: move AX Agent from parity-oriented stability into `claw-code`-grade maintainability and transcript quality, without copying implementation expression.
|
|
||||||
|
|
||||||
### Track 1. Transcript Renderer Decomposition
|
|
||||||
- `claw-code` references:
|
|
||||||
- `src/components/Messages.tsx`
|
|
||||||
- `src/components/MessageRow.tsx`
|
|
||||||
- `src/components/messages/AssistantToolUseMessage.tsx`
|
|
||||||
- `src/components/messages/PlanApprovalMessage.tsx`
|
|
||||||
- AX apply targets:
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- new partial/helper files under `src/AxCopilot/Views/`
|
|
||||||
- Completion criteria:
|
|
||||||
- `plan / permission / ask / tool-result / task-summary` rendering no longer lives as one large block inside `ChatWindow.xaml.cs`
|
|
||||||
- each transcript concern has a dedicated helper/partial/class boundary
|
|
||||||
- Quality criteria:
|
|
||||||
- render changes for one message type do not regress unrelated timeline behavior
|
|
||||||
- transcript behavior remains stable after reopen / retry / regenerate
|
|
||||||
|
|
||||||
### Track 2. Permission Presentation Catalog
|
|
||||||
- `claw-code` references:
|
|
||||||
- `src/components/permissions/PermissionRequest.tsx`
|
|
||||||
- `src/components/permissions/PermissionDialog.tsx`
|
|
||||||
- tool-specific permission request components under `src/components/permissions/*`
|
|
||||||
- AX apply targets:
|
|
||||||
- `src/AxCopilot/Services/Agent/PermissionModeCatalog.cs`
|
|
||||||
- new `src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- permission request title, subtitle, icon, severity, and choice set are resolved by tool/request type
|
|
||||||
- file edit / shell / skill / ask-user / web-like permission requests use distinct presentation metadata
|
|
||||||
- Quality criteria:
|
|
||||||
- permission prompts feel explicit and predictable
|
|
||||||
- user can distinguish request type without reading raw tool names or payload
|
|
||||||
|
|
||||||
### Track 3. Tool Result Message Taxonomy
|
|
||||||
- `claw-code` references:
|
|
||||||
- `src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx`
|
|
||||||
- `src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx`
|
|
||||||
- `src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx`
|
|
||||||
- `src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx`
|
|
||||||
- AX apply targets:
|
|
||||||
- new `src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.TranscriptPolicy.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- transcript display rules differ for `success / error / reject / cancel`
|
|
||||||
- tool-result badges and summaries are resolved from presentation metadata instead of inline ad-hoc branches
|
|
||||||
- Quality criteria:
|
|
||||||
- result cards read as stable UX language, not raw execution logs
|
|
||||||
- failed and rejected tool runs are visually distinct without increasing noise
|
|
||||||
|
|
||||||
### Track 4. Plan Approval Transcript-Only Flow
|
|
||||||
- `claw-code` references:
|
|
||||||
- `src/components/messages/PlanApprovalMessage.tsx`
|
|
||||||
- `src/components/messages/UserPlanMessage.tsx`
|
|
||||||
- AX apply targets:
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- `src/AxCopilot/Views/PlanViewerWindow.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- default approval / reject / revise flow completes inline in transcript
|
|
||||||
- `PlanViewerWindow` is detail-only and never required for primary approval flow
|
|
||||||
- Quality criteria:
|
|
||||||
- planning feels like part of the conversation, not a modal interruption
|
|
||||||
- approval history is replayable from persisted conversation state
|
|
||||||
|
|
||||||
### Track 5. Runtime Summary Layer
|
|
||||||
- `claw-code` references:
|
|
||||||
- `src/components/StatusLine.tsx`
|
|
||||||
- `src/components/PromptInput/PromptInputFooter.tsx`
|
|
||||||
- `src/bootstrap/state.ts`
|
|
||||||
- AX apply targets:
|
|
||||||
- `src/AxCopilot/Services/AppStateService.cs`
|
|
||||||
- `src/AxCopilot/Views/ChatWindow.xaml.cs`
|
|
||||||
- Completion criteria:
|
|
||||||
- one runtime/status summary model feeds the status line, queue summary, runtime badge, and completion hint
|
|
||||||
- status rendering no longer depends on scattered imperative refresh branches
|
|
||||||
- Quality criteria:
|
|
||||||
- no contradictory or stale runtime badges
|
|
||||||
- long-running Cowork/Code sessions stay visually calm
|
|
||||||
|
|
||||||
### Track 6. Regression Prompt Ritual
|
|
||||||
- `claw-code` references:
|
|
||||||
- runtime validation scenarios implied by `sessionRunner`, `Messages`, `StatusLine`, and permission components
|
|
||||||
- AX apply targets:
|
|
||||||
- `docs/AX_AGENT_REGRESSION_PROMPTS.md`
|
|
||||||
- `docs/claw-code-parity-plan.md`
|
|
||||||
- developer workflow / release checklist
|
|
||||||
- Completion criteria:
|
|
||||||
- Chat / Cowork / Code prompt set is treated as mandatory regression for runtime-affecting changes
|
|
||||||
- each prompt is mapped to a failure class (`blank reply`, `duplicate banner`, `bad approval flow`, `queue drift`, `restore drift`)
|
|
||||||
- Quality criteria:
|
|
||||||
- parity claims are based on repeatable checks instead of visual spot-checks
|
|
||||||
- regressions are easier to catch before release
|
|
||||||
|
|
||||||
## Recommended Execution Order
|
|
||||||
1. Transcript renderer decomposition
|
|
||||||
2. Permission presentation catalog
|
|
||||||
3. Tool result taxonomy
|
|
||||||
4. Plan approval transcript-only flow
|
|
||||||
5. Runtime summary layer
|
|
||||||
6. Regression prompt ritual hardening
|
|
||||||
|
|
||||||
## Settings and Logic Review
|
|
||||||
- Updated: 2026-04-06 00:22 (KST)
|
|
||||||
- Candidate to move to developer-only:
|
|
||||||
- `FreeTierDelaySeconds`
|
|
||||||
- `MaxAgentIterations`
|
|
||||||
- `MaxRetryOnError`
|
|
||||||
- Keep as runtime-critical user settings:
|
|
||||||
- `OperationMode`
|
|
||||||
- `MaxContextTokens`
|
|
||||||
- `ContextCompactTriggerPercent`
|
|
||||||
- `EnableProactiveContextCompact`
|
|
||||||
- `EnableCoworkVerification`
|
|
||||||
- `EnableCodeVerification`
|
|
||||||
- code tool exposure toggles
|
|
||||||
- Rule:
|
|
||||||
- if a setting changes the main execution route or recovery semantics without representing a stable real-world user choice, move it out of default user-facing surfaces
|
|
||||||
- 목표는 “본문 우선 + 필요 시 열기” 기준으로 더 단일한 timeline 언어로 수렴시키는 것입니다.
|
|
||||||
|
|
||||||
3. Status line / composer parity
|
|
||||||
- `claw-code` reference: `src/components/StatusLine.tsx`, `src/components/PromptInput/PromptInput.tsx`
|
|
||||||
- AX gap:
|
|
||||||
- 하단 상태바와 composer 옵션은 많이 줄었지만, 상태 메타가 여전히 분산돼 있고 일부 토글/빠른 설정이 별도 행으로 남아 있습니다.
|
|
||||||
- 목표는 transcript 하단의 작업 바 한 축으로 더 압축하는 것입니다.
|
|
||||||
|
|
||||||
4. Runtime event density parity
|
|
||||||
- `claw-code` reference: `src/bridge/sessionRunner.ts`, `src/components/StatusNotices.tsx`
|
|
||||||
- AX gap:
|
|
||||||
- non-debug 기본 로그는 줄었지만, 일부 Cowork/Code 이벤트는 여전히 timeline을 자주 흔듭니다.
|
|
||||||
- 목표는 `permission / tool / error / complete / paused / resumed`를 더 안정된 event shape로 정규화하는 것입니다.
|
|
||||||
|
|
||||||
## Settings Review
|
|
||||||
- Remove candidate:
|
|
||||||
- `PlanMode`
|
|
||||||
- current state: 사용자 노출 UI와 저장 경로는 `off` 고정으로 정리됐지만 `AppSettings`, `SettingsViewModel`, `AppStateService` 타입 잔재가 남아 있음
|
|
||||||
- rationale: 현재 정책이 `off` 고정이라 사용자 선택값이 엔진에 의미 있게 기여하지 않음
|
|
||||||
- `Code.EnablePlanModeTools`
|
|
||||||
- current state: UI/저장 경로와 기본값은 `false` 고정으로 정리됐지만 모델/설정 타입에 호환용 잔재가 남아 있음
|
|
||||||
- rationale: 현재 엔진 정책에서 실제 실행 경로를 더 이상 바꾸지 않음
|
|
||||||
- Move to developer-only candidate:
|
|
||||||
- `FreeTierDelaySeconds`
|
|
||||||
- rationale: 일반 사용자가 조정할 이유가 적고 엔진 지연 정책에 직접 영향
|
|
||||||
- `MaxAgentIterations`
|
|
||||||
- `MaxRetryOnError`
|
|
||||||
- rationale: 핵심 실행 루프 품질에 직접 영향하는 런타임 튜닝값
|
|
||||||
- Keep as runtime-critical:
|
|
||||||
- `OperationMode`
|
|
||||||
- `MaxContextTokens`
|
|
||||||
- `ContextCompactTriggerPercent`
|
|
||||||
- `EnableProactiveContextCompact`
|
|
||||||
- `EnableCoworkVerification`
|
|
||||||
- `EnableCodeVerification`
|
|
||||||
- `Code.EnableWorktreeTools / EnableTeamTools / EnableCronTools`
|
|
||||||
|
|
||||||
## Known UX / Performance Risks
|
|
||||||
- Topic preset hover flicker was caused by duplicate hover systems:
|
|
||||||
- custom hover label
|
|
||||||
- default WPF `ToolTip`
|
|
||||||
- AX fix:
|
|
||||||
- remove default `ToolTip` from topic cards and keep a single hover label path
|
|
||||||
- Remaining runtime performance review targets:
|
|
||||||
- `RefreshContextUsageVisual()` frequency
|
|
||||||
- `BuildTopicButtons()` rebuild frequency
|
|
||||||
- `OnAgentEvent` timeline churn during long Cowork/Code runs
|
|
||||||
- compact queue summary still needs one more pass to fully match `claw-code` footer minimalism
|
|
||||||
|
|
||||||
## Progress Notes
|
|
||||||
- 업데이트: 2026-04-06 00:58 (KST)
|
|
||||||
- transcript renderer 분리 1차 완료
|
|
||||||
- AX 적용: [ChatWindow.InlineInteractions.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.InlineInteractions.cs), [ChatWindow.TaskSummary.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.TaskSummary.cs)
|
|
||||||
- 완료 조건: `plan / ask / task-summary` 렌더 helper가 메인 `ChatWindow.xaml.cs` 밖으로 이동
|
|
||||||
- permission / tool-result presentation catalog 도입
|
|
||||||
- AX 적용: [PermissionRequestPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs), [ToolResultPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs)
|
|
||||||
- 완료 조건: `AddAgentEventBanner(...)`가 권한/도구 결과 badge 메타를 inline switch가 아니라 catalog에서 해석
|
|
||||||
- runtime summary 전용 계층 1차 반영
|
|
||||||
- AX 적용: [AppStateService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AppStateService.cs)
|
|
||||||
- 완료 조건: 상태선 UI가 `OperationalStatusPresentationState`를 소비해 strip/runtime badge visibility를 계산
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|||||||
@@ -0,0 +1,350 @@
|
|||||||
|
using AxCopilot.Core;
|
||||||
|
using AxCopilot.Models;
|
||||||
|
using FluentAssertions;
|
||||||
|
using System.IO;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Core;
|
||||||
|
|
||||||
|
public class ContextManagerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void NormalizeWindowTitle_StripsKnownBrowserSuffix()
|
||||||
|
{
|
||||||
|
var normalized = ContextManager.NormalizeWindowTitle("Inbox - Google Chrome");
|
||||||
|
|
||||||
|
normalized.Should().Be("inbox");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateTitleMatchScore_ExactTitle_BeatsPartialTitle()
|
||||||
|
{
|
||||||
|
var exact = ContextManager.CalculateTitleMatchScore("Inbox - Google Chrome", "Inbox - Google Chrome");
|
||||||
|
var partial = ContextManager.CalculateTitleMatchScore("Inbox - Google Chrome", "Inbox - Chrome");
|
||||||
|
|
||||||
|
exact.Should().BeGreaterThan(partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SelectBestMatchingWindow_PrefersExactTitleAmongSameExe()
|
||||||
|
{
|
||||||
|
var snapshot = new WindowSnapshot
|
||||||
|
{
|
||||||
|
Exe = @"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||||
|
Title = "Inbox - Google Chrome"
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = new[]
|
||||||
|
{
|
||||||
|
new ContextManager.WindowCandidate(new IntPtr(1), snapshot.Exe, "Docs - Google Chrome"),
|
||||||
|
new ContextManager.WindowCandidate(new IntPtr(2), snapshot.Exe, "Inbox - Google Chrome")
|
||||||
|
};
|
||||||
|
|
||||||
|
var selected = ContextManager.SelectBestMatchingWindow(snapshot, candidates);
|
||||||
|
|
||||||
|
selected.Should().Be(new IntPtr(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SelectBestMatchingWindow_SkipsAlreadyUsedHandle()
|
||||||
|
{
|
||||||
|
var snapshot = new WindowSnapshot
|
||||||
|
{
|
||||||
|
Exe = @"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||||
|
Title = "Inbox - Google Chrome"
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = new[]
|
||||||
|
{
|
||||||
|
new ContextManager.WindowCandidate(new IntPtr(1), snapshot.Exe, "Inbox - Google Chrome")
|
||||||
|
};
|
||||||
|
|
||||||
|
var selected = ContextManager.SelectBestMatchingWindow(snapshot, candidates, new HashSet<IntPtr> { new(1) });
|
||||||
|
|
||||||
|
selected.Should().Be(IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BrowserLaunchPlan_CreatesChromiumWindowWithProfileAndTabs()
|
||||||
|
{
|
||||||
|
var state = new BrowserWindowState
|
||||||
|
{
|
||||||
|
Kind = "edge",
|
||||||
|
UserDataDir = @"C:\Users\tester\AppData\Local\Microsoft\Edge\User Data",
|
||||||
|
ProfileDirectory = "Profile 2",
|
||||||
|
TabUrls = ["https://example.com", "edge://settings/profiles"]
|
||||||
|
};
|
||||||
|
|
||||||
|
var plan = BrowserWorkspaceStateHelper.CreateLaunchPlan(
|
||||||
|
@"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||||||
|
state);
|
||||||
|
|
||||||
|
plan.Should().NotBeNull();
|
||||||
|
plan!.Arguments.Should().ContainInOrder(
|
||||||
|
"--new-window",
|
||||||
|
"--user-data-dir=C:\\Users\\tester\\AppData\\Local\\Microsoft\\Edge\\User Data",
|
||||||
|
"--profile-directory=Profile 2",
|
||||||
|
"https://example.com",
|
||||||
|
"edge://settings/profiles");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BrowserLaunchPlan_CreatesFirefoxWindowWithTabs()
|
||||||
|
{
|
||||||
|
var state = new BrowserWindowState
|
||||||
|
{
|
||||||
|
Kind = "firefox",
|
||||||
|
ProfileDirectory = "work-profile",
|
||||||
|
TabUrls = ["https://www.mozilla.org", "about:newtab"]
|
||||||
|
};
|
||||||
|
|
||||||
|
var plan = BrowserWorkspaceStateHelper.CreateLaunchPlan(
|
||||||
|
@"C:\Program Files\Mozilla Firefox\firefox.exe",
|
||||||
|
state);
|
||||||
|
|
||||||
|
plan.Should().NotBeNull();
|
||||||
|
plan!.Arguments.Should().ContainInOrder(
|
||||||
|
"-P",
|
||||||
|
"work-profile",
|
||||||
|
"-new-window",
|
||||||
|
"https://www.mozilla.org",
|
||||||
|
"-new-tab",
|
||||||
|
"about:newtab");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShouldLaunchNewWindow_WithBrowserStateAndPartialTitleMatch_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var snapshot = new WindowSnapshot
|
||||||
|
{
|
||||||
|
Exe = @"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||||
|
Title = "Inbox - Google Chrome",
|
||||||
|
Browser = new BrowserWindowState
|
||||||
|
{
|
||||||
|
Kind = "chrome",
|
||||||
|
TabUrls = ["https://mail.google.com"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidate = new ContextManager.WindowCandidate(
|
||||||
|
new IntPtr(5),
|
||||||
|
snapshot.Exe,
|
||||||
|
"Docs - Google Chrome");
|
||||||
|
|
||||||
|
BrowserWorkspaceStateHelper.ShouldLaunchNewWindow(snapshot, candidate).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShouldLaunchNewWindow_WithBrowserStateAndExactTitleMatch_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var snapshot = new WindowSnapshot
|
||||||
|
{
|
||||||
|
Exe = @"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||||
|
Title = "Inbox - Google Chrome",
|
||||||
|
Browser = new BrowserWindowState
|
||||||
|
{
|
||||||
|
Kind = "chrome",
|
||||||
|
TabUrls = ["https://mail.google.com"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidate = new ContextManager.WindowCandidate(
|
||||||
|
new IntPtr(5),
|
||||||
|
snapshot.Exe,
|
||||||
|
"Inbox - Google Chrome");
|
||||||
|
|
||||||
|
BrowserWorkspaceStateHelper.ShouldLaunchNewWindow(snapshot, candidate).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppLaunchPlan_CreatesExplorerWindowWithCapturedFolder()
|
||||||
|
{
|
||||||
|
using var folder1 = new TempDirectory();
|
||||||
|
using var folder2 = new TempDirectory();
|
||||||
|
|
||||||
|
var state = new AppWindowState
|
||||||
|
{
|
||||||
|
Kind = "explorer",
|
||||||
|
PrimaryPath = folder1.Path,
|
||||||
|
Paths = [folder1.Path, folder2.Path]
|
||||||
|
};
|
||||||
|
|
||||||
|
var plan = AppWorkspaceStateHelper.CreateLaunchPlan(
|
||||||
|
@"C:\Windows\explorer.exe",
|
||||||
|
state);
|
||||||
|
|
||||||
|
plan.Should().NotBeNull();
|
||||||
|
plan!.Arguments.Should().ContainSingle()
|
||||||
|
.Which.Should().Be(folder1.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppLaunchPlan_CreatesNotepadWindowWithCapturedFiles()
|
||||||
|
{
|
||||||
|
using var file1 = new TempFile(".txt");
|
||||||
|
using var file2 = new TempFile(".log");
|
||||||
|
|
||||||
|
var state = new AppWindowState
|
||||||
|
{
|
||||||
|
Kind = "notepad",
|
||||||
|
PrimaryPath = file1.Path,
|
||||||
|
Paths = [file1.Path, file2.Path]
|
||||||
|
};
|
||||||
|
|
||||||
|
var plan = AppWorkspaceStateHelper.CreateLaunchPlan(
|
||||||
|
@"C:\Windows\System32\notepad.exe",
|
||||||
|
state);
|
||||||
|
|
||||||
|
plan.Should().NotBeNull();
|
||||||
|
plan!.Arguments.Should().ContainInOrder(file1.Path, file2.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppLaunchPlan_UsesSystemExecutableFallbackForPackagedNotepad()
|
||||||
|
{
|
||||||
|
using var file1 = new TempFile(".txt");
|
||||||
|
|
||||||
|
var state = new AppWindowState
|
||||||
|
{
|
||||||
|
Kind = "notepad",
|
||||||
|
PrimaryPath = file1.Path,
|
||||||
|
Paths = [file1.Path]
|
||||||
|
};
|
||||||
|
|
||||||
|
var plan = AppWorkspaceStateHelper.CreateLaunchPlan(
|
||||||
|
@"C:\Program Files\WindowsApps\Microsoft.WindowsNotepad_11.2401.32.0_x64__8wekyb3d8bbwe\Notepad.exe",
|
||||||
|
state);
|
||||||
|
|
||||||
|
plan.Should().NotBeNull();
|
||||||
|
plan!.FileName.Should().Be("notepad.exe");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppShouldLaunchNewWindow_WithCapturedStateAndDifferentTitle_ReturnsTrue()
|
||||||
|
{
|
||||||
|
using var folder = new TempDirectory();
|
||||||
|
|
||||||
|
var snapshot = new WindowSnapshot
|
||||||
|
{
|
||||||
|
Exe = @"C:\Windows\explorer.exe",
|
||||||
|
Title = "문서 - 파일 탐색기",
|
||||||
|
AppState = new AppWindowState
|
||||||
|
{
|
||||||
|
Kind = "explorer",
|
||||||
|
PrimaryPath = folder.Path,
|
||||||
|
Paths = [folder.Path]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidate = new ContextManager.WindowCandidate(
|
||||||
|
new IntPtr(7),
|
||||||
|
snapshot.Exe,
|
||||||
|
"다운로드 - 파일 탐색기");
|
||||||
|
|
||||||
|
AppWorkspaceStateHelper.ShouldLaunchNewWindow(snapshot, candidate).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppShouldLaunchNewWindow_WithCapturedStateAndExactTitle_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var snapshot = new WindowSnapshot
|
||||||
|
{
|
||||||
|
Exe = @"C:\Windows\System32\notepad.exe",
|
||||||
|
Title = "todo.txt - 메모장",
|
||||||
|
AppState = new AppWindowState
|
||||||
|
{
|
||||||
|
Kind = "notepad",
|
||||||
|
PrimaryPath = @"C:\Temp\todo.txt",
|
||||||
|
Paths = [@"C:\Temp\todo.txt"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidate = new ContextManager.WindowCandidate(
|
||||||
|
new IntPtr(8),
|
||||||
|
snapshot.Exe,
|
||||||
|
"todo.txt - 메모장");
|
||||||
|
|
||||||
|
AppWorkspaceStateHelper.ShouldLaunchNewWindow(snapshot, candidate).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateRestoreLaunchDelayMs_Disabled_ReturnsZero()
|
||||||
|
{
|
||||||
|
var settings = new LauncherSettings
|
||||||
|
{
|
||||||
|
EnableAdaptiveWorkspaceRestoreThrottle = false,
|
||||||
|
WorkspaceRestoreBaseDelayMs = 250,
|
||||||
|
WorkspaceRestoreMaxDelayMs = 1200
|
||||||
|
};
|
||||||
|
|
||||||
|
var delay = ContextManager.CalculateRestoreLaunchDelayMs(settings, 85, 92, 3);
|
||||||
|
|
||||||
|
delay.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateRestoreLaunchDelayMs_HighLoadIncreasesDelay()
|
||||||
|
{
|
||||||
|
var settings = new LauncherSettings
|
||||||
|
{
|
||||||
|
EnableAdaptiveWorkspaceRestoreThrottle = true,
|
||||||
|
WorkspaceRestoreBaseDelayMs = 250,
|
||||||
|
WorkspaceRestoreMaxDelayMs = 1200
|
||||||
|
};
|
||||||
|
|
||||||
|
var lowLoadDelay = ContextManager.CalculateRestoreLaunchDelayMs(settings, 20, 45, 1);
|
||||||
|
var highLoadDelay = ContextManager.CalculateRestoreLaunchDelayMs(settings, 88, 91, 4);
|
||||||
|
|
||||||
|
lowLoadDelay.Should().Be(250);
|
||||||
|
highLoadDelay.Should().BeGreaterThan(lowLoadDelay);
|
||||||
|
highLoadDelay.Should().BeLessOrEqualTo(1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TempFile : IDisposable
|
||||||
|
{
|
||||||
|
public TempFile(string extension)
|
||||||
|
{
|
||||||
|
Path = System.IO.Path.ChangeExtension(System.IO.Path.GetTempFileName(), extension);
|
||||||
|
File.WriteAllText(Path, "temp");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(Path))
|
||||||
|
File.Delete(Path);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 테스트 정리 실패는 무시합니다.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TempDirectory : IDisposable
|
||||||
|
{
|
||||||
|
public TempDirectory()
|
||||||
|
{
|
||||||
|
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"axcopilot-test-{Guid.NewGuid():N}");
|
||||||
|
Directory.CreateDirectory(Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Directory.Exists(Path))
|
||||||
|
Directory.Delete(Path, true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 테스트 정리 실패는 무시합니다.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using AxCopilot.Core;
|
||||||
|
using AxCopilot.Handlers;
|
||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Handlers;
|
||||||
|
|
||||||
|
public class WorkspaceHandlerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task GetItemsAsync_RestoreSubcommand_ReturnsRestoreAction()
|
||||||
|
{
|
||||||
|
var settings = new SettingsService();
|
||||||
|
settings.Settings.Profiles.Add(new WorkspaceProfile
|
||||||
|
{
|
||||||
|
Name = "업무",
|
||||||
|
Windows = new List<WindowSnapshot> { new() { Exe = "chrome.exe", Title = "메일" } },
|
||||||
|
CreatedAt = new DateTime(2026, 4, 15, 9, 0, 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
var handler = new WorkspaceHandler(new ContextManager(settings), settings);
|
||||||
|
|
||||||
|
var items = (await handler.GetItemsAsync("restore 업무", CancellationToken.None)).ToList();
|
||||||
|
|
||||||
|
items.Should().ContainSingle();
|
||||||
|
items[0].Title.Should().Contain("업무");
|
||||||
|
items[0].Data.Should().BeOfType<WorkspaceAction>();
|
||||||
|
|
||||||
|
var action = (WorkspaceAction)items[0].Data!;
|
||||||
|
action.Type.Should().Be(WorkspaceActionType.Restore);
|
||||||
|
action.Name.Should().Be("업무");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetItemsAsync_ListSubcommand_ReturnsSavedProfiles()
|
||||||
|
{
|
||||||
|
var settings = new SettingsService();
|
||||||
|
settings.Settings.Profiles.Add(new WorkspaceProfile
|
||||||
|
{
|
||||||
|
Name = "업무",
|
||||||
|
Windows = new List<WindowSnapshot> { new() { Exe = "chrome.exe", Title = "메일" } },
|
||||||
|
CreatedAt = new DateTime(2026, 4, 15, 9, 0, 0)
|
||||||
|
});
|
||||||
|
settings.Settings.Profiles.Add(new WorkspaceProfile
|
||||||
|
{
|
||||||
|
Name = "개발",
|
||||||
|
Windows = new List<WindowSnapshot> { new() { Exe = "code.exe", Title = "AX Copilot" } },
|
||||||
|
CreatedAt = new DateTime(2026, 4, 15, 10, 0, 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
var handler = new WorkspaceHandler(new ContextManager(settings), settings);
|
||||||
|
|
||||||
|
var items = (await handler.GetItemsAsync("list", CancellationToken.None)).ToList();
|
||||||
|
|
||||||
|
items.Should().HaveCount(2);
|
||||||
|
items[0].Title.Should().Be("~개발");
|
||||||
|
items[1].Title.Should().Be("~업무");
|
||||||
|
items.All(item => item.Data is WorkspaceAction action && action.Type == WorkspaceActionType.Restore)
|
||||||
|
.Should()
|
||||||
|
.BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentCommandQueueTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DrainAll_ShouldRespectPriorityThenSequence()
|
||||||
|
{
|
||||||
|
var queue = new AgentCommandQueue();
|
||||||
|
queue.EnqueuePrompt("later", "later");
|
||||||
|
queue.EnqueuePrompt("next", "next");
|
||||||
|
queue.EnqueuePrompt("now", "now", requestInterrupt: true);
|
||||||
|
|
||||||
|
var drained = queue.DrainAll();
|
||||||
|
|
||||||
|
drained.Select(x => x.Content).Should().Equal("now", "next", "later");
|
||||||
|
drained[0].RequestInterrupt.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Clear_ShouldRemoveQueuedItems()
|
||||||
|
{
|
||||||
|
var queue = new AgentCommandQueue();
|
||||||
|
queue.EnqueuePrompt("first");
|
||||||
|
queue.EnqueueNotification("note");
|
||||||
|
|
||||||
|
queue.Clear();
|
||||||
|
|
||||||
|
queue.DrainAll().Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DrainAll_ShouldIncludeSteeringPermissionAndResumeKinds()
|
||||||
|
{
|
||||||
|
var queue = new AgentCommandQueue();
|
||||||
|
queue.EnqueueNotification("later-note");
|
||||||
|
queue.EnqueueResume("resume");
|
||||||
|
queue.EnqueuePermissionContinuation("continue-tool");
|
||||||
|
queue.EnqueueSteering("steer-now");
|
||||||
|
|
||||||
|
var drained = queue.DrainAll();
|
||||||
|
|
||||||
|
drained.Select(x => x.Kind).Should().Equal(
|
||||||
|
AgentCommandKind.Resume,
|
||||||
|
AgentCommandKind.PermissionContinuation,
|
||||||
|
AgentCommandKind.Steering,
|
||||||
|
AgentCommandKind.Notification);
|
||||||
|
drained[0].RequestInterrupt.Should().BeFalse();
|
||||||
|
drained[1].RequestInterrupt.Should().BeTrue();
|
||||||
|
drained[2].RequestInterrupt.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DequeuePriorityBatch_ShouldLeaveLowerPriorityItemsQueued()
|
||||||
|
{
|
||||||
|
var queue = new AgentCommandQueue();
|
||||||
|
queue.EnqueueNotification("later-note");
|
||||||
|
queue.EnqueuePrompt("next-prompt");
|
||||||
|
queue.EnqueueSteering("now-steer");
|
||||||
|
|
||||||
|
var firstBatch = queue.DequeuePriorityBatch();
|
||||||
|
var secondBatch = queue.DequeuePriorityBatch();
|
||||||
|
var thirdBatch = queue.DequeuePriorityBatch();
|
||||||
|
|
||||||
|
firstBatch.Select(x => x.Content).Should().Equal("now-steer");
|
||||||
|
secondBatch.Select(x => x.Content).Should().Equal("next-prompt");
|
||||||
|
thirdBatch.Select(x => x.Content).Should().Equal("later-note");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PeekAndDequeueAllMatching_ShouldHonorPredicate()
|
||||||
|
{
|
||||||
|
var queue = new AgentCommandQueue();
|
||||||
|
queue.EnqueueNotification("later-note");
|
||||||
|
queue.EnqueuePrompt("next-prompt");
|
||||||
|
queue.EnqueueSteering("now-steer");
|
||||||
|
|
||||||
|
var peeked = queue.Peek(x => x.Kind == AgentCommandKind.Notification);
|
||||||
|
var removed = queue.DequeueAllMatching(x => x.Kind == AgentCommandKind.Notification);
|
||||||
|
var remaining = queue.DrainAll();
|
||||||
|
|
||||||
|
peeked.Should().NotBeNull();
|
||||||
|
peeked!.Content.Should().Be("later-note");
|
||||||
|
removed.Select(x => x.Content).Should().Equal("later-note");
|
||||||
|
remaining.Select(x => x.Content).Should().Equal("now-steer", "next-prompt");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,10 +63,10 @@ public class AgentLoopCodeQualityTests
|
|||||||
"bugfix");
|
"bugfix");
|
||||||
|
|
||||||
prompt.Should().Contain("baseline build/test");
|
prompt.Should().Contain("baseline build/test");
|
||||||
prompt.Should().Contain("grep/glob");
|
prompt.Should().Contain("grep 또는 glob");
|
||||||
prompt.Should().Contain("build_run");
|
prompt.Should().Contain("build_run");
|
||||||
prompt.Should().Contain("테스트 부재 사실");
|
prompt.Should().Contain("테스트 부재 사실");
|
||||||
prompt.Should().Contain("작업 유형: bugfix");
|
prompt.Should().Contain("Task type: bugfix");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -87,7 +87,7 @@ public class AgentLoopCodeQualityTests
|
|||||||
prompt.Should().Contain("grep 또는 glob");
|
prompt.Should().Contain("grep 또는 glob");
|
||||||
prompt.Should().Contain("테스트 부재 사실");
|
prompt.Should().Contain("테스트 부재 사실");
|
||||||
prompt.Should().Contain("영향 범위가 넓을 가능성");
|
prompt.Should().Contain("영향 범위가 넓을 가능성");
|
||||||
prompt.Should().Contain("작업 유형: refactor");
|
prompt.Should().Contain("Task type: refactor");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -182,7 +182,7 @@ public class AgentLoopCodeQualityTests
|
|||||||
prompt.Should().Contain("spawn_agent");
|
prompt.Should().Contain("spawn_agent");
|
||||||
prompt.Should().Contain("build/test");
|
prompt.Should().Contain("build/test");
|
||||||
prompt.Should().Contain("테스트 부재 사실");
|
prompt.Should().Contain("테스트 부재 사실");
|
||||||
prompt.Should().Contain("재현 조건");
|
prompt.Should().Contain("symptom is no longer reproducible");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -202,8 +202,27 @@ public class AgentLoopCodeQualityTests
|
|||||||
false,
|
false,
|
||||||
"refactor");
|
"refactor");
|
||||||
|
|
||||||
featurePrompt.Should().Contain("새 기능 경로");
|
featurePrompt.Should().Contain("feature path and caller linkage");
|
||||||
refactorPrompt.Should().Contain("동작 보존");
|
refactorPrompt.Should().Contain("behavior-compatible");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildFailureInvestigationPrompt_PrefersMultiReadWhenBuildOutputMentionsMultipleFiles()
|
||||||
|
{
|
||||||
|
var prompt = InvokePrivateStatic<string>(
|
||||||
|
"BuildFailureInvestigationPrompt",
|
||||||
|
"build_run",
|
||||||
|
@"E:\code\MainWindow.xaml.cs",
|
||||||
|
false,
|
||||||
|
TaskTypePolicy.FromTaskType("feature"),
|
||||||
|
"""
|
||||||
|
E:\code\Themes\ControlStyles.xaml(14,17): error MC4005
|
||||||
|
E:\code\Themes\Effects.xaml(166,50): error MC3072
|
||||||
|
""");
|
||||||
|
|
||||||
|
prompt.Should().Contain("multi_read");
|
||||||
|
prompt.Should().Contain(@"Themes\ControlStyles.xaml");
|
||||||
|
prompt.Should().Contain(@"Themes\Effects.xaml");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -219,10 +238,51 @@ public class AgentLoopCodeQualityTests
|
|||||||
"bugfix");
|
"bugfix");
|
||||||
|
|
||||||
message.Should().Contain("Fallback sequence");
|
message.Should().Contain("Fallback sequence");
|
||||||
message.Should().Contain("file_read -> grep/glob -> git_tool(diff)");
|
message.Should().Contain("git_tool(diff)");
|
||||||
message.Should().Contain("repro/root-cause");
|
message.Should().Contain("repro/root-cause");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildFailureReflectionMessage_UsesFileReviewFallbackWhenWorkspaceIsNotGitRepository()
|
||||||
|
{
|
||||||
|
var prompt = InvokePrivateStatic<string>(
|
||||||
|
"BuildFailureReflectionMessage",
|
||||||
|
"git_tool",
|
||||||
|
ToolResult.Fail("현재 작업 폴더는 Git 저장소가 아닙니다."),
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
"review");
|
||||||
|
|
||||||
|
prompt.Should().Contain("저장소 컨텍스트");
|
||||||
|
prompt.Should().Contain("code_review(file_review)");
|
||||||
|
prompt.Should().NotContain("git_tool(diff) -> targeted tool retry");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildProjectLayoutGatePrompt_CallsForFolderReorganization()
|
||||||
|
{
|
||||||
|
var profile = ProjectScaffoldProfileCatalog.Detect(
|
||||||
|
"Create a C# WPF MVVM desktop app with ResourceDictionary themes",
|
||||||
|
"Code")!;
|
||||||
|
var assessment = new ProjectScaffoldLayoutAssessment(
|
||||||
|
IsSatisfied: false,
|
||||||
|
MatchedDirectoryCount: 0,
|
||||||
|
ExistingDirectories: [],
|
||||||
|
MissingDirectories: ["Views", "ViewModels", "Themes"],
|
||||||
|
SuspiciousRootFiles: ["MainWindow.xaml", "MainWindowViewModel.cs"]);
|
||||||
|
|
||||||
|
var prompt = InvokePrivateStatic<string>(
|
||||||
|
"BuildProjectLayoutGatePrompt",
|
||||||
|
profile,
|
||||||
|
assessment);
|
||||||
|
|
||||||
|
prompt.Should().Contain("[System:ProjectLayoutGate]");
|
||||||
|
prompt.Should().Contain("Views");
|
||||||
|
prompt.Should().Contain("ViewModels");
|
||||||
|
prompt.Should().Contain("MainWindow.xaml");
|
||||||
|
prompt.Should().Contain("file_manage(mkdir/move)");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BuildFailureNextToolPriorityPrompt_IncludesOrderedPriority()
|
public void BuildFailureNextToolPriorityPrompt_IncludesOrderedPriority()
|
||||||
{
|
{
|
||||||
@@ -239,6 +299,33 @@ public class AgentLoopCodeQualityTests
|
|||||||
prompt.Should().Contain("고영향 변경");
|
prompt.Should().Contain("고영향 변경");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("bugfix", true)]
|
||||||
|
[InlineData("feature", true)]
|
||||||
|
[InlineData("refactor", true)]
|
||||||
|
[InlineData("review", true)]
|
||||||
|
[InlineData("docs", false)]
|
||||||
|
public void ShouldApplyExecutionResultGate_MatchesCodeExecutionTasks(string taskType, bool expected)
|
||||||
|
{
|
||||||
|
var result = InvokePrivateStatic<bool>(
|
||||||
|
"ShouldApplyExecutionResultGate",
|
||||||
|
TaskTypePolicy.FromTaskType(taskType));
|
||||||
|
|
||||||
|
result.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("balanced")]
|
||||||
|
[InlineData("reasoning_first")]
|
||||||
|
[InlineData("tool_call_strict")]
|
||||||
|
public void CodeFocusedExecutionProfiles_EnableExecutionRecoveryGates(string profileKey)
|
||||||
|
{
|
||||||
|
var policy = ModelExecutionProfileCatalog.Get(profileKey);
|
||||||
|
|
||||||
|
policy.RecentExecutionGateMaxRetries.Should().BeGreaterThan(0);
|
||||||
|
policy.ExecutionSuccessGateMaxRetries.Should().BeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ComputeAdaptiveMaxRetry_AdjustsByTaskType()
|
public void ComputeAdaptiveMaxRetry_AdjustsByTaskType()
|
||||||
{
|
{
|
||||||
@@ -370,7 +457,7 @@ public class AgentLoopCodeQualityTests
|
|||||||
|
|
||||||
prompt.Should().Contain("무엇을 변경했는지");
|
prompt.Should().Contain("무엇을 변경했는지");
|
||||||
prompt.Should().Contain("build/test/검증 근거");
|
prompt.Should().Contain("build/test/검증 근거");
|
||||||
prompt.Should().Contain("원인, 수정 내용, 재현/회귀 검증 근거");
|
prompt.Should().Contain("bug fix");
|
||||||
prompt.Should().Contain("남은 리스크");
|
prompt.Should().Contain("남은 리스크");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +481,7 @@ public class AgentLoopCodeQualityTests
|
|||||||
"bugfix");
|
"bugfix");
|
||||||
|
|
||||||
guidance.Should().Contain("[System:FailurePatterns]");
|
guidance.Should().Contain("[System:FailurePatterns]");
|
||||||
guidance.Should().Contain("재현 조건과 원인 연결");
|
guidance.Should().Contain("reproduction");
|
||||||
guidance.Should().Contain("CS1002");
|
guidance.Should().Contain("CS1002");
|
||||||
guidance.Should().Contain("NRE");
|
guidance.Should().Contain("NRE");
|
||||||
}
|
}
|
||||||
@@ -603,7 +690,7 @@ public class AgentLoopCodeQualityTests
|
|||||||
public void BuildToolCallSignature_IncludesToolAndCanonicalInput()
|
public void BuildToolCallSignature_IncludesToolAndCanonicalInput()
|
||||||
{
|
{
|
||||||
var input = JsonDocument.Parse("""{"path":"src/A.cs","line":10}""").RootElement.Clone();
|
var input = JsonDocument.Parse("""{"path":"src/A.cs","line":10}""").RootElement.Clone();
|
||||||
var call = new LlmService.ContentBlock
|
var call = new ContentBlock
|
||||||
{
|
{
|
||||||
Type = "tool_use",
|
Type = "tool_use",
|
||||||
ToolName = "file_edit",
|
ToolName = "file_edit",
|
||||||
@@ -641,16 +728,17 @@ public class AgentLoopCodeQualityTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void CreateParallelExecutionPlan_DisabledFlagKeepsSequentialOnly()
|
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_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() }
|
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",
|
"CreateParallelExecutionPlan",
|
||||||
false,
|
false,
|
||||||
calls);
|
calls,
|
||||||
|
0);
|
||||||
|
|
||||||
plan.ShouldRun.Should().BeFalse();
|
plan.ShouldRun.Should().BeFalse();
|
||||||
plan.ParallelBatch.Should().BeEmpty();
|
plan.ParallelBatch.Should().BeEmpty();
|
||||||
@@ -660,7 +748,7 @@ public class AgentLoopCodeQualityTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void CreateParallelExecutionPlan_UsesOnlyReadOnlyPrefixForParallelBatch()
|
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 = "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() },
|
new() { Type = "tool_use", ToolName = "glob", ToolId = "t2", ToolInput = JsonDocument.Parse("""{"pattern":"*.cs"}""").RootElement.Clone() },
|
||||||
@@ -668,10 +756,11 @@ public class AgentLoopCodeQualityTests
|
|||||||
new() { Type = "tool_use", ToolName = "file_read", ToolId = "t4", ToolInput = JsonDocument.Parse("""{"path":"b.txt"}""").RootElement.Clone() }
|
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",
|
"CreateParallelExecutionPlan",
|
||||||
true,
|
true,
|
||||||
calls);
|
calls,
|
||||||
|
0);
|
||||||
|
|
||||||
plan.ShouldRun.Should().BeTrue();
|
plan.ShouldRun.Should().BeTrue();
|
||||||
plan.ParallelBatch.Select(x => x.ToolId).Should().Equal("t1", "t2");
|
plan.ParallelBatch.Select(x => x.ToolId).Should().Equal("t1", "t2");
|
||||||
@@ -681,17 +770,18 @@ public class AgentLoopCodeQualityTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void CreateParallelExecutionPlan_RecognizesAliasReadOnlyToolInPrefix()
|
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 = "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 = "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() }
|
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",
|
"CreateParallelExecutionPlan",
|
||||||
true,
|
true,
|
||||||
calls);
|
calls,
|
||||||
|
0);
|
||||||
|
|
||||||
plan.ShouldRun.Should().BeTrue();
|
plan.ShouldRun.Should().BeTrue();
|
||||||
plan.ParallelBatch.Select(x => x.ToolId).Should().Equal("t1", "t2");
|
plan.ParallelBatch.Select(x => x.ToolId).Should().Equal("t1", "t2");
|
||||||
@@ -778,6 +868,38 @@ public class AgentLoopCodeQualityTests
|
|||||||
blocked.Should().BeFalse();
|
blocked.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShouldBlockRepeatedNoProgressToolLoop_BlocksRepeatedExecutionTools()
|
||||||
|
{
|
||||||
|
var blocked = InvokePrivateStatic<bool>(
|
||||||
|
"ShouldBlockRepeatedNoProgressToolLoop",
|
||||||
|
"build_run",
|
||||||
|
6);
|
||||||
|
var notYet = InvokePrivateStatic<bool>(
|
||||||
|
"ShouldBlockRepeatedNoProgressToolLoop",
|
||||||
|
"build_run",
|
||||||
|
5);
|
||||||
|
|
||||||
|
blocked.Should().BeTrue();
|
||||||
|
notYet.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShouldBlockRepeatedNoProgressToolLoop_GivesPollingToolsMoreRoom()
|
||||||
|
{
|
||||||
|
var notYet = InvokePrivateStatic<bool>(
|
||||||
|
"ShouldBlockRepeatedNoProgressToolLoop",
|
||||||
|
"wait_agents",
|
||||||
|
6);
|
||||||
|
var blocked = InvokePrivateStatic<bool>(
|
||||||
|
"ShouldBlockRepeatedNoProgressToolLoop",
|
||||||
|
"wait_agents",
|
||||||
|
7);
|
||||||
|
|
||||||
|
notYet.Should().BeFalse();
|
||||||
|
blocked.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ShouldRequestDocumentArtifact_RequiresDocsTaskAndMissingArtifact()
|
public void ShouldRequestDocumentArtifact_RequiresDocsTaskAndMissingArtifact()
|
||||||
{
|
{
|
||||||
@@ -878,7 +1000,7 @@ public class AgentLoopCodeQualityTests
|
|||||||
response,
|
response,
|
||||||
"docs",
|
"docs",
|
||||||
false,
|
false,
|
||||||
withoutVerification).Should().BeFalse();
|
withoutVerification).Should().BeTrue();
|
||||||
|
|
||||||
InvokePrivateStatic<bool>(
|
InvokePrivateStatic<bool>(
|
||||||
"HasSufficientFinalReportEvidence",
|
"HasSufficientFinalReportEvidence",
|
||||||
@@ -1168,65 +1290,33 @@ public class AgentLoopCodeQualityTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ResolveNoToolCallResponseThreshold_UsesDefaultAndClamps()
|
public void ResolveNoToolCallResponseThreshold_UsesDefaultAndClamps()
|
||||||
{
|
{
|
||||||
InvokePrivateStatic<int>(
|
AgentLoopRuntimeThresholds.ResolveNoToolCallResponseThreshold(null).Should().Be(2);
|
||||||
"ResolveNoToolCallResponseThreshold",
|
AgentLoopRuntimeThresholds.ResolveNoToolCallResponseThreshold("0").Should().Be(1);
|
||||||
(string?)null).Should().Be(2);
|
AgentLoopRuntimeThresholds.ResolveNoToolCallResponseThreshold("99").Should().Be(6);
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolveNoToolCallResponseThreshold",
|
|
||||||
"0").Should().Be(1);
|
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolveNoToolCallResponseThreshold",
|
|
||||||
"99").Should().Be(6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ResolveNoToolCallRecoveryMaxRetries_UsesDefaultAndClamps()
|
public void ResolveNoToolCallRecoveryMaxRetries_UsesDefaultAndClamps()
|
||||||
{
|
{
|
||||||
InvokePrivateStatic<int>(
|
AgentLoopRuntimeThresholds.ResolveNoToolCallRecoveryMaxRetries(null).Should().Be(3);
|
||||||
"ResolveNoToolCallRecoveryMaxRetries",
|
AgentLoopRuntimeThresholds.ResolveNoToolCallRecoveryMaxRetries("-1").Should().Be(0);
|
||||||
(string?)null).Should().Be(2);
|
AgentLoopRuntimeThresholds.ResolveNoToolCallRecoveryMaxRetries("99").Should().Be(6);
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolveNoToolCallRecoveryMaxRetries",
|
|
||||||
"-1").Should().Be(0);
|
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolveNoToolCallRecoveryMaxRetries",
|
|
||||||
"99").Should().Be(6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ResolvePlanExecutionRetryMax_UsesDefaultAndClamps()
|
public void ResolvePlanExecutionRetryMax_UsesDefaultAndClamps()
|
||||||
{
|
{
|
||||||
InvokePrivateStatic<int>(
|
AgentLoopRuntimeThresholds.ResolvePlanExecutionRetryMax(null).Should().Be(2);
|
||||||
"ResolvePlanExecutionRetryMax",
|
AgentLoopRuntimeThresholds.ResolvePlanExecutionRetryMax("-5").Should().Be(0);
|
||||||
(string?)null).Should().Be(2);
|
AgentLoopRuntimeThresholds.ResolvePlanExecutionRetryMax("10").Should().Be(6);
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolvePlanExecutionRetryMax",
|
|
||||||
"-5").Should().Be(0);
|
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolvePlanExecutionRetryMax",
|
|
||||||
"10").Should().Be(6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ResolveTerminalEvidenceGateMaxRetries_UsesDefaultAndClamps()
|
public void ResolveTerminalEvidenceGateMaxRetries_UsesDefaultAndClamps()
|
||||||
{
|
{
|
||||||
InvokePrivateStatic<int>(
|
AgentLoopRuntimeThresholds.ResolveTerminalEvidenceGateMaxRetries(null).Should().Be(1);
|
||||||
"ResolveTerminalEvidenceGateMaxRetries",
|
AgentLoopRuntimeThresholds.ResolveTerminalEvidenceGateMaxRetries("-2").Should().Be(0);
|
||||||
(string?)null).Should().Be(1);
|
AgentLoopRuntimeThresholds.ResolveTerminalEvidenceGateMaxRetries("9").Should().Be(3);
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolveTerminalEvidenceGateMaxRetries",
|
|
||||||
"-2").Should().Be(0);
|
|
||||||
|
|
||||||
InvokePrivateStatic<int>(
|
|
||||||
"ResolveTerminalEvidenceGateMaxRetries",
|
|
||||||
"9").Should().Be(3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopDiagnosticsFormatterTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void BuildCompactionCompleteMessage_ShouldIncludeStageSummaryAndSavedTokens()
|
||||||
|
{
|
||||||
|
var result = new ContextCompactionResult
|
||||||
|
{
|
||||||
|
BeforeTokens = 18_400,
|
||||||
|
AfterTokens = 7_900,
|
||||||
|
};
|
||||||
|
result.AppliedStages.Add("tool-result");
|
||||||
|
result.AppliedStages.Add("session-memory");
|
||||||
|
|
||||||
|
var message = AgentLoopDiagnosticsFormatter.BuildCompactionCompleteMessage(result);
|
||||||
|
|
||||||
|
message.Should().Be("Context compaction complete: tool-result -> session-memory, saved 10.3K tokens");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildQueryViewSummary_ShouldIncludeWindowAndPreviewReuseMetrics()
|
||||||
|
{
|
||||||
|
var result = new AgentQueryContextWindowResult
|
||||||
|
{
|
||||||
|
Messages = new(),
|
||||||
|
SourceMessageCount = 34,
|
||||||
|
ViewMessageCount = 12,
|
||||||
|
WindowStartIndex = 21,
|
||||||
|
BoundaryApplied = true,
|
||||||
|
ToolPairExpanded = true,
|
||||||
|
PreservedToolPairCount = 2,
|
||||||
|
TruncatedToolResultCount = 3,
|
||||||
|
ReusedToolResultPreviewCount = 4,
|
||||||
|
TokensBeforeBudget = 8_600,
|
||||||
|
TokensAfterBudget = 6_100,
|
||||||
|
};
|
||||||
|
|
||||||
|
var summary = AgentLoopDiagnosticsFormatter.BuildQueryViewSummary(result);
|
||||||
|
|
||||||
|
summary.Should().Be("query-view 34->12, start=21, pairs=2, tool_result_budget=3, tool_result_preview_reuse=4, tokens 8600->6100");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ public class AgentLoopE2ETests
|
|||||||
var settings = BuildLoopSettings(server.Endpoint);
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
using var llm = new LlmService(settings);
|
using var llm = new LlmService(settings);
|
||||||
using var tools = ToolRegistry.CreateDefault();
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Cowork" };
|
||||||
|
|
||||||
var events = new List<AgentEvent>();
|
var events = new List<AgentEvent>();
|
||||||
loop.EventOccurred += evt => events.Add(evt);
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
@@ -54,7 +54,7 @@ public class AgentLoopE2ETests
|
|||||||
var settings = BuildLoopSettings(server.Endpoint);
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
using var llm = new LlmService(settings);
|
using var llm = new LlmService(settings);
|
||||||
using var tools = ToolRegistry.CreateDefault();
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Cowork" };
|
||||||
|
|
||||||
var events = new List<AgentEvent>();
|
var events = new List<AgentEvent>();
|
||||||
loop.EventOccurred += evt => events.Add(evt);
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
@@ -74,17 +74,14 @@ public class AgentLoopE2ETests
|
|||||||
{
|
{
|
||||||
using var server = new FakeOllamaServer(
|
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"),
|
BuildTextResponse("완료: 결과는 5"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var settings = BuildLoopSettings(server.Endpoint);
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
settings.Settings.Llm.PlanMode = "always";
|
|
||||||
|
|
||||||
using var llm = new LlmService(settings);
|
using var llm = new LlmService(settings);
|
||||||
using var tools = ToolRegistry.CreateDefault();
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Cowork" };
|
||||||
|
|
||||||
var events = new List<AgentEvent>();
|
var events = new List<AgentEvent>();
|
||||||
loop.EventOccurred += evt => events.Add(evt);
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
@@ -95,7 +92,7 @@ public class AgentLoopE2ETests
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
result.Should().Contain("5");
|
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.ToolCall && e.ToolName == "math_eval");
|
||||||
events.Should().Contain(e => e.Type == AgentEventType.Complete);
|
events.Should().Contain(e => e.Type == AgentEventType.Complete);
|
||||||
}
|
}
|
||||||
@@ -110,11 +107,12 @@ public class AgentLoopE2ETests
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
var settings = BuildLoopSettings(server.Endpoint);
|
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 llm = new LlmService(settings);
|
||||||
using var tools = ToolRegistry.CreateDefault();
|
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);
|
loop.AskPermissionCallback = (_, _) => Task.FromResult(false);
|
||||||
|
|
||||||
var events = new List<AgentEvent>();
|
var events = new List<AgentEvent>();
|
||||||
@@ -169,7 +167,7 @@ public class AgentLoopE2ETests
|
|||||||
|
|
||||||
using var llm = new LlmService(settings);
|
using var llm = new LlmService(settings);
|
||||||
using var tools = ToolRegistry.CreateDefault();
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Cowork" };
|
||||||
|
|
||||||
var events = new List<AgentEvent>();
|
var events = new List<AgentEvent>();
|
||||||
loop.EventOccurred += evt => events.Add(evt);
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
@@ -357,8 +355,8 @@ public class AgentLoopE2ETests
|
|||||||
{
|
{
|
||||||
using var server = new FakeOllamaServer(
|
using var server = new FakeOllamaServer(
|
||||||
[
|
[
|
||||||
BuildToolCallResponse("EnterPlanMode", new { }, "plan alias"),
|
BuildToolCallResponse("MathEval", new { expression = "1+1" }, "alias resolution"),
|
||||||
BuildTextResponse("?꾨즺"),
|
BuildTextResponse("완료"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var settings = BuildLoopSettings(server.Endpoint);
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
@@ -372,12 +370,12 @@ public class AgentLoopE2ETests
|
|||||||
|
|
||||||
var result = await loop.RunAsync(
|
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("?꾨즺");
|
result.Should().Contain("완료");
|
||||||
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "enter_plan_mode");
|
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "math_eval");
|
||||||
events.Should().Contain(e => e.Type == AgentEventType.ToolResult && e.ToolName == "enter_plan_mode" && e.Success);
|
events.Should().Contain(e => e.Type == AgentEventType.ToolResult && e.ToolName == "math_eval" && e.Success);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -453,7 +451,6 @@ public class AgentLoopE2ETests
|
|||||||
|
|
||||||
var settings = BuildLoopSettings(server.Endpoint);
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
settings.Settings.Llm.WorkFolder = tempDir;
|
settings.Settings.Llm.WorkFolder = tempDir;
|
||||||
settings.Settings.Llm.Code.EnablePlanModeTools = false;
|
|
||||||
using var llm = new LlmService(settings);
|
using var llm = new LlmService(settings);
|
||||||
using var tools = ToolRegistry.CreateDefault();
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
|
||||||
@@ -487,7 +484,8 @@ public class AgentLoopE2ETests
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
var settings = BuildLoopSettings(server.Endpoint);
|
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 llm = new LlmService(settings);
|
||||||
using var tools = ToolRegistry.CreateDefault();
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
@@ -502,13 +500,288 @@ public class AgentLoopE2ETests
|
|||||||
new ChatMessage { Role = "user", Content = "장애가 있어도 복구해서 끝내줘" }
|
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.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 == "file_write");
|
||||||
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "math_eval");
|
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);
|
events.Should().Contain(e => e.Type == AgentEventType.ToolResult && e.ToolName == "math_eval" && e.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RunAsync_EmptyWorkspace_BlocksExternalFallbackAndRecoversToFileWrite()
|
||||||
|
{
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-empty-workspace-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var server = new FakeOllamaServer(
|
||||||
|
[
|
||||||
|
BuildToolCallResponse("folder_map", new { path = ".", depth = 2, include_files = true }, "empty workspace check"),
|
||||||
|
BuildToolCallResponse("grep", new { pattern = "\\.html$", path = @"C:\\", files_only = true }, "bad external fallback"),
|
||||||
|
BuildToolCallResponse("file_write", new { path = "index.html", content = "<html><body>clock</body></html>" }, "create file directly"),
|
||||||
|
BuildTextResponse("완료"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
|
settings.Settings.Llm.WorkFolder = tempDir;
|
||||||
|
using var llm = new LlmService(settings);
|
||||||
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
|
||||||
|
|
||||||
|
var events = new List<AgentEvent>();
|
||||||
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
|
|
||||||
|
var result = await loop.RunAsync(
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "실시간으로 시간을 표시하는 웹페이지 만들어줘" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
result.Should().Contain("완료");
|
||||||
|
File.Exists(Path.Combine(tempDir, "index.html")).Should().BeTrue();
|
||||||
|
events.Should().Contain(e =>
|
||||||
|
e.Type == AgentEventType.Error
|
||||||
|
&& (e.ToolName == "grep" || e.ToolName == "folder_map")
|
||||||
|
&& e.Summary.Contains("Empty workspace guard blocked", StringComparison.OrdinalIgnoreCase));
|
||||||
|
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "file_write");
|
||||||
|
events.Should().NotContain(e => e.Type == AgentEventType.PermissionRequest && e.ToolName == "grep");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RunAsync_EmptyWorkspace_DisallowsSkillManagerAndRecoversToFileWrite()
|
||||||
|
{
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-empty-workspace-skill-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var server = new FakeOllamaServer(
|
||||||
|
[
|
||||||
|
BuildToolCallResponse("folder_map", new { path = ".", depth = 2, include_files = true }, "empty workspace check"),
|
||||||
|
BuildToolCallResponse("skill_manager", new { action = "list" }, "irrelevant meta tool"),
|
||||||
|
BuildToolCallResponse("file_write", new { path = "index.html", content = "<html><body>ok</body></html>" }, "create file"),
|
||||||
|
BuildTextResponse("완료"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
|
settings.Settings.Llm.WorkFolder = tempDir;
|
||||||
|
using var llm = new LlmService(settings);
|
||||||
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
|
||||||
|
|
||||||
|
var events = new List<AgentEvent>();
|
||||||
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
|
|
||||||
|
var result = await loop.RunAsync(
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "빈 폴더에 index.html 만들어줘" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
result.Should().Contain("완료");
|
||||||
|
File.Exists(Path.Combine(tempDir, "index.html")).Should().BeTrue();
|
||||||
|
events.Should().Contain(e => e.Type == AgentEventType.Error && e.ToolName == "skill_manager");
|
||||||
|
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "file_write");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RunAsync_TextEmbeddedToolCall_RecoversAndExecutesFileWrite()
|
||||||
|
{
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-text-toolcall-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var server = new FakeOllamaServer(
|
||||||
|
[
|
||||||
|
BuildRawResponse("""
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "<tool_call>{\"name\":\"file_write\",\"arguments\":{\"path\":\"index.html\",\"content\":\"<html><body>clock</body></html>\"}}</tool_call>\n파일을 작성합니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
BuildTextResponse("완료"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
|
settings.Settings.Llm.WorkFolder = tempDir;
|
||||||
|
using var llm = new LlmService(settings);
|
||||||
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
|
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
|
||||||
|
|
||||||
|
var events = new List<AgentEvent>();
|
||||||
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
|
|
||||||
|
var result = await loop.RunAsync(
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "index.html 파일 만들어줘" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
result.Should().Contain("완료");
|
||||||
|
File.Exists(Path.Combine(tempDir, "index.html")).Should().BeTrue();
|
||||||
|
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "file_write");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RunAsync_CodeRuntimeWorkspaceOverride_PrefersConversationWorkspaceOverSettingsFolder()
|
||||||
|
{
|
||||||
|
var settingsDir = Path.Combine(Path.GetTempPath(), "axcopilot-settings-workspace-" + Guid.NewGuid().ToString("N"));
|
||||||
|
var conversationDir = Path.Combine(Path.GetTempPath(), "axcopilot-conversation-workspace-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(settingsDir);
|
||||||
|
Directory.CreateDirectory(conversationDir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var server = new FakeOllamaServer(
|
||||||
|
[
|
||||||
|
BuildToolCallResponse("file_write", new { path = "index.html", content = "<html><body>override</body></html>" }, "write to conversation workspace"),
|
||||||
|
BuildTextResponse("?꾨즺"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
|
settings.Settings.Llm.WorkFolder = settingsDir;
|
||||||
|
settings.Settings.Llm.CodeWorkFolder = settingsDir;
|
||||||
|
using var llm = new LlmService(settings);
|
||||||
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
|
var loop = new AgentLoopService(llm, tools, settings)
|
||||||
|
{
|
||||||
|
ActiveTab = "Code",
|
||||||
|
RuntimeWorkFolderOverride = conversationDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await loop.RunAsync(
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "index.html ?뚯씪 留뚮뱾?댁쨾" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
result.Should().Contain("?꾨즺");
|
||||||
|
File.Exists(Path.Combine(conversationDir, "index.html")).Should().BeTrue();
|
||||||
|
File.Exists(Path.Combine(settingsDir, "index.html")).Should().BeFalse();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { if (Directory.Exists(settingsDir)) Directory.Delete(settingsDir, true); } catch { }
|
||||||
|
try { if (Directory.Exists(conversationDir)) Directory.Delete(conversationDir, true); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RunAsync_InternalMode_BypassPermissions_AllowsWorkspaceWriteWithoutPrompt()
|
||||||
|
{
|
||||||
|
var workspaceDir = Path.Combine(Path.GetTempPath(), "axcopilot-internal-workspace-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(workspaceDir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var server = new FakeOllamaServer(
|
||||||
|
[
|
||||||
|
BuildToolCallResponse("file_write", new { path = "inside.txt", content = "ok" }, "write inside workspace"),
|
||||||
|
BuildTextResponse("?꾨즺"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
|
settings.Settings.OperationMode = OperationModePolicy.InternalMode;
|
||||||
|
settings.Settings.Llm.WorkFolder = workspaceDir;
|
||||||
|
settings.Settings.Llm.CodeWorkFolder = workspaceDir;
|
||||||
|
using var llm = new LlmService(settings);
|
||||||
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
|
var loop = new AgentLoopService(llm, tools, settings)
|
||||||
|
{
|
||||||
|
ActiveTab = "Code",
|
||||||
|
RuntimeWorkFolderOverride = workspaceDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
var promptCount = 0;
|
||||||
|
loop.AskPermissionCallback = (_, _) =>
|
||||||
|
{
|
||||||
|
promptCount++;
|
||||||
|
return Task.FromResult(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await loop.RunAsync(
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "inside.txt ?뚯씪 留뚮뱾?댁쨾" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
result.Should().Contain("?꾨즺");
|
||||||
|
File.Exists(Path.Combine(workspaceDir, "inside.txt")).Should().BeTrue();
|
||||||
|
promptCount.Should().Be(0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { if (Directory.Exists(workspaceDir)) Directory.Delete(workspaceDir, true); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RunAsync_InternalMode_BypassPermissions_RequestsApprovalForPathOutsideWorkspace()
|
||||||
|
{
|
||||||
|
var workspaceDir = Path.Combine(Path.GetTempPath(), "axcopilot-internal-base-" + Guid.NewGuid().ToString("N"));
|
||||||
|
var externalDir = Path.Combine(Path.GetTempPath(), "axcopilot-internal-external-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(workspaceDir);
|
||||||
|
Directory.CreateDirectory(externalDir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var externalTarget = Path.Combine(externalDir, "outside.txt");
|
||||||
|
using var server = new FakeOllamaServer(
|
||||||
|
[
|
||||||
|
BuildToolCallResponse("file_write", new { path = externalTarget, content = "blocked" }, "write outside workspace"),
|
||||||
|
BuildTextResponse("?꾨즺"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var settings = BuildLoopSettings(server.Endpoint);
|
||||||
|
settings.Settings.OperationMode = OperationModePolicy.InternalMode;
|
||||||
|
settings.Settings.Llm.WorkFolder = workspaceDir;
|
||||||
|
settings.Settings.Llm.CodeWorkFolder = workspaceDir;
|
||||||
|
using var llm = new LlmService(settings);
|
||||||
|
using var tools = ToolRegistry.CreateDefault();
|
||||||
|
var loop = new AgentLoopService(llm, tools, settings)
|
||||||
|
{
|
||||||
|
ActiveTab = "Code",
|
||||||
|
RuntimeWorkFolderOverride = workspaceDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
var promptCount = 0;
|
||||||
|
var events = new List<AgentEvent>();
|
||||||
|
loop.EventOccurred += evt => events.Add(evt);
|
||||||
|
loop.AskPermissionCallback = (_, _) =>
|
||||||
|
{
|
||||||
|
promptCount++;
|
||||||
|
return Task.FromResult(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await loop.RunAsync(
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "외부 경로에 파일을 써줘" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
result.Should().Contain("?꾨즺");
|
||||||
|
File.Exists(externalTarget).Should().BeFalse();
|
||||||
|
promptCount.Should().Be(1);
|
||||||
|
events.Should().Contain(e =>
|
||||||
|
(e.Type == AgentEventType.PermissionDenied || e.Type == AgentEventType.Error) &&
|
||||||
|
e.ToolName == "file_write");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { if (Directory.Exists(workspaceDir)) Directory.Delete(workspaceDir, true); } catch { }
|
||||||
|
try { if (Directory.Exists(externalDir)) Directory.Delete(externalDir, true); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static SettingsService BuildLoopSettings(string endpoint)
|
private static SettingsService BuildLoopSettings(string endpoint)
|
||||||
{
|
{
|
||||||
var settings = new SettingsService();
|
var settings = new SettingsService();
|
||||||
@@ -519,10 +792,19 @@ public class AgentLoopE2ETests
|
|||||||
settings.Settings.Llm.Model = "test-model";
|
settings.Settings.Llm.Model = "test-model";
|
||||||
settings.Settings.Llm.MaxAgentIterations = 6;
|
settings.Settings.Llm.MaxAgentIterations = 6;
|
||||||
settings.Settings.Llm.MaxRetryOnError = 1;
|
settings.Settings.Llm.MaxRetryOnError = 1;
|
||||||
settings.Settings.Llm.PlanMode = "off";
|
|
||||||
settings.Settings.Llm.EnableToolHooks = false;
|
settings.Settings.Llm.EnableToolHooks = false;
|
||||||
settings.Settings.Llm.EnableAutoRouter = false;
|
settings.Settings.Llm.EnableAutoRouter = false;
|
||||||
settings.Settings.Llm.EnableForkSkillDelegationEnforcement = true;
|
settings.Settings.Llm.EnableForkSkillDelegationEnforcement = true;
|
||||||
|
settings.Settings.Llm.EnableProjectSkillDiscovery = false;
|
||||||
|
settings.Settings.Llm.EnablePluginSkillDiscovery = false;
|
||||||
|
settings.Settings.Llm.EnableMcpSkillDiscovery = false;
|
||||||
|
settings.Settings.Llm.EnableLegacyCommandSkills = false;
|
||||||
|
settings.Settings.Llm.SkillsFolderPath = "";
|
||||||
|
settings.Settings.Llm.AdditionalSkillFolders.Clear();
|
||||||
|
settings.Settings.Llm.FilePermission = "BypassPermissions";
|
||||||
|
settings.Settings.Llm.DefaultAgentPermission = "BypassPermissions";
|
||||||
|
settings.Settings.Llm.BlockedPaths.Clear();
|
||||||
|
settings.Settings.Llm.BlockedExtensions.Clear();
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,6 +848,9 @@ public class AgentLoopE2ETests
|
|||||||
private static string BuildTextOnlyResponse(string content)
|
private static string BuildTextOnlyResponse(string content)
|
||||||
=> BuildTextResponse(content);
|
=> BuildTextResponse(content);
|
||||||
|
|
||||||
|
private static string BuildRawResponse(string rawJson)
|
||||||
|
=> rawJson;
|
||||||
|
|
||||||
private sealed class FakeOllamaServer : IDisposable
|
private sealed class FakeOllamaServer : IDisposable
|
||||||
{
|
{
|
||||||
private readonly HttpListener _listener;
|
private readonly HttpListener _listener;
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopIterationPreparationServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldProjectQueuedCommandsAndBuildUpdatedQueryView()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "assistant-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "existing assistant message"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var queue = new AgentCommandQueue();
|
||||||
|
queue.EnqueueSteering("focus on the changed files", priority: "now", requestInterrupt: true);
|
||||||
|
queue.EnqueueNotification("low priority note", priority: "later");
|
||||||
|
|
||||||
|
var result = AgentLoopIterationPreparationService.Prepare(
|
||||||
|
messages,
|
||||||
|
queue,
|
||||||
|
lastToolResultAtUtc: null,
|
||||||
|
lastToolResultToolName: null,
|
||||||
|
utcNow: DateTime.UtcNow);
|
||||||
|
|
||||||
|
messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queued_input_interrupt" &&
|
||||||
|
message.Role == "system");
|
||||||
|
messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queued_steering" &&
|
||||||
|
message.Role == "user" &&
|
||||||
|
message.Content == "focus on the changed files");
|
||||||
|
result.QueueProjection.Events.Should().Contain(evt =>
|
||||||
|
evt.Type == AgentEventType.Thinking &&
|
||||||
|
evt.Summary.Contains("Deferred 1 lower-priority", StringComparison.OrdinalIgnoreCase));
|
||||||
|
result.QueryView.Messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queued_steering" &&
|
||||||
|
message.Role == "user");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldForwardCodeQueryOptions()
|
||||||
|
{
|
||||||
|
var longContent = new string('Z', 2_200);
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-build","tool_name":"build_run","content":"{{longContent}}"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "assistant-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent assistant message"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopIterationPreparationService.Prepare(
|
||||||
|
messages,
|
||||||
|
new AgentCommandQueue(),
|
||||||
|
lastToolResultAtUtc: null,
|
||||||
|
lastToolResultToolName: null,
|
||||||
|
utcNow: DateTime.UtcNow,
|
||||||
|
queryOptions: AgentQueryContextBuilder.AgentQueryContextBuildOptions.CreateCodeDefault());
|
||||||
|
|
||||||
|
result.QueryView.ProfileName.Should().Be("code");
|
||||||
|
result.QueryView.ToolResultSoftCharLimit.Should().BeGreaterThan(AgentToolResultBudget.DefaultSoftCharLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildToolResultWaitSummary_ShouldFormatToolNameAndElapsedMilliseconds()
|
||||||
|
{
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
var waitSummary = AgentLoopIterationPreparationService.BuildToolResultWaitSummary(
|
||||||
|
utcNow.AddMilliseconds(-1250),
|
||||||
|
"file_read",
|
||||||
|
utcNow);
|
||||||
|
|
||||||
|
waitSummary.Should().StartWith("file_read:");
|
||||||
|
waitSummary.Should().Contain("ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopLlmDispatchStageServiceTests
|
||||||
|
{
|
||||||
|
private sealed class FakeAgentTool : IAgentTool
|
||||||
|
{
|
||||||
|
public string Name { get; init; } = "file_read";
|
||||||
|
public string Description { get; init; } = "Reads a file";
|
||||||
|
public ToolParameterSchema Parameters { get; init; } = new();
|
||||||
|
|
||||||
|
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
|
||||||
|
=> Task.FromResult(ToolResult.Ok("ok"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeToolExecutionCoordinator : IToolExecutionCoordinator
|
||||||
|
{
|
||||||
|
public List<ChatMessage>? CapturedMessages { get; private set; }
|
||||||
|
public IReadOnlyCollection<IAgentTool>? CapturedTools { get; private set; }
|
||||||
|
public string? CapturedPhaseLabel { get; private set; }
|
||||||
|
public bool CapturedForceToolCall { get; private set; }
|
||||||
|
public AgentLoopService.RunState? CapturedRunState { get; private set; }
|
||||||
|
public int PrefetchCallCount { get; private set; }
|
||||||
|
public AgentContext? PrefetchContext { get; private set; }
|
||||||
|
public Func<Func<ToolStreamEvent, Task>?, Func<ContentBlock, Task<ToolPrefetchResult?>>?, Task<List<ContentBlock>>>? DispatchAsync { get; init; }
|
||||||
|
|
||||||
|
public Task<ToolPrefetchResult?> TryPrefetchReadOnlyToolAsync(
|
||||||
|
ContentBlock block,
|
||||||
|
IReadOnlyCollection<IAgentTool> tools,
|
||||||
|
AgentContext context,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
PrefetchCallCount++;
|
||||||
|
PrefetchContext = context;
|
||||||
|
return Task.FromResult<ToolPrefetchResult?>(
|
||||||
|
new ToolPrefetchResult(ToolResult.Ok("prefetched"), 5, block.ToolName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ContentBlock>> SendWithToolsWithRecoveryAsync(
|
||||||
|
List<ChatMessage> messages,
|
||||||
|
IReadOnlyCollection<IAgentTool> tools,
|
||||||
|
CancellationToken ct,
|
||||||
|
string phaseLabel,
|
||||||
|
AgentLoopService.RunState? runState = null,
|
||||||
|
bool forceToolCall = false,
|
||||||
|
Func<ContentBlock, Task<ToolPrefetchResult?>>? prefetchToolCallAsync = null,
|
||||||
|
Func<ToolStreamEvent, Task>? onStreamEventAsync = null)
|
||||||
|
{
|
||||||
|
CapturedMessages = messages;
|
||||||
|
CapturedTools = tools;
|
||||||
|
CapturedPhaseLabel = phaseLabel;
|
||||||
|
CapturedForceToolCall = forceToolCall;
|
||||||
|
CapturedRunState = runState;
|
||||||
|
|
||||||
|
if (DispatchAsync != null)
|
||||||
|
return await DispatchAsync(onStreamEventAsync, prefetchToolCallAsync);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_ShouldDispatchPreparedRequestAndResetRunState()
|
||||||
|
{
|
||||||
|
var coordinator = new FakeToolExecutionCoordinator
|
||||||
|
{
|
||||||
|
DispatchAsync = (_, _) => Task.FromResult(new List<ContentBlock>
|
||||||
|
{
|
||||||
|
new() { Type = "tool_use", ToolName = "file_read", ToolId = "tool-1" }
|
||||||
|
})
|
||||||
|
};
|
||||||
|
var events = new List<(AgentEventType Type, string Tool, string Summary)>();
|
||||||
|
var service = new AgentLoopLlmDispatchStageService(
|
||||||
|
coordinator,
|
||||||
|
block => $"{block.ToolName}:{block.ToolId}",
|
||||||
|
(type, tool, summary) => events.Add((type, tool, summary)));
|
||||||
|
|
||||||
|
var runState = new AgentLoopService.RunState
|
||||||
|
{
|
||||||
|
ContextRecoveryAttempts = 2,
|
||||||
|
TransientLlmErrorRetries = 1,
|
||||||
|
};
|
||||||
|
var request = new AgentLoopLlmRequestPreparationResult(
|
||||||
|
SendMessages:
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "fix the build" }
|
||||||
|
],
|
||||||
|
ForceInitialToolCall: true,
|
||||||
|
InjectedToolReminder: false,
|
||||||
|
SupplementalMessageCount: 0,
|
||||||
|
FlattenedStructuredAssistantCount: 0,
|
||||||
|
ConvertedOrphanToolResultCount: 0,
|
||||||
|
InjectedCodeWorkingSet: false,
|
||||||
|
InjectedSemanticSummary: false,
|
||||||
|
ProtectedActiveDiagnostics: false,
|
||||||
|
InjectedWorkspaceContext: false);
|
||||||
|
|
||||||
|
var result = await service.ExecuteAsync(
|
||||||
|
new AgentLoopLlmDispatchStageInput(
|
||||||
|
Iteration: 3,
|
||||||
|
ConversationId: "conv-1",
|
||||||
|
RunId: "run-1",
|
||||||
|
PhaseLabel: "Main loop 3",
|
||||||
|
CurrentModel: "model-a",
|
||||||
|
QueryContextDetail: "context=ready",
|
||||||
|
LlmRequest: request,
|
||||||
|
ActiveTools: [new FakeAgentTool()],
|
||||||
|
Context: new AgentContext { WorkFolder = @"E:\code", ActiveTab = "Code" },
|
||||||
|
RunState: runState),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
result.Blocks.Should().ContainSingle();
|
||||||
|
coordinator.CapturedMessages.Should().BeSameAs(request.SendMessages);
|
||||||
|
coordinator.CapturedPhaseLabel.Should().Be("Main loop 3");
|
||||||
|
coordinator.CapturedForceToolCall.Should().BeTrue();
|
||||||
|
runState.ContextRecoveryAttempts.Should().Be(0);
|
||||||
|
runState.TransientLlmErrorRetries.Should().Be(0);
|
||||||
|
events.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_ShouldEmitStreamPreviewToolReadyAndRetryResetEvents()
|
||||||
|
{
|
||||||
|
var coordinator = new FakeToolExecutionCoordinator
|
||||||
|
{
|
||||||
|
DispatchAsync = async (onStreamEventAsync, prefetchToolCallAsync) =>
|
||||||
|
{
|
||||||
|
using var readInput = JsonDocument.Parse("""{"path":"Views/MainWindow.xaml"}""");
|
||||||
|
if (prefetchToolCallAsync != null)
|
||||||
|
{
|
||||||
|
await prefetchToolCallAsync(new ContentBlock
|
||||||
|
{
|
||||||
|
Type = "tool_use",
|
||||||
|
ToolName = "file_read",
|
||||||
|
ToolId = "prefetch-1",
|
||||||
|
ToolInput = readInput.RootElement.Clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onStreamEventAsync != null)
|
||||||
|
{
|
||||||
|
await onStreamEventAsync(new ToolStreamEvent(ToolStreamEventKind.TextDelta, "Inspecting WPF files"));
|
||||||
|
await onStreamEventAsync(new ToolStreamEvent(
|
||||||
|
ToolStreamEventKind.ToolCallReady,
|
||||||
|
ToolCall: new ContentBlock
|
||||||
|
{
|
||||||
|
Type = "tool_use",
|
||||||
|
ToolName = "file_manage",
|
||||||
|
ToolId = "tool-9",
|
||||||
|
}));
|
||||||
|
await onStreamEventAsync(new ToolStreamEvent(ToolStreamEventKind.RetryReset, "retry"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var events = new List<(AgentEventType Type, string Tool, string Summary)>();
|
||||||
|
var service = new AgentLoopLlmDispatchStageService(
|
||||||
|
coordinator,
|
||||||
|
block => $"{block.ToolName}:{block.ToolId}",
|
||||||
|
(type, tool, summary) => events.Add((type, tool, summary)));
|
||||||
|
|
||||||
|
var request = new AgentLoopLlmRequestPreparationResult(
|
||||||
|
SendMessages:
|
||||||
|
[
|
||||||
|
new ChatMessage { Role = "user", Content = "create WPF shell" }
|
||||||
|
],
|
||||||
|
ForceInitialToolCall: false,
|
||||||
|
InjectedToolReminder: false,
|
||||||
|
SupplementalMessageCount: 0,
|
||||||
|
FlattenedStructuredAssistantCount: 0,
|
||||||
|
ConvertedOrphanToolResultCount: 0,
|
||||||
|
InjectedCodeWorkingSet: false,
|
||||||
|
InjectedSemanticSummary: false,
|
||||||
|
ProtectedActiveDiagnostics: false,
|
||||||
|
InjectedWorkspaceContext: false);
|
||||||
|
var context = new AgentContext { WorkFolder = @"E:\code", ActiveTab = "Code" };
|
||||||
|
|
||||||
|
await service.ExecuteAsync(
|
||||||
|
new AgentLoopLlmDispatchStageInput(
|
||||||
|
Iteration: 1,
|
||||||
|
ConversationId: "conv-2",
|
||||||
|
RunId: "run-2",
|
||||||
|
PhaseLabel: "Main loop 1",
|
||||||
|
CurrentModel: "model-b",
|
||||||
|
QueryContextDetail: "context=ready",
|
||||||
|
LlmRequest: request,
|
||||||
|
ActiveTools: [new FakeAgentTool(), new FakeAgentTool { Name = "file_manage" }],
|
||||||
|
Context: context,
|
||||||
|
RunState: new AgentLoopService.RunState()),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
coordinator.PrefetchCallCount.Should().Be(1);
|
||||||
|
coordinator.PrefetchContext.Should().BeSameAs(context);
|
||||||
|
events.Should().Contain(entry => entry.Type == AgentEventType.Thinking && entry.Summary.Contains("Inspecting WPF files"));
|
||||||
|
events.Should().Contain(entry => entry.Type == AgentEventType.Thinking && entry.Summary.Contains("Streaming tool ready: file_manage:tool-9"));
|
||||||
|
events.Should().Contain(entry => entry.Type == AgentEventType.Thinking && entry.Summary.Contains("Resetting the streamed partial response before retry."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopLlmRequestPreparationServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldInjectToolReminderOnFirstForcedCall()
|
||||||
|
{
|
||||||
|
var queryMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = "inspect the repository"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopLlmRequestPreparationService.Prepare(
|
||||||
|
queryMessages,
|
||||||
|
totalToolCalls: 0,
|
||||||
|
forceInitialToolCallEnabled: true,
|
||||||
|
injectPreCallToolReminder: true,
|
||||||
|
noToolCallLoopRetry: 0);
|
||||||
|
|
||||||
|
result.ForceInitialToolCall.Should().BeTrue();
|
||||||
|
result.InjectedToolReminder.Should().BeTrue();
|
||||||
|
result.SendMessages.Should().HaveCount(2);
|
||||||
|
result.SendMessages.Last().Content.Should().Contain("[TOOL_REQUIRED]");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldSkipReminderWhenRetryLoopIsAlreadyActive()
|
||||||
|
{
|
||||||
|
var queryMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = "inspect the repository"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopLlmRequestPreparationService.Prepare(
|
||||||
|
queryMessages,
|
||||||
|
totalToolCalls: 0,
|
||||||
|
forceInitialToolCallEnabled: true,
|
||||||
|
injectPreCallToolReminder: true,
|
||||||
|
noToolCallLoopRetry: 1);
|
||||||
|
|
||||||
|
result.ForceInitialToolCall.Should().BeTrue();
|
||||||
|
result.InjectedToolReminder.Should().BeFalse();
|
||||||
|
result.SendMessages.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldAppendSupplementalMessagesBeforeReminder()
|
||||||
|
{
|
||||||
|
var queryMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = "fix the latest build failure"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var supplemental = new ChatMessage
|
||||||
|
{
|
||||||
|
Role = "system",
|
||||||
|
MetaKind = "code_working_set",
|
||||||
|
Content = "[code-working-set]\n- Active diagnostic: MC4005 - Themes/ControlStyles.xaml"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopLlmRequestPreparationService.Prepare(
|
||||||
|
queryMessages,
|
||||||
|
totalToolCalls: 0,
|
||||||
|
forceInitialToolCallEnabled: true,
|
||||||
|
injectPreCallToolReminder: true,
|
||||||
|
noToolCallLoopRetry: 0,
|
||||||
|
supplementalMessages: [supplemental]);
|
||||||
|
|
||||||
|
result.SupplementalMessageCount.Should().Be(1);
|
||||||
|
result.SendMessages[1].MetaKind.Should().Be("code_working_set");
|
||||||
|
result.SendMessages.Last().Content.Should().Contain("[TOOL_REQUIRED]");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldNormalizeBrokenHistoricalToolTraceBeforeSend()
|
||||||
|
{
|
||||||
|
var queryMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "assistant",
|
||||||
|
Content = """{"_tool_use_blocks":[{"type":"text","text":"Inspecting source"},{"type":"tool_use","id":"call-missing","name":"file_read","input":{"path":"App.xaml"}}]}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = "fix the latest build failure"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopLlmRequestPreparationService.Prepare(
|
||||||
|
queryMessages,
|
||||||
|
totalToolCalls: 1,
|
||||||
|
forceInitialToolCallEnabled: true,
|
||||||
|
injectPreCallToolReminder: true,
|
||||||
|
noToolCallLoopRetry: 0);
|
||||||
|
|
||||||
|
result.FlattenedStructuredAssistantCount.Should().Be(1);
|
||||||
|
result.ConvertedOrphanToolResultCount.Should().Be(0);
|
||||||
|
result.SendMessages[0].Content.Should().Be("Inspecting source\n[previous tool call] file_read");
|
||||||
|
queryMessages[0].Content.Should().StartWith("{\"_tool_use_blocks\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopNoToolResponseRecoveryServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void BuildNoToolLoopRecovery_ShouldTriggerImmediatelyForProbeOnlyRuns()
|
||||||
|
{
|
||||||
|
var result = AgentLoopNoToolResponseRecoveryService.BuildNoToolLoopRecovery(
|
||||||
|
requiresConcreteArtifactOrEdit: true,
|
||||||
|
onlyProbeToolsUsed: true,
|
||||||
|
totalToolCalls: 1,
|
||||||
|
consecutiveNoToolResponses: 1,
|
||||||
|
noToolResponseThreshold: 2,
|
||||||
|
currentRetryCount: 0,
|
||||||
|
maxRetryCount: 2,
|
||||||
|
preferredInitialToolSequence: "file_read -> file_edit",
|
||||||
|
activeToolNames: ["dev_env_detect", "file_read", "file_edit"]);
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.NextRetryCount.Should().Be(1);
|
||||||
|
result.RecoveryContent.Should().Contain("도구를 호출하지 않았습니다");
|
||||||
|
result.RecoveryContent.Should().Contain("file_read -> file_edit");
|
||||||
|
result.RecoveryContent.Should().Contain("dev_env_detect, file_read, file_edit");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildNoToolLoopRecovery_ShouldReturnFinalWarningOnLastRetry()
|
||||||
|
{
|
||||||
|
var result = AgentLoopNoToolResponseRecoveryService.BuildNoToolLoopRecovery(
|
||||||
|
requiresConcreteArtifactOrEdit: true,
|
||||||
|
onlyProbeToolsUsed: false,
|
||||||
|
totalToolCalls: 0,
|
||||||
|
consecutiveNoToolResponses: 2,
|
||||||
|
noToolResponseThreshold: 2,
|
||||||
|
currentRetryCount: 1,
|
||||||
|
maxRetryCount: 2,
|
||||||
|
preferredInitialToolSequence: "file_read -> file_edit",
|
||||||
|
activeToolNames: ["file_read", "file_edit", "file_read"]);
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.NextRetryCount.Should().Be(2);
|
||||||
|
result.RecoveryContent.Should().Contain("최종 경고");
|
||||||
|
result.RecoveryContent.Should().Contain("file_read, file_edit");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildPlanExecutionRecovery_ShouldGeneratePlanSpecificRetryPrompt()
|
||||||
|
{
|
||||||
|
var result = AgentLoopNoToolResponseRecoveryService.BuildPlanExecutionRecovery(
|
||||||
|
requiresConcreteArtifactOrEdit: true,
|
||||||
|
forceToolCallAfterPlan: true,
|
||||||
|
planStepCount: 3,
|
||||||
|
totalToolCalls: 0,
|
||||||
|
currentRetryCount: 0,
|
||||||
|
maxRetryCount: 2,
|
||||||
|
preferredInitialToolSequence: "file_read -> file_edit",
|
||||||
|
activeToolNames: ["file_read", "file_edit", "git_tool"]);
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.NextRetryCount.Should().Be(1);
|
||||||
|
result.RecoveryContent.Should().Contain("계획을 세웠지만 도구를 호출하지 않았습니다");
|
||||||
|
result.EventSummary.Should().Contain("실행 재시도 1/2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildPlanExecutionRecovery_ShouldReturnNullWhenRetryIsExhausted()
|
||||||
|
{
|
||||||
|
var result = AgentLoopNoToolResponseRecoveryService.BuildPlanExecutionRecovery(
|
||||||
|
requiresConcreteArtifactOrEdit: true,
|
||||||
|
forceToolCallAfterPlan: true,
|
||||||
|
planStepCount: 2,
|
||||||
|
totalToolCalls: 0,
|
||||||
|
currentRetryCount: 2,
|
||||||
|
maxRetryCount: 2,
|
||||||
|
preferredInitialToolSequence: "file_read -> file_edit",
|
||||||
|
activeToolNames: ["file_read", "file_edit"]);
|
||||||
|
|
||||||
|
result.Should().BeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopPreLlmStageServiceTests
|
||||||
|
{
|
||||||
|
private sealed class FakeAgentTool : IAgentTool
|
||||||
|
{
|
||||||
|
public string Name { get; init; } = "file_read";
|
||||||
|
public string Description { get; init; } = "Reads a file";
|
||||||
|
public ToolParameterSchema Parameters { get; init; } = new();
|
||||||
|
|
||||||
|
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
|
||||||
|
=> Task.FromResult(ToolResult.Ok("ok"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldPlanFreeTierDelayForGeminiAfterFirstIteration()
|
||||||
|
{
|
||||||
|
var result = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||||
|
QueryMessages: [new ChatMessage { Role = "user", Content = "inspect" }],
|
||||||
|
ActiveTools: [new FakeAgentTool()],
|
||||||
|
CodeWorkingSet: null,
|
||||||
|
ActiveTab: "Code",
|
||||||
|
AgentLogLevel: "info",
|
||||||
|
Iteration: 2,
|
||||||
|
MaxIterations: 40,
|
||||||
|
FreeTierMode: true,
|
||||||
|
ActiveService: "gemini",
|
||||||
|
FreeTierDelaySeconds: 5,
|
||||||
|
LatestUserPrompt: "inspect",
|
||||||
|
LastUserPromptHookFingerprint: null,
|
||||||
|
RunId: "run-1",
|
||||||
|
TotalToolCalls: 1,
|
||||||
|
ForceInitialToolCallEnabled: true,
|
||||||
|
InjectPreCallToolReminder: true,
|
||||||
|
NoToolCallLoopRetry: 0,
|
||||||
|
HasRuntimeOverride: false,
|
||||||
|
RuntimeAllowedToolCount: 0));
|
||||||
|
|
||||||
|
result.FreeTierDelayMilliseconds.Should().Be(5000);
|
||||||
|
result.FreeTierDelaySummary.Should().Contain("5s");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldEmitUserPromptHookOnlyWhenFingerprintChanges()
|
||||||
|
{
|
||||||
|
var unchanged = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||||
|
QueryMessages: [new ChatMessage { Role = "user", Content = "fix build" }],
|
||||||
|
ActiveTools: [new FakeAgentTool()],
|
||||||
|
CodeWorkingSet: null,
|
||||||
|
ActiveTab: "Code",
|
||||||
|
AgentLogLevel: "debug",
|
||||||
|
Iteration: 1,
|
||||||
|
MaxIterations: 20,
|
||||||
|
FreeTierMode: false,
|
||||||
|
ActiveService: "vllm",
|
||||||
|
FreeTierDelaySeconds: 0,
|
||||||
|
LatestUserPrompt: "fix build",
|
||||||
|
LastUserPromptHookFingerprint: $"{ "fix build".Length}:{ "fix build".GetHashCode()}",
|
||||||
|
RunId: "run-2",
|
||||||
|
TotalToolCalls: 0,
|
||||||
|
ForceInitialToolCallEnabled: true,
|
||||||
|
InjectPreCallToolReminder: true,
|
||||||
|
NoToolCallLoopRetry: 0,
|
||||||
|
HasRuntimeOverride: false,
|
||||||
|
RuntimeAllowedToolCount: 0));
|
||||||
|
|
||||||
|
unchanged.ShouldRunUserPromptSubmitHook.Should().BeFalse();
|
||||||
|
unchanged.UserPromptSubmitHookPayloadJson.Should().BeNull();
|
||||||
|
|
||||||
|
var changed = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||||
|
QueryMessages: [new ChatMessage { Role = "user", Content = "fix build" }],
|
||||||
|
ActiveTools: [new FakeAgentTool()],
|
||||||
|
CodeWorkingSet: null,
|
||||||
|
ActiveTab: "Code",
|
||||||
|
AgentLogLevel: "debug",
|
||||||
|
Iteration: 1,
|
||||||
|
MaxIterations: 20,
|
||||||
|
FreeTierMode: false,
|
||||||
|
ActiveService: "vllm",
|
||||||
|
FreeTierDelaySeconds: 0,
|
||||||
|
LatestUserPrompt: "fix build",
|
||||||
|
LastUserPromptHookFingerprint: null,
|
||||||
|
RunId: "run-3",
|
||||||
|
TotalToolCalls: 0,
|
||||||
|
ForceInitialToolCallEnabled: true,
|
||||||
|
InjectPreCallToolReminder: true,
|
||||||
|
NoToolCallLoopRetry: 0,
|
||||||
|
HasRuntimeOverride: false,
|
||||||
|
RuntimeAllowedToolCount: 0));
|
||||||
|
|
||||||
|
changed.ShouldRunUserPromptSubmitHook.Should().BeTrue();
|
||||||
|
changed.UserPromptSubmitHookPayloadJson.Should().Contain("\"runId\":\"run-3\"");
|
||||||
|
changed.ThinkingSummary.Should().Contain("iteration 1/20");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldReturnRuntimeSpecificMissingToolsFailure()
|
||||||
|
{
|
||||||
|
var result = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||||
|
QueryMessages: [new ChatMessage { Role = "user", Content = "fix build" }],
|
||||||
|
ActiveTools: [],
|
||||||
|
CodeWorkingSet: null,
|
||||||
|
ActiveTab: "Code",
|
||||||
|
AgentLogLevel: "info",
|
||||||
|
Iteration: 1,
|
||||||
|
MaxIterations: 20,
|
||||||
|
FreeTierMode: false,
|
||||||
|
ActiveService: "vllm",
|
||||||
|
FreeTierDelaySeconds: 0,
|
||||||
|
LatestUserPrompt: "fix build",
|
||||||
|
LastUserPromptHookFingerprint: null,
|
||||||
|
RunId: "run-4",
|
||||||
|
TotalToolCalls: 0,
|
||||||
|
ForceInitialToolCallEnabled: true,
|
||||||
|
InjectPreCallToolReminder: true,
|
||||||
|
NoToolCallLoopRetry: 0,
|
||||||
|
HasRuntimeOverride: true,
|
||||||
|
RuntimeAllowedToolCount: 3));
|
||||||
|
|
||||||
|
result.LlmRequest.Should().BeNull();
|
||||||
|
result.MissingToolsEventSummary.Should().Contain("skill runtime policy");
|
||||||
|
result.MissingToolsReturnMessage.Should().Contain("allowed-tools");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Prepare_ShouldBuildLlmRequestWhenToolsExist()
|
||||||
|
{
|
||||||
|
var workingSet = new CodeTaskWorkingSetService(
|
||||||
|
"create WPF shell",
|
||||||
|
@"E:\code",
|
||||||
|
"wpf-mvvm",
|
||||||
|
startedFromEmptyWorkspace: true);
|
||||||
|
using var mkdirDoc = JsonDocument.Parse("""{"action":"mkdir","paths":["Views","ViewModels"]}""");
|
||||||
|
workingSet.RecordToolResult("file_manage", mkdirDoc.RootElement.Clone(), ToolResult.Ok("created directories"));
|
||||||
|
|
||||||
|
var result = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||||
|
QueryMessages: [new ChatMessage { Role = "user", Content = "create WPF shell" }],
|
||||||
|
ActiveTools: [new FakeAgentTool()],
|
||||||
|
CodeWorkingSet: workingSet,
|
||||||
|
ActiveTab: "Code",
|
||||||
|
AgentLogLevel: "info",
|
||||||
|
Iteration: 1,
|
||||||
|
MaxIterations: 30,
|
||||||
|
FreeTierMode: false,
|
||||||
|
ActiveService: "vllm",
|
||||||
|
FreeTierDelaySeconds: 0,
|
||||||
|
LatestUserPrompt: "create WPF shell",
|
||||||
|
LastUserPromptHookFingerprint: null,
|
||||||
|
RunId: "run-5",
|
||||||
|
TotalToolCalls: 0,
|
||||||
|
ForceInitialToolCallEnabled: true,
|
||||||
|
InjectPreCallToolReminder: true,
|
||||||
|
NoToolCallLoopRetry: 0,
|
||||||
|
HasRuntimeOverride: false,
|
||||||
|
RuntimeAllowedToolCount: 0));
|
||||||
|
|
||||||
|
result.LlmRequest.Should().NotBeNull();
|
||||||
|
result.LlmRequest!.SupplementalMessageCount.Should().Be(2);
|
||||||
|
result.LlmRequest.SendMessages.Should().Contain(message => message.MetaKind == "code_working_set");
|
||||||
|
result.LlmRequest.SendMessages.Should().Contain(message => message.MetaKind == "code_semantic_summary");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopQueryAssemblyServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void PrepareHistory_ShouldInjectSessionLearningsForNonCodeTabs()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = "fix the build"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var collector = new SessionLearningCollector();
|
||||||
|
collector.TryExtract("build_run", "error CS0246: Missing type", success: false);
|
||||||
|
|
||||||
|
var result = AgentLoopQueryAssemblyService.PrepareHistory(
|
||||||
|
messages,
|
||||||
|
new AgentCommandQueue(),
|
||||||
|
lastToolResultAtUtc: null,
|
||||||
|
lastToolResultToolName: null,
|
||||||
|
utcNow: DateTime.UtcNow,
|
||||||
|
isCodeTab: false,
|
||||||
|
sessionLearnings: collector,
|
||||||
|
queryOptions: AgentQueryContextBuilder.AgentQueryContextBuildOptions.CreateDefault());
|
||||||
|
|
||||||
|
result.RefreshedSessionLearnings.Should().BeTrue();
|
||||||
|
messages[0].MetaKind.Should().Be("session_learnings");
|
||||||
|
messages[0].Content.Should().Contain("[System:SessionLearnings]");
|
||||||
|
result.IterationPreparation.QueryView.Messages.Should().Contain(message => message.MetaKind == "session_learnings");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PrepareHistory_ShouldSkipSessionLearningsForCodeTabs()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = "fix the build"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var collector = new SessionLearningCollector();
|
||||||
|
collector.TryExtract("build_run", "error CS0246: Missing type", success: false);
|
||||||
|
|
||||||
|
var result = AgentLoopQueryAssemblyService.PrepareHistory(
|
||||||
|
messages,
|
||||||
|
new AgentCommandQueue(),
|
||||||
|
lastToolResultAtUtc: null,
|
||||||
|
lastToolResultToolName: null,
|
||||||
|
utcNow: DateTime.UtcNow,
|
||||||
|
isCodeTab: true,
|
||||||
|
sessionLearnings: collector,
|
||||||
|
queryOptions: AgentQueryContextBuilder.AgentQueryContextBuildOptions.CreateCodeDefault());
|
||||||
|
|
||||||
|
result.RefreshedSessionLearnings.Should().BeFalse();
|
||||||
|
messages.Should().OnlyContain(message => message.MetaKind != "session_learnings");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PrepareRequest_ShouldAppendWorkingSetMessageForCodeRuns()
|
||||||
|
{
|
||||||
|
var queryMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = "create the WPF shell"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var workingSet = new CodeTaskWorkingSetService(
|
||||||
|
"create the WPF shell",
|
||||||
|
@"E:\code",
|
||||||
|
"wpf-mvvm",
|
||||||
|
startedFromEmptyWorkspace: true);
|
||||||
|
using var fileManageInput = JsonDocument.Parse("""{"action":"mkdir","paths":["Views","ViewModels","Themes"]}""");
|
||||||
|
workingSet.RecordToolResult("file_manage", fileManageInput.RootElement.Clone(), ToolResult.Ok("created directories"));
|
||||||
|
|
||||||
|
var result = AgentLoopQueryAssemblyService.PrepareRequest(
|
||||||
|
queryMessages,
|
||||||
|
workingSet,
|
||||||
|
totalToolCalls: 0,
|
||||||
|
forceInitialToolCallEnabled: true,
|
||||||
|
injectPreCallToolReminder: true,
|
||||||
|
noToolCallLoopRetry: 0);
|
||||||
|
|
||||||
|
result.SupplementalMessageCount.Should().Be(2);
|
||||||
|
result.SendMessages.Should().Contain(message => message.MetaKind == "code_working_set");
|
||||||
|
result.SendMessages.Should().Contain(message => message.MetaKind == "code_semantic_summary");
|
||||||
|
result.InjectedCodeWorkingSet.Should().BeTrue();
|
||||||
|
result.InjectedSemanticSummary.Should().BeTrue();
|
||||||
|
result.SendMessages.Last().Content.Should().Contain("[TOOL_REQUIRED]");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using AxCopilot.Services;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentLoopResponseClassificationServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Classify_ShouldSplitTextAndToolCalls_AndResetNoToolCounter()
|
||||||
|
{
|
||||||
|
var blocks = new List<ContentBlock>
|
||||||
|
{
|
||||||
|
new() { Type = "text", Text = "I will inspect the repository." },
|
||||||
|
new() { Type = "tool_use", ToolName = "file_read", ToolId = "tool-1" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopResponseClassificationService.Classify(blocks, consecutiveNoToolResponses: 2);
|
||||||
|
|
||||||
|
result.TextResponse.Should().Contain("inspect the repository");
|
||||||
|
result.ToolCalls.Should().HaveCount(1);
|
||||||
|
result.NextConsecutiveNoToolResponses.Should().Be(0);
|
||||||
|
result.BuildThinkingSummary().Should().Contain("inspect the repository");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Classify_ShouldIncrementNoToolCounter_WhenOnlyTextBlocksArePresent()
|
||||||
|
{
|
||||||
|
var blocks = new List<ContentBlock>
|
||||||
|
{
|
||||||
|
new() { Type = "text", Text = "Plan step one." },
|
||||||
|
new() { Type = "text", Text = "Plan step two." }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopResponseClassificationService.Classify(blocks, consecutiveNoToolResponses: 1);
|
||||||
|
|
||||||
|
result.ToolCalls.Should().BeEmpty();
|
||||||
|
result.TextParts.Should().HaveCount(2);
|
||||||
|
result.NextConsecutiveNoToolResponses.Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildThinkingSummary_ShouldDropTranscriptArtifacts()
|
||||||
|
{
|
||||||
|
var blocks = new List<ContentBlock>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Type = "text",
|
||||||
|
Text = """
|
||||||
|
[이전 도구 호출: file_read]
|
||||||
|
1.
|
||||||
|
file_read]
|
||||||
|
실제 수정 범위를 다시 확인합니다.
|
||||||
|
"""
|
||||||
|
},
|
||||||
|
new() { Type = "tool_use", ToolName = "file_read", ToolId = "tool-1" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopResponseClassificationService.Classify(blocks, consecutiveNoToolResponses: 0);
|
||||||
|
|
||||||
|
result.BuildThinkingSummary().Should().Be("실제 수정 범위를 다시 확인합니다.");
|
||||||
|
}
|
||||||
|
[Fact]
|
||||||
|
public void Classify_ShouldRecoverToolCallEmbeddedInText()
|
||||||
|
{
|
||||||
|
var blocks = new List<ContentBlock>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Type = "text",
|
||||||
|
Text = """
|
||||||
|
<tool_call>{"name":"file_write","arguments":{"path":"index.html","content":"<html>clock</html>"}}</tool_call>
|
||||||
|
이제 파일을 생성합니다.
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentLoopResponseClassificationService.Classify(blocks, consecutiveNoToolResponses: 0);
|
||||||
|
|
||||||
|
result.ToolCalls.Should().ContainSingle();
|
||||||
|
result.ToolCalls[0].ToolName.Should().Be("file_write");
|
||||||
|
result.TextResponse.Should().Contain("이제 파일을 생성합니다.");
|
||||||
|
result.TextResponse.Should().NotContain("<tool_call>");
|
||||||
|
result.NextConsecutiveNoToolResponses.Should().Be(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentMessageInvariantHelperTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void NormalizeHistoricalToolTrace_ShouldFlattenAssistantWithMissingToolResult()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "assistant",
|
||||||
|
Content = """{"_tool_use_blocks":[{"type":"text","text":"Reviewing the project"},{"type":"tool_use","id":"call-missing","name":"file_read","input":{"path":"App.xaml"}}]}"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentMessageInvariantHelper.NormalizeHistoricalToolTrace(messages);
|
||||||
|
|
||||||
|
result.FlattenedAssistantCount.Should().Be(1);
|
||||||
|
messages[0].Content.Should().Be("Reviewing the project\n[previous tool call] file_read");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NormalizeHistoricalToolTrace_ShouldConvertOrphanToolResultToPlainUserTranscript()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-orphan","tool_name":"file_read","content":"line1\nline2"}"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentMessageInvariantHelper.NormalizeHistoricalToolTrace(messages);
|
||||||
|
|
||||||
|
result.ConvertedOrphanToolResultCount.Should().Be(1);
|
||||||
|
messages[0].Content.Should().Be("[previous tool result: file_read]\nline1 line2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NormalizeHistoricalToolTrace_ShouldPreserveValidStructuredToolPair()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "assistant",
|
||||||
|
Content = """{"_tool_use_blocks":[{"type":"text","text":"Reading source"},{"type":"tool_use","id":"call-valid","name":"file_read","input":{"path":"App.xaml"}}]}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-valid","tool_name":"file_read","content":"file content"}"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentMessageInvariantHelper.NormalizeHistoricalToolTrace(messages);
|
||||||
|
|
||||||
|
result.TotalAdjustedCount.Should().Be(0);
|
||||||
|
messages[0].Content.Should().StartWith("{\"_tool_use_blocks\"");
|
||||||
|
messages[1].Content.Should().StartWith("{\"type\":\"tool_result\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PopulateMissingToolResultPreviews_ShouldSynthesizePreview_WhenNoStoredPreviewExists()
|
||||||
|
{
|
||||||
|
var longContent = string.Join(' ', Enumerable.Repeat("Detailed output row for recovery.", 40));
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-result-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-synth","tool_name":"file_read","content":"{{longContent}}"}"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var changed = AgentMessageInvariantHelper.PopulateMissingToolResultPreviews(messages);
|
||||||
|
|
||||||
|
changed.Should().BeTrue();
|
||||||
|
messages[0].QueryPreviewContent.Should().NotBeNullOrWhiteSpace();
|
||||||
|
messages[0].QueryPreviewContent.Should().Contain("call-synth");
|
||||||
|
messages[0].QueryPreviewContent.Should().Contain("...");
|
||||||
|
messages[0].QueryPreviewContent.Should().NotContain(longContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildToolResultPreviewMap_ShouldPreferExplicitPreview_WhenAvailable()
|
||||||
|
{
|
||||||
|
var explicitPreview = """{"type":"tool_result","tool_use_id":"call-explicit","tool_name":"file_read","content":"preview"}""";
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-result-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-explicit","tool_name":"file_read","content":"long output content"}""",
|
||||||
|
QueryPreviewContent = explicitPreview
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-result-2",
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-explicit","tool_name":"file_read","content":"another long output content"}"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var map = AgentMessageInvariantHelper.BuildToolResultPreviewMap(messages);
|
||||||
|
|
||||||
|
map["call-explicit"].Should().Be(explicitPreview);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PopulateMissingToolResultPreviews_ShouldRebindPreview_WhenFingerprintMatchesDifferentToolUseId()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-result-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-original","tool_name":"file_read","content":"same long output that should map across sessions"}""",
|
||||||
|
QueryPreviewContent = """{"type":"tool_result","tool_use_id":"call-original","tool_name":"file_read","content":"stable preview"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-result-2",
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-replayed","tool_name":"file_read","content":"same long output that should map across sessions"}"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var changed = AgentMessageInvariantHelper.PopulateMissingToolResultPreviews(messages);
|
||||||
|
|
||||||
|
changed.Should().BeTrue();
|
||||||
|
messages[1].QueryPreviewContent.Should().Contain("call-replayed");
|
||||||
|
messages[1].QueryPreviewContent.Should().Contain("stable preview");
|
||||||
|
messages[1].QueryPreviewContent.Should().NotContain("call-original");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentProgressSummarySanitizerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void IsLowSignalStatusSummary_ReturnsTrueForModelWaitMessages()
|
||||||
|
{
|
||||||
|
AgentProgressSummarySanitizer.IsLowSignalStatusSummary(
|
||||||
|
"수정 단계: 모델 첫 응답을 기다리는 중입니다... (8초)",
|
||||||
|
"")
|
||||||
|
.Should()
|
||||||
|
.BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsLowSignalStatusSummary_ReturnsFalseForConcreteWorkMessages()
|
||||||
|
{
|
||||||
|
AgentProgressSummarySanitizer.IsLowSignalStatusSummary(
|
||||||
|
"ChatWindow.xaml.cs와 AgentLoopService.cs를 비교해 수정 범위를 정리하고 있습니다.",
|
||||||
|
"file_read")
|
||||||
|
.Should()
|
||||||
|
.BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentQueryContextBuilderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Build_ShouldPopulateMissingToolResultPreviewBeforeCreatingQueryView()
|
||||||
|
{
|
||||||
|
var longContent = new string('Z', 1800);
|
||||||
|
var sourceMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-source-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-view","tool_name":"file_read","content":"{{longContent}}"}""",
|
||||||
|
QueryPreviewContent = """{"type":"tool_result","tool_use_id":"call-view","tool_name":"file_read","content":"preview-view"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-source-2",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-view","tool_name":"file_read","content":"{{longContent}}"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentQueryContextBuilder.Build(sourceMessages);
|
||||||
|
|
||||||
|
sourceMessages[1].QueryPreviewContent.Should().Be(sourceMessages[0].QueryPreviewContent);
|
||||||
|
result.Messages[1].QueryPreviewContent.Should().Be(sourceMessages[0].QueryPreviewContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_ShouldSynthesizeToolResultPreview_WhenNoStoredPreviewExists()
|
||||||
|
{
|
||||||
|
var longContent = string.Join(' ', Enumerable.Repeat("long tool output", 120));
|
||||||
|
var sourceMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-source-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-synth-view","tool_name":"file_read","content":"{{longContent}}"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentQueryContextBuilder.Build(sourceMessages);
|
||||||
|
|
||||||
|
sourceMessages[0].QueryPreviewContent.Should().NotBeNullOrWhiteSpace();
|
||||||
|
sourceMessages[0].QueryPreviewContent.Should().Contain("call-synth-view");
|
||||||
|
result.Messages.Should().Contain(message =>
|
||||||
|
message.QueryPreviewContent != null &&
|
||||||
|
message.QueryPreviewContent.Contains("call-synth-view", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_ShouldExposeCodeProfileMetadata()
|
||||||
|
{
|
||||||
|
var sourceMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-source-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-code","tool_name":"build_run","content":"short"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentQueryContextBuilder.Build(
|
||||||
|
sourceMessages,
|
||||||
|
AgentQueryContextBuilder.AgentQueryContextBuildOptions.CreateCodeDefault());
|
||||||
|
|
||||||
|
result.ProfileName.Should().Be("code");
|
||||||
|
result.ProtectedRecentNonSystemMessages.Should().BeGreaterThan(8);
|
||||||
|
result.ToolResultAggregateBudgetChars.Should().BeGreaterThan(AgentToolResultBudget.DefaultAggregateBudgetChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_ShouldRestoreRecentCompactedToolSnippetsIntoPostCompactContextMessage()
|
||||||
|
{
|
||||||
|
var sourceMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-call-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = """{"_tool_use_blocks":[{"type":"tool_use","id":"call-restore","name":"file_read","input":{"path":"MainWindow.xaml"}}]}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-result-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = """{"type":"tool_result","tool_use_id":"call-restore","tool_name":"file_read","content":"<Window Title=\"Preview\" />"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "boundary-1",
|
||||||
|
Role = "system",
|
||||||
|
MetaKind = "microcompact_boundary",
|
||||||
|
Content = "[previous conversation summary]"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentQueryContextBuilder.Build(sourceMessages);
|
||||||
|
var postCompactMessage = result.Messages.Single(message => message.MetaKind == "post_compact_context");
|
||||||
|
|
||||||
|
postCompactMessage.Content.Should().Contain("recent compacted tool trace:");
|
||||||
|
postCompactMessage.Content.Should().Contain("file_read result:");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentQueuedCommandProjectorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Project_ShouldCreateInterruptMessageAndDeferredEvent()
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var commands = new List<AgentQueuedCommand>
|
||||||
|
{
|
||||||
|
new(1, AgentCommandKind.Steering, AgentQueuePriority.Now, "steer-now", now, true),
|
||||||
|
new(2, AgentCommandKind.Notification, AgentQueuePriority.Now, "later-note", now.AddSeconds(1), false),
|
||||||
|
};
|
||||||
|
|
||||||
|
var projection = AgentQueuedCommandProjector.Project(commands, deferredCount: 2);
|
||||||
|
|
||||||
|
projection.Messages.Should().ContainSingle(message =>
|
||||||
|
message.MetaKind == "queued_input_interrupt" &&
|
||||||
|
message.Role == "system");
|
||||||
|
projection.Messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queued_steering" &&
|
||||||
|
message.Role == "user" &&
|
||||||
|
message.Content == "steer-now");
|
||||||
|
projection.Messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queue_notification" &&
|
||||||
|
message.Role == "system" &&
|
||||||
|
message.Content == "later-note");
|
||||||
|
projection.Events.Should().Contain(evt =>
|
||||||
|
evt.Type == AgentEventType.Thinking &&
|
||||||
|
evt.Summary.Contains("Deferred 2 lower-priority", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Project_ShouldMapPermissionResumeAndPromptKindsConsistently()
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var commands = new List<AgentQueuedCommand>
|
||||||
|
{
|
||||||
|
new(1, AgentCommandKind.PermissionContinuation, AgentQueuePriority.Now, "continue-tool", now, true),
|
||||||
|
new(2, AgentCommandKind.Resume, AgentQueuePriority.Now, "resume-run", now.AddSeconds(1), false),
|
||||||
|
new(3, AgentCommandKind.Prompt, AgentQueuePriority.Now, "follow-up", now.AddSeconds(2), false),
|
||||||
|
new(4, AgentCommandKind.UserDecision, AgentQueuePriority.Now, "choose-option-b", now.AddSeconds(3), false),
|
||||||
|
};
|
||||||
|
|
||||||
|
var projection = AgentQueuedCommandProjector.Project(commands, deferredCount: 0);
|
||||||
|
|
||||||
|
projection.Messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queue_permission_continuation" &&
|
||||||
|
message.Role == "system");
|
||||||
|
projection.Messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queue_resume" &&
|
||||||
|
message.Role == "system");
|
||||||
|
projection.Messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queued_prompt" &&
|
||||||
|
message.Role == "user");
|
||||||
|
projection.Messages.Should().Contain(message =>
|
||||||
|
message.MetaKind == "queued_user_decision" &&
|
||||||
|
message.Role == "user");
|
||||||
|
projection.Events.Should().Contain(evt =>
|
||||||
|
evt.Type == AgentEventType.UserMessage &&
|
||||||
|
evt.Summary == "follow-up");
|
||||||
|
projection.Events.Should().Contain(evt =>
|
||||||
|
evt.Type == AgentEventType.Thinking &&
|
||||||
|
evt.Summary == "continue-tool");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentStatusNarrativeCatalogTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void BuildFromEvent_ShouldDescribeCodeReadWithTargetDetail()
|
||||||
|
{
|
||||||
|
var evt = new AgentEvent
|
||||||
|
{
|
||||||
|
Type = AgentEventType.ToolCall,
|
||||||
|
ToolName = "file_read",
|
||||||
|
Summary = "관련 파일 읽기",
|
||||||
|
ToolInput = "{\"path\":\"src/AxCopilot/Views/ChatWindow.xaml.cs\"}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var narrative = AgentStatusNarrativeCatalog.BuildFromEvent(evt, "Code");
|
||||||
|
|
||||||
|
narrative.Message.Should().Contain("코드");
|
||||||
|
narrative.Detail.Should().Contain("ChatWindow.xaml.cs");
|
||||||
|
narrative.Category.Should().Be("read");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildFromEvent_ShouldExplainPermissionWaitClearly()
|
||||||
|
{
|
||||||
|
var evt = new AgentEvent
|
||||||
|
{
|
||||||
|
Type = AgentEventType.PermissionRequest,
|
||||||
|
ToolName = "file_edit",
|
||||||
|
Summary = "권한 확인 필요: src/AxCopilot/Views/ChatWindow.xaml.cs"
|
||||||
|
};
|
||||||
|
|
||||||
|
var narrative = AgentStatusNarrativeCatalog.BuildFromEvent(evt, "Code");
|
||||||
|
|
||||||
|
narrative.Message.Should().Contain("권한");
|
||||||
|
narrative.Detail.Should().NotBeNullOrWhiteSpace();
|
||||||
|
narrative.Category.Should().Be("permission");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildIdle_ShouldReflectLastExecutionCategory()
|
||||||
|
{
|
||||||
|
var evt = new AgentEvent
|
||||||
|
{
|
||||||
|
Type = AgentEventType.ToolResult,
|
||||||
|
ToolName = "build_run",
|
||||||
|
Summary = "빌드 결과 확인"
|
||||||
|
};
|
||||||
|
|
||||||
|
var narrative = AgentStatusNarrativeCatalog.BuildIdle(
|
||||||
|
evt,
|
||||||
|
"Code",
|
||||||
|
TimeSpan.FromSeconds(16),
|
||||||
|
TimeSpan.FromSeconds(24),
|
||||||
|
pendingPostCompaction: false);
|
||||||
|
|
||||||
|
narrative.Message.Should().Contain("실행");
|
||||||
|
narrative.Detail.Should().Contain("로그");
|
||||||
|
narrative.Category.Should().Be("execute");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildProgressStepLabel_ShouldPromoteDocumentWorkToReadableSummary()
|
||||||
|
{
|
||||||
|
var evt = new AgentEvent
|
||||||
|
{
|
||||||
|
Type = AgentEventType.ToolCall,
|
||||||
|
ToolName = "html_create",
|
||||||
|
Summary = "html_create 실행"
|
||||||
|
};
|
||||||
|
|
||||||
|
var label = AgentStatusNarrativeCatalog.BuildProgressStepLabel(evt, "문서", "HTML 생성");
|
||||||
|
|
||||||
|
label.Should().Contain("문서");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildProgressPhaseMeta_ShouldExposeReadableEditCompletionLabel()
|
||||||
|
{
|
||||||
|
var evt = new AgentEvent
|
||||||
|
{
|
||||||
|
Type = AgentEventType.ToolResult,
|
||||||
|
ToolName = "file_edit",
|
||||||
|
Summary = "파일 수정 완료"
|
||||||
|
};
|
||||||
|
|
||||||
|
AgentStatusNarrativeCatalog.BuildProgressPhaseMeta(evt).Should().Be("수정 완료");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,6 @@ public class AgentTabSettingsResolverTests
|
|||||||
{
|
{
|
||||||
var code = new CodeSettings
|
var code = new CodeSettings
|
||||||
{
|
{
|
||||||
EnablePlanModeTools = false,
|
|
||||||
EnableWorktreeTools = true,
|
EnableWorktreeTools = true,
|
||||||
EnableTeamTools = false,
|
EnableTeamTools = false,
|
||||||
EnableCronTools = false,
|
EnableCronTools = false,
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterExposureByPermission_ShouldRemoveBlanketDeniedTools()
|
||||||
|
{
|
||||||
|
var tools = new[]
|
||||||
|
{
|
||||||
|
new { Name = "file_read" },
|
||||||
|
new { Name = "git_tool" },
|
||||||
|
new { Name = "build_run" },
|
||||||
|
};
|
||||||
|
var permissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["git"] = "deny",
|
||||||
|
["build_run@*.sln"] = "deny",
|
||||||
|
};
|
||||||
|
|
||||||
|
var filtered = AgentToolCatalog.FilterExposureByPermission(tools, tool => tool.Name, permissions);
|
||||||
|
|
||||||
|
filtered.Select(tool => tool.Name).Should().BeEquivalentTo(["file_read", "build_run"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AxCopilot.Tests.Services;
|
||||||
|
|
||||||
|
public class AgentToolResultBudgetTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Apply_ShouldPersistPreviewToSourceAndReuseItOnNextQueryView()
|
||||||
|
{
|
||||||
|
var longContent = new string('A', 1400);
|
||||||
|
var sourceMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-1",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-1","tool_name":"file_read","content":"{{longContent}}"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var firstWindow = sourceMessages.Select(message => new ChatMessage
|
||||||
|
{
|
||||||
|
MsgId = message.MsgId,
|
||||||
|
Role = message.Role,
|
||||||
|
Content = message.Content,
|
||||||
|
QueryPreviewContent = message.QueryPreviewContent,
|
||||||
|
Timestamp = message.Timestamp
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var first = AgentToolResultBudget.Apply(
|
||||||
|
firstWindow,
|
||||||
|
protectedRecentNonSystemMessages: 1,
|
||||||
|
sourceMessages: sourceMessages);
|
||||||
|
|
||||||
|
sourceMessages[0].QueryPreviewContent.Should().NotBeNullOrWhiteSpace();
|
||||||
|
first.TruncatedCount.Should().Be(1);
|
||||||
|
|
||||||
|
var secondWindow = sourceMessages.Select(message => new ChatMessage
|
||||||
|
{
|
||||||
|
MsgId = message.MsgId,
|
||||||
|
Role = message.Role,
|
||||||
|
Content = message.Content,
|
||||||
|
QueryPreviewContent = message.QueryPreviewContent,
|
||||||
|
Timestamp = message.Timestamp
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var second = AgentToolResultBudget.Apply(
|
||||||
|
secondWindow,
|
||||||
|
protectedRecentNonSystemMessages: 1,
|
||||||
|
sourceMessages: sourceMessages);
|
||||||
|
|
||||||
|
second.ReusedPreviewCount.Should().Be(1);
|
||||||
|
secondWindow[0].Content.Should().Be(sourceMessages[0].QueryPreviewContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Apply_ShouldPreserveLargerBuildResultsInCodeMode()
|
||||||
|
{
|
||||||
|
var longContent = new string('B', 2_600);
|
||||||
|
var queryView = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tool-build",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-build","tool_name":"build_run","content":"{{longContent}}"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentToolResultBudget.Apply(
|
||||||
|
queryView,
|
||||||
|
AgentToolResultBudget.CreateCodeOptions(),
|
||||||
|
sourceMessages: queryView);
|
||||||
|
|
||||||
|
result.TruncatedCount.Should().Be(0);
|
||||||
|
queryView[0].Content.Should().Contain(longContent[..256]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Apply_ShouldReusePreviewFingerprintAcrossSessions()
|
||||||
|
{
|
||||||
|
var longContent = new string('C', 1700);
|
||||||
|
var sourceMessages = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "source-tool",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-original","tool_name":"file_read","content":"{{longContent}}"}""",
|
||||||
|
QueryPreviewContent = """{"type":"tool_result","tool_use_id":"call-original","tool_name":"file_read","content":"preview-fingerprint"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-1",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var queryView = new List<ChatMessage>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "rebuilt-tool",
|
||||||
|
Role = "user",
|
||||||
|
Content = $$"""{"type":"tool_result","tool_use_id":"call-replayed","tool_name":"file_read","content":"{{longContent}}"}"""
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
MsgId = "tail-2",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "recent tail"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = AgentToolResultBudget.Apply(
|
||||||
|
queryView,
|
||||||
|
protectedRecentNonSystemMessages: 1,
|
||||||
|
sourceMessages: sourceMessages);
|
||||||
|
|
||||||
|
result.ReusedPreviewCount.Should().Be(1);
|
||||||
|
queryView[0].Content.Should().Contain("call-replayed");
|
||||||
|
queryView[0].Content.Should().Contain("preview-fingerprint");
|
||||||
|
queryView[0].Content.Should().NotContain("call-original");
|
||||||
|
}
|
||||||
|
}
|
||||||