diff --git a/tools/cube-notes-agent.py b/tools/cube-notes-agent.py index 784f5c8..401f5ff 100644 --- a/tools/cube-notes-agent.py +++ b/tools/cube-notes-agent.py @@ -160,11 +160,26 @@ def uncovered_findings(project=None): un = "".join(f"{coord} {subj}\n" for coord, cat, subj in _log_findings(project) if coord not in covered) return un.strip() or "(no new findings)" +# Canonical issue key: collapse duplicate representations of the same root cause +# (e.g. FK1-SELINUX_RESTORECON vs SELINUX-RESTORECON) so the report dedupes by ISSUE, +# not just by coordinate. Prefixes (FK, FK#, FA, FB, FLAG, FIX) are stripped and +# separators normalized; a small alias map unifies differently-worded duplicates. +_ISSUE_PREFIX_RE = _re.compile(r'^(FK\d*|FA|FB|FLAG|FIX)[-_]?', _re.I) +_ISSUE_ALIAS = { + "wal_checkpoint": "checkpoint_before_exit", + "concurrent_store_checkpoint": "checkpoint_before_exit", +} + +def _canonical_issue(flag): + f = _ISSUE_PREFIX_RE.sub('', flag) + f = _re.sub(r'[-_/\s]+', '_', f).strip('_').lower() + return _ISSUE_ALIAS.get(f, f) + def _uncovered_meta(project=None): - """[(coord, flag, desc)] for the currently-UNCOVERED findings (grounded). - desc is the FULL note body (not the truncated list subject).""" + """[(coord, flag, desc)] for the currently-UNCOVERED findings, DEDUPED BY ISSUE. + desc is the FULL note body; the most-detailed note per canonical issue is kept.""" covered = _covered(project) - out = [] + best = {} # issue -> (coord, flag, desc, len) for coord, cat, subj in _log_findings(project): if coord in covered: continue @@ -175,8 +190,11 @@ def _uncovered_meta(project=None): flag, sep, rest = body_text.partition(":") flag = flag.strip() desc = rest.strip() if sep else body_text - out.append((coord, flag, desc)) - return out + issue = _canonical_issue(flag) + cur = best.get(issue) + if cur is None or len(desc) > cur[3]: + best[issue] = (coord, flag, desc, len(desc)) + return [(c, f, d) for c, f, d, _ in best.values()] # Snapshot of the uncovered findings captured when read_findings ran, so the # report can be composed faithfully even after mark_covered has covered them. @@ -253,10 +271,11 @@ def compose_report(project=None, model_text=""): return "\n".join(lines) def mark_covered(project=None, text=""): - meta = CURRENT_META or _uncovered_meta(project) - if not meta: + covered = _covered(project) + coords = [c for c, cat, subj in _log_findings(project) if c not in covered] + if not coords: return "nothing to cover" - body = "covered coords:\n" + "\n".join(f"{coord} {flag}" for coord, flag, _ in meta) + body = "covered coords:\n" + "\n".join(coords) args = [CUBE, "note", "add", "--cat", "checkpoint"] if project: args += ["--project", project] @@ -368,10 +387,10 @@ def main(): if not report_text: # Run ended without an explicit "done" — still produce a grounded report. report_text = compose_report(a.project, last_assistant) - if CURRENT_META: - # Deterministic coverage: mark the reported findings covered regardless of - # whether the model did it, so the next run is genuinely incremental. - mark_covered(a.project) + # Deterministic coverage: mark all uncovered findings covered (including any + # deduped-away duplicates) regardless of whether the model did it, so the next + # run is genuinely incremental. No-op when there are no uncovered findings. + mark_covered(a.project) with open(a.report, "w") as f: f.write(report_text) os.makedirs(os.path.dirname(a.report), exist_ok=True) if os.path.dirname(a.report) else None