mantis-dedupe

Consolidates raw security findings to eliminate redundant reports. Use when raw findings have been generated by the researcher and need consolidation before review. Don't use for initial code auditing or patch generation.

By google · 1,009 installs

npx skills add google/mantis --skill mantis-dedupe

Source repository · Upstream listing

Deduplicator (/mantis dedupe) System Goal Duplicate Finding Merger. Evaluates lists of raw findings to cluster and consolidate identical or highly overlapping issues into singular, descriptive records. Command Definition Command: /mantis dedupe Description: Consolidates raw security findings to eliminate redundant reports. Arguments (optional; supplied by the orchestrator, consumed by Block A): snapshot root / snapshot id / state root . All absent MODE OFF/legacy mode (behaves as today; snapshot gating disabled). Input/Output Contract Reads : workspace/findings/ (raw finding JSON files, ignoring .trash/ ). workspace/archive/findings pass / .json and workspace/archive/loop findings/ .json (to skip findings already evaluated and triaged in previous passes). workspace/.mantis state.json (to track current loop pass). Writes : Moves duplicate findings to workspace/findings/.trash/ after setting "status": "DUPLICATE" and "duplicate of" . Sets "possible duplicate of" (soft, non terminal) on NOT MATCHED matches and stamps "discovery commit" on current findings that lack it. Reads active snapshot / snapshot pinned from .mantis state.json . Appends transaction logs to workspace/.tx log.jsonl . Generates/executes merging script workspace/helpers/merge findings.py . Updates primary finding workspace/findings/<primary id .json (merges fields and history). Preconditions : workspace/findings/ must exist and contain finding files. Idempotency Guarantee : Cross references against archived findings in workspace/archive/ to filter out any findings already processed in previous passes of this run. Snapshot gated: a resolved archived finding on a differing snapshot is flagged as POSSIBLE REGRESSION (never silently filtered). Logs transactions to workspace/.tx log.jsonl to support tracking and potential rollbacks. Deterministic merging rules implemented in merge findings.py . Instructions Step 0: Locator Resolution (run first) [!NOTE] CURRENT PASS CHECK (defensive; the binding guarantee is on the harness per mantis pipeline adapter Scenario 2): if active snapshot is present AND active snapshot.pass != state.pass number , treat the snapshot as STALE for this pass — STOP "stale active snapshot: pass mismatch" or degrade as HALT ( snapshot pinned effectively false: no authoritative verdicts, Block B NOT MATCHED, reproduce not attempted ). This catches a custom harness that preserved active snapshot across the Stage 15 pass increment without re pinning. The reference meta agent re pins every pass, so this check never fires there. Block B itself cannot detect this (it is snapshot id only, not pass aware). Notes: workspace/findings/ , workspace/archive/ , workspace/.tx log.jsonl , workspace/helpers/ and .mantis state.json are STATE RELATIVE (under state root). Any code snippet you inspect for a finding is SNAPSHOT RELATIVE (under CODE ROOT). Never write under CODE ROOT. Review a list of security findings and merge duplicate findings that refer to the exact same security flaw or adjacent code paths. Execute your task as follows: 1. Load Raw Findings & Archived Findings Queue: List the contents of the directory and read the files in workspace/findings/ . If the directory is empty or does not exist, notify the user and exit. Important: Ignore hidden files and directories (such as the .trash/ subdirectory) when listing or processing findings. Locate and load all archived finding JSON files from previous loop passes, if they exist, under workspace/archive/findings pass / .json and workspace/archive/loop findings/ .json . These files represent vulnerabilities that have already been fully evaluated, triaged, and potentially patched in previous passes. Important: Do NOT read or deduplicate against workspace/historical learnings.jsonl (VCS history), as we want to catch regressions if old bugs were reintroduced. 2. Filter Loop Duplicates (snapshot gated). First, stamp discovery commit on any CURRENT finding that lacks it, using active snapshot.snapshot id from .mantis state.json (skip when unpinned). Then, for each current finding that matches an archived finding (by code paths + title similarity), run: Signature based candidate matching (Phase 3) — TIGHTENS, never replaces: signature may only PROMOTE a pair to "candidate for the pairwise snapshot check"; it may NEVER by itself cause a hard DUPLICATE/trash. A pair is a candidate for the Pairwise Snapshot Match Check below ONLY if it satisfies BOTH: it matches under today's code paths + title similarity, comparing code paths entries line inclusively (WITH their trailing :line ); AND (when both findings have a signature ) their signature fields are equal. A signature match WITHOUT the code paths + title agreement is NOT a duplicate — at most a soft possible duplicate of (keep the finding ACTIVE), never a trash. Rationale: signature strips the line number and all but first code paths entry, so two DISTINCT bugs in the same file (e.g. parser.c:100 vs parser.c:900 ) with the same title+CWE share one signature ; trashing on signature alone would silently delete a real finding. If EITHER lacks signature , use today's code paths + title similarity matching unchanged. PAIRWISE SNAPSHOT MATCH CHECK (decides MATCHED vs NOT MATCHED) — compares the CURRENT finding's discovery commit against the ARCHIVED finding's discovery commit for this pair (NOT against SNAPSHOT ID ): 1. If snapshot pinned is false AND there is NO active snapshot in state (MODE OFF) NOT MATCHED. Stop. (In HALT — active snapshot present but snapshot pinned=false — do NOT short circuit here; fall through to the pairwise comparison below, which will be NOT MATCHED because the current finding's discovery commit is a live: id that will not equal the archived one.) 2. Read the CURRENT finding's discovery commit and the ARCHIVED finding's discovery commit : If EITHER is missing, empty, or the literal "MIXED" NOT MATCHED. If they are NOT byte for byte equal to each other NOT MATCHED. If they ARE byte for byte equal to each other (both present, non MIXED) MATCHED. There is no other route to MATCHED; never fuzzy compare. The global "default the field and proceed" backward compat rule does NOT apply to discovery commit : absent = NOT MATCHED. (There is NO separate "dirty" gate: a dirty tree's SNAPSHOT ID already embeds the working tree content hash, so within pass findings MATCH and cross pass bare commit findings do not.) Note: this is a PAIRWISE check (current vs archived), NOT a check against the global SNAPSHOT ID — dedupe stamps the current finding's discovery commit to SNAPSHOT ID in Step 2 above, so a check against SNAPSHOT ID would always be MATCHED and would trash reintroduced/regression bugs as DUPLICATE. Then, using the idempotency rule (Input/Output Contract → Idempotency Guarantee) to avoid double writes, decide mechanically: MATCHED (both present and equal): soft delete the current finding as a loop duplicate exactly as before — set "status": "DUPLICATE" and "duplicate of": "<archived uuid " , clear possible duplicate of if present, ensure mkdir p workspace/findings/.trash/ , move it there, and log a loop filter transaction in workspace/.tx log.jsonl . If the current finding lacks lineage id but the archived finding has one, inherit the archived finding's lineage id onto the current finding before moving it (so the lineage chain is preserved across the merge). NOT MATCHED (differ, or either absent): do NOT set DUPLICATE and do NOT move to trash. Keep the current finding ACTIVE and set "possible duplicate of": "<archived uuid " (a soft, non terminal hint). If the current finding lacks lineage id but the archived finding has one, inherit the archived finding's lineage id onto the current finding (so the lineage chain is preserved for report folding even when the findings are on different snapshots). [!IMPORTANT] STATUS & DUPLICATE INVARIANTS: A finding MUST NOT carry both duplicate of and possible duplicate of pointing to the same target UUID. status = "DUPLICATE" MUST NOT coexist with possible duplicate of pointing to the same target UUID. Under NOT MATCHED (outside the MODE OFF fallback exception), the finding's status MUST remain active (e.g. VALID , PROVISIONALLY VALID , NEEDS RESEARCH ), duplicate of MUST NOT be set, and the finding MUST NOT be moved to .trash/ . Setting possible duplicate of is a non terminal hint only. POSSIBLE REGRESSION: if the archived match has a RESOLVED status ( patch status in { VERIFIED SECURE , MITIGATION PROPOSED } OR status == FALSE POSITIVE OR production viability == NON VIABLE ) AND the pair is NOT MATCHED, keep the current finding ACTIVE, add a history note "POSSIBLE REGRESSION vs \<archived uuid ", and do NOT filter it. (A reverted fix re discovered on new code must never be trashed.) EXACT UUID retry exception (unchanged): if the current finding has the EXACT SAME UUID as the archived one, it was intentionally copied back for a retry — do NOT filter it (keep as is). Permanently unpinned exception (MODE OFF only — no active snapshot ): if there is NO active snapshot in state (MODE OFF = today's default; a target with no snapshot boundary, e.g. a live endpoint), snapshot pinned is false at the PASS level ( active snapshot.snapshot pinned — it is NOT a per finding field) and Block B is uninformative — fall back to today's dedup by signature if present, else stable key = normalized title + first code paths entry including its trailing :line (line inclusive, same as Step 2). Rationale: stripping :line would collapse two DISTINCT bugs in the same file (e.g. parser.c:100 vs parser.c:900 ) with the same title+CWE into one — silently deleting a real finding. Note: signature itself strips :line by design (it is a coarse identity for cross pass lineage, not a dedup key); this fallback therefore prefers signature only when stable key 's line inclusive match ALSO agrees, never on signature alone. This preserves dedup for targets that can never MATCH. When active snapshot IS present but unpinned (HALT mode), this exception does NOT fire: keep the snapshot gated behavior above (NOT MATCHED → keep ACTIVE + possible duplicate of , never DUPLICATE ). POSSIBLE REGRESSION takes precedence over this fallback: a resolved archived finding paired with a NOT MATCHED current finding is ALWAYS kept active (never trashed), regardless of mode. 3. 3 Tier Deduplication Ladder (Deterministic & Semantic Matching): When resolving finding lineages and evaluating deduplication candidates, the deduplicator uses a hierarchical 3 tier ladder designed for sub millisecond fast path execution with semantic RAG fallback: Tier 1 (Fast Path Exact Heuristic Anchors — < 1ms, 0 tokens): 1. Exact content identity signature match: hashlib.sha256(canonical fp canonical cwe target symbol) (invariant to line shifts, backticks, and title paraphrasing). 2. Exact canonical filepath + normalized CWE + target symbol match. 3. Strict line proximity window