diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index 14907f1..c5f535c 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -31,6 +31,7 @@ func (s *PrepSubsystem) registerCommands(ctx context.Context) { c.Command("brain/ingest", core.Command{Description: "Bulk ingest memories into OpenBrain", Action: s.cmdBrainIngest}) c.Command("brain/seed-memory", core.Command{Description: "Import markdown memories into OpenBrain from a project memory directory", Action: s.cmdBrainSeedMemory}) c.Command("brain/list", core.Command{Description: "List memories in OpenBrain", Action: s.cmdBrainList}) + c.Command("brain/forget", core.Command{Description: "Forget a memory in OpenBrain", Action: s.cmdBrainForget}) c.Command("lang/detect", core.Command{Description: "Detect the primary language for a repository or workspace", Action: s.cmdLangDetect}) c.Command("lang/list", core.Command{Description: "List supported language identifiers", Action: s.cmdLangList}) c.Command("plan-cleanup", core.Command{Description: "Permanently delete archived plans past the retention period", Action: s.cmdPlanCleanup}) @@ -368,6 +369,31 @@ func (s *PrepSubsystem) cmdBrainList(options core.Options) core.Result { return core.Result{Value: output, OK: true} } +// result := c.Command("brain/forget").Run(ctx, core.NewOptions(core.Option{Key: "_arg", Value: "mem-1"})) +func (s *PrepSubsystem) cmdBrainForget(options core.Options) core.Result { + id := optionStringValue(options, "id", "_arg") + if id == "" { + core.Print(nil, "usage: core-agent brain forget [--reason=\"superseded\"]") + return core.Result{Value: core.E("agentic.cmdBrainForget", "memory id is required", nil), OK: false} + } + + result := s.Core().Action("brain.forget").Run(s.commandContext(), core.NewOptions( + core.Option{Key: "id", Value: id}, + core.Option{Key: "reason", Value: optionStringValue(options, "reason")}, + )) + if !result.OK { + err := commandResultError("agentic.cmdBrainForget", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "forgotten: %s", id) + if reason := optionStringValue(options, "reason"); reason != "" { + core.Print(nil, "reason: %s", reason) + } + return core.Result{Value: result.Value, OK: true} +} + func (s *PrepSubsystem) cmdStatus(options core.Options) core.Result { workspaceRoot := WorkspaceRoot() filesystem := s.Core().Fs() diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index bd0e245..43cabd2 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -287,6 +287,54 @@ func TestCommands_CmdBrainList_Ugly_InvalidOutput(t *testing.T) { assert.Contains(t, err.Error(), "invalid brain list output") } +func TestCommands_CmdBrainForget_Good(t *testing.T) { + s, c := testPrepWithCore(t, nil) + c.Action("brain.forget", func(_ context.Context, options core.Options) core.Result { + assert.Equal(t, "mem-1", options.String("id")) + assert.Equal(t, "superseded", options.String("reason")) + return core.Result{Value: map[string]any{ + "success": true, + "forgotten": "mem-1", + }, OK: true} + }) + + output := captureStdout(t, func() { + result := s.cmdBrainForget(core.NewOptions( + core.Option{Key: "_arg", Value: "mem-1"}, + core.Option{Key: "reason", Value: "superseded"}, + )) + require.True(t, result.OK) + }) + + assert.Contains(t, output, "forgotten: mem-1") + assert.Contains(t, output, "reason: superseded") +} + +func TestCommands_CmdBrainForget_Bad_MissingID(t *testing.T) { + s, _ := testPrepWithCore(t, nil) + + result := s.cmdBrainForget(core.NewOptions()) + + require.False(t, result.OK) + err, ok := result.Value.(error) + require.True(t, ok) + assert.Contains(t, err.Error(), "memory id is required") +} + +func TestCommands_CmdBrainForget_Ugly_ActionFailure(t *testing.T) { + s, c := testPrepWithCore(t, nil) + c.Action("brain.forget", func(_ context.Context, _ core.Options) core.Result { + return core.Result{Value: core.E("brain.forget", "failed to forget memory", nil), OK: false} + }) + + result := s.cmdBrainForget(core.NewOptions(core.Option{Key: "_arg", Value: "mem-1"})) + + require.False(t, result.OK) + err, ok := result.Value.(error) + require.True(t, ok) + assert.Contains(t, err.Error(), "failed to forget memory") +} + func TestCommandsforge_CmdIssueCreate_Bad_APIError(t *testing.T) { callCount := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/agentic/prep_test.go b/pkg/agentic/prep_test.go index 652864a..1987be1 100644 --- a/pkg/agentic/prep_test.go +++ b/pkg/agentic/prep_test.go @@ -624,6 +624,7 @@ func TestPrep_OnStartup_Good_RegistersGenerateCommand(t *testing.T) { assert.Contains(t, c.Commands(), "brain/ingest") assert.Contains(t, c.Commands(), "brain/seed-memory") assert.Contains(t, c.Commands(), "brain/list") + assert.Contains(t, c.Commands(), "brain/forget") assert.Contains(t, c.Commands(), "lang/detect") assert.Contains(t, c.Commands(), "lang/list") assert.Contains(t, c.Commands(), "plan-cleanup")