Add Scriptorium render diagnostics
This commit is contained in:
@@ -94,13 +94,14 @@ type ScriptoriumConfig struct {
|
|||||||
|
|
||||||
// ScriptoriumArtifactConfig configures one named output artifact workflow.
|
// ScriptoriumArtifactConfig configures one named output artifact workflow.
|
||||||
type ScriptoriumArtifactConfig struct {
|
type ScriptoriumArtifactConfig struct {
|
||||||
Enabled bool `yaml:"enabled"`
|
Enabled bool `yaml:"enabled"`
|
||||||
PromptID string `yaml:"prompt_id"`
|
RenderDebug *bool `yaml:"render_debug"`
|
||||||
ProfileID string `yaml:"profile_id"`
|
PromptID string `yaml:"prompt_id"`
|
||||||
OutputPath string `yaml:"output_path"`
|
ProfileID string `yaml:"profile_id"`
|
||||||
Timeout string `yaml:"timeout"`
|
OutputPath string `yaml:"output_path"`
|
||||||
Inputs map[string]ScriptoriumInputConfig `yaml:"inputs"`
|
Timeout string `yaml:"timeout"`
|
||||||
Vars map[string]any `yaml:"vars"`
|
Inputs map[string]ScriptoriumInputConfig `yaml:"inputs"`
|
||||||
|
Vars map[string]any `yaml:"vars"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScriptoriumInputConfig configures one named prompt input source.
|
// ScriptoriumInputConfig configures one named prompt input source.
|
||||||
|
|||||||
@@ -108,6 +108,30 @@ func TestScriptoriumLoadAndValidate(t *testing.T) {
|
|||||||
output_kind: session_recap
|
output_kind: session_recap
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "artifact render_debug override is accepted",
|
||||||
|
scriptoriumYAML: `scriptorium:
|
||||||
|
binary: scriptorium
|
||||||
|
render_debug: false
|
||||||
|
artifacts:
|
||||||
|
session_recap:
|
||||||
|
enabled: true
|
||||||
|
render_debug: true
|
||||||
|
prompt_id: dnd.session_recap
|
||||||
|
output_path: artifacts/session_recap.md
|
||||||
|
inputs:
|
||||||
|
transcript:
|
||||||
|
source: processed_transcript
|
||||||
|
required: true
|
||||||
|
`,
|
||||||
|
assert: func(t *testing.T, cfg *Config) {
|
||||||
|
t.Helper()
|
||||||
|
artifact := cfg.Pipeline.Scriptorium.Artifacts["session_recap"]
|
||||||
|
if artifact.RenderDebug == nil || *artifact.RenderDebug != true {
|
||||||
|
t.Fatalf("artifact render_debug = %#v, want true", artifact.RenderDebug)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "multiple artifact definitions can be decoded",
|
name: "multiple artifact definitions can be decoded",
|
||||||
scriptoriumYAML: `scriptorium:
|
scriptoriumYAML: `scriptorium:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package stage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -130,6 +131,79 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
|||||||
return nil, fmt.Errorf("analyze: resolve timeout: %w", err)
|
return nil, fmt.Errorf("analyze: resolve timeout: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logPaths := []string{}
|
||||||
|
generatedConfigs := []string{}
|
||||||
|
meta := map[string]any{
|
||||||
|
"stage": "analyze",
|
||||||
|
"artifact_name": artifactName,
|
||||||
|
"prompt_id": artifactCfg.PromptID,
|
||||||
|
"profile_id": artifactCfg.ProfileID,
|
||||||
|
"binary": env.Config.Pipeline.Scriptorium.Binary,
|
||||||
|
"config_path": env.Config.Pipeline.Scriptorium.ConfigPath,
|
||||||
|
"input_paths": inputPaths,
|
||||||
|
"input_names": sortedMapKeys(inputPaths),
|
||||||
|
"omitted_optional_inputs": omittedOptionalInputs,
|
||||||
|
"vars": vars,
|
||||||
|
"timeout": timeout.String(),
|
||||||
|
"processed_transcript_path": processedTranscriptPath,
|
||||||
|
"processed_transcript_source": processedSource,
|
||||||
|
"render_debug_enabled": resolveRenderDebugEnabled(env.Config.Pipeline.Scriptorium.RenderDebug, artifactCfg.RenderDebug),
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta["render_debug_enabled"] == true {
|
||||||
|
renderOutputPath := filepath.Join(paths.ArtifactsDir, artifactName+".render.json")
|
||||||
|
renderStdoutPath := filepath.Join(paths.LogsDir, "scriptorium."+artifactName+".render.stdout.log")
|
||||||
|
renderStderrPath := filepath.Join(paths.LogsDir, "scriptorium."+artifactName+".render.stderr.log")
|
||||||
|
renderGeneratedConfigPath := filepath.Join(paths.ConfigDir, "scriptorium."+artifactName+".render.generated.yml")
|
||||||
|
|
||||||
|
renderReq := scriptorium.RenderArtifactRequest{
|
||||||
|
Binary: env.Config.Pipeline.Scriptorium.Binary,
|
||||||
|
ConfigPath: env.Config.Pipeline.Scriptorium.ConfigPath,
|
||||||
|
PromptID: artifactCfg.PromptID,
|
||||||
|
ProfileID: artifactCfg.ProfileID,
|
||||||
|
InputPaths: inputPaths,
|
||||||
|
Vars: vars,
|
||||||
|
OutputPath: renderOutputPath,
|
||||||
|
StdoutLogPath: renderStdoutPath,
|
||||||
|
StderrLogPath: renderStderrPath,
|
||||||
|
GeneratedConfigPath: renderGeneratedConfigPath,
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
renderRes, renderErr := env.Scriptorium.RenderArtifact(ctx, renderReq)
|
||||||
|
if renderErr != nil {
|
||||||
|
return nil, fmt.Errorf("analyze: scriptorium render failed: %w", renderErr)
|
||||||
|
}
|
||||||
|
if renderRes.ValidationFailed {
|
||||||
|
return nil, fmt.Errorf("analyze: scriptorium render returned validation_failed=true")
|
||||||
|
}
|
||||||
|
finalRenderOutputPath := coalesceString(renderRes.OutputPath, renderReq.OutputPath)
|
||||||
|
if err := requireNonEmptyFile(finalRenderOutputPath, "session recap render output"); err != nil {
|
||||||
|
return nil, fmt.Errorf("analyze: %w", err)
|
||||||
|
}
|
||||||
|
if err := validateJSONFile(finalRenderOutputPath); err != nil {
|
||||||
|
return nil, fmt.Errorf("analyze: render diagnostics %q invalid json: %w", finalRenderOutputPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logPaths = append(logPaths, renderStdoutPath, renderStderrPath)
|
||||||
|
generatedConfigs = append(generatedConfigs, renderGeneratedConfigPath)
|
||||||
|
meta["render_output_path"] = finalRenderOutputPath
|
||||||
|
meta["render_stdout_log_path"] = renderStdoutPath
|
||||||
|
meta["render_stderr_log_path"] = renderStderrPath
|
||||||
|
meta["render_generated_config_path"] = renderGeneratedConfigPath
|
||||||
|
meta["render_adapter_exit_code"] = renderRes.ExitCode
|
||||||
|
meta["render_adapter_duration_ms"] = renderRes.Duration.Milliseconds()
|
||||||
|
meta["render_adapter_command_mode"] = renderRes.CommandMode
|
||||||
|
meta["render_adapter_prompt_id"] = renderRes.PromptID
|
||||||
|
meta["render_adapter_profile_id"] = renderRes.ProfileID
|
||||||
|
meta["render_adapter_output_path"] = renderRes.OutputPath
|
||||||
|
meta["render_adapter_generated_config"] = renderRes.GeneratedConfigPath
|
||||||
|
meta["render_adapter_stdout_log_path"] = renderRes.StdoutLogPath
|
||||||
|
meta["render_adapter_stderr_log_path"] = renderRes.StderrLogPath
|
||||||
|
if renderRes.Metadata != nil {
|
||||||
|
meta["render_adapter_metadata"] = renderRes.Metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req := scriptorium.RunArtifactRequest{
|
req := scriptorium.RunArtifactRequest{
|
||||||
Binary: env.Config.Pipeline.Scriptorium.Binary,
|
Binary: env.Config.Pipeline.Scriptorium.Binary,
|
||||||
ConfigPath: env.Config.Pipeline.Scriptorium.ConfigPath,
|
ConfigPath: env.Config.Pipeline.Scriptorium.ConfigPath,
|
||||||
@@ -175,43 +249,30 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
|||||||
AbsolutePath: finalOutputPath,
|
AbsolutePath: finalOutputPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := map[string]any{
|
logPaths = append(logPaths, stdoutLogPath, stderrLogPath)
|
||||||
"stage": "analyze",
|
generatedConfigs = append(generatedConfigs, generatedConfigPath)
|
||||||
"artifact_name": artifactName,
|
meta["output_path"] = finalOutputPath
|
||||||
"prompt_id": artifactCfg.PromptID,
|
meta["generated_config_path"] = generatedConfigPath
|
||||||
"profile_id": artifactCfg.ProfileID,
|
meta["stdout_log_path"] = stdoutLogPath
|
||||||
"output_path": finalOutputPath,
|
meta["stderr_log_path"] = stderrLogPath
|
||||||
"generated_config_path": generatedConfigPath,
|
meta["adapter_exit_code"] = res.ExitCode
|
||||||
"stdout_log_path": stdoutLogPath,
|
meta["adapter_duration_ms"] = res.Duration.Milliseconds()
|
||||||
"stderr_log_path": stderrLogPath,
|
meta["adapter_command_mode"] = res.CommandMode
|
||||||
"input_paths": inputPaths,
|
meta["adapter_prompt_id"] = res.PromptID
|
||||||
"input_names": sortedMapKeys(inputPaths),
|
meta["adapter_profile_id"] = res.ProfileID
|
||||||
"omitted_optional_inputs": omittedOptionalInputs,
|
meta["adapter_validation_failed"] = res.ValidationFailed
|
||||||
"vars": vars,
|
meta["adapter_output_path"] = res.OutputPath
|
||||||
"timeout": timeout.String(),
|
meta["adapter_generated_config"] = res.GeneratedConfigPath
|
||||||
"binary": env.Config.Pipeline.Scriptorium.Binary,
|
meta["adapter_stdout_log_path"] = res.StdoutLogPath
|
||||||
"config_path": env.Config.Pipeline.Scriptorium.ConfigPath,
|
meta["adapter_stderr_log_path"] = res.StderrLogPath
|
||||||
"processed_transcript_path": processedTranscriptPath,
|
|
||||||
"processed_transcript_source": processedSource,
|
|
||||||
"adapter_exit_code": res.ExitCode,
|
|
||||||
"adapter_duration_ms": res.Duration.Milliseconds(),
|
|
||||||
"adapter_command_mode": res.CommandMode,
|
|
||||||
"adapter_prompt_id": res.PromptID,
|
|
||||||
"adapter_profile_id": res.ProfileID,
|
|
||||||
"adapter_validation_failed": res.ValidationFailed,
|
|
||||||
"adapter_output_path": res.OutputPath,
|
|
||||||
"adapter_generated_config": res.GeneratedConfigPath,
|
|
||||||
"adapter_stdout_log_path": res.StdoutLogPath,
|
|
||||||
"adapter_stderr_log_path": res.StderrLogPath,
|
|
||||||
}
|
|
||||||
if res.Metadata != nil {
|
if res.Metadata != nil {
|
||||||
meta["adapter_metadata"] = res.Metadata
|
meta["adapter_metadata"] = res.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
return &StageResult{
|
return &StageResult{
|
||||||
Outputs: []artifacts.Ref{artifactRef},
|
Outputs: []artifacts.Ref{artifactRef},
|
||||||
Logs: []string{stdoutLogPath, stderrLogPath},
|
Logs: logPaths,
|
||||||
GeneratedConfigs: []string{generatedConfigPath},
|
GeneratedConfigs: generatedConfigs,
|
||||||
Metadata: meta,
|
Metadata: meta,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -460,3 +521,22 @@ func coalesceString(primary, fallback string) string {
|
|||||||
}
|
}
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveRenderDebugEnabled(global bool, perArtifact *bool) bool {
|
||||||
|
if perArtifact == nil {
|
||||||
|
return global
|
||||||
|
}
|
||||||
|
return *perArtifact
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateJSONFile(path string) error {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read file: %w", err)
|
||||||
|
}
|
||||||
|
var payload any
|
||||||
|
if err := json.Unmarshal(data, &payload); err != nil {
|
||||||
|
return fmt.Errorf("decode json: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,6 +68,133 @@ func TestAnalyzeGeneratesSessionRecapFromProcessedTranscript(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnalyzeRenderDebugFalseDoesNotCallRenderArtifact(t *testing.T) {
|
||||||
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
|
|
||||||
|
env.Config.Pipeline.Scriptorium.RenderDebug = false
|
||||||
|
|
||||||
|
_, err := (analyzeStage{}).Run(context.Background(), env, m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(fake.RenderRequests) != 0 {
|
||||||
|
t.Fatalf("render requests = %d, want 0", len(fake.RenderRequests))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnalyzeRenderDebugTrueCallsRenderBeforeRun(t *testing.T) {
|
||||||
|
env, m, _ := setupAnalyzeEnv(t)
|
||||||
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
|
|
||||||
|
env.Config.Pipeline.Scriptorium.RenderDebug = true
|
||||||
|
runner := &orderedScriptoriumRunner{
|
||||||
|
RenderBody: `{"rendered":true}`,
|
||||||
|
RunBody: "session recap\n",
|
||||||
|
}
|
||||||
|
env.Scriptorium = runner
|
||||||
|
|
||||||
|
_, err := (analyzeStage{}).Run(context.Background(), env, m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := strings.Join(runner.Calls, ","); got != "render,run" {
|
||||||
|
t.Fatalf("call order = %q, want render,run", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnalyzeRenderOutputPathIsRecorded(t *testing.T) {
|
||||||
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
|
env.Config.Pipeline.Scriptorium.RenderDebug = true
|
||||||
|
|
||||||
|
result, err := (analyzeStage{}).Run(context.Background(), env, m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(fake.RenderRequests) != 1 {
|
||||||
|
t.Fatalf("render requests = %d, want 1", len(fake.RenderRequests))
|
||||||
|
}
|
||||||
|
if result.Metadata["render_output_path"] != filepath.Join(paths.ArtifactsDir, "session_recap.render.json") {
|
||||||
|
t.Fatalf("render_output_path = %#v, want session_recap.render.json path", result.Metadata["render_output_path"])
|
||||||
|
}
|
||||||
|
if len(result.Logs) != 4 {
|
||||||
|
t.Fatalf("logs = %#v, want render+run stdout/stderr logs", result.Logs)
|
||||||
|
}
|
||||||
|
if len(result.GeneratedConfigs) != 2 {
|
||||||
|
t.Fatalf("generated configs = %#v, want render+run generated configs", result.GeneratedConfigs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnalyzeRenderFailurePreventsRun(t *testing.T) {
|
||||||
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
|
env.Config.Pipeline.Scriptorium.RenderDebug = true
|
||||||
|
fake.RenderErr = errors.New("render boom")
|
||||||
|
|
||||||
|
_, err := (analyzeStage{}).Run(context.Background(), env, m)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "scriptorium render failed") {
|
||||||
|
t.Fatalf("error = %q, want render failure context", err.Error())
|
||||||
|
}
|
||||||
|
if len(fake.RenderRequests) != 1 {
|
||||||
|
t.Fatalf("render requests = %d, want 1", len(fake.RenderRequests))
|
||||||
|
}
|
||||||
|
if len(fake.RunRequests) != 0 {
|
||||||
|
t.Fatalf("run requests = %d, want 0 when render fails", len(fake.RunRequests))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnalyzeRenderInvalidJSONFailsClearly(t *testing.T) {
|
||||||
|
env, m, _ := setupAnalyzeEnv(t)
|
||||||
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
|
env.Config.Pipeline.Scriptorium.RenderDebug = true
|
||||||
|
runner := &orderedScriptoriumRunner{
|
||||||
|
RenderBody: `not-json`,
|
||||||
|
RunBody: "session recap\n",
|
||||||
|
}
|
||||||
|
env.Scriptorium = runner
|
||||||
|
|
||||||
|
_, err := (analyzeStage{}).Run(context.Background(), env, m)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "render diagnostics") {
|
||||||
|
t.Fatalf("error = %q, want render diagnostics context", err.Error())
|
||||||
|
}
|
||||||
|
if got := strings.Join(runner.Calls, ","); got != "render" {
|
||||||
|
t.Fatalf("call order = %q, want only render before failure", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnalyzeRunStillSucceedsWhenRenderSucceeds(t *testing.T) {
|
||||||
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
|
env.Config.Pipeline.Scriptorium.RenderDebug = true
|
||||||
|
|
||||||
|
result, err := (analyzeStage{}).Run(context.Background(), env, m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(fake.RenderRequests) != 1 || len(fake.RunRequests) != 1 {
|
||||||
|
t.Fatalf("render requests = %d, run requests = %d, want 1/1", len(fake.RenderRequests), len(fake.RunRequests))
|
||||||
|
}
|
||||||
|
if result.Metadata["render_debug_enabled"] != true {
|
||||||
|
t.Fatalf("render_debug_enabled = %#v, want true", result.Metadata["render_debug_enabled"])
|
||||||
|
}
|
||||||
|
if err := requireNonEmptyFile(filepath.Join(paths.ArtifactsDir, "session_recap.md"), "session recap output"); err != nil {
|
||||||
|
t.Fatalf("session recap output validation failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAnalyzeOmitsOptionalPreviousRecapWhenUnavailable(t *testing.T) {
|
func TestAnalyzeOmitsOptionalPreviousRecapWhenUnavailable(t *testing.T) {
|
||||||
env, m, fake := setupAnalyzeEnv(t)
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
@@ -106,6 +233,66 @@ func TestAnalyzeOmitsOptionalPreviousRecapWhenUnavailable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type orderedScriptoriumRunner struct {
|
||||||
|
Calls []string
|
||||||
|
RenderErr error
|
||||||
|
RunErr error
|
||||||
|
RenderBody string
|
||||||
|
RunBody string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *orderedScriptoriumRunner) RenderArtifact(_ context.Context, req scriptorium.RenderArtifactRequest) (scriptorium.ArtifactResult, error) {
|
||||||
|
r.Calls = append(r.Calls, "render")
|
||||||
|
if r.RenderErr != nil {
|
||||||
|
return scriptorium.ArtifactResult{}, r.RenderErr
|
||||||
|
}
|
||||||
|
body := r.RenderBody
|
||||||
|
if strings.TrimSpace(body) == "" {
|
||||||
|
body = `{"rendered":true}`
|
||||||
|
}
|
||||||
|
writeAnalyzeFileNoTest(req.OutputPath, body)
|
||||||
|
writeAnalyzeFileNoTest(req.StdoutLogPath, "render stdout\n")
|
||||||
|
writeAnalyzeFileNoTest(req.StderrLogPath, "render stderr\n")
|
||||||
|
if req.GeneratedConfigPath != "" {
|
||||||
|
writeAnalyzeFileNoTest(req.GeneratedConfigPath, "schema: scriptorium.generated.v1\n")
|
||||||
|
}
|
||||||
|
return scriptorium.ArtifactResult{
|
||||||
|
OutputPath: req.OutputPath,
|
||||||
|
StdoutLogPath: req.StdoutLogPath,
|
||||||
|
StderrLogPath: req.StderrLogPath,
|
||||||
|
GeneratedConfigPath: req.GeneratedConfigPath,
|
||||||
|
CommandMode: scriptorium.CommandModeRender,
|
||||||
|
PromptID: req.PromptID,
|
||||||
|
ProfileID: req.ProfileID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *orderedScriptoriumRunner) RunArtifact(_ context.Context, req scriptorium.RunArtifactRequest) (scriptorium.ArtifactResult, error) {
|
||||||
|
r.Calls = append(r.Calls, "run")
|
||||||
|
if r.RunErr != nil {
|
||||||
|
return scriptorium.ArtifactResult{}, r.RunErr
|
||||||
|
}
|
||||||
|
body := r.RunBody
|
||||||
|
if strings.TrimSpace(body) == "" {
|
||||||
|
body = "session recap\n"
|
||||||
|
}
|
||||||
|
writeAnalyzeFileNoTest(req.OutputPath, body)
|
||||||
|
writeAnalyzeFileNoTest(req.StdoutLogPath, "run stdout\n")
|
||||||
|
writeAnalyzeFileNoTest(req.StderrLogPath, "run stderr\n")
|
||||||
|
if req.GeneratedConfigPath != "" {
|
||||||
|
writeAnalyzeFileNoTest(req.GeneratedConfigPath, "schema: scriptorium.generated.v1\n")
|
||||||
|
}
|
||||||
|
return scriptorium.ArtifactResult{
|
||||||
|
OutputPath: req.OutputPath,
|
||||||
|
StdoutLogPath: req.StdoutLogPath,
|
||||||
|
StderrLogPath: req.StderrLogPath,
|
||||||
|
GeneratedConfigPath: req.GeneratedConfigPath,
|
||||||
|
CommandMode: scriptorium.CommandModeRun,
|
||||||
|
PromptID: req.PromptID,
|
||||||
|
ProfileID: req.ProfileID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestAnalyzeIncludesPreviousRecapWhenConfiguredAndAvailable(t *testing.T) {
|
func TestAnalyzeIncludesPreviousRecapWhenConfiguredAndAvailable(t *testing.T) {
|
||||||
env, m, fake := setupAnalyzeEnv(t)
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
paths := env.ArtifactStore.SessionPaths(m.SessionID)
|
||||||
@@ -377,3 +564,11 @@ func writeAnalyzeFile(t *testing.T, path, contents string) {
|
|||||||
t.Fatalf("WriteFile(%q): %v", path, err)
|
t.Fatalf("WriteFile(%q): %v", path, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeAnalyzeFileNoTest(path, contents string) {
|
||||||
|
if strings.TrimSpace(path) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = os.MkdirAll(filepath.Dir(path), 0o755)
|
||||||
|
_ = os.WriteFile(path, []byte(contents), 0o644)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user