diff --git a/pkg/agentic/commands_workspace.go b/pkg/agentic/commands_workspace.go index c9d88c9..fa45ee6 100644 --- a/pkg/agentic/commands_workspace.go +++ b/pkg/agentic/commands_workspace.go @@ -18,21 +18,18 @@ func (s *PrepSubsystem) registerWorkspaceCommands() { c.Command("workspace/dispatch", core.Command{Description: "Dispatch an agent to work on a repo task", Action: s.cmdWorkspaceDispatch}) } -func (s *PrepSubsystem) cmdWorkspaceList(opts core.Options) core.Result { - fsys := s.Core().Fs() - +func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result { statusFiles := WorkspaceStatusPaths() count := 0 for _, sf := range statusFiles { - wsName := WorkspaceName(core.PathDir(sf)) - if sr := fsys.Read(sf); sr.OK { - content := sr.Value.(string) - status := extractField(content, "status") - repo := extractField(content, "repo") - agent := extractField(content, "agent") - core.Print(nil, " %-8s %-8s %-10s %s", status, agent, repo, wsName) - count++ + wsDir := core.PathDir(sf) + wsName := WorkspaceName(wsDir) + st, err := ReadStatus(wsDir) + if err != nil { + continue } + core.Print(nil, " %-8s %-8s %-10s %s", st.Status, st.Agent, st.Repo, wsName) + count++ } if count == 0 { core.Print(nil, " no workspaces") @@ -52,12 +49,13 @@ func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result { var toRemove []string for _, sf := range statusFiles { - wsName := WorkspaceName(core.PathDir(sf)) - sr := fsys.Read(sf) - if !sr.OK { + wsDir := core.PathDir(sf) + wsName := WorkspaceName(wsDir) + st, err := ReadStatus(wsDir) + if err != nil { continue } - status := extractField(sr.Value.(string), "status") + status := st.Status switch filter { case "all": @@ -130,30 +128,3 @@ func (s *PrepSubsystem) cmdWorkspaceDispatch(opts core.Options) core.Result { } return core.Result{OK: true} } - -// extractField does a quick JSON field extraction without full unmarshal. -func extractField(jsonStr, field string) string { - needle := core.Concat("\"", field, "\"") - idx := -1 - for i := 0; i <= len(jsonStr)-len(needle); i++ { - if jsonStr[i:i+len(needle)] == needle { - idx = i + len(needle) - break - } - } - if idx < 0 { - return "" - } - for idx < len(jsonStr) && (jsonStr[idx] == ':' || jsonStr[idx] == ' ' || jsonStr[idx] == '\t') { - idx++ - } - if idx >= len(jsonStr) || jsonStr[idx] != '"' { - return "" - } - idx++ - end := idx - for end < len(jsonStr) && jsonStr[end] != '"' { - end++ - } - return jsonStr[idx:end] -} diff --git a/pkg/agentic/commands_workspace_example_test.go b/pkg/agentic/commands_workspace_example_test.go deleted file mode 100644 index b4d0988..0000000 --- a/pkg/agentic/commands_workspace_example_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: EUPL-1.2 - -package agentic - -import core "dappco.re/go/core" - -func Example_extractField() { - json := `{"status":"completed","repo":"go-io"}` - core.Println(extractField(json, "status")) - core.Println(extractField(json, "repo")) - // Output: - // completed - // go-io -} diff --git a/pkg/agentic/commands_workspace_test.go b/pkg/agentic/commands_workspace_test.go index 761340c..ceec326 100644 --- a/pkg/agentic/commands_workspace_test.go +++ b/pkg/agentic/commands_workspace_test.go @@ -10,61 +10,6 @@ import ( "github.com/stretchr/testify/assert" ) -// --- extractField --- - -func TestCommandsworkspace_ExtractField_Good_SimpleJSON(t *testing.T) { - json := `{"status":"running","repo":"go-io","agent":"codex"}` - assert.Equal(t, "running", extractField(json, "status")) - assert.Equal(t, "go-io", extractField(json, "repo")) - assert.Equal(t, "codex", extractField(json, "agent")) -} - -func TestCommandsworkspace_ExtractField_Good_PrettyPrinted(t *testing.T) { - json := `{ - "status": "completed", - "repo": "go-crypt" -}` - assert.Equal(t, "completed", extractField(json, "status")) - assert.Equal(t, "go-crypt", extractField(json, "repo")) -} - -func TestCommandsworkspace_ExtractField_Good_TabSeparated(t *testing.T) { - json := `{"status": "blocked"}` - assert.Equal(t, "blocked", extractField(json, "status")) -} - -func TestCommandsworkspace_ExtractField_Bad_MissingField(t *testing.T) { - json := `{"status":"running"}` - assert.Empty(t, extractField(json, "nonexistent")) -} - -func TestCommandsworkspace_ExtractField_Bad_EmptyJSON(t *testing.T) { - assert.Empty(t, extractField("", "status")) - assert.Empty(t, extractField("{}", "status")) -} - -func TestCommandsworkspace_ExtractField_Bad_NoValue(t *testing.T) { - // Field key exists but no quoted value after colon - json := `{"status": 42}` - assert.Empty(t, extractField(json, "status")) -} - -func TestCommandsworkspace_ExtractField_Bad_TruncatedJSON(t *testing.T) { - // Field key exists but string is truncated - json := `{"status":` - assert.Empty(t, extractField(json, "status")) -} - -func TestCommandsworkspace_ExtractField_Good_EmptyValue(t *testing.T) { - json := `{"status":""}` - assert.Equal(t, "", extractField(json, "status")) -} - -func TestCommandsworkspace_ExtractField_Good_ValueWithSpaces(t *testing.T) { - json := `{"task":"fix the failing tests"}` - assert.Equal(t, "fix the failing tests", extractField(json, "task")) -} - // --- CmdWorkspaceList Bad/Ugly --- func TestCommandsworkspace_CmdWorkspaceList_Bad_NoWorkspaceRootDir(t *testing.T) { @@ -75,8 +20,8 @@ func TestCommandsworkspace_CmdWorkspaceList_Bad_NoWorkspaceRootDir(t *testing.T) c := core.New() s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } r := s.cmdWorkspaceList(core.NewOptions()) @@ -105,8 +50,8 @@ func TestCommandsworkspace_CmdWorkspaceList_Ugly_NonDirAndCorruptStatus(t *testi c := core.New() s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } r := s.cmdWorkspaceList(core.NewOptions()) @@ -134,8 +79,8 @@ func TestCommandsworkspace_CmdWorkspaceClean_Bad_UnknownFilterLeavesEverything(t c := core.New() s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } // Filter "unknown" matches no switch case — nothing gets removed @@ -169,8 +114,8 @@ func TestCommandsworkspace_CmdWorkspaceClean_Ugly_MixedStatuses(t *testing.T) { c := core.New() s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } // "all" filter removes completed, failed, blocked, merged, ready-for-review but NOT running/queued @@ -196,8 +141,8 @@ func TestCommandsworkspace_CmdWorkspaceDispatch_Ugly_AllFieldsSet(t *testing.T) c := core.New() s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } r := s.cmdWorkspaceDispatch(core.NewOptions( @@ -212,22 +157,3 @@ func TestCommandsworkspace_CmdWorkspaceDispatch_Ugly_AllFieldsSet(t *testing.T) // The test verifies the CLI correctly passes all fields through to dispatch. assert.False(t, r.OK) } - -// --- ExtractField Ugly --- - -func TestCommandsworkspace_ExtractField_Ugly_NestedJSON(t *testing.T) { - // Nested JSON — extractField only finds top-level keys (simple scan) - j := `{"outer":{"inner":"value"},"status":"ok"}` - assert.Equal(t, "ok", extractField(j, "status")) - // "inner" is inside the nested object — extractField should still find it - assert.Equal(t, "value", extractField(j, "inner")) -} - -func TestCommandsworkspace_ExtractField_Ugly_EscapedQuotes(t *testing.T) { - // Value with escaped quotes — extractField stops at the first unescaped quote - j := `{"msg":"hello \"world\"","status":"done"}` - // extractField will return "hello \" because it stops at first quote after open - // The important thing is it doesn't panic - _ = extractField(j, "msg") - assert.Equal(t, "done", extractField(j, "status")) -}