From 2e9529c0189e1faa8ad9777c15315dcc5b5556ab Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 02:45:26 +0000 Subject: [PATCH] feat(agentic): expose commit command Co-Authored-By: Virgil --- pkg/agentic/commands.go | 1 + pkg/agentic/commands_commit.go | 48 ++++++++++++ pkg/agentic/commands_commit_test.go | 112 ++++++++++++++++++++++++++++ pkg/agentic/commands_test.go | 2 + pkg/agentic/prep_test.go | 2 + 5 files changed, 165 insertions(+) create mode 100644 pkg/agentic/commands_commit.go create mode 100644 pkg/agentic/commands_commit_test.go diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index 115d89b..2768e3c 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -72,6 +72,7 @@ func (s *PrepSubsystem) registerCommands(ctx context.Context) { c.Command("agentic:prompt/version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion}) c.Command("extract", core.Command{Description: "Extract a workspace template to a directory", Action: s.cmdExtract}) s.registerPlanCommands() + s.registerCommitCommands() s.registerSessionCommands() s.registerTaskCommands() s.registerStateCommands() diff --git a/pkg/agentic/commands_commit.go b/pkg/agentic/commands_commit.go new file mode 100644 index 0000000..d106111 --- /dev/null +++ b/pkg/agentic/commands_commit.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import core "dappco.re/go/core" + +func (s *PrepSubsystem) registerCommitCommands() { + c := s.Core() + c.Command("commit", core.Command{Description: "Write the final dispatch record to the workspace journal", Action: s.cmdCommit}) + c.Command("agentic:commit", core.Command{Description: "Write the final dispatch record to the workspace journal", Action: s.cmdCommit}) +} + +// core-agent commit core/go-io/task-42 +func (s *PrepSubsystem) cmdCommit(options core.Options) core.Result { + workspace := optionStringValue(options, "workspace", "_arg") + if workspace == "" { + core.Print(nil, "usage: core-agent commit ") + return core.Result{Value: core.E("agentic.cmdCommit", "workspace is required", nil), OK: false} + } + + result := s.handleCommit(s.commandContext(), core.NewOptions( + core.Option{Key: "workspace", Value: workspace}, + )) + if !result.OK { + err := commandResultError("agentic.cmdCommit", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := result.Value.(CommitOutput) + if !ok { + err := core.E("agentic.cmdCommit", "invalid commit output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "workspace: %s", output.Workspace) + core.Print(nil, "journal: %s", output.JournalPath) + if output.MarkerPath != "" { + core.Print(nil, "marker: %s", output.MarkerPath) + } + if output.Skipped { + core.Print(nil, "skipped: true") + } else { + core.Print(nil, "committed: %s", output.CommittedAt) + } + return core.Result{Value: output, OK: true} +} diff --git a/pkg/agentic/commands_commit_test.go b/pkg/agentic/commands_commit_test.go new file mode 100644 index 0000000..c7d6998 --- /dev/null +++ b/pkg/agentic/commands_commit_test.go @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "testing" + + core "dappco.re/go/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCommandsCommit_RegisterCommitCommands_Good(t *testing.T) { + c := core.New(core.WithOption("name", "test")) + s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} + + s.registerCommitCommands() + + assert.Contains(t, c.Commands(), "commit") + assert.Contains(t, c.Commands(), "agentic:commit") +} + +func TestCommandsCommit_CmdCommit_Good(t *testing.T) { + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + + workspaceName := "core/go-io/task-42" + workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) + require.True(t, fs.EnsureDir(workspaceDir).OK) + require.True(t, writeStatus(workspaceDir, &WorkspaceStatus{ + Status: "completed", + Agent: "codex", + Repo: "go-io", + Org: "core", + Task: "Fix tests", + Branch: "agent/fix-tests", + Runs: 2, + }) == nil) + + s := &PrepSubsystem{} + output := captureStdout(t, func() { + result := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) + require.True(t, result.OK) + + commitOutput, ok := result.Value.(CommitOutput) + require.True(t, ok) + assert.Equal(t, workspaceName, commitOutput.Workspace) + assert.False(t, commitOutput.Skipped) + assert.NotEmpty(t, commitOutput.JournalPath) + assert.NotEmpty(t, commitOutput.MarkerPath) + assert.NotEmpty(t, commitOutput.CommittedAt) + }) + + assert.Contains(t, output, "workspace: core/go-io/task-42") + assert.Contains(t, output, "journal:") + assert.Contains(t, output, "committed:") +} + +func TestCommandsCommit_CmdCommit_Bad_MissingWorkspace(t *testing.T) { + s := &PrepSubsystem{} + result := s.cmdCommit(core.NewOptions()) + + assert.False(t, result.OK) + require.Error(t, result.Value.(error)) + assert.Contains(t, result.Value.(error).Error(), "workspace is required") +} + +func TestCommandsCommit_CmdCommit_Ugly_MissingStatus(t *testing.T) { + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + + workspaceName := "core/go-io/task-99" + workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) + require.True(t, fs.EnsureDir(workspaceDir).OK) + + s := &PrepSubsystem{} + result := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) + + assert.False(t, result.OK) + require.Error(t, result.Value.(error)) + assert.Contains(t, result.Value.(error).Error(), "status not found") +} + +func TestCommandsCommit_CmdCommit_Ugly_Idempotent(t *testing.T) { + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + + workspaceName := "core/go-io/task-100" + workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) + require.True(t, fs.EnsureDir(workspaceDir).OK) + require.True(t, writeStatus(workspaceDir, &WorkspaceStatus{ + Status: "merged", + Agent: "codex", + Repo: "go-io", + Org: "core", + Task: "Merge cleanly", + Branch: "agent/merge-cleanly", + Runs: 1, + }) == nil) + + s := &PrepSubsystem{} + first := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) + require.True(t, first.OK) + + second := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) + require.True(t, second.OK) + + commitOutput, ok := second.Value.(CommitOutput) + require.True(t, ok) + assert.True(t, commitOutput.Skipped) + assert.NotEmpty(t, commitOutput.MarkerPath) +} diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index 9ea3b37..7ed8297 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -1499,6 +1499,8 @@ func TestCommands_RegisterCommands_Good_AllRegistered(t *testing.T) { assert.Contains(t, cmds, "agentic:plan/delete") assert.Contains(t, cmds, "plan/delete") assert.Contains(t, cmds, "agentic:plan-cleanup") + assert.Contains(t, cmds, "commit") + assert.Contains(t, cmds, "agentic:commit") assert.Contains(t, cmds, "session/start") assert.Contains(t, cmds, "session/get") assert.Contains(t, cmds, "agentic:session/get") diff --git a/pkg/agentic/prep_test.go b/pkg/agentic/prep_test.go index ab82d37..680490c 100644 --- a/pkg/agentic/prep_test.go +++ b/pkg/agentic/prep_test.go @@ -709,6 +709,8 @@ func TestPrep_OnStartup_Good_RegistersGenerateCommand(t *testing.T) { assert.Contains(t, c.Commands(), "epic") assert.Contains(t, c.Commands(), "agentic:epic") assert.Contains(t, c.Commands(), "plan-cleanup") + assert.Contains(t, c.Commands(), "commit") + assert.Contains(t, c.Commands(), "agentic:commit") assert.Contains(t, c.Commands(), "plan/from-issue") assert.Contains(t, c.Commands(), "session/end") assert.Contains(t, c.Commands(), "agentic:session/end")