From fbcd1eb1c2bf2378df7749844af26c4448f5be8a Mon Sep 17 00:00:00 2001 From: Greta Date: Mon, 5 Oct 2026 15:11:04 +0100 Subject: [PATCH 01/16] New Mellea post on LintQ for IVR Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 188 ++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 content/blogs/qiskit-ivr-lintq.md diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md new file mode 100644 index 0000000..5a283d7 --- /dev/null +++ b/content/blogs/qiskit-ivr-lintq.md @@ -0,0 +1,188 @@ +--- +title: "Tests Pass, Smells Stay: Adding a Quantum Linter to Qiskit IVR" +date: "2026-10-15" +author: "Greta Dolcetti" +excerpt: "Quantum programs can pass every test and still carry quantum-specific code smells. We wired LintQ into Mellea's Instruct-Validate-Repair loop next to the test suite and roughly tripled the share of generations that are both correct and smell-free." +tags: ["qiskit", "IVR", "validation", "static-analysis", "LLM", "quantum", "code-generation"] +--- + +This series has been adding validators to Mellea's Instruct-Validate-Repair +(IVR) loop for Qiskit, one gap at a time. The +[first post](https://mellea.ai/blogs/qiskit-ivr-code-validation/) used +`flake8-qiskit-migration` to catch deprecated APIs. The +[second](https://mellea.ai/blogs/qiskit-ivr-functional-validation/) put each benchmark +problem's `check()` test in the loop, so the model could see when its code +ran but gave the wrong answer. + +This post covers a third gap: code that uses the right APIs, passes its +tests, and is still containing code smells that might silently impact the +program execution. + +## Code smells that tests don't see + +A *code smell* is code that runs without error but is likely to cause +incorrect or unexpected behavior. Python developers have a mature linting +culture for these, however, quantum programs have their own class of smells, which +classical linters and unit tests are not able to catch: a +gate applied after a qubit has already been measured, a register that is +allocated and never used, etc.. + +[LintQ](https://github.com/sola-st/LintQ) is a static analyzer for Qiskit +built on [CodeQL](https://codeql.github.com/). It models circuits, +registers, and gates as CodeQL abstractions and ships queries for ten +quantum-specific smells, including: + +| LintQ rule | What it flags | +| --- | --- | +| `measure-all-abuse` | `measure_all()` used although a classical register already exists | +| `operation-after-measurement` | Gate applied to a qubit after it was measured | +| `constant-classic-bit` | A qubit is measured but was never transformed | +| `oversized-circuit` | Register with allocated but unused qubits | +| `double-measurement` | Two measurements in a row on the same qubit | + +LintQ is neither sound nor complete, like most linters. But it is +deterministic and reproducible, and when it flags something it names the +rule and the circuit involved. As the earlier posts showed, that is the +property that matters for a repair loop. + +## Two validators, one requirement + +The setup follows the earlier posts: Mellea's `m.instruct()` with a +requirement and a `MultiTurnStrategy`. The difference is that the +requirement's validator now runs two checks in sequence: + +```python +def make_validator(problem, attempts): + def validate(model_output) -> ValidationResult: + code = extract_code_from_markdown(_as_text(model_output)) + + passed, error_msg = validate_correctness(problem, code) + if not passed: + return ValidationResult(False, reason=error_msg) + + passed, error_msg = validate_lintq(code) + return ValidationResult(passed, reason=error_msg) + + return validate +``` + +Correctness goes first, because there's no point linting a program that +doesn't run. `validate_correctness()` executes the code against the +problem's own test suite with a 30-second timeout. In order to keep functional +correctness as a first order priority, only a program that +passes the tests gets linted. + +`validate_lintq()` writes the code to a temporary directory, builds a +CodeQL database from it, runs the `LintQ-all.qls` query suite, and turns the +SARIF results into repair feedback. + +```python +def validate_lintq(code: str) -> tuple[bool, str]: + findings = run_lintq(code) + if not findings: + return True, "" + + findings_text = "\n".join( + f"[{f.get('ruleId', '?')}] {f.get('message', {}).get('text', '')}" + for f in findings + ) + return False, f"LintQ warnings:\n{findings_text}" +``` + +Both validators return `(is_valid, error_message)`, so whichever one fails +passes its message back to the model as the reason for the next attempt. +Wiring it into Mellea is the same `m.instruct()` call as before: + +```python +result = m.instruct( + problem["prompt"], + requirements=[ + req( + "The code must be a complete, runnable Qiskit v2 program that passes " + "the problem's test suite and raises no LintQ warnings. " + "Return only Python code.", + validation_fn=make_validator(problem, attempts), + ) + ], + strategy=MultiTurnStrategy(loop_budget=3), + return_sampling_results=True, +) +``` + +Each problem gets a fresh `ChatContext`, so one task's repair conversation +never leaks into the next. The validator also records both verdicts for +every generation, which is how the tables below can tell a test failure +apart from a LintQ rejection. + +## Results + +We ran `gpt-oss-120b` on +the *hard* variant of Qiskit HumanEval, where the prompt is a +natural-language instruction with no code stub, so the model also has to +write the function signature. We also ran it on the full Quantum Katas set. +The loop budget was three attempts: the first generation plus up to two +repairs. "First pass" is generation 0 on its own; "post repair" is the +outcome of the whole loop. + +> **Note:** LintQ only runs on programs that pass their tests. If the +> "Flagged by LintQ" or "LintQ warnings raised" count goes up after repair, +> it's only because the loop fixed correctness for more programs, so more of +> them reached LintQ. It doesn't mean repair made the code smellier. + +### Qiskit HumanEval (hard), 151 tasks + +| Metric | First pass | Post repair | +| --- | --- | --- | +| Passed both validators | 24/151 (16%) | **70/151 (46%)** | +| Failed | 127/151 (84%) | 81/151 (54%) | +| Failed the test suite | 113 | 77 | +| Flagged by LintQ | 14 | 4 | +| LintQ warnings raised | 17 | 4 | +| Fixed by the repair loop | — | 46 | + +### Qiskit Quantum Katas, 350 tasks + +| Metric | First pass | Post repair | +| --- | --- | --- | +| Passed both validators | 25/350 (7%) | **163/350 (47%)** | +| Failed | 325/350 (93%) | 187/350 (53%) | +| Failed the test suite | 297 | 148 | +| Flagged by LintQ | 28 | 39 | +| LintQ warnings raised | 30 | 47 | +| Fixed by the repair loop | — | 138 | + +With a budget of three attempts, the share of tasks that are both correct and +smell-free went from 16% to 46% on Qiskit HumanEval (hard) and from 7% to +47% on Quantum Katas. + +## Takeaways + +The three posts in this series use three kinds of validator, and each one +catches problems the others miss: + +| Validator | Catches | Misses | +| --- | --- | --- | +| `flake8-qiskit-migration` | Deprecated and removed APIs | Behavior | +| Functional tests (`check()`) | Wrong answers, crashes | Quantum-specific smells | +| LintQ | Quantum code smells | Behavior, and intent | + +None of them required changes to Mellea. Each is a function that returns +`(is_valid, error_message)`, wrapped in a requirement. The loop takes care of +turning failures into repair prompts. The more validators you stack, the +more aspects are checked and enforced during the generation. + +A few practical notes if you want to try LintQ in your own loop: + +- **Check correctness first.** LintQ builds a CodeQL database for every + candidate, which takes a few seconds. Skipping that for programs that fail + their tests saves time and keeps the repair feedback focused on one + problem at a time. +- **Keep the rule ID in the message.** `[ql-unmeasurable-qubits] Circuit + 'qc' has more qubits (1) than classical bits (0)` tells the model which + rule fired and on which circuit. + +LintQ is available at [sola-st/LintQ](https://github.com/sola-st/LintQ) and +needs the [CodeQL CLI](https://codeql.github.com/) on your `PATH`. The +Qiskit IVR example this work builds on is in the +[Mellea repo](https://github.com/generative-computing/mellea/tree/main/docs/examples/instruct_validate_repair/qiskit_code_validation). +Adding LintQ to it means adding one more validation function. From fbaf4daabd9ba5050899012d598ec471b58c4c5f Mon Sep 17 00:00:00 2001 From: Greta Date: Fri, 9 Oct 2026 10:46:52 +0100 Subject: [PATCH 02/16] Use simple_validate, start_session and remove attempts Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 56 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 5a283d7..b8c2009 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -49,23 +49,29 @@ property that matters for a repair loop. The setup follows the earlier posts: Mellea's `m.instruct()` with a requirement and a `MultiTurnStrategy`. The difference is that the -requirement's validator now runs two checks in sequence: +requirement's validation function now runs two checks in sequence: ```python -def make_validator(problem, attempts): - def validate(model_output) -> ValidationResult: - code = extract_code_from_markdown(_as_text(model_output)) +from mellea.stdlib.requirements import req, simple_validate +from validation_helpers import extract_code_from_markdown, validate_correctness, validate_lintq - passed, error_msg = validate_correctness(problem, code) - if not passed: - return ValidationResult(False, reason=error_msg) - passed, error_msg = validate_lintq(code) - return ValidationResult(passed, reason=error_msg) +def tests_and_lintq(problem: dict): + def check(output: str) -> tuple[bool, str]: + code = extract_code_from_markdown(output) + passed, reason = validate_correctness(problem, code) + return validate_lintq(code) if passed else (passed, reason) - return validate + return req( + "The code must pass the problem's test suite and raise no LintQ warnings", + validation_fn=simple_validate(check), + ) ``` +`simple_validate` hands `check()` the model's latest output as a string, and +turns the `(passed, reason)` tuple it returns into a validation result whose +reason becomes the repair feedback. + Correctness goes first, because there's no point linting a program that doesn't run. `validate_correctness()` executes the code against the problem's own test suite with a 30-second timeout. In order to keep functional @@ -89,24 +95,22 @@ def validate_lintq(code: str) -> tuple[bool, str]: return False, f"LintQ warnings:\n{findings_text}" ``` -Both validators return `(is_valid, error_message)`, so whichever one fails -passes its message back to the model as the reason for the next attempt. -Wiring it into Mellea is the same `m.instruct()` call as before: +Wiring it into Mellea is the same `m.instruct()` call as before. Each problem +gets its own session with a fresh `ChatContext`, so one task's repair +conversation never leaks into the next: ```python -result = m.instruct( - problem["prompt"], - requirements=[ - req( - "The code must be a complete, runnable Qiskit v2 program that passes " - "the problem's test suite and raises no LintQ warnings. " - "Return only Python code.", - validation_fn=make_validator(problem, attempts), - ) - ], - strategy=MultiTurnStrategy(loop_budget=3), - return_sampling_results=True, -) +from mellea import start_session +from mellea.stdlib.context import ChatContext +from mellea.stdlib.sampling import MultiTurnStrategy + +with start_session(backend_name="", model_id="", ctx=ChatContext()) as m: + result = m.instruct( + problem["prompt"], + requirements=[tests_and_lintq(problem)], + strategy=MultiTurnStrategy(loop_budget=3), + return_sampling_results=True, + ) ``` Each problem gets a fresh `ChatContext`, so one task's repair conversation From 2b46bf77676629e0fe020179693fd47f97fcce4e Mon Sep 17 00:00:00 2001 From: Greta Date: Fri, 9 Oct 2026 10:49:25 +0100 Subject: [PATCH 03/16] Run LintQ inside validate_lintq Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 41 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index b8c2009..c1ff4cc 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -80,19 +80,37 @@ passes the tests gets linted. `validate_lintq()` writes the code to a temporary directory, builds a CodeQL database from it, runs the `LintQ-all.qls` query suite, and turns the -SARIF results into repair feedback. +SARIF results into repair feedback, one `[rule-id] message` line per finding: ```python +import json +import os +import subprocess +import tempfile + + def validate_lintq(code: str) -> tuple[bool, str]: - findings = run_lintq(code) - if not findings: + lintq = os.environ["LINTQ_DIR"] + with tempfile.TemporaryDirectory() as tmp: + os.makedirs(f"{tmp}/src") + with open(f"{tmp}/src/candidate.py", "w") as f: + f.write(code) + for cmd in ( + ["database", "create", f"{tmp}/db", "--language=python", f"--source-root={tmp}/src"], + [ + "database", "analyze", f"{tmp}/db", f"{lintq}/LintQ-all.qls", + "--format=sarifv2.1.0", f"--output={tmp}/out.sarif", + f"--additional-packs={lintq}/qlint/codeql/src:{lintq}/qlint/codeql/lib", + ], + ): + subprocess.run(["codeql", *cmd], check=True, capture_output=True) + with open(f"{tmp}/out.sarif") as f: + results = [r for run in json.load(f)["runs"] for r in run["results"]] + + if not results: return True, "" - - findings_text = "\n".join( - f"[{f.get('ruleId', '?')}] {f.get('message', {}).get('text', '')}" - for f in findings - ) - return False, f"LintQ warnings:\n{findings_text}" + findings = "\n".join(f"[{r['ruleId']}] {r['message']['text']}" for r in results) + return False, f"LintQ warnings:\n{findings}" ``` Wiring it into Mellea is the same `m.instruct()` call as before. Each problem @@ -113,11 +131,6 @@ with start_session(backend_name="", model_id="", ctx=ChatCont ) ``` -Each problem gets a fresh `ChatContext`, so one task's repair conversation -never leaks into the next. The validator also records both verdicts for -every generation, which is how the tables below can tell a test failure -apart from a LintQ rejection. - ## Results We ran `gpt-oss-120b` on From 463615e6060100ab2f0faad612cb0c8513e26762 Mon Sep 17 00:00:00 2001 From: Greta Date: Fri, 9 Oct 2026 10:55:47 +0100 Subject: [PATCH 04/16] More details for results reproduction Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index c1ff4cc..dcc30c8 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -141,6 +141,18 @@ The loop budget was three attempts: the first generation plus up to two repairs. "First pass" is generation 0 on its own; "post repair" is the outcome of the whole loop. +To reproduce these numbers, this is the setup we used: + +| Component | Version or setting | +| --- | --- | +| Model | `gpt-oss-120b`, served through an OpenAI-compatible endpoint and called with Mellea's `OpenAIBackend` | +| Strategy | `MultiTurnStrategy(loop_budget=3)` | +| Python | 3.12.8 | +| Mellea | 0.7.0 | +| Qiskit | `qiskit` 2.4.2, `qiskit-aer` 0.17.2, `qiskit-ibm-runtime` 0.45.1, `qiskit-ibm-transpiler` 0.18.0 | +| CodeQL CLI | 2.26.3 | +| IBM Quantum account | Saved using (`QiskitRuntimeService.save_account(...)`) | + > **Note:** LintQ only runs on programs that pass their tests. If the > "Flagged by LintQ" or "LintQ warnings raised" count goes up after repair, > it's only because the loop fixed correctness for more programs, so more of From 8108f085016e246257b8f54e6860809e19253540 Mon Sep 17 00:00:00 2001 From: Greta Date: Fri, 9 Oct 2026 10:58:47 +0100 Subject: [PATCH 05/16] How to install CodeQL and LintQ Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index dcc30c8..6fc0fa7 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -210,8 +210,28 @@ A few practical notes if you want to try LintQ in your own loop: 'qc' has more qubits (1) than classical bits (0)` tells the model which rule fired and on which circuit. -LintQ is available at [sola-st/LintQ](https://github.com/sola-st/LintQ) and -needs the [CodeQL CLI](https://codeql.github.com/) on your `PATH`. The -Qiskit IVR example this work builds on is in the +## Try it + +LintQ is a set of CodeQL queries, so it needs the CodeQL CLI and a checkout +of the LintQ repository: + +1. Install the + [CodeQL CLI](https://docs.github.com/en/code-security/codeql-cli/getting-started-with-the-codeql-cli/setting-up-the-codeql-cli) + and put `codeql` on your `PATH` (`brew install codeql` works on macOS). + Check it with `codeql version`. +2. Clone LintQ and install its query pack dependencies: + + ```bash + git clone --depth 1 https://github.com/sola-st/LintQ.git + (cd LintQ/qlint/codeql/src && codeql pack install) + ``` + +3. Point `LINTQ_DIR` at the checkout: + + ```bash + export LINTQ_DIR="$(pwd)/LintQ" + ``` + + [Mellea repo](https://github.com/generative-computing/mellea/tree/main/docs/examples/instruct_validate_repair/qiskit_code_validation). Adding LintQ to it means adding one more validation function. From 9c9799dfd586013d4d67401595b480f0f579a075 Mon Sep 17 00:00:00 2001 From: Greta Date: Fri, 9 Oct 2026 11:00:36 +0100 Subject: [PATCH 06/16] Pointer to the prev example folder Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 6fc0fa7..b49f783 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -232,6 +232,6 @@ of the LintQ repository: export LINTQ_DIR="$(pwd)/LintQ" ``` - +This blog post is built as an extension of the example present in the [Mellea repo](https://github.com/generative-computing/mellea/tree/main/docs/examples/instruct_validate_repair/qiskit_code_validation). -Adding LintQ to it means adding one more validation function. +Adding LintQ to it means adding one more validation function with the code shown above. From 202463daa832cde63513b46ec6b120158509ba9f Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:52:53 +0100 Subject: [PATCH 07/16] Update content/blogs/qiskit-ivr-lintq.md Co-authored-by: Alex Bozarth Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index b49f783..0a56986 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -15,7 +15,7 @@ problem's `check()` test in the loop, so the model could see when its code ran but gave the wrong answer. This post covers a third gap: code that uses the right APIs, passes its -tests, and is still containing code smells that might silently impact the +tests, and still contains code smells that might silently impact the program execution. ## Code smells that tests don't see From 8cafe979b3bb1bf163db4977e4af8f7f2891f02e Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:53:10 +0100 Subject: [PATCH 08/16] Update content/blogs/qiskit-ivr-lintq.md Co-authored-by: Alex Bozarth Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 0a56986..d3aee0c 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -23,7 +23,7 @@ program execution. A *code smell* is code that runs without error but is likely to cause incorrect or unexpected behavior. Python developers have a mature linting culture for these, however, quantum programs have their own class of smells, which -classical linters and unit tests are not able to catch: a +culture for these. However, quantum programs have their own class of smells, which gate applied after a qubit has already been measured, a register that is allocated and never used, etc.. From d7a0b7b22b98675dab846d18e8e3841fffa64e65 Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:53:38 +0100 Subject: [PATCH 09/16] Update content/blogs/qiskit-ivr-lintq.md Co-authored-by: Alex Bozarth Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index d3aee0c..3d3726c 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -26,7 +26,7 @@ culture for these, however, quantum programs have their own class of smells, whi culture for these. However, quantum programs have their own class of smells, which gate applied after a qubit has already been measured, a register that is allocated and never used, etc.. - +allocated and never used, etc. [LintQ](https://github.com/sola-st/LintQ) is a static analyzer for Qiskit built on [CodeQL](https://codeql.github.com/). It models circuits, registers, and gates as CodeQL abstractions and ships queries for ten From c3447a1c115747724e1dadb8597a85b713628570 Mon Sep 17 00:00:00 2001 From: Greta Date: Wed, 14 Oct 2026 09:57:37 +0100 Subject: [PATCH 10/16] Mention LintQ Paper Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 3d3726c..a44aae7 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -30,7 +30,7 @@ allocated and never used, etc. [LintQ](https://github.com/sola-st/LintQ) is a static analyzer for Qiskit built on [CodeQL](https://codeql.github.com/). It models circuits, registers, and gates as CodeQL abstractions and ships queries for ten -quantum-specific smells, including: +quantum-specific smells described in the [LintQ paper](https://doi.org/10.1145/3660802), including: | LintQ rule | What it flags | | --- | --- | From 95968a2cd8bff3cd9fa3dba55bb82c66b3396d43 Mon Sep 17 00:00:00 2001 From: Greta Date: Wed, 14 Oct 2026 10:04:09 +0100 Subject: [PATCH 11/16] Add codehelm reference Signed-off-by: Greta --- content/blogs/qiskit-ivr-lintq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index a44aae7..17fb8cf 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -232,6 +232,6 @@ of the LintQ repository: export LINTQ_DIR="$(pwd)/LintQ" ``` -This blog post is built as an extension of the example present in the -[Mellea repo](https://github.com/generative-computing/mellea/tree/main/docs/examples/instruct_validate_repair/qiskit_code_validation). +This blog post is built as an extension of the [Mellea IVR example](https://github.com/generative-computing/mellea/tree/main/docs/examples/instruct_validate_repair/qiskit_code_validation). +The full reproduction code, including the LintQ validator, will be released in the IBM open-source project [CodeHelm](https://github.com/IBM/codehelm). Adding LintQ to it means adding one more validation function with the code shown above. From 33975a7675f7eece9929ed73e7207cd69a30c8ee Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Thu, 15 Oct 2026 10:01:47 +0100 Subject: [PATCH 12/16] Update content/blogs/qiskit-ivr-lintq.md Co-authored-by: Alex Bozarth Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 17fb8cf..72d6efc 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -22,11 +22,11 @@ program execution. A *code smell* is code that runs without error but is likely to cause incorrect or unexpected behavior. Python developers have a mature linting -culture for these, however, quantum programs have their own class of smells, which culture for these. However, quantum programs have their own class of smells, which +classical linters and unit tests are not able to catch: a gate applied after a qubit has already been measured, a register that is -allocated and never used, etc.. allocated and never used, etc. + [LintQ](https://github.com/sola-st/LintQ) is a static analyzer for Qiskit built on [CodeQL](https://codeql.github.com/). It models circuits, registers, and gates as CodeQL abstractions and ships queries for ten From 7615aadd974d3230f667b8047fcfac95a6a88ba8 Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Thu, 15 Oct 2026 10:02:49 +0100 Subject: [PATCH 13/16] Update content/blogs/qiskit-ivr-lintq.md Co-authored-by: Alex Bozarth Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 72d6efc..3256ff5 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -30,7 +30,7 @@ allocated and never used, etc. [LintQ](https://github.com/sola-st/LintQ) is a static analyzer for Qiskit built on [CodeQL](https://codeql.github.com/). It models circuits, registers, and gates as CodeQL abstractions and ships queries for ten -quantum-specific smells described in the [LintQ paper](https://doi.org/10.1145/3660802), including: +quantum-specific smells described in the [LintQ paper](https://arxiv.org/abs/2310.00718), including: | LintQ rule | What it flags | | --- | --- | From 589243ae407ec7a0c61ce28d8c3163c4e7d6e817 Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Fri, 16 Oct 2026 09:19:25 +0100 Subject: [PATCH 14/16] Update release date Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 3256ff5..e818e0d 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -1,6 +1,6 @@ --- title: "Tests Pass, Smells Stay: Adding a Quantum Linter to Qiskit IVR" -date: "2026-10-15" +date: "2026-10-19" author: "Greta Dolcetti" excerpt: "Quantum programs can pass every test and still carry quantum-specific code smells. We wired LintQ into Mellea's Instruct-Validate-Repair loop next to the test suite and roughly tripled the share of generations that are both correct and smell-free." tags: ["qiskit", "IVR", "validation", "static-analysis", "LLM", "quantum", "code-generation"] From b6f3d1994a3d5471fe917bfe079a2a90ec1d64f1 Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:37:14 +0100 Subject: [PATCH 15/16] Update content/blogs/qiskit-ivr-lintq.md Co-authored-by: Alex Bozarth Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index e818e0d..85b412e 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -1,6 +1,6 @@ --- title: "Tests Pass, Smells Stay: Adding a Quantum Linter to Qiskit IVR" -date: "2026-10-19" +date: "2026-10-23" author: "Greta Dolcetti" excerpt: "Quantum programs can pass every test and still carry quantum-specific code smells. We wired LintQ into Mellea's Instruct-Validate-Repair loop next to the test suite and roughly tripled the share of generations that are both correct and smell-free." tags: ["qiskit", "IVR", "validation", "static-analysis", "LLM", "quantum", "code-generation"] From d305c3f4c63cbbcb7c46e442a7cf3ad1518a0920 Mon Sep 17 00:00:00 2001 From: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:40:06 +0100 Subject: [PATCH 16/16] Clarify where validate_lintq comes from in the LintQ blog post Signed-off-by: Greta Dolcetti <53076361+gretadolcetti@users.noreply.github.com> --- content/blogs/qiskit-ivr-lintq.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/blogs/qiskit-ivr-lintq.md b/content/blogs/qiskit-ivr-lintq.md index 85b412e..9237d31 100644 --- a/content/blogs/qiskit-ivr-lintq.md +++ b/content/blogs/qiskit-ivr-lintq.md @@ -53,7 +53,9 @@ requirement's validation function now runs two checks in sequence: ```python from mellea.stdlib.requirements import req, simple_validate -from validation_helpers import extract_code_from_markdown, validate_correctness, validate_lintq +from validation_helpers import extract_code_from_markdown, validate_correctness + +# validate_lintq() is defined in the next snippet def tests_and_lintq(problem: dict):