Static analysis tools output noise. A human still has to read hundreds of warnings. A free AI model can digest that noise into three sentences — if you feed it structured data.
This article builds a small pipeline that runs three well-known Python checkers, collects their JSON output, and sends a compact prompt to MonkeyCode's free model access. The whole thing runs on MonkeyCode's free server option, so no local dependencies and no credit card are needed.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The end result is a reusable script that turns raw lint results into a short, prioritized review summary with suggested fixes.
Why Bother With Three Tools Instead of One
Every linter has a blind spot. pyflakes catches unused imports but misses type errors. mypy validates annotations but ignores security smells. bandit spots insecure patterns but stays silent about dead code.
Running all three produces overlapping, contradictory output. Merging them by hand is tedious. But merging them with a template and sending the result to a model is mechanical.
The pipeline below does exactly that:
- Write a deliberately buggy Python file.
- Run each checker with a JSON output flag.
- Concatenate the three JSON blobs.
- Send the merged text to a free model with a simple prompt.
- Print the model's summary and recommended actions.
The Example File: order_service.py
Create a file with realistic problems. Not contrived one-liners, but the kind of mistakes that appear after a late-night refactor:
import sqlite3
import random
from flask import Flask, request, jsonify
app = Flask(__name__)
def fetch_order(order_id: int):
conn = sqlite3.connect("orders.db")
cur = conn.cursor()
query = "SELECT * FROM orders WHERE id = {}".format(order_id)
cur.execute(query)
row = cur.fetchone()
conn.close()
return row
@app.route("/order/<int:order_id>", methods=["GET"])
def order_endpoint(order_id):
return jsonify(fetch_order(order_id))
def random_total():
return random.random() * 100
This file imports random but never uses it. It builds SQL with string formatting, which bandit will flag as SQL injection. The Flask import is used, fine, but the endpoint returns None when no row exists. Good material for three separate tools.
The Runner Script: run_checks.py
The script assumes pyflakes, mypy, and bandit are installed on the server. Install them in one line if needed:
pip install pyflakes mypy bandit
Here is the entire pipeline:
import json
import subprocess
import sys
TARGET = "order_service.py"
results = {}
# pyflakes -- JSON output via pyflakes.json?
# pyflakes has no native JSON. Use its plain text and wrap it.
pyflakes_out = subprocess.run(
["pyflakes", TARGET], capture_output=True, text=True
).stdout
results["pyflakes"] = pyflakes_out.splitlines()
# mypy with --json (available in mypy >= 0.800)
mypy_out = subprocess.run(
["mypy", "--json", TARGET], capture_output=True, text=True
).stdout
results["mypy"] = json.loads(mypy_out or "[]")
# bandit with -f json
bandit_out = subprocess.run(
["bandit", "-f", "json", "-q", TARGET], capture_output=True, text=True
).stdout
results["bandit"] = json.loads(bandit_out or "{}")
def compact_prompt(data: dict, filename: str) -> str:
return f"""You are a senior reviewer. Analyze the static analysis output.
Filename: {filename}
Pyflakes output:
{data['pyflakes']}
Mypy results:
{data['mypy']}
Bandit findings:
{str(data['bandit'])}
Write a concise report: 3 bullet risks sorted by severity, then a single recommended fix per risk.
No praise, only findings."""
print(compact_prompt(results, TARGET))
The script prints a prompt. The next step is copying that prompt into MonkeyCode's free model chat. That part is manual, but only takes ten seconds.
What a Good Model Summary Looks Like
A well-formed answer from the free model should look like this (abbreviated from actual testing):
Risk 1 (High): SQL injection via string formatting
fetch_orderinsertsorder_iddirectly into SQL. Fix: use a parameterized query.Risk 2 (Medium): Missing return value handling
Whenidis not found, the endpoint returnsnull. Fix: raise a 404.Risk 3 (Low): Unused import
random
Remove the import. It also makes the module non-deterministic.
That summary beats reading 47 pyflakes lines blind. The model does not replace the human reviewer. It does replace the human scanner.
A Decision Table for Your Own Repo
Not every project benefits from this three-tool pipeline. Use the table below to decide:
| Condition | Recommended setup |
|---|---|
| Small script, no external libs | Only pyflakes; skip the model |
| Flask/Django app with user input | Add bandit, send output to free model |
| Legacy code with type hints | Add mypy, use model summary as refactor checklist |
| CI time budget under 60s | Run tools locally, only summarize failing paths |
| API gateway with many endpoints | Use tracing instead; linters won't catch latency |
Limitations You Should Know
The pipeline has boundaries. It is not a security audit. It is not a code review. It is a triage step that tells you where to look.
First, free model responses are non-deterministic. The same prompt may produce slightly different wording each time. Keep the original tool JSON as the source of truth.
Second, free server capacity varies. Running mypy on a 10k-line project may time out. For large codebases, run the checkers locally and only upload the JSON summary to the server.
Third, models can hallucinate fixes. A suggested parameterized query is safe, but a suggested architectural change should be verified by a senior engineer.
Who Should Not Use This Workflow
Teams with dedicated security engineers or a full SAST platform can skip this. If you already have Semgrep or CodeQL in CI, the model summary adds little.
Also skip it if you can't accept non-deterministic output in a regulated environment. A linter is deterministic; a language model is not.
For everyone else — a weekend project, a prototype, or a small internal tool — this pipeline is a cheap way to get a second opinion without leaving your browser.
Making It Repeatable
One improvement turns the manual prompt-copying step into a one-command report:
python run_checks.py > prompt.txt
# paste prompt.txt into MonkeyCode free model
Or, if the free server shell supports curl to the model API, you can pipe the prompt directly. That integration is left as an exercise because model API specifics change over time.
The core value is consistency. Same tools, same prompt, same file — a teammate can reproduce your exact workflow and get a comparable summary. That reproducibility matters more than any single warning.
Static analysis never told you to fix something. It only told you to look. The free model tells you the least surprising reason to look. Run the pipeline once on a small file and decide for yourself whether the time savings are worth it.
Top comments (0)