From a136c04aa1db44d7ca38f6f1a69e1b33e254d181 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 05:44:39 +0000 Subject: [PATCH] feat(agentic): extract structured agent output Co-Authored-By: Virgil --- pkg/agentic/commands.go | 79 +++++++++++++++++++++++++++++++++++- pkg/agentic/commands_test.go | 33 +++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index a28200f..97aaa18 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -81,7 +81,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("prompt/version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion}) 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}) + c.Command("extract", core.Command{Description: "Extract data from agent output or scaffold a workspace template", Action: s.cmdExtract}) s.registerPlanCommands() s.registerCommitCommands() s.registerSessionCommands() @@ -887,11 +887,53 @@ func (s *PrepSubsystem) cmdPromptVersion(options core.Options) core.Result { } func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result { + sourcePath := optionStringValue(options, "source", "input", "file") templateName := options.String("_arg") if templateName == "" { templateName = "default" } target := options.String("target") + + if sourcePath == "" && fs.Exists(templateName) && fs.IsFile(templateName) { + sourcePath = templateName + templateName = "" + } + + if sourcePath != "" { + readResult := fs.Read(sourcePath) + if !readResult.OK { + err, _ := readResult.Value.(error) + if err == nil { + err = core.E("agentic.cmdExtract", core.Concat("read agent output ", sourcePath), nil) + } + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + extracted := extractAgentOutputContent(readResult.Value.(string)) + if extracted == "" { + err := core.E("agentic.cmdExtract", "agent output did not contain extractable content", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + if target != "" { + if writeResult := fs.WriteMode(target, extracted, 0644); !writeResult.OK { + err, _ := writeResult.Value.(error) + if err == nil { + err = core.E("agentic.cmdExtract", core.Concat("write extracted output ", target), nil) + } + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + core.Print(nil, "written: %s", target) + } else { + core.Print(nil, "%s", extracted) + } + + return core.Result{Value: extracted, OK: true} + } + if target == "" { target = core.JoinPath(WorkspaceRoot(), "test-extract") } @@ -926,6 +968,41 @@ func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result { return core.Result{OK: true} } +func extractAgentOutputContent(content string) string { + trimmed := core.Trim(content) + if trimmed == "" { + return "" + } + + if core.HasPrefix(trimmed, "{") || core.HasPrefix(trimmed, "[") { + return trimmed + } + + blocks := core.Split(content, "```") + for index := 1; index < len(blocks); index += 2 { + block := core.Trim(blocks[index]) + if block == "" { + continue + } + + lines := core.SplitN(block, "\n", 2) + if len(lines) == 2 { + language := core.Trim(lines[0]) + body := core.Trim(lines[1]) + if language != "" && !core.Contains(language, " ") && body != "" { + block = body + } + } + + block = core.Trim(block) + if block != "" { + return block + } + } + + return "" +} + // parseIntString("issue-42") // 42 func parseIntString(s string) int { n := 0 diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index 9277d91..088fde6 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -1364,6 +1364,39 @@ func TestCommands_CmdExtract_Good(t *testing.T) { assert.True(t, r.OK) } +func TestCommands_CmdExtract_Good_FromAgentOutput(t *testing.T) { + s, _ := testPrepWithCore(t, nil) + dir := t.TempDir() + source := core.JoinPath(dir, "agent-output.md") + target := core.JoinPath(dir, "extracted.json") + require.True(t, fs.Write(source, "Agent run complete.\n\n```json\n{\"summary\":\"done\",\"findings\":2}\n```\n").OK) + + output := captureStdout(t, func() { + r := s.cmdExtract(core.NewOptions( + core.Option{Key: "source", Value: source}, + core.Option{Key: "target", Value: target}, + )) + assert.True(t, r.OK) + assert.Equal(t, "{\"summary\":\"done\",\"findings\":2}", r.Value) + }) + + assert.Contains(t, output, "written: ") + assert.True(t, fs.Exists(target)) + written := fs.Read(target) + require.True(t, written.OK) + assert.Equal(t, "{\"summary\":\"done\",\"findings\":2}", written.Value) +} + +func TestCommands_CmdExtract_Bad_NoExtractableContent(t *testing.T) { + s, _ := testPrepWithCore(t, nil) + dir := t.TempDir() + source := core.JoinPath(dir, "agent-output.md") + require.True(t, fs.Write(source, "Agent run complete.\nNothing structured here.\n").OK) + + r := s.cmdExtract(core.NewOptions(core.Option{Key: "source", Value: source})) + assert.False(t, r.OK) +} + func TestCommands_CmdRunTask_Bad_MissingArgs(t *testing.T) { s, _ := testPrepWithCore(t, nil) ctx, cancel := context.WithCancel(context.Background())