From afc7b063ee363b8af9a8e4f57b0c3f3342a930cc Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 16:32:03 +0000 Subject: [PATCH] feat(agentic): hard-delete plans on plan delete Co-Authored-By: Virgil --- pkg/agentic/commands_plan.go | 2 +- pkg/agentic/commands_test.go | 11 ++++------- pkg/agentic/plan_compat.go | 28 +++++++++++++++++++++++++++- pkg/agentic/plan_crud_test.go | 17 +++++++++++------ 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/pkg/agentic/commands_plan.go b/pkg/agentic/commands_plan.go index ab3348c..9adcb33 100644 --- a/pkg/agentic/commands_plan.go +++ b/pkg/agentic/commands_plan.go @@ -303,7 +303,7 @@ func (s *PrepSubsystem) cmdPlanDelete(options core.Options) core.Result { return core.Result{Value: err, OK: false} } - core.Print(nil, "archived: %s", output.Deleted) + core.Print(nil, "deleted: %s", output.Deleted) return core.Result{Value: output, OK: true} } diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index d64e713..bd0e245 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -1093,15 +1093,12 @@ func TestCommands_CmdPlanDelete_Good(t *testing.T) { assert.True(t, r.OK) }) - assert.Contains(t, output, "archived:") + assert.Contains(t, output, "deleted:") - assert.True(t, fs.Exists(created.Path)) + assert.False(t, fs.Exists(created.Path)) - plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) - assert.Equal(t, "archived", plan.Status) - assert.False(t, plan.ArchivedAt.IsZero()) - assert.Contains(t, plan.Notes, "RFC contract says soft delete") + _, readErr := readPlan(PlansRoot(), created.ID) + require.Error(t, readErr) } func TestCommands_CmdExtract_Good(t *testing.T) { diff --git a/pkg/agentic/plan_compat.go b/pkg/agentic/plan_compat.go index 9a20b3b..e162573 100644 --- a/pkg/agentic/plan_compat.go +++ b/pkg/agentic/plan_compat.go @@ -366,5 +366,31 @@ func archivePlanResult(input PlanDeleteInput, missingMessage, op string) (*Plan, } func deletePlanResult(input PlanDeleteInput, missingMessage, op string) (*Plan, error) { - return archivePlanResult(input, missingMessage, op) + 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 + } + + planPath := planPath(PlansRoot(), plan.ID) + if deleteResult := fs.Delete(planPath); !deleteResult.OK { + deleteErr, _ := deleteResult.Value.(error) + return nil, core.E(op, "failed to delete plan", deleteErr) + } + + if plan.Slug != "" { + stateFile := statePath(plan.Slug) + if fs.Exists(stateFile) { + if deleteResult := fs.Delete(stateFile); !deleteResult.OK { + deleteErr, _ := deleteResult.Value.(error) + return nil, core.E(op, "failed to delete plan state", deleteErr) + } + } + } + + return plan, nil } diff --git a/pkg/agentic/plan_crud_test.go b/pkg/agentic/plan_crud_test.go index 93025c3..6c34307 100644 --- a/pkg/agentic/plan_crud_test.go +++ b/pkg/agentic/plan_crud_test.go @@ -263,6 +263,13 @@ func TestPlan_PlanDelete_Good(t *testing.T) { Title: "Delete Me", Objective: "Will be deleted", }) + planBeforeDelete, err := readPlan(PlansRoot(), createOut.ID) + require.NoError(t, err) + require.NoError(t, writePlanStates(planBeforeDelete.Slug, []WorkspaceState{{ + Key: "pattern", + Value: "observer", + }})) + _, delOut, err := s.planDelete(context.Background(), nil, PlanDeleteInput{ ID: createOut.ID, Reason: "No longer needed", @@ -271,13 +278,11 @@ func TestPlan_PlanDelete_Good(t *testing.T) { assert.True(t, delOut.Success) assert.Equal(t, createOut.ID, delOut.Deleted) - assert.True(t, fs.Exists(createOut.Path)) + assert.False(t, fs.Exists(createOut.Path)) + assert.False(t, fs.Exists(statePath(planBeforeDelete.Slug))) - archivedPlan, readErr := readPlan(PlansRoot(), createOut.ID) - require.NoError(t, readErr) - assert.Equal(t, "archived", archivedPlan.Status) - assert.False(t, archivedPlan.ArchivedAt.IsZero()) - assert.Contains(t, archivedPlan.Notes, "No longer needed") + _, readErr := readPlan(PlansRoot(), createOut.ID) + require.Error(t, readErr) } func TestPlan_PlanDelete_Bad_MissingID(t *testing.T) {