项目文件夹

文件
wehub-resource-sync 26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:16 +08:00

197 行
7.2 KiB
Go

package db
import "fmt"
const (
RoundSelectionSourceUser = "user"
RoundSelectionSourceAutoFix = "auto_fix"
)
// StepRound represents one execution round within a pipeline step.
type StepRound struct {
ID string
StepResultID string
Round int
Trigger string // "initial", "auto_fix"; legacy "user_fix" is treated as "auto_fix"
FindingsJSON *string // nullable - findings produced by this round
// UserFindingsJSON, when non-nil, is the merged finding list that was
// dispatched to the fix agent after the user edited per-finding
// instructions or added their own findings. It includes both the
// selected agent-produced findings (with any attached user
// instructions) and the user-authored findings.
UserFindingsJSON *string
// SelectedFindingIDs, when non-nil, is a JSON array of finding IDs that
// were chosen (by the user or auto-fix filter) to be fixed AFTER this
// round. It is populated on the round whose findings triggered the next
// round, so that later rounds' prompts can tell which findings were
// deliberately left unselected.
SelectedFindingIDs *string
SelectionSource *string
// FixSummary, when non-nil, is the agent's one-line commit summary for
// the fix attempt performed during this round. It is only set when the
// round itself was a fix round (trigger=="auto_fix").
FixSummary *string
DurationMS int64
CreatedAt int64
}
// StepRoundStats summarizes execution rounds for a step. It lets status
// surfaces show whether a running/fixing step is in an initial pass or a fix
// pass without reloading every round in callers.
type StepRoundStats struct {
TotalRounds int
FixRounds int
LatestRound int
LatestTrigger string
LatestSelection string
LatestRoundAt int64
LatestFixRound int
LatestFixRoundAt int64
SelectedForFix bool
AutoSelectedForFix bool
PendingFixSource string
}
// IsFixRound reports whether this round was a fix attempt. Legacy "user_fix"
// rounds count: they were fix rounds dispatched by an explicit user selection.
func (r *StepRound) IsFixRound() bool {
return r.Trigger == "auto_fix" || r.Trigger == "user_fix"
}
// StepFixSummaries returns one entry per fix round for a step, in round order:
// the agent's one-line fix summary, or "" when the round recorded none.
func (d *DB) StepFixSummaries(stepResultID string) ([]string, error) {
rounds, err := d.GetRoundsByStep(stepResultID)
if err != nil {
return nil, err
}
var summaries []string
for _, r := range rounds {
if !r.IsFixRound() {
continue
}
summary := ""
if r.FixSummary != nil {
summary = *r.FixSummary
}
summaries = append(summaries, summary)
}
return summaries, nil
}
// StepRoundStats returns aggregate round information for a step result.
func (d *DB) StepRoundStats(stepResultID string) (StepRoundStats, error) {
rounds, err := d.GetRoundsByStep(stepResultID)
if err != nil {
return StepRoundStats{}, err
}
var stats StepRoundStats
latestSelectedRound := 0
latestSelectedSource := ""
for _, r := range rounds {
stats.TotalRounds++
stats.LatestRound = r.Round
stats.LatestTrigger = r.Trigger
stats.LatestRoundAt = r.CreatedAt
if r.SelectionSource != nil {
stats.LatestSelection = *r.SelectionSource
}
if r.SelectedFindingIDs != nil && *r.SelectedFindingIDs != "" {
stats.SelectedForFix = true
stats.AutoSelectedForFix = r.SelectionSource != nil && *r.SelectionSource == RoundSelectionSourceAutoFix
latestSelectedRound = r.Round
latestSelectedSource = stats.LatestSelection
}
if r.IsFixRound() {
stats.FixRounds++
stats.LatestFixRound = stats.FixRounds
stats.LatestFixRoundAt = r.CreatedAt
}
}
if latestSelectedRound == stats.LatestRound {
stats.PendingFixSource = latestSelectedSource
}
return stats, nil
}
// InsertStepRound creates a new round record for a step result. fixSummary may
// be nil for non-fix rounds or when the agent produced no summary.
func (d *DB) InsertStepRound(stepResultID string, round int, trigger string, findingsJSON *string, fixSummary *string, durationMS int64) (*StepRound, error) {
r := &StepRound{
ID: newID(),
StepResultID: stepResultID,
Round: round,
Trigger: trigger,
FindingsJSON: findingsJSON,
FixSummary: fixSummary,
DurationMS: durationMS,
CreatedAt: now(),
}
_, err := d.sql.Exec(
`INSERT INTO step_rounds (id, step_result_id, round, trigger_type, findings_json, user_findings_json, selected_finding_ids, selection_source, fix_summary, duration_ms, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
r.ID, r.StepResultID, r.Round, r.Trigger, r.FindingsJSON, r.UserFindingsJSON, r.SelectedFindingIDs, r.SelectionSource, r.FixSummary, r.DurationMS, r.CreatedAt,
)
if err != nil {
return nil, fmt.Errorf("insert step round: %w", err)
}
return r, nil
}
// SetStepRoundSelection records which findings were selected for fix AFTER the
// given round produced its findings, along with whether that selection came
// from the user or auto-fix filtering. Passing a nil or empty JSON array clears
// both columns.
func (d *DB) SetStepRoundSelection(id string, selectedFindingIDs *string, source string) error {
var selectionSource *string
if selectedFindingIDs != nil && *selectedFindingIDs != "" && source != "" {
selectionSource = &source
}
if _, err := d.sql.Exec(
`UPDATE step_rounds SET selected_finding_ids = ?, selection_source = ? WHERE id = ?`,
selectedFindingIDs, selectionSource, id,
); err != nil {
return fmt.Errorf("set step round selection: %w", err)
}
return nil
}
// SetStepRoundSelectedFindingIDs preserves the old API for callers that do not
// need to distinguish how the selection was made.
func (d *DB) SetStepRoundSelectedFindingIDs(id string, selectedFindingIDs *string) error {
return d.SetStepRoundSelection(id, selectedFindingIDs, RoundSelectionSourceUser)
}
// SetStepRoundUserFindings records the merged finding list (with user
// instructions attached and user-added findings appended) that was
// dispatched to the fix agent for the round. Passing nil clears the column.
func (d *DB) SetStepRoundUserFindings(id string, userFindingsJSON *string) error {
if _, err := d.sql.Exec(
`UPDATE step_rounds SET user_findings_json = ? WHERE id = ?`,
userFindingsJSON, id,
); err != nil {
return fmt.Errorf("set step round user findings: %w", err)
}
return nil
}
// GetRoundsByStep returns all rounds for a step result, ordered by round number.
func (d *DB) GetRoundsByStep(stepResultID string) ([]*StepRound, error) {
rows, err := d.sql.Query(
`SELECT id, step_result_id, round, trigger_type, findings_json, user_findings_json, selected_finding_ids, selection_source, fix_summary, duration_ms, created_at FROM step_rounds WHERE step_result_id = ? ORDER BY round`,
stepResultID,
)
if err != nil {
return nil, fmt.Errorf("get rounds by step: %w", err)
}
defer rows.Close()
var rounds []*StepRound
for rows.Next() {
r := &StepRound{}
if err := rows.Scan(&r.ID, &r.StepResultID, &r.Round, &r.Trigger, &r.FindingsJSON, &r.UserFindingsJSON, &r.SelectedFindingIDs, &r.SelectionSource, &r.FixSummary, &r.DurationMS, &r.CreatedAt); err != nil {
return nil, fmt.Errorf("scan step round: %w", err)
}
rounds = append(rounds, r)
}
return rounds, rows.Err()
}