update devices page and test, archive review documents
This commit is contained in:
parent
9c9f4cc217
commit
124ecf07c4
5 changed files with 181 additions and 24 deletions
|
|
@ -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로 이동한다.
|
||||
|
|
@ -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
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue