Add Seriatim trim adapter support

This commit is contained in:
2026-05-08 16:58:26 +00:00
parent ea39594b33
commit 79a8bcf37b
5 changed files with 495 additions and 13 deletions

View File

@@ -111,11 +111,11 @@ func (r *SubprocessRunner) Run(ctx context.Context, req MergeRequest) (MergeResu
return MergeResult{}, fmt.Errorf("seriatim report is enabled but report path is missing")
}
args := r.buildArgs(req)
args := r.buildMergeArgs(req)
env := r.buildEnvOverrides()
if req.GeneratedConfigPath != "" {
if err := r.writeInvocationConfig(req, args); err != nil {
if err := r.writeMergeInvocationConfig(req, args); err != nil {
return MergeResult{}, fmt.Errorf("write seriatim invocation config %q: %w", req.GeneratedConfigPath, err)
}
}
@@ -188,7 +188,90 @@ func (r *SubprocessRunner) Run(ctx context.Context, req MergeRequest) (MergeResu
}, nil
}
func (r *SubprocessRunner) buildArgs(req MergeRequest) []string {
// Trim executes Seriatim trim with deterministic flags and validates output artifacts.
func (r *SubprocessRunner) Trim(ctx context.Context, req TrimRequest) (TrimResult, error) {
if r == nil {
return TrimResult{}, fmt.Errorf("seriatim subprocess runner is nil")
}
if strings.TrimSpace(req.InputTranscriptPath) == "" {
return TrimResult{}, fmt.Errorf("seriatim trim input path is required")
}
if strings.TrimSpace(req.OutputTrimmedPath) == "" {
return TrimResult{}, fmt.Errorf("seriatim trim output path is required")
}
if strings.TrimSpace(req.KeepSelector) == "" {
return TrimResult{}, fmt.Errorf("seriatim trim keep selector is required")
}
binary := r.binary
if strings.TrimSpace(req.Binary) != "" {
binary = strings.TrimSpace(req.Binary)
}
timeout := r.timeout
if req.Timeout < 0 {
return TrimResult{}, fmt.Errorf("seriatim trim timeout must be >= 0")
}
if req.Timeout > 0 {
timeout = req.Timeout
}
args := buildTrimArgs(req)
if req.GeneratedConfigPath != "" {
if err := writeTrimInvocationConfig(req, args, binary, timeout); err != nil {
return TrimResult{}, fmt.Errorf("write seriatim trim invocation config %q: %w", req.GeneratedConfigPath, err)
}
}
runRes, err := subprocess.Run(ctx, subprocess.RunRequest{
Executable: binary,
Args: args,
Timeout: timeout,
StdoutLogPath: req.StdoutLogPath,
StderrLogPath: req.StderrLogPath,
})
if err != nil {
return TrimResult{
OutputTrimmedPath: req.OutputTrimmedPath,
StdoutLogPath: req.StdoutLogPath,
StderrLogPath: req.StderrLogPath,
GeneratedConfigPath: req.GeneratedConfigPath,
ExitCode: runRes.ExitCode,
Duration: runRes.Duration,
InvokedBinary: binary,
KeepSelector: req.KeepSelector,
}, fmt.Errorf("run seriatim trim (binary=%q): %w", binary, err)
}
if err := validateJSONFileWithSegments(req.OutputTrimmedPath); err != nil {
return TrimResult{
OutputTrimmedPath: req.OutputTrimmedPath,
StdoutLogPath: req.StdoutLogPath,
StderrLogPath: req.StderrLogPath,
GeneratedConfigPath: req.GeneratedConfigPath,
ExitCode: runRes.ExitCode,
Duration: runRes.Duration,
InvokedBinary: binary,
KeepSelector: req.KeepSelector,
}, fmt.Errorf("validate seriatim trimmed output %q: %w", req.OutputTrimmedPath, err)
}
return TrimResult{
OutputTrimmedPath: req.OutputTrimmedPath,
StdoutLogPath: req.StdoutLogPath,
StderrLogPath: req.StderrLogPath,
GeneratedConfigPath: req.GeneratedConfigPath,
ExitCode: runRes.ExitCode,
Duration: runRes.Duration,
InvokedBinary: binary,
KeepSelector: req.KeepSelector,
Metadata: map[string]any{
"adapter": "seriatim_subprocess",
},
}, nil
}
func (r *SubprocessRunner) buildMergeArgs(req MergeRequest) []string {
args := []string{"merge"}
for _, path := range req.InputTranscriptPaths {
@@ -234,10 +317,11 @@ func (r *SubprocessRunner) buildEnvOverrides() map[string]string {
return out
}
func (r *SubprocessRunner) writeInvocationConfig(req MergeRequest, args []string) error {
func (r *SubprocessRunner) writeMergeInvocationConfig(req MergeRequest, args []string) error {
payload := map[string]any{
"schema": "seriatim.generated.v1",
"binary": r.binary,
"command": "merge",
"args": args,
"timeout": r.timeout.String(),
"output_schema": r.outputSchema,
@@ -261,6 +345,29 @@ func (r *SubprocessRunner) writeInvocationConfig(req MergeRequest, args []string
return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, 0o644)
}
func buildTrimArgs(req TrimRequest) []string {
return []string{
"trim",
"--input-file", req.InputTranscriptPath,
"--output-file", req.OutputTrimmedPath,
"--keep", req.KeepSelector,
}
}
func writeTrimInvocationConfig(req TrimRequest, args []string, binary string, timeout time.Duration) error {
payload := map[string]any{
"schema": "seriatim.generated.v1",
"command": "trim",
"binary": binary,
"args": args,
"timeout": timeout.String(),
"input_path": req.InputTranscriptPath,
"output_path": req.OutputTrimmedPath,
"keep_selector": req.KeepSelector,
}
return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, 0o644)
}
func validateJSONFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
@@ -272,3 +379,24 @@ func validateJSONFile(path string) error {
}
return nil
}
func validateJSONFileWithSegments(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file: %w", err)
}
var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
return fmt.Errorf("parse json: %w", err)
}
segments, ok := payload["segments"]
if !ok {
return fmt.Errorf("top-level segments is required")
}
if _, ok := segments.([]any); !ok {
return fmt.Errorf("top-level segments must be an array")
}
return nil
}