update devices page and test, archive review documents

This commit is contained in:
toki 2026-06-16 14:40:07 +09:00
parent 9c9f4cc217
commit 124ecf07c4
5 changed files with 181 additions and 24 deletions

View file

@ -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-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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로 이동한다.

View file

@ -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
- 없음
## 후속 작업
- 없음

View file

@ -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<void> 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 = [

View file

@ -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 = <String>[];
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(