How Findings from Pipeline Steps Are Processed in no-mistakes
no-mistakes handles findings from pipeline steps by encoding them as JSON within StepOutcome structs, then parsing, normalizing, and evaluating them through actionability predicates to determine whether to auto-fix, request human review, or mark the step as successful.
The no-mistakes repository implements a robust pipeline execution engine where discrete steps—such as test, lint, review, and rebase—generate structured findings that drive automation decisions. Each step produces a StepOutcome containing a JSON-encoded findings payload, which the executor processes through a deterministic workflow to manage code quality interventions safely.
The StepOutcome Payload Structure
Each pipeline step constructs a types.Findings struct and marshals it into the outcome’s payload. In internal/pipeline/steps/test.go, the test step builds findings containing a human-readable summary and optional artifact metadata:
// internal/pipeline/steps/test.go
findings := types.Findings{
Summary: result.Text, // Description of test results
Tested: tested, // []string of test artifact labels
}
findingsJSON, _ := json.Marshal(findings)
outcome := &pipeline.StepOutcome{
Findings: string(findingsJSON), // JSON payload sent to executor
FixSummary: fixSummary,
}
The types.Findings struct serves as the canonical wire format. Steps like review (internal/pipeline/steps/review.go) and rebase (internal/pipeline/steps/rebase.go) follow this same pattern, creating specialized findings for code review issues or merge conflicts respectively.
Parsing and Normalizing Findings
When the executor receives a StepOutcome, it converts the JSON string back into structured data using types.ParseFindingsJSON. Located in internal/types/findings.go (lines 89-100), this function handles both the current wire format and legacy keys like items for backward compatibility.
After parsing, the executor calls types.NormalizeFindings to assign deterministic identifiers to any finding lacking an explicit ID. This function generates stable IDs using the pattern <prefix>-<n> (e.g., test-1, lint-2), ensuring consistent referencing during filtering and user interaction:
// internal/pipeline/executor.go (simplified)
func (e *Executor) handleOutcome(out *pipeline.StepOutcome, stepName string) error {
// Decode JSON payload
f, err := types.ParseFindingsJSON(out.Findings)
if err != nil {
return err
}
// Assign deterministic IDs based on step name
f = types.NormalizeFindings(f, stepName)
// ... further processing
}
Merging User Overrides and Filtering
The pipeline supports human-in-the-loop workflows through types.MergeUserOverrides (lines 66-78 in internal/types/findings.go). When users supply custom instructions or add findings via the terminal UI, this function stamps user-contributed entries with Source: "user" and assigns IDs like user-1 to distinguish them from automated detections.
The system also implements selective processing through FilterFindings and ExcludeFindings. These functions preserve the original summary unless the item count changes, in which case they recompute the summary using summarizeSelectedFindings to maintain accuracy.
Decision Logic: Actionable vs. Ask-User Findings
The executor routes normalized findings through three boolean predicates to determine the next action:
- Ask-User Detection:
types.HasAskUserFindingsscans for findings whereFinding.actionOrDefaultreturnsActionAskUser. This helper treats missing action values as ask-user (the safe default), ensuring uncertain findings always receive human validation. - Auto-Fix Extraction:
types.AutoFixableFindings(lines 53-62) filters for findings explicitly marked withActionAutoFix, isolating candidates for automated remediation. - General Actionability:
types.HasActionableFindingsreturns true if any finding is not ano-op, distinguishing between findings requiring intervention and informational-only results.
// internal/pipeline/executor.go (decision logic)
switch {
case types.HasAskUserFindings(f):
// Park the run – TUI will prompt for manual resolution
e.parkRun(f)
case types.HasActionableFindings(f):
// Invoke fixer agent for auto-fixable findings
e.runFixer(f)
default:
// No actionable issues detected
e.markStepSuccess()
}
If ask-user findings exist, the executor parks the pipeline run and presents them in the TUI for manual review. When only auto-fixable findings exist and the step permits automatic remediation, the executor triggers the fixer agent.
Persistence and Round History
After processing, final findings—including any user edits—persist to the database as JSON in FindingsJSON and UserFindingsJSON columns. The internal/pipeline/steps/round_history.go file handles this persistence, storing round-level findings for UI rendering and historical audit trails. This storage pattern enables the round-history view, allowing users to trace how findings evolved across pipeline executions.
Summary
- StepOutcome carries JSON-encoded findings from individual steps (test, lint, review) to the central executor.
- ParseFindingsJSON and NormalizeFindings convert wire formats and assign deterministic IDs like
<step>-<n>. - MergeUserOverrides incorporates human contributions with
Source: "user"tagging. - HasAskUserFindings and AutoFixableFindings predicates route findings to human review, auto-fix, or success states based on action metadata.
- Round history persists processed findings to the database for auditability and UI rendering.
Frequently Asked Questions
How does no-mistakes distinguish between user-added and automated findings?
The types.MergeUserOverrides function in internal/types/findings.go automatically assigns Source: "user" to any findings added through custom instructions or the UI, while automated step findings retain their original source identification. This distinction allows the UI to render user contributions differently and ensures user overrides take precedence during conflict resolution.
What happens when a pipeline step produces findings without explicit action IDs?
The Finding.actionOrDefault helper treats missing or unspecified action fields as ActionAskUser as a safe default. This ensures that any finding with unclear remediation requirements automatically triggers human review rather than risking unintended auto-fixes.
Can findings be filtered or excluded before processing?
Yes. The types.FilterFindings and types.ExcludeFindings functions in internal/types/findings.go allow the executor to retain only specific finding subsets or drop selected items before running actionability predicates. These functions preserve the original summary when possible or recompute it using summarizeSelectedFindings when the item count changes.
Where does the pipeline store findings after step completion?
The internal/pipeline/steps/round_history.go file persists final findings to the database as JSON strings in FindingsJSON and UserFindingsJSON fields. This storage supports the round-history view in the terminal UI and maintains an audit trail of what the pipeline detected and how it was resolved.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →