- Plane Todo projection 및 idempotency/retry 메커니즘 구현 - Roadmap sync identity 및 step 모델/DB 마이그레이션/쿼리 추가 - OpenAI 및 Plane adapter 개선 - roadmaps sync package: plane_format, plane_projection, retry 모듈 추가 - Storage layer: roadmap_sync_identities, roadmap_sync_steps 추가 - agent-task 아카이브 정리
102 lines
3.9 KiB
Go
102 lines
3.9 KiB
Go
package roadmapsync
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/nomadcode/nomadcode-core/internal/workitem"
|
|
)
|
|
|
|
// ErrNotReady is returned when the develop projection candidate is not ready,
|
|
// so a caller never silently projects a Plane Todo for an unmatched or unpushed
|
|
// Milestone.
|
|
var ErrNotReady = errors.New("develop projection candidate is not ready")
|
|
|
|
// ErrInvalidProjectionInput is returned when a required projection input
|
|
// (provider ref, original body, todo state, or milestone content) is missing.
|
|
var ErrInvalidProjectionInput = errors.New("invalid plane todo projection input")
|
|
|
|
// preserveCommentPrefix opens the comment that preserves the original Plane
|
|
// body before the develop content replaces it. Kept as a stable prefix so the
|
|
// preserved request is recognizable in the Plane timeline.
|
|
const preserveCommentPrefix = "사용자 요청:"
|
|
|
|
// PlaneTodoProvider is the minimal set of provider facets the Todo projection
|
|
// drives, in execution order: preserve the original body as a comment, replace
|
|
// title/body, then move the work item to the Todo state. It is satisfied by the
|
|
// Plane adapter Client and by test fakes.
|
|
type PlaneTodoProvider interface {
|
|
workitem.Commenter
|
|
workitem.BodyProjector
|
|
workitem.StatusProjector
|
|
}
|
|
|
|
// ProjectPlaneTodoInput carries everything ProjectPlaneTodo needs without
|
|
// reaching into provider DTOs: the develop match decision, the original Plane
|
|
// body to preserve, the Todo state id to move to, and the develop Milestone
|
|
// markdown to project. Ref is the provider work item the projection targets.
|
|
type ProjectPlaneTodoInput struct {
|
|
Candidate ProjectionCandidate
|
|
Ref workitem.Ref
|
|
OriginalBody string
|
|
TodoStateID string
|
|
// MilestoneMarkdown is the develop Milestone content used to build the new
|
|
// Plane title/body via FormatPlaneProjection.
|
|
MilestoneMarkdown string
|
|
// ExternalSource/ExternalID tag the preservation comment so a re-run can be
|
|
// recognized as nomadcode-authored rather than a user request.
|
|
ExternalSource string
|
|
ExternalID string
|
|
}
|
|
|
|
// ProjectPlaneTodo runs the Plane Todo projection in the order the Milestone
|
|
// requires (doc lines 57/102): preserve the original body as a `사용자 요청:`
|
|
// comment, then replace the title/body with the develop Milestone content, then
|
|
// move the work item to the Todo state. A failure at any step stops the
|
|
// sequence so a later step never runs on an unpreserved or unupdated ticket.
|
|
//
|
|
// When the develop projection candidate is not ready it returns ErrNotReady
|
|
// without calling the provider, so a Plane Todo is never produced for an
|
|
// unmatched or unpushed Milestone.
|
|
func ProjectPlaneTodo(ctx context.Context, p PlaneTodoProvider, in ProjectPlaneTodoInput) error {
|
|
if !in.Candidate.Ready {
|
|
return ErrNotReady
|
|
}
|
|
if p == nil ||
|
|
strings.TrimSpace(string(in.Ref.Provider)) == "" ||
|
|
strings.TrimSpace(in.Ref.ID) == "" ||
|
|
strings.TrimSpace(in.OriginalBody) == "" ||
|
|
strings.TrimSpace(in.TodoStateID) == "" ||
|
|
strings.TrimSpace(in.MilestoneMarkdown) == "" {
|
|
return ErrInvalidProjectionInput
|
|
}
|
|
|
|
projection := FormatPlaneProjection(in.Candidate.Identity.RoadmapMilestonePath, in.MilestoneMarkdown)
|
|
|
|
// 1. Preserve the original Plane body as a comment before anything mutates it.
|
|
if err := p.AppendComment(ctx, workitem.CommentInput{
|
|
Ref: in.Ref,
|
|
Body: preserveCommentPrefix + " " + in.OriginalBody,
|
|
Format: workitem.CommentFormatHTML,
|
|
ExternalSource: in.ExternalSource,
|
|
ExternalID: in.ExternalID,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 2. Replace title/body with the develop Milestone projection.
|
|
if err := p.ProjectBody(ctx, workitem.BodyProjection{
|
|
Ref: in.Ref,
|
|
Title: projection.Title,
|
|
DescriptionHTML: projection.BodyHTML,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 3. Move the work item to the Todo state.
|
|
return p.ProjectStatus(ctx, workitem.StatusProjection{
|
|
Ref: in.Ref,
|
|
ProviderState: in.TodoStateID,
|
|
})
|
|
}
|