feat(agentic): archive plan delete action

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 12:11:25 +00:00
parent 3883466cc4
commit c2e1263493
4 changed files with 38 additions and 39 deletions

View file

@ -438,26 +438,11 @@ func (s *PrepSubsystem) planUpdate(_ context.Context, _ *mcp.CallToolRequest, in
}
func (s *PrepSubsystem) planDelete(_ context.Context, _ *mcp.CallToolRequest, input PlanDeleteInput) (*mcp.CallToolResult, PlanDeleteOutput, error) {
ref := planReference(input.ID, input.Slug)
if ref == "" {
return nil, PlanDeleteOutput{}, core.E("planDelete", "id is required", nil)
}
plan, err := readPlan(PlansRoot(), ref)
plan, err := archivePlanResult(input, "id is required", "planDelete")
if err != nil {
return nil, PlanDeleteOutput{}, err
}
path := planPath(PlansRoot(), plan.ID)
if !fs.Exists(path) {
return nil, PlanDeleteOutput{}, core.E("planDelete", core.Concat("plan not found: ", ref), nil)
}
if r := fs.Delete(path); !r.OK {
err, _ := r.Value.(error)
return nil, PlanDeleteOutput{}, core.E("planDelete", "failed to delete plan", err)
}
return nil, PlanDeleteOutput{
Success: true,
Deleted: plan.ID,

View file

@ -176,31 +176,11 @@ func (s *PrepSubsystem) planUpdateStatusCompat(ctx context.Context, _ *mcp.CallT
}
func (s *PrepSubsystem) planArchiveCompat(ctx context.Context, _ *mcp.CallToolRequest, input PlanDeleteInput) (*mcp.CallToolResult, PlanArchiveOutput, error) {
ref := planReference(input.ID, input.Slug)
if ref == "" {
return nil, PlanArchiveOutput{}, core.E("planArchiveCompat", "slug is required", nil)
}
plan, err := readPlan(PlansRoot(), ref)
plan, err := archivePlanResult(input, "slug is required", "planArchiveCompat")
if err != nil {
return nil, PlanArchiveOutput{}, err
}
now := time.Now()
plan.Status = "archived"
plan.ArchivedAt = now
plan.UpdatedAt = now
if notes := archiveReasonValue(input.Reason); notes != "" {
plan.Notes = appendPlanNote(plan.Notes, notes)
}
if result := writePlanResult(PlansRoot(), plan); !result.OK {
err, _ := result.Value.(error)
if err == nil {
err = core.E("planArchiveCompat", "failed to write plan", nil)
}
return nil, PlanArchiveOutput{}, err
}
return nil, PlanArchiveOutput{
Success: true,
Archived: plan.Slug,
@ -312,3 +292,32 @@ func planCompatibilityOutputStatus(status string) string {
return status
}
}
func archivePlanResult(input PlanDeleteInput, missingMessage, op string) (*Plan, error) {
ref := planReference(input.ID, input.Slug)
if ref == "" {
return nil, core.E(op, missingMessage, nil)
}
plan, err := readPlan(PlansRoot(), ref)
if err != nil {
return nil, err
}
now := time.Now()
plan.Status = "archived"
plan.ArchivedAt = now
plan.UpdatedAt = now
if notes := archiveReasonValue(input.Reason); notes != "" {
plan.Notes = appendPlanNote(plan.Notes, notes)
}
if result := writePlanResult(PlansRoot(), plan); !result.OK {
err, _ := result.Value.(error)
if err == nil {
err = core.E(op, "failed to write plan", nil)
}
return nil, err
}
return plan, nil
}

View file

@ -268,7 +268,12 @@ func TestPlan_PlanDelete_Good(t *testing.T) {
assert.True(t, delOut.Success)
assert.Equal(t, createOut.ID, delOut.Deleted)
assert.False(t, fs.Exists(createOut.Path))
assert.True(t, fs.Exists(createOut.Path))
plan, readErr := readPlan(PlansRoot(), createOut.ID)
require.NoError(t, readErr)
assert.Equal(t, "archived", plan.Status)
assert.False(t, plan.ArchivedAt.IsZero())
}
func TestPlan_PlanDelete_Bad_MissingID(t *testing.T) {

View file

@ -185,7 +185,7 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
c.Action("plan.update", s.handlePlanUpdate).Description = "Update plan status, phases, notes, or agent assignment"
c.Action("plan.update_status", s.handlePlanUpdateStatus).Description = "Update an implementation plan lifecycle status by slug"
c.Action("plan.archive", s.handlePlanArchive).Description = "Archive an implementation plan by slug"
c.Action("plan.delete", s.handlePlanDelete).Description = "Delete an implementation plan by ID"
c.Action("plan.delete", s.handlePlanDelete).Description = "Archive an implementation plan by ID"
c.Action("plan.list", s.handlePlanList).Description = "List implementation plans with optional filters"
c.Action("phase.get", s.handlePhaseGet).Description = "Read a plan phase by slug and order"
c.Action("phase.update_status", s.handlePhaseUpdateStatus).Description = "Update plan phase status by slug and order"