Add Scriptorium render diagnostics

This commit is contained in:
2026-05-06 21:30:19 +00:00
parent 1ea24accd1
commit 4aafeef830
4 changed files with 338 additions and 38 deletions

View File

@@ -2,6 +2,7 @@ package stage
import (
"context"
"encoding/json"
"fmt"
"os"
"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)
}
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{
Binary: env.Config.Pipeline.Scriptorium.Binary,
ConfigPath: env.Config.Pipeline.Scriptorium.ConfigPath,
@@ -175,43 +249,30 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
AbsolutePath: finalOutputPath,
}
meta := map[string]any{
"stage": "analyze",
"artifact_name": artifactName,
"prompt_id": artifactCfg.PromptID,
"profile_id": artifactCfg.ProfileID,
"output_path": finalOutputPath,
"generated_config_path": generatedConfigPath,
"stdout_log_path": stdoutLogPath,
"stderr_log_path": stderrLogPath,
"input_paths": inputPaths,
"input_names": sortedMapKeys(inputPaths),
"omitted_optional_inputs": omittedOptionalInputs,
"vars": vars,
"timeout": timeout.String(),
"binary": env.Config.Pipeline.Scriptorium.Binary,
"config_path": env.Config.Pipeline.Scriptorium.ConfigPath,
"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,
}
logPaths = append(logPaths, stdoutLogPath, stderrLogPath)
generatedConfigs = append(generatedConfigs, generatedConfigPath)
meta["output_path"] = finalOutputPath
meta["generated_config_path"] = generatedConfigPath
meta["stdout_log_path"] = stdoutLogPath
meta["stderr_log_path"] = stderrLogPath
meta["adapter_exit_code"] = res.ExitCode
meta["adapter_duration_ms"] = res.Duration.Milliseconds()
meta["adapter_command_mode"] = res.CommandMode
meta["adapter_prompt_id"] = res.PromptID
meta["adapter_profile_id"] = res.ProfileID
meta["adapter_validation_failed"] = res.ValidationFailed
meta["adapter_output_path"] = res.OutputPath
meta["adapter_generated_config"] = res.GeneratedConfigPath
meta["adapter_stdout_log_path"] = res.StdoutLogPath
meta["adapter_stderr_log_path"] = res.StderrLogPath
if res.Metadata != nil {
meta["adapter_metadata"] = res.Metadata
}
return &StageResult{
Outputs: []artifacts.Ref{artifactRef},
Logs: []string{stdoutLogPath, stderrLogPath},
GeneratedConfigs: []string{generatedConfigPath},
Logs: logPaths,
GeneratedConfigs: generatedConfigs,
Metadata: meta,
}, nil
}
@@ -460,3 +521,22 @@ func coalesceString(primary, fallback string) string {
}
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
}