update authoring request and model, scheduler tests, archive task files
This commit is contained in:
parent
8803d29554
commit
91e3aabd16
7 changed files with 280 additions and 56 deletions
|
|
@ -42,43 +42,46 @@ task=m-milestone-work-item-creation-sync/10+09_plane_draft_authoring, plan=0, ta
|
|||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Preserve Full Work Item Identity For Authoring | [ ] |
|
||||
| [API-2] Idempotent Milestone Draft Instructions | [ ] |
|
||||
| [API-3] Scheduler Authoring Contract Regression | [ ] |
|
||||
| [API-1] Preserve Full Work Item Identity For Authoring | [x] |
|
||||
| [API-2] Idempotent Milestone Draft Instructions | [x] |
|
||||
| [API-3] Scheduler Authoring Contract Regression | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `09_creation_trigger_gate` predecessor complete.log가 active 또는 archive 경로에 있는지 확인하고, 없으면 구현하지 않고 review stub의 사용자 리뷰 요청이 아니라 plan dependency 미충족으로 보고한다.
|
||||
- [ ] Plane work item identity context를 authoring input에서 구조적으로 보존한다. 검증: provider, tenant, project, work item id가 instructions 또는 client-side metadata에 포함된다.
|
||||
- [ ] fallback authoring instructions에 1상위 티켓=1마일스톤, 기존 Provider identity 검색 후 갱신, `## Provider identity` block 작성, `develop` commit/push, duplicate 금지 조건을 명시한다.
|
||||
- [ ] focused Go tests와 `git diff --check`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
- [x] `09_creation_trigger_gate` predecessor complete.log가 active 또는 archive 경로에 있는지 확인하고, 없으면 구현하지 않고 review stub의 사용자 리뷰 요청이 아니라 plan dependency 미충족으로 보고한다.
|
||||
- [x] Plane work item identity context를 authoring input에서 구조적으로 보존한다. 검증: provider, tenant, project, work item id가 instructions 또는 client-side metadata에 포함된다.
|
||||
- [x] fallback authoring instructions에 1상위 티켓=1마일스톤, 기존 Provider identity 검색 후 갱신, `## Provider identity` block 작성, `develop` commit/push, duplicate 금지 조건을 명시한다.
|
||||
- [x] focused Go tests와 `git diff --check`를 실행한다.
|
||||
- [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로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
계획과 동일하게 정상 구현되었으며, 변경 사항은 없습니다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
- `workItemIdentity` 구조체와 `getWorkItemIdentity` 헬퍼 함수를 추가하여 payload `work_item` 및 task external fields(ExternalProvider, ExternalID, ExternalUrl, ExternalMetadata) 모두에서 tenant, project, external_url, state_id 정보를 구조적으로 파싱하도록 하였습니다.
|
||||
- `model.WorkspaceMetadata` 구조체에 `Tenant`, `Project`, `ExternalURL`, `StateID` 필드들을 추가하여 full identity context를 보존하도록 하였습니다.
|
||||
- OpenAI 어댑터의 `buildRequestMetadata`에서 `metadata.workspace` API payload는 기존과 동일하게 flat `workspace` path 만을 가지도록 유지하여 호환성을 확보하였습니다.
|
||||
- `buildAuthoringInstructions`에서 instructions 본문에 규칙(1상위 티켓=1마일스톤, 기존 Provider identity 검색 후 갱신, `## Provider identity` block 작성, `develop` commit/push, duplicate 금지 조건 등)을 명확하게 명시하고, 추출된 work item 상세 속성들도 본문에 동적으로 추가하여 모델이 올바르게 Milestone을 작성하도록 유도했습니다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
|
|
@ -101,39 +104,37 @@ _기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외
|
|||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
|
||||
중간 및 최종 검증 명령 실행 결과는 다음과 같습니다.
|
||||
|
||||
### API-1 중간 검증
|
||||
```bash
|
||||
$ cd services/core && go test -count=1 ./internal/authoring ./internal/adapters/openai
|
||||
(output)
|
||||
ok github.com/nomadcode/nomadcode-core/internal/authoring 0.003s
|
||||
ok github.com/nomadcode/nomadcode-core/internal/adapters/openai 0.009s
|
||||
```
|
||||
|
||||
### API-2 중간 검증
|
||||
```bash
|
||||
$ cd services/core && go test -count=1 ./internal/authoring ./internal/roadmapsync
|
||||
(output)
|
||||
ok github.com/nomadcode/nomadcode-core/internal/authoring 0.003s
|
||||
ok github.com/nomadcode/nomadcode-core/internal/roadmapsync 0.003s
|
||||
```
|
||||
|
||||
### API-3 중간 검증
|
||||
```bash
|
||||
$ cd services/core && go test -count=1 ./internal/scheduler
|
||||
(output)
|
||||
ok github.com/nomadcode/nomadcode-core/internal/scheduler 2.011s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ cd services/core && go test -count=1 ./internal/authoring ./internal/roadmapsync ./internal/adapters/openai ./internal/scheduler
|
||||
(output)
|
||||
ok github.com/nomadcode/nomadcode-core/internal/authoring 0.003s
|
||||
ok github.com/nomadcode/nomadcode-core/internal/roadmapsync 0.003s
|
||||
ok github.com/nomadcode/nomadcode-core/internal/adapters/openai 0.006s
|
||||
ok github.com/nomadcode/nomadcode-core/internal/scheduler 2.011s
|
||||
$ git diff --check
|
||||
(output)
|
||||
(출력 없음 - 정상 종료)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -156,3 +157,22 @@ 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; command changes require a `계획 대비 변경 사항` entry |
|
||||
| 코드리뷰 결과 | 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 directory를 archive로 이동한다.
|
||||
|
||||
리뷰 메모:
|
||||
- `09_creation_trigger_gate` 선행 `complete.log`는 `agent-task/archive/2026/06/m-milestone-work-item-creation-sync/09_creation_trigger_gate/complete.log`에서 확인했다.
|
||||
- 리뷰 중 `services/core/internal/authoring/request_test.go`의 contains helper 임시 주석/수동 구현은 비동작성 Nit로 직접 정리했다.
|
||||
- 추가 검증으로 `cd services/core && go test -count=1 ./...`를 실행해 core 전체 smoke 기준도 확인했다.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Complete - m-milestone-work-item-creation-sync/10+09_plane_draft_authoring
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-14
|
||||
|
||||
## 요약
|
||||
|
||||
Plane-origin Milestone draft authoring contract를 보강했고, 1회 리뷰 루프에서 PASS로 종결했다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_local_G06_0.log` | `code_review_local_G06_0.log` | PASS | Provider identity context 보존, idempotent Milestone draft instructions, scheduler authoring regression 검증 완료 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `authoring` 입력에 provider, tenant, project, work item id, external URL, state id를 보존하는 `workItemIdentity` 경로를 추가했다.
|
||||
- fallback authoring instructions에 Provider identity block 작성/검색, 기존 Milestone 갱신 우선, duplicate 금지, develop push 계약을 명시했다.
|
||||
- OpenAI adapter의 `metadata.workspace` flat string wire shape를 유지한 채 client-side `WorkspaceMetadata`만 additive 확장했다.
|
||||
- scheduler authoring route가 계속 model bridge로 향하는지 regression assertion을 보강했다.
|
||||
- 리뷰 중 `services/core/internal/authoring/request_test.go`의 contains helper 임시 주석/수동 구현을 비동작성 Nit로 정리했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `cd services/core && go test -count=1 ./internal/authoring ./internal/roadmapsync ./internal/adapters/openai ./internal/scheduler` - PASS; focused packages all returned `ok`.
|
||||
- `cd services/core && go test -count=1 ./...` - PASS; core 전체 package가 `ok` 또는 `[no test files]`로 종료했다.
|
||||
- `git diff --check` - PASS; 출력 없음.
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||
- Completed task ids:
|
||||
- `plane-draft`: PASS; evidence=`agent-task/archive/2026/06/m-milestone-work-item-creation-sync/10+09_plane_draft_authoring/plan_local_G06_0.log`, `agent-task/archive/2026/06/m-milestone-work-item-creation-sync/10+09_plane_draft_authoring/code_review_local_G06_0.log`; verification=`cd services/core && go test -count=1 ./internal/authoring ./internal/roadmapsync ./internal/adapters/openai ./internal/scheduler`, `cd services/core && go test -count=1 ./...`, `git diff --check`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -46,29 +46,69 @@ func isAuthoringTask(task storage.Task) (workitempipeline.CheckoutMetadata, bool
|
|||
return *meta.Checkout, true
|
||||
}
|
||||
|
||||
// providerIdentity resolves the Plane provider and work item ID for a task.
|
||||
// Priority: storage.Task.ExternalProvider/ExternalID (set by workitem pipeline)
|
||||
// → work_item payload map → task.Source / task.ID fallback.
|
||||
func providerIdentity(task storage.Task, payload pipelinePayload) (provider, workItemID string) {
|
||||
provider = task.Source
|
||||
workItemID = task.ID
|
||||
type workItemIdentity struct {
|
||||
Provider string
|
||||
Tenant string
|
||||
Project string
|
||||
WorkItemID string
|
||||
ExternalURL string
|
||||
StateID string
|
||||
}
|
||||
|
||||
func getWorkItemIdentity(task storage.Task, payload pipelinePayload) workItemIdentity {
|
||||
var ident workItemIdentity
|
||||
|
||||
ident.Provider = task.Source
|
||||
ident.WorkItemID = task.ID
|
||||
|
||||
if task.ExternalProvider != nil && *task.ExternalProvider != "" {
|
||||
provider = *task.ExternalProvider
|
||||
ident.Provider = *task.ExternalProvider
|
||||
}
|
||||
if task.ExternalID != nil && *task.ExternalID != "" {
|
||||
workItemID = *task.ExternalID
|
||||
return provider, workItemID
|
||||
ident.WorkItemID = *task.ExternalID
|
||||
}
|
||||
if task.ExternalUrl != nil && *task.ExternalUrl != "" {
|
||||
ident.ExternalURL = *task.ExternalUrl
|
||||
}
|
||||
|
||||
// fall back to work_item map in payload (also set by workitem pipeline)
|
||||
if id := strings.TrimSpace(payload.WorkItem["id"]); id != "" {
|
||||
workItemID = id
|
||||
}
|
||||
if p := strings.TrimSpace(payload.WorkItem["provider"]); p != "" {
|
||||
provider = p
|
||||
ident.Provider = p
|
||||
}
|
||||
return provider, workItemID
|
||||
if id := strings.TrimSpace(payload.WorkItem["id"]); id != "" {
|
||||
ident.WorkItemID = id
|
||||
}
|
||||
if t := strings.TrimSpace(payload.WorkItem["tenant"]); t != "" {
|
||||
ident.Tenant = t
|
||||
}
|
||||
if prj := strings.TrimSpace(payload.WorkItem["project"]); prj != "" {
|
||||
ident.Project = prj
|
||||
}
|
||||
if urlStr := strings.TrimSpace(payload.WorkItem["external_url"]); urlStr != "" {
|
||||
ident.ExternalURL = urlStr
|
||||
}
|
||||
if state := strings.TrimSpace(payload.WorkItem["state_id"]); state != "" {
|
||||
ident.StateID = state
|
||||
}
|
||||
|
||||
if len(task.ExternalMetadata) > 0 && string(task.ExternalMetadata) != "null" && string(task.ExternalMetadata) != "{}" {
|
||||
var extMeta map[string]any
|
||||
if err := json.Unmarshal(task.ExternalMetadata, &extMeta); err == nil {
|
||||
if t, ok := extMeta["tenant"].(string); ok && t != "" && ident.Tenant == "" {
|
||||
ident.Tenant = t
|
||||
}
|
||||
if p, ok := extMeta["project"].(string); ok && p != "" && ident.Project == "" {
|
||||
ident.Project = p
|
||||
}
|
||||
if s, ok := extMeta["state_id"].(string); ok && s != "" && ident.StateID == "" {
|
||||
ident.StateID = s
|
||||
}
|
||||
if u, ok := extMeta["external_url"].(string); ok && u != "" && ident.ExternalURL == "" {
|
||||
ident.ExternalURL = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ident
|
||||
}
|
||||
|
||||
// BuildAuthoringGenerateInput constructs a workspace-bound model.GenerateInput
|
||||
|
|
@ -97,11 +137,11 @@ func BuildAuthoringGenerateInput(task storage.Task) (model.GenerateInput, bool)
|
|||
}
|
||||
instructions = strings.TrimSpace(p.Instructions)
|
||||
|
||||
if instructions == "" {
|
||||
instructions = buildAuthoringInstructions(task, checkout)
|
||||
}
|
||||
ident := getWorkItemIdentity(task, p)
|
||||
|
||||
provider, workItemID := providerIdentity(task, p)
|
||||
if instructions == "" {
|
||||
instructions = buildAuthoringInstructions(task, checkout, ident)
|
||||
}
|
||||
|
||||
return model.GenerateInput{
|
||||
Input: input,
|
||||
|
|
@ -113,18 +153,46 @@ func BuildAuthoringGenerateInput(task storage.Task) (model.GenerateInput, bool)
|
|||
WorkspaceMetadata: &model.WorkspaceMetadata{
|
||||
Path: checkout.SlotPath,
|
||||
SourceBranch: checkout.SourceBranch,
|
||||
Provider: provider,
|
||||
WorkItemID: workItemID,
|
||||
Provider: ident.Provider,
|
||||
WorkItemID: ident.WorkItemID,
|
||||
Tenant: ident.Tenant,
|
||||
Project: ident.Project,
|
||||
ExternalURL: ident.ExternalURL,
|
||||
StateID: ident.StateID,
|
||||
},
|
||||
}, true
|
||||
}
|
||||
|
||||
func buildAuthoringInstructions(task storage.Task, checkout workitempipeline.CheckoutMetadata) string {
|
||||
func buildAuthoringInstructions(task storage.Task, checkout workitempipeline.CheckoutMetadata, ident workItemIdentity) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("You are a workspace agent running inside a NomadCode slot checkout.\n")
|
||||
b.WriteString("Your task is to author or update the agent-roadmap Milestone file for the following Plane work item.\n\n")
|
||||
b.WriteString("Use the roadmap skill to create or update the Milestone under the active Phase, then commit and push to the develop branch.\n\n")
|
||||
b.WriteString("Work item title: " + task.Title + "\n")
|
||||
|
||||
b.WriteString("Please strictly follow these rules:\n")
|
||||
b.WriteString("- One Plane top-level work item maps to exactly one agent-roadmap Milestone.\n")
|
||||
b.WriteString("- First search active Milestones for a Provider identity block matching provider/tenant/project/work item id; update that file if found.\n")
|
||||
b.WriteString("- If none exists, create one Milestone under the active Phase and include a `## Provider identity` block.\n")
|
||||
b.WriteString("- The block must include provider, tenant, project, work item id, and milestone id/roadmap item id when known.\n")
|
||||
b.WriteString("- Commit and push the Milestone file change to develop from the slot checkout.\n")
|
||||
b.WriteString("- Do not create duplicate Milestones or Plane child work items in this slice.\n\n")
|
||||
|
||||
b.WriteString("Work item details:\n")
|
||||
b.WriteString("Title: " + task.Title + "\n")
|
||||
b.WriteString("Provider: " + ident.Provider + "\n")
|
||||
if ident.Tenant != "" {
|
||||
b.WriteString("Tenant: " + ident.Tenant + "\n")
|
||||
}
|
||||
if ident.Project != "" {
|
||||
b.WriteString("Project: " + ident.Project + "\n")
|
||||
}
|
||||
b.WriteString("Work Item ID: " + ident.WorkItemID + "\n")
|
||||
if ident.ExternalURL != "" {
|
||||
b.WriteString("External URL: " + ident.ExternalURL + "\n")
|
||||
}
|
||||
if ident.StateID != "" {
|
||||
b.WriteString("State ID: " + ident.StateID + "\n")
|
||||
}
|
||||
b.WriteString("\nCheckout details:\n")
|
||||
b.WriteString("Workspace slot path: " + checkout.SlotPath + "\n")
|
||||
b.WriteString("Source branch: " + checkout.SourceBranch + "\n")
|
||||
return b.String()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package authoring
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nomadcode/nomadcode-core/internal/storage"
|
||||
|
|
@ -183,3 +184,84 @@ func TestBuildAuthoringGenerateInputEmptySlotPath(t *testing.T) {
|
|||
t.Error("expected task with empty slot path to not be detected as authoring task")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAuthoringGenerateInputCarriesProviderIdentityContext(t *testing.T) {
|
||||
pipelineBody := `{"message":"Plane ticket body text","work_item":{"provider":"plane","tenant":"acme","project":"proj-1","id":"NOMAD-42","state_id":"backlog","external_url":"https://plane.example.com/NOMAD-42"}}`
|
||||
extID := "NOMAD-42"
|
||||
extProvider := "plane"
|
||||
extURL := "https://plane.example.com/NOMAD-42"
|
||||
extMeta := json.RawMessage(`{"tenant":"acme-fallback","project":"proj-fallback","state_id":"state-fallback","external_url":"https://fallback.com"}`)
|
||||
|
||||
task := storage.Task{
|
||||
ID: "task-2",
|
||||
Title: "fallback title",
|
||||
Source: "plane",
|
||||
Payload: json.RawMessage(pipelineBody),
|
||||
Metadata: checkoutMeta("/home/user/workspace/nomadcode/slots/001", "develop"),
|
||||
ExternalProvider: &extProvider,
|
||||
ExternalID: &extID,
|
||||
ExternalUrl: &extURL,
|
||||
ExternalMetadata: extMeta,
|
||||
}
|
||||
|
||||
input, ok := BuildAuthoringGenerateInput(task)
|
||||
if !ok {
|
||||
t.Fatal("expected authoring task to be detected")
|
||||
}
|
||||
|
||||
if input.WorkspaceMetadata == nil {
|
||||
t.Fatal("expected WorkspaceMetadata to be set")
|
||||
}
|
||||
|
||||
// Payload values should take precedence or fallback to task fields
|
||||
if input.WorkspaceMetadata.Provider != "plane" {
|
||||
t.Errorf("unexpected provider: %q", input.WorkspaceMetadata.Provider)
|
||||
}
|
||||
if input.WorkspaceMetadata.WorkItemID != "NOMAD-42" {
|
||||
t.Errorf("unexpected work_item_id: %q", input.WorkspaceMetadata.WorkItemID)
|
||||
}
|
||||
if input.WorkspaceMetadata.Tenant != "acme" { // payload value
|
||||
t.Errorf("unexpected tenant: %q", input.WorkspaceMetadata.Tenant)
|
||||
}
|
||||
if input.WorkspaceMetadata.Project != "proj-1" { // payload value
|
||||
t.Errorf("unexpected project: %q", input.WorkspaceMetadata.Project)
|
||||
}
|
||||
if input.WorkspaceMetadata.ExternalURL != "https://plane.example.com/NOMAD-42" { // task/payload value
|
||||
t.Errorf("unexpected external URL: %q", input.WorkspaceMetadata.ExternalURL)
|
||||
}
|
||||
if input.WorkspaceMetadata.StateID != "backlog" { // payload value
|
||||
t.Errorf("unexpected state id: %q", input.WorkspaceMetadata.StateID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAuthoringGenerateInputFallbackInstructionsRequireIdentityBlockAndNoDuplicate(t *testing.T) {
|
||||
task := storage.Task{
|
||||
ID: "task-2",
|
||||
Title: "My work item title",
|
||||
Source: "plane",
|
||||
Payload: json.RawMessage(`{}`),
|
||||
Metadata: checkoutMeta("/home/user/workspace/nomadcode/slots/001", "develop"),
|
||||
}
|
||||
|
||||
input, ok := BuildAuthoringGenerateInput(task)
|
||||
if !ok {
|
||||
t.Fatal("expected authoring task to be detected")
|
||||
}
|
||||
|
||||
assertions := []string{
|
||||
"Provider identity block",
|
||||
"duplicate",
|
||||
"active Milestone",
|
||||
"develop",
|
||||
}
|
||||
|
||||
for _, ast := range assertions {
|
||||
if !containsIgnoreCase(input.Instructions, ast) {
|
||||
t.Errorf("expected instructions to contain %q, but got:\n%s", ast, input.Instructions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containsIgnoreCase(s, substr string) bool {
|
||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ type WorkspaceMetadata struct {
|
|||
SourceBranch string `json:"source_branch,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
WorkItemID string `json:"work_item_id,omitempty"`
|
||||
Tenant string `json:"tenant,omitempty"`
|
||||
Project string `json:"project,omitempty"`
|
||||
ExternalURL string `json:"external_url,omitempty"`
|
||||
StateID string `json:"state_id,omitempty"`
|
||||
}
|
||||
|
||||
type GenerateInput struct {
|
||||
|
|
|
|||
|
|
@ -505,6 +505,12 @@ func TestRunTaskRoutesPlaneOriginToAuthoringBridge(t *testing.T) {
|
|||
if capturedInput.WorkspaceMetadata.SourceBranch != "develop" {
|
||||
t.Errorf("unexpected source branch: %q", capturedInput.WorkspaceMetadata.SourceBranch)
|
||||
}
|
||||
if capturedInput.WorkspaceMetadata.Provider != "plane" {
|
||||
t.Errorf("unexpected provider: %q", capturedInput.WorkspaceMetadata.Provider)
|
||||
}
|
||||
if capturedInput.WorkspaceMetadata.WorkItemID != "task-plane-1" {
|
||||
t.Errorf("unexpected work_item_id: %q", capturedInput.WorkspaceMetadata.WorkItemID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunTaskAuthoringPreemptsA2AClient(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue