Skip to content

[AAASM-6206] 🔧 (ci): Derive the Sonar copy-paste exclusion list from the snippet manifest - #632

Merged
Chisanan232 merged 2 commits into
mainfrom
v0.0.1/AAASM-6206/fix/cpd_exclusion_completeness_gate
Sep 28, 2026
Merged

Chisanan232 merged 2 commits into
mainfrom
v0.0.1/AAASM-6206/fix/cpd_exclusion_completeness_gate

Conversation

@Chisanan232

Copy link
Copy Markdown
Contributor

What changed

Two commits.

acbae7d adds the 20 generated snippets that were missing from sonar.cpd.exclusions in .sonarcloud.properties, taking the list from 32 to 52 entries and keeping it sorted. Nothing is removed.

9b86b05 adds scripts/check_cpd_exclusions.py and runs it from Example Metadata Drift Check, so the list is derived from snippets/manifest.json instead of hand-maintained against a duplication report.

Why

examples uses SonarCloud Automatic Analysis (sonar.autoscan.enabled = true), which ignores sonar-project.properties; the only honoured file is .sonarcloud.properties, and that file forbids wildcard patterns. So snippets/** cannot be expressed and every generated snippet must be named individually.

The list's own comment recorded how it was built: "taken from the duplication report as of 2026-07-23". That captured the pairs that exceeded Sonar's minimum block size on that one day — not the pairs that are duplicated by construction. Those are different sets.

Every snippets/<sdk>/<framework>.<ext> is a verbatim (dedented) extraction of the region: quickstart … endregion slice of its example entrypoint, produced by scripts/extract_snippets.py and drift-gated by example-metadata-check.yml. All 26 therefore duplicate their source by design. They merely become visible to the detector once the region grows past its minimum block size.

That is exactly what happened in #631. AAASM-6204 added one argument plus a two-line comment to all 16 Python quickstart regions, which pushed llamaindex-tool-policy across the boundary. The gate reported 3 new duplicated lines on each side of a 31-line block that is the entire extracted region and was already byte-identical on main — where Sonar reports zero duplication for the same file. Three siblings (agno, crewai, haystack) were already excluded for precisely this reason; llamaindex was absent only because it was under threshold on 2026-07-23.

Running the new gate against main before this change shows the exposure:

A1: generated snippet(s) absent from sonar.cpd.exclusions:
    snippets/go/basic-agent.go
    snippets/go/cli-runtime-integration.go
    snippets/go/langchaingo.go
    snippets/go/tool-policy.go
    snippets/node/langchain-js-basic-agent.ts
    snippets/node/langgraph-js.ts
    snippets/node/mastra.ts
    snippets/node/vercel-ai.ts
    snippets/python/autogen-tool-policy.py
    snippets/python/custom-tool-policy.py
    snippets/python/google-adk.py
    snippets/python/langchain-basic-agent.py
    snippets/python/langchain-research-agent.py
    snippets/python/langgraph.py
    snippets/python/microsoft-agent-framework-tool-policy.py
    snippets/python/openai-agents-sdk.py
    snippets/python/pydantic-ai.py
    snippets/python/semantic-kernel-tool-policy.py
    snippets/python/smolagents-tool-policy.py
    snippets/python/strands-agents-tool-policy.py

20 of the 26 snippets were unprotected. Each was one region-growth away from reddening an unrelated PR that introduced no duplication at all.

This narrows nothing that matters

Only the generated side is excluded, and only from copy-paste detection. Every hand-written source stays under full duplication analysis, and all other rules continue to apply to the snippets themselves. Because a snippet is a verbatim copy of a source that is itself analysed, no signal is lost — what is removed is double-counting. That matches the established precedent in agent-assembly/sonar-project.properties, where openapi/v1.yaml and dashboard/src/api/generated/** are excluded under AAASM-5095 "so the duplication metric reflects only hand-written code".

The gate deliberately does not require source_example to be excluded. Retiring the generated side is enough to retire a pair, and leaving the hand-written side analysed is the stronger posture. The 26 non-snippet entries are untouched — those are the per-framework main.py / index.ts / policy.* templates that duplicate each other, independently of any snippet, which is the by-design duplication the file's comment already describes.

No paid SonarCloud capacity is involved.

The contract

Assertion
A1 Every snippet_path in snippets/manifest.json appears verbatim in sonar.cpd.exclusions
A2 The list is sorted and duplicate-free, so adding one path stays a one-entry diff
A3 No entry names a path that does not exist — a stale entry survives a rename while protecting nothing
A4 The scan is non-vacuous: the manifest yielded snippets and the property was found
A5 No entry contains a wildcard, because one would silently match no file here and look like a fix while protecting nothing

A5 is the one that matters most for recurrence. The intuitive fix for a stale exact-path list is to replace it with snippets/**; in this file that silently protects nothing, and the gate now rejects it outright rather than letting it look correct.

Anti-vacuity

A gate is only worth its runtime if it can fail. --selftest mutates an in-memory copy of the real files once per assertion and requires each mutation to be caught. It runs before the real check in CI.

PASS  baseline: the real repo satisfies the contract
PASS  caught a generated snippet dropped from the list (A1)
PASS  caught the list left unsorted (A2)
PASS  caught a duplicated entry (A2)
PASS  caught a wildcard pattern that silently matches nothing (A5)
PASS  caught an entry naming a path that no longer exists (A3)
PASS  caught an empty manifest, so the scan covers nothing (A4)
PASS  caught the property removed entirely (A4)

All 7 mutations detected.

How to verify

python3 scripts/check_cpd_exclusions.py --verbose
python3 scripts/check_cpd_exclusions.py --selftest

Expected from the first:

  sonar.cpd.exclusions: 52 entries, sorted, all present on disk
  manifest snippets: 26, all excluded from copy-paste detection
  non-snippet entries: 26 (per-framework templates that duplicate each other)

actionlint is clean on the modified workflow.

Why this workflow

Wired into Example Metadata Drift Check rather than a new workflow: that job already regenerates these snippets and so already owns their contract. .sonarcloud.properties and the script were added to its paths: filter so editing the exclusion list re-triggers the gate. It is one Python file with no third-party imports and finishes in well under a second.

Note on scope

This addresses the structural half of AAASM-6206. The immediate symptom was already fixed in #631 by adding the llamaindex pair. Two options recorded on the ticket are not taken here and are explained there: moving to CI-based analysis (where snippets/** would be one wildcard, but which needs a SONAR_TOKEN and autoscan disabled), and querying the live duplication report from CI (which would make a required gate depend on an external service and reintroduce the skip-on-unreachable failure mode).

Refs AAASM-6206

Chisanan232 and others added 2 commits September 28, 2026 15:51
…n list

`.sonarcloud.properties` is the only config SonarCloud's Automatic Analysis
honours here, and it forbids wildcards, so `snippets/**` cannot be expressed
and each generated snippet has to be named. The list was seeded from the
duplication report of 2026-07-23 — the pairs that exceeded the detector's
minimum block size on that one day, not the pairs that are duplicated by
construction. Only 6 of the 26 snippets were listed.

Every `snippets/<sdk>/<framework>.<ext>` is a verbatim extraction of the
`region: quickstart` slice of its example entrypoint, so all 26 duplicate
their source by design and merely become visible to the detector once the
region grows. AAASM-6204 grew all 16 Python regions by one argument plus a
comment, which pushed llamaindex-tool-policy across that boundary and failed
the new-code duplication gate on a pair already byte-identical on main. The
remaining 20 were each one region-growth from the same failure.

Adds those 20, keeping the list sorted: 32 -> 52 entries. Nothing is removed,
and only the generated side is excluded — every hand-written source stays
under full duplication analysis, matching the AAASM-5095 precedent in
agent-assembly of excluding machine-generated artefacts so the metric
reflects hand-written code.

Also corrects the snippet count in the comment introduced by 8af190f: there
are 26 snippets (16 python, 6 node, 4 go), not 28.

Refs AAASM-6206

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The list above is only correct as of the day it was written unless something
keeps it correct. This derives the required set from `snippets/manifest.json`
rather than from a duplication report, so a new example cannot reach main with
its snippet unprotected.

`scripts/check_cpd_exclusions.py` asserts five things: every manifest
`snippet_path` is excluded (A1); the list is sorted and duplicate-free so
adding one path stays a one-entry diff (A2); no entry names a path that no
longer exists, since a stale entry survives a rename while protecting nothing
(A3); the scan is non-vacuous — the manifest yielded snippets and the property
was found (A4); and no entry contains a wildcard, because one would silently
match no file here and look like a fix while protecting nothing (A5).

It does not require `source_example` to be excluded. Retiring the generated
side is enough to retire a pair, and leaving the hand-written side analysed is
the stronger posture. The non-snippet entries stay untouched: those are the
per-framework templates that duplicate each other, independently of any
snippet.

`--selftest` mutates an in-memory copy of the real files once per assertion —
dropping a snippet, unsorting, duplicating, adding a wildcard, naming a
deleted path, emptying the manifest, removing the property — and requires each
to be caught. It runs before the real check, because a gate that has stopped
detecting anything passes everything. All 7 mutations are detected.

Wired into Example Metadata Drift Check, which already regenerates these
snippets and so already owns their contract, rather than a new workflow.
`.sonarcloud.properties` and the script join its `paths:` filter.

Refs AAASM-6206

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@Chisanan232
Chisanan232 merged commit 1d0d397 into main Sep 28, 2026
14 checks passed
@Chisanan232
Chisanan232 deleted the v0.0.1/AAASM-6206/fix/cpd_exclusion_completeness_gate branch September 28, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant