From 124ecf07c4b174aae75515484a4ccbac540262af Mon Sep 17 00:00:00 2001 From: toki Date: Tue, 16 Jun 2026 14:40:07 +0900 Subject: [PATCH] update devices page and test, archive review documents --- .../code_review_local_G04_0.log} | 65 +++++++++++++------ .../02+01_current_result_ui/complete.log | 46 +++++++++++++ .../plan_local_G04_0.log} | 0 lib/src/features/devices/devices_page.dart | 43 ++++++++++-- test/widget_test.dart | 51 +++++++++++++++ 5 files changed, 181 insertions(+), 24 deletions(-) rename agent-task/{m-install-session-history/02+01_current_result_ui/CODE_REVIEW-local-G04.md => archive/2026/06/m-install-session-history/02+01_current_result_ui/code_review_local_G04_0.log} (79%) create mode 100644 agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/complete.log rename agent-task/{m-install-session-history/02+01_current_result_ui/PLAN-local-G04.md => archive/2026/06/m-install-session-history/02+01_current_result_ui/plan_local_G04_0.log} (100%) diff --git a/agent-task/m-install-session-history/02+01_current_result_ui/CODE_REVIEW-local-G04.md b/agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/code_review_local_G04_0.log similarity index 79% rename from agent-task/m-install-session-history/02+01_current_result_ui/CODE_REVIEW-local-G04.md rename to agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/code_review_local_G04_0.log index cbbfcb3..39c32cc 100644 --- a/agent-task/m-install-session-history/02+01_current_result_ui/CODE_REVIEW-local-G04.md +++ b/agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/code_review_local_G04_0.log @@ -42,31 +42,31 @@ task=m-install-session-history/02+01_current_result_ui, plan=0, tag=REFACTOR | 항목 | 완료 여부 | |------|---------| -| [REFACTOR-1] 현재 설치 결과 배너 확장 | [ ] | +| [REFACTOR-1] 현재 설치 결과 배너 확장 | [x] | ## 구현 체크리스트 -- [ ] `01_install_result_model` predecessor `complete.log`를 확인한다. -- [ ] `_InstallResultBanner`가 `InstallAttemptResult`를 받아 artifact file name, build number, job name, device display name, result summary를 표시하게 한다. -- [ ] 실패 결과에서 stdout/stderr 전체 dump 대신 현재 배너에 들어갈 짧은 실패 요약을 유지하되, 원문 저장이나 logcat 저장을 추가하지 않는다. -- [ ] `test/widget_test.dart`에 현재 설치 결과 배너가 build/artifact/device/result 맥락을 표시하는 regression을 추가한다. -- [ ] `git diff --check`, `flutter analyze`, `flutter test`를 실행한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. +- [x] `01_install_result_model` predecessor `complete.log`를 확인한다. +- [x] `_InstallResultBanner`가 `InstallAttemptResult`를 받아 artifact file name, build number, job name, device display name, result summary를 표시하게 한다. +- [x] 실패 결과에서 stdout/stderr 전체 dump 대신 현재 배너에 들어갈 짧은 실패 요약을 유지하되, 원문 저장이나 logcat 저장을 추가하지 않는다. +- [x] `test/widget_test.dart`에 현재 설치 결과 배너가 build/artifact/device/result 맥락을 표시하는 regression을 추가한다. +- [x] `git diff --check`, `flutter analyze`, `flutter test`를 실행한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. ## 코드리뷰 전용 체크리스트 > **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. > 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. -- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다. -- [ ] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다. -- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. -- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. -- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. -- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. -- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. - [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다. - [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. - [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. @@ -75,10 +75,16 @@ task=m-install-session-history/02+01_current_result_ui, plan=0, tag=REFACTOR _구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ +- 기존 계획 대비 회귀 범위를 추가로 보강했습니다. + - `cleans staged apk once ...` 테스트는 기존 검증이었으나, `test/widget_test.dart`에 `shows concise failure summary in result banner`를 추가해 실패 요약이 1줄로 제한되는지를 검증하도록 했습니다. + ## 주요 설계 결정 _구현 에이전트가 주요 설계 결정 사항을 기록한다._ +- 실패 메시지는 긴 출력 전체를 표시하지 않고, 실패 시 `stderr`에서 첫 번째 유효 라인만을 요약 텍스트로 사용하도록 했습니다. +- 설치 요약 배너에는 기존 라벨(`Device에 설치 성공/실패`) 외에 `#build · job · artifact` 형식 메타데이터 라인을 추가해, 최근 목록 없이도 맥락이 보이도록 유지했습니다. + ## 사용자 리뷰 요청 _기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ @@ -111,19 +117,24 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 ### REFACTOR-1 중간 검증 ``` $ flutter test test/widget_test.dart -(output) +00:00 +0: loading /config/workspace/appsok/test/widget_test.dart +... +00:09 +224: All tests passed! ``` ### 최종 검증 ``` $ git diff --check -(output) +No output $ flutter analyze -(output) +Analyzing appsok... +No issues found! (ran in 6.5s) $ flutter test -(output) +00:00 +0: loading /config/workspace/appsok/test/jenkins_client_test.dart +... +00:09 +224: All tests passed! ``` --- @@ -146,3 +157,17 @@ Sections and their ownership: | 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan | | 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only | | 코드리뷰 결과 | Review agent appends | Not included in stub | + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - correctness: Pass + - completeness: Pass + - test coverage: Pass + - API contract: Pass + - code quality: Pass + - plan deviation: Pass + - verification trust: Pass +- 발견된 문제: 없음 +- 다음 단계: PASS이므로 `complete.log`를 작성하고 active task 디렉터리를 archive로 이동한다. diff --git a/agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/complete.log b/agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/complete.log new file mode 100644 index 0000000..07999d0 --- /dev/null +++ b/agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/complete.log @@ -0,0 +1,46 @@ +# Complete - m-install-session-history/02+01_current_result_ui + +## 완료 일시 + +2026-06-16 + +## 요약 + +현재 설치 결과 배너가 build/job/artifact/device/result 맥락과 짧은 실패 요약을 표시하도록 확장했고, 1회 리뷰 루프로 검증해 최종 판정은 PASS다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_local_G04_0.log` | `code_review_local_G04_0.log` | PASS | 결과 배너 metadata 표시, 실패 요약 축약, widget regression이 계획대로 반영되고 로컬 및 remote runner 검증이 통과했다. | + +## 구현/정리 내용 + +- `_InstallResultBanner`에 `#build · job · artifact` metadata 라인을 추가해 최근 설치 목록 없이도 현재 설치 맥락을 확인할 수 있게 했다. +- 실패 설치 결과에서는 `stderr` 첫 번째 유효 라인, 없으면 `stdout` 첫 번째 유효 라인만 배너 요약으로 표시하게 했다. +- `test/widget_test.dart`에 build/job/artifact metadata와 실패 출력 축약 regression을 추가했다. + +## 최종 검증 + +- `git diff --check` - PASS; 출력 없음. +- `flutter analyze` - PASS; `No issues found!`. +- `flutter test` - PASS; `224: All tests passed!`. +- `ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc "cd \"$HOME/docker/services/code-server/data/volume/workspace/appsok\" && git diff --check"'` - PASS; 출력 없음. +- `ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc "cd \"$HOME/docker/services/code-server/data/volume/workspace/appsok\" && flutter analyze"'` - PASS; `No issues found!`. +- `ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc "cd \"$HOME/docker/services/code-server/data/volume/workspace/appsok\" && flutter test"'` - PASS; `224: All tests passed!`. +- `ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc "cd \"$HOME/docker/services/code-server/data/volume/workspace/appsok\" && dart format --output=none --set-exit-if-changed lib/src/features/devices/devices_page.dart test/widget_test.dart"'` - PASS; `Formatted 2 files (0 changed)`. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/workflow-integration/milestones/install-session-history.md` +- Completed task ids: + - `recent-list`: PASS; evidence=`agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/plan_local_G04_0.log`, `agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/code_review_local_G04_0.log`; verification=`git diff --check`, `flutter analyze`, `flutter test`, remote `git diff --check`, remote `flutter analyze`, remote `flutter test`, remote `dart format --output=none --set-exit-if-changed` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-install-session-history/02+01_current_result_ui/PLAN-local-G04.md b/agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/plan_local_G04_0.log similarity index 100% rename from agent-task/m-install-session-history/02+01_current_result_ui/PLAN-local-G04.md rename to agent-task/archive/2026/06/m-install-session-history/02+01_current_result_ui/plan_local_G04_0.log diff --git a/lib/src/features/devices/devices_page.dart b/lib/src/features/devices/devices_page.dart index bdd6109..e1a33a9 100644 --- a/lib/src/features/devices/devices_page.dart +++ b/lib/src/features/devices/devices_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import '../../models/adb_device.dart'; import '../../models/install_attempt_result.dart'; import '../../models/pending_install.dart'; +import '../../services/adb_service.dart'; typedef PendingInstallRunner = Future Function(AdbDevice device, PendingInstall pending); @@ -601,10 +602,7 @@ class _InstallResultBanner extends StatelessWidget { final colorScheme = Theme.of(context).colorScheme; final isSuccess = attempt.result.succeeded; final color = isSuccess ? colorScheme.primary : colorScheme.error; - final output = [ - if (attempt.result.stdout.trim().isNotEmpty) attempt.result.stdout.trim(), - if (attempt.result.stderr.trim().isNotEmpty) attempt.result.stderr.trim(), - ].join('\n'); + final output = _resultOutputSummary(attempt.result); final metadataKey = ValueKey( 'install-result-attempt:${attempt.pendingInstall.jobName}:' '${attempt.pendingInstall.buildNumber}:${attempt.pendingInstall.fileName}', @@ -644,6 +642,17 @@ class _InstallResultBanner extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, ), + Text( + '#${attempt.pendingInstall.buildNumber} · ${attempt.pendingInstall.jobName} · ${attempt.pendingInstall.fileName}', + key: const ValueKey('install-result-metadata'), + style: TextStyle( + color: colorScheme.onSurfaceVariant, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), if (output.isNotEmpty) Text( key: const ValueKey('install-result-output'), @@ -664,6 +673,32 @@ class _InstallResultBanner extends StatelessWidget { ), ); } + + String _resultOutputSummary(AdbInstallResult result) { + final stdout = result.stdout.trim(); + final stderr = result.stderr.trim(); + if (result.succeeded) { + return [stdout, stderr].where((it) => it.isNotEmpty).join('\n'); + } + + if (stderr.isNotEmpty) { + return _firstNonEmptyLine(stderr); + } + + if (stdout.isNotEmpty) { + return _firstNonEmptyLine(stdout); + } + + return '설치 실패'; + } + + String _firstNonEmptyLine(String text) { + final lines = text + .split('\n') + .map((line) => line.trim()) + .where((line) => line.isNotEmpty); + return lines.isEmpty ? '' : lines.first; + } } const _sampleDevices = [ diff --git a/test/widget_test.dart b/test/widget_test.dart index 84f5c3f..81615a3 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -700,6 +700,10 @@ void main() { await tester.pumpAndSettle(); expect(find.byKey(const ValueKey('install-result-banner')), findsOneWidget); + expect( + find.textContaining('#7 · android-app · app-release.apk'), + findsOneWidget, + ); expect(find.byKey(const ValueKey('install-result-output')), findsOneWidget); expect(find.textContaining('Success'), findsWidgets); }); @@ -753,6 +757,10 @@ void main() { await tester.pumpAndSettle(); expect(cleanedPaths, ['/tmp/staged/app-release.apk']); + expect( + find.textContaining('#7 · android-app · app-release.apk'), + findsOneWidget, + ); expect( find.byKey(const ValueKey('install-result-banner')), findsOneWidget, @@ -764,6 +772,44 @@ void main() { }, ); + testWidgets('shows concise failure summary in result banner', (tester) async { + final cleanedPaths = []; + await tester.pumpWidget( + shellWithInstaller( + installerFn: (device, pending) async => const AdbInstallResult( + exitCode: 1, + stdout: 'Performing Streamed Install', + stderr: + 'INSTALL_FAILED_VERSION_DOWNGRADE\nDetailed adb output\nExtra line', + ), + artifactCleaner: (path) async => cleanedPaths.add(path), + ), + ); + await navigateToDevicesWithPending(tester); + + final installButtons = find.byWidgetPredicate( + (w) => w is IconButton && w.tooltip == '설치', + ); + await tester.tap(installButtons.first); + await tester.pumpAndSettle(); + + expect(cleanedPaths, ['/tmp/staged/app-release.apk']); + expect(find.byKey(const ValueKey('install-result-banner')), findsOneWidget); + expect( + find.byKey(const ValueKey('install-result-metadata')), + findsOneWidget, + ); + expect( + find.textContaining('#7 · android-app · app-release.apk'), + findsOneWidget, + ); + expect( + find.textContaining('INSTALL_FAILED_VERSION_DOWNGRADE'), + findsOneWidget, + ); + expect(find.textContaining('Detailed adb output'), findsNothing); + }); + testWidgets('does not clean staged apk before install future completes', ( tester, ) async { @@ -985,6 +1031,11 @@ void main() { 'install-result-attempt:$jobName:$buildNumber:$fileName', ); expect(find.byKey(attemptKey), findsOneWidget); + expect(find.textContaining('SM S918N에 설치 성공'), findsOneWidget); + expect( + find.textContaining('#7 · android-app · app-release.apk'), + findsOneWidget, + ); }); testWidgets(