---
title: 수정 작업과 PR
description: 피드백을 수정 작업으로 만들고 GitHub PR을 연결하는 API와 멱등성 규칙.
---

# 수정 작업과 PR {#change-requests-and-prs}

수정 작업은 피드백, 수정 지시, 완료 기준, PR URL을 함께 관리하는 기록입니다. 생성에는 `work-items:write`, 조회에는 `work-items:read` 권한이 필요합니다.

## 수정 작업 생성 {#create-a-change-request}

```http
POST /api/v1/apps/:appId/automation/change-requests
Content-Type: application/json
```

| 필드                 | 필수   | 제약                                      |
| -------------------- | ------ | ----------------------------------------- |
| `title`              | 예     | 공백 제거 후 1–160자                      |
| `instructions`       | 예     | 공백 제거 후 1–10000자                    |
| `acceptanceCriteria` | 아니요 | 공백 제거 후 최대 10000자. 기본 빈 문자열 |
| `issueIds`           | 예     | 같은 앱의 피드백 UUID 1–20개. 중복 불가   |
| `clientRequestId`    | 예     | 이 생성 요청을 구분하는 UUID              |

아래 예시의 피드백 ID는 실제 조회한 ID로, `clientRequestId`는 새 작업을 만들기 전에 생성한 UUID로 바꾸세요. 응답을 받지 못해 재시도할 때는 같은 값을 유지합니다.

```sh
curl --fail-with-body \
  "$OPENTESTER_API_URL/apps/$OPENTESTER_APP_ID/automation/change-requests" \
  --header "Authorization: Bearer $OPENTESTER_AGENT_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "title": "저장 직후 목록 갱신 수정",
    "instructions": "저장 성공 후 목록 상태를 갱신하고 중복 추가가 없는지 확인합니다.",
    "acceptanceCriteria": "새 항목이 즉시 한 번 표시되고 화면 재진입 후에도 유지됩니다.",
    "issueIds": ["bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"],
    "clientRequestId": "dddddddd-dddd-4ddd-8ddd-dddddddddddd"
  }'
```

응답 `201` 예시:

```json
{
  "changeRequest": {
    "id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
    "appId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
    "title": "저장 직후 목록 갱신 수정",
    "instructions": "저장 성공 후 목록 상태를 갱신하고 중복 추가가 없는지 확인합니다.",
    "acceptanceCriteria": "새 항목이 즉시 한 번 표시되고 화면 재진입 후에도 유지됩니다.",
    "issueIds": ["bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"],
    "status": "open",
    "pullRequestUrl": null,
    "createdBy": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
    "createdAt": "2026-10-28T02:00:00.000Z",
    "updatedAt": "2026-10-28T02:00:00.000Z"
  }
}
```

### 중복 생성 방지 {#prevent-duplicate-creation}

서버는 앱·작업 생성자·`clientRequestId` 조합으로 같은 요청을 식별합니다. 같은 토큰 소유자의 다른 토큰으로 재시도해도 이 기준을 적용합니다.

- 같은 ID와 같은 내용: 이미 생성한 작업을 반환합니다.
- 같은 ID와 다른 내용: `409 Conflict`입니다.
- 다른 ID: 같은 피드백과 같은 내용이어도 새 작업을 만듭니다.

비교할 때 앞뒤 공백을 제거하고, UUID를 소문자로 바꾸며, `issueIds`를 정렬합니다. 따라서 배열 순서만 달라진 재시도는 같은 요청입니다. 이 처리는 작업의 선점이나 자동 중복 병합을 제공하지 않습니다. 작업을 만들기 전에 기존 작업을 조회하세요.

## 목록과 상세 조회 {#list-and-retrieve-requests}

```http
GET /api/v1/apps/:appId/automation/change-requests?limit=20&offset=0
GET /api/v1/apps/:appId/automation/change-requests/:requestId
```

목록은 최신 생성 순서입니다. `limit`은 기본 20·최대 50, `offset`은 기본 0·최대 100000입니다. 상태 필터는 제공하지 않습니다.

목록의 `200` 응답은 다음 구조이며 각 항목은 생성 응답의 `changeRequest`와 같은 객체입니다.

```json
{
  "changeRequests": [],
  "total": 0,
  "limit": 20,
  "offset": 0
}
```

```sh
export CHANGE_REQUEST_ID="cccccccc-cccc-4ccc-8ccc-cccccccccccc"

curl --fail-with-body \
  "$OPENTESTER_API_URL/apps/$OPENTESTER_APP_ID/automation/change-requests/$CHANGE_REQUEST_ID" \
  --header "Authorization: Bearer $OPENTESTER_AGENT_TOKEN"
```

상세 `200` 응답은 `{ "changeRequest": { ... } }`입니다. 대상 앱에서 찾을 수 없는 작업은 `404`입니다.

## PR 연결 {#attach-a-pr}

코딩 도구에서 수정과 테스트를 마친 뒤 생성한 GitHub PR URL을 연결합니다. 필요한 권한은 `work-items:write`입니다.

```http
POST /api/v1/apps/:appId/automation/change-requests/:requestId/pull-request
Content-Type: application/json
```

```sh
curl --fail-with-body \
  "$OPENTESTER_API_URL/apps/$OPENTESTER_APP_ID/automation/change-requests/$CHANGE_REQUEST_ID/pull-request" \
  --header "Authorization: Bearer $OPENTESTER_AGENT_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"url":"https://github.com/your-org/your-app/pull/42"}'
```

응답 `201`은 `{ "changeRequest": { ... } }`이며 `status`가 `pr_linked`, `pullRequestUrl`이 저장한 주소로 바뀝니다.

| 규칙            | 동작                                              |
| --------------- | ------------------------------------------------- |
| 허용 URL        | `https://github.com/{owner}/{repo}/pull/{number}` |
| PR 번호         | 0으로 시작하지 않는 양의 정수, 최대 10자리        |
| URL 추가 요소   | query, fragment, 인증 정보, 포트는 허용하지 않음  |
| 정규화          | owner·repo를 소문자로 저장하고 마지막 `/` 제거    |
| 같은 URL 재연결 | 기존 작업 반환. 안전하게 재시도 가능              |
| 다른 URL 연결   | `409`. 작업당 PR 한 개                            |

현재 GitHub.com 주소만 지원합니다. GitLab이나 GitHub Enterprise의 별도 호스트는 지원하지 않습니다.

::: info PR 연결의 의미
서버는 URL 형식을 검사해 저장합니다. 실제 PR의 존재, 저장소 접근 권한, CI 결과, 병합 여부는 GitHub에서 확인하세요. `pr_linked`는 수정 완료를 뜻하지 않으며, PR 생성·병합·Play Console 배포를 실행하지 않습니다. 피드백 상태와 보상도 자동으로 변경하지 않습니다.
:::

## 수정 작업 객체 {#change-request-object}

| 필드                                          | 형식                  | 의미                          |
| --------------------------------------------- | --------------------- | ----------------------------- |
| `id`, `appId`                                 | UUID                  | 작업 및 앱 ID                 |
| `title`, `instructions`, `acceptanceCriteria` | string                | 제목·수정 지시·완료 기준      |
| `issueIds`                                    | UUID[]                | 연결한 피드백 ID. 정렬된 배열 |
| `status`                                      | `open` \| `pr_linked` | 생성 또는 PR 연결 상태        |
| `pullRequestUrl`                              | string \| null        | 연결 전에는 `null`            |
| `createdBy`                                   | UUID                  | 작업을 만든 앱 소유자         |
| `createdAt`, `updatedAt`                      | ISO 8601 string       | 생성 및 마지막 변경 시각      |

현재 작업 본문 수정·삭제, PR 교체·해제, 실행 담당자 지정 API는 제공하지 않습니다.
