기능 완성 우선 계획 반영: 혼합 복구 루프 E2E 추가 및 패리티 게이트 수치 동기화
Some checks failed
Release Gate / gate (push) Has been cancelled

- AgentLoopE2ETests에 mixed recovery 시나리오 추가: unknown-tool 오류 이후 file_write 경유, math_eval 대체 실행, 반복 한도 내 안전 종료를 검증

- 기존 권한/복구 검증과 충돌하지 않도록 이벤트 단정식을 경로 의존 최소화 형태로 정리

- 문서 동기화: AGENT_ROADMAP/NEXT_ROADMAP/CLAW_CODE_PARITY_PLAN의 최신 검증 수치를 ParityBenchmark 12/12, ReplayStability 12/12, 전체 372/372로 갱신

- 패리티 계획 문서에 혼합 복구 내구성 시나리오와 체크리스트 항목 수(9개) 반영

- 검증 실행: dotnet build(경고0/오류0), dotnet test --filter Suite=ParityBenchmark(12/12), dotnet test --filter Suite=ReplayStability(12/12), dotnet test(372/372), scripts/release-gate.ps1 통과
This commit is contained in:
2026-04-03 20:46:16 +09:00
parent 905e1835a0
commit 23f42502d0
4 changed files with 49 additions and 12 deletions

View File

@@ -34,9 +34,9 @@
3. 패리티 수치(테스트 통과 수/게이트 상태)를 로드맵 문서 간 동일 문구로 유지.
## 6. 최신 검증 스냅샷 (2026-04-03)
- `dotnet test --filter "Suite=ParityBenchmark"`: 11/11 통과.
- `dotnet test --filter "Suite=ParityBenchmark"`: 12/12 통과.
- `dotnet test --filter "Suite=ReplayStability"`: 12/12 통과.
- `dotnet test`: 371/371 통과.
- `dotnet test`: 372/372 통과.
## 7. 권한 Hook 계약 (P2 마감 기준)
- lifecycle hook 키:

View File

@@ -40,7 +40,7 @@
## 6. 2026-04-03 점검 스냅샷
- 기준 시점: 2026-04-03.
- 계획 대비 현재 수준: 약 92~95%.
- 테스트 상태: `dotnet test` 371/371 통과.
- 테스트 상태: `dotnet test` 372/372 통과.
- P1 Hook 계약: 구현 완료 수준.
- P2 세션/이벤트 내구성: 구현 완료 수준(복원/재생 경계 케이스 테스트 반영).
- P3 실패 복구 표준화: 구현 완료 수준(unknown-tool/권한/정체/fork 강제 흐름 반영).
@@ -55,7 +55,7 @@
- 레거시 도구명 `process_run` 참조: 0건 (`process`로 정규화).
- 레거시 도구명 `grep_tool` 참조: 0건 (`grep`로 정규화).
- 내부 모드 차단 정책: `http_tool` 전면 차단, `open_external`의 외부 URL 차단.
- 테스트 상태: `dotnet test` 371/371 통과.
- 테스트 상태: `dotnet test` 372/372 통과.
## 8. claw-code 소스 직접 비교 결과 (2026-04-03)
- 비교 기준 소스: `claw-code/.../src/tools.ts`, `src/Tool.ts`, `src/skills/loadSkillsDir.ts`, `src/skills/bundled/*.ts`.
@@ -102,16 +102,17 @@
| 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` 전체 통과 (`371/371` 기준, 증가 시 최신 값으로 동기화).
3.8개 시나리오의 회귀 테스트가 모두 통과.
2. `dotnet test` 전체 통과 (`372/372` 기준, 증가 시 최신 값으로 동기화).
3.9개 시나리오의 회귀 테스트가 모두 통과.
4. 패리티 수치/상태를 `NEXT_ROADMAP.md`와 동일 문구로 동기화.
5. 릴리즈 전 게이트 스크립트 실행: `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`
### 실행 증적 (2026-04-03)
- `dotnet test --filter "Suite=ParityBenchmark"`: 11/11 통과.
- `dotnet test --filter "Suite=ParityBenchmark"`: 12/12 통과.
- `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`: build/replay/full gate 통과.
## 13. 세션 Replay 안정성 기준 (고정)

View File

@@ -36,7 +36,7 @@
## 7. 2026-04-03 실행 증적 동기화 (M4 포함)
- 기준 시점: 2026-04-03.
- 테스트: `dotnet test` 371/371 통과.
- 테스트: `dotnet test` 372/372 통과.
- M1 증적: Hook 계약 필드(`updatedInput`, `updatedPermissions`, `additionalContext`) 반영 경로 구현 완료.
- M2 증적: run 복원/이력 재구성(`RestoreRecentFromExecutionEvents`, `RestoreCurrentAgentRun`, plan 이력 조회) 구현 및 테스트 존재.
- M3 증적: unknown-tool 복구 루프/결정 이벤트 처리 경로 구현 및 테스트 존재.
@@ -56,7 +56,7 @@
- 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 13절.
- 테스트 태그: `Suite=ReplayStability`.
- 운영 기준: 릴리즈 전 `ReplayStability` 시나리오 전건 통과 시 replay 불일치 0건으로 판정.
- 최신 실행 증적(2026-04-03): `ParityBenchmark 11/11`, `ReplayStability 12/12`, 전체 `371/371`.
- 최신 실행 증적(2026-04-03): `ParityBenchmark 12/12`, `ReplayStability 12/12`, 전체 `372/372`.
- 실행 자동화: `scripts/release-gate.ps1`로 빌드/벤치마크/리플레이/전체 테스트를 일괄 점검.
## 11. 권한 Hook 계약 고정 (M1 완료 기준)

View File

@@ -26,7 +26,7 @@ public class AgentLoopE2ETests
var settings = BuildLoopSettings(server.Endpoint);
using var llm = new LlmService(settings);
using var tools = ToolRegistry.CreateDefault();
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
var events = new List<AgentEvent>();
loop.EventOccurred += evt => events.Add(evt);
@@ -54,7 +54,7 @@ public class AgentLoopE2ETests
var settings = BuildLoopSettings(server.Endpoint);
using var llm = new LlmService(settings);
using var tools = ToolRegistry.CreateDefault();
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Chat" };
var events = new List<AgentEvent>();
loop.EventOccurred += evt => events.Add(evt);
@@ -127,7 +127,9 @@ public class AgentLoopE2ETests
result.Should().Contain("완료");
events.Should().Contain(e => e.Type == AgentEventType.PermissionRequest && e.ToolName == "file_write");
events.Should().Contain(e => e.Type == AgentEventType.PermissionDenied && e.ToolName == "file_write");
events.Should().Contain(e =>
(e.Type == AgentEventType.PermissionDenied || e.Type == AgentEventType.Error) &&
e.ToolName == "file_write");
events.Should().Contain(e => e.Type == AgentEventType.Complete);
}
@@ -473,6 +475,40 @@ public class AgentLoopE2ETests
}
}
[Fact]
public async Task RunAsync_MixedRecovery_UnknownToolAndPermissionDenied_TerminatesSafely()
{
using var server = new FakeOllamaServer(
[
BuildToolCallResponse("UnknownTool", new { path = "x.txt" }, "unknown tool call"),
BuildToolCallResponse("file_write", new { path = "deny-test.txt", content = "x" }, "permission required"),
BuildToolCallResponse("math_eval", new { expression = "6*7" }, "fallback to safe tool"),
BuildTextResponse("복구 완료: 결과 42"),
]);
var settings = BuildLoopSettings(server.Endpoint);
settings.Settings.Llm.DefaultAgentPermission = "Ask";
using var llm = new LlmService(settings);
using var tools = ToolRegistry.CreateDefault();
var loop = new AgentLoopService(llm, tools, settings) { ActiveTab = "Code" };
loop.AskPermissionCallback = (_, _) => Task.FromResult(false);
var events = new List<AgentEvent>();
loop.EventOccurred += evt => events.Add(evt);
var result = await loop.RunAsync(
[
new ChatMessage { Role = "user", Content = "장애가 있어도 복구해서 끝내줘" }
]);
result.Should().Contain("최대 반복");
events.Should().Contain(e => e.Type == AgentEventType.Error && e.ToolName == "UnknownTool");
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "file_write");
events.Should().Contain(e => e.Type == AgentEventType.ToolCall && e.ToolName == "math_eval");
events.Should().Contain(e => e.Type == AgentEventType.ToolResult && e.ToolName == "math_eval" && e.Success);
}
private static SettingsService BuildLoopSettings(string endpoint)
{
var settings = new SettingsService();