fix: stop verify, reports and briefs from claiming more than pipe checked #1
2 changed files with 47 additions and 2 deletions
fix(suggest): break score ties by file, line and rule
The plan sorted by score alone. List.sort is not stable, so equal-score actions came out in an order nothing guaranteed, and two runs over the same code could hand an agent a different first action. Ties now fall back to file, line, rule and label, both in the global order and within each file.
commit
17c9f9afab
|
|
@ -14,6 +14,8 @@ class FixAction {
|
|||
required this.resolves,
|
||||
required this.score,
|
||||
required this.why,
|
||||
this.file = '',
|
||||
this.line,
|
||||
});
|
||||
|
||||
final String rule;
|
||||
|
|
@ -36,6 +38,10 @@ class FixAction {
|
|||
/// Human-readable breakdown of the score, so the order is not a black box.
|
||||
final String why;
|
||||
|
||||
/// Where the action's first finding is: the tiebreak between equal scores.
|
||||
final String file;
|
||||
final int? line;
|
||||
|
||||
String get verify => 'pipe check --rule $rule';
|
||||
|
||||
Map<String, Object?> toJson() => {
|
||||
|
|
@ -91,7 +97,7 @@ class WorkPlan {
|
|||
/// Global ordering of causes, highest impact first.
|
||||
List<FixAction> get ordered {
|
||||
final all = [for (final plan in files) ...plan.actions];
|
||||
all.sort((a, b) => b.score.compareTo(a.score));
|
||||
all.sort(_byImpact);
|
||||
return all;
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +214,21 @@ class WorkPlan {
|
|||
}
|
||||
}
|
||||
|
||||
/// Highest score first, then file, line and rule. List.sort is not stable, so
|
||||
/// sorting by score alone left equal-score actions in an arbitrary order that
|
||||
/// could differ between runs - and the agent plan is read top-down.
|
||||
int _byImpact(FixAction a, FixAction b) {
|
||||
final byScore = b.score.compareTo(a.score);
|
||||
if (byScore != 0) return byScore;
|
||||
final byFile = a.file.compareTo(b.file);
|
||||
if (byFile != 0) return byFile;
|
||||
final byLine = (a.line ?? 0).compareTo(b.line ?? 0);
|
||||
if (byLine != 0) return byLine;
|
||||
final byRule = a.rule.compareTo(b.rule);
|
||||
if (byRule != 0) return byRule;
|
||||
return a.label.compareTo(b.label);
|
||||
}
|
||||
|
||||
/// Severity is the dominant term: it is what distinguishes a broken
|
||||
/// interaction from a tidiness issue.
|
||||
const Map<PipeSeverity, int> _severityBase = {
|
||||
|
|
@ -363,6 +384,8 @@ WorkPlan buildWorkPlan({required String root, required FindingReport report}) {
|
|||
resolves: group.length,
|
||||
score: score,
|
||||
why: _whyOf(group, score),
|
||||
file: file,
|
||||
line: head.line,
|
||||
);
|
||||
byFile.putIfAbsent(file, () => []).add(action);
|
||||
fileScores.update(file, (value) => value + score, ifAbsent: () => score);
|
||||
|
|
@ -370,7 +393,7 @@ WorkPlan buildWorkPlan({required String root, required FindingReport report}) {
|
|||
|
||||
final files = <FilePlan>[];
|
||||
for (final entry in byFile.entries) {
|
||||
final actions = entry.value..sort((a, b) => b.score.compareTo(a.score));
|
||||
final actions = entry.value..sort(_byImpact);
|
||||
files.add(
|
||||
FilePlan(
|
||||
file: entry.key,
|
||||
|
|
|
|||
|
|
@ -309,4 +309,26 @@ void main() {
|
|||
expect(block, contains('6 further cause(s) omitted'));
|
||||
expect(block.length, lessThanOrEqualTo(500));
|
||||
});
|
||||
|
||||
test('equal scores are ordered by file, line and rule, not by chance', () {
|
||||
// Sorting by score alone left ties in an order List.sort does not promise
|
||||
// to keep, so two runs could hand an agent a different first action.
|
||||
Finding dead(String file, int line, String event) => _finding(
|
||||
rule: ruleDeadHandler,
|
||||
severity: PipeSeverity.info,
|
||||
file: file,
|
||||
line: line,
|
||||
data: {'event': event},
|
||||
);
|
||||
final plan = buildWorkPlan(
|
||||
root: '/root',
|
||||
report: _report([
|
||||
dead('lib/z.dart', 1, 'Z'),
|
||||
dead('lib/a.dart', 9, 'A9'),
|
||||
dead('lib/a.dart', 2, 'A2'),
|
||||
dead('lib/m.dart', 5, 'M'),
|
||||
]),
|
||||
);
|
||||
expect(plan.ordered.map((action) => action.label), ['A2', 'A9', 'M', 'Z']);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue