Posted on

The QA Assistant on demand.

Thoughts of the Fractional ChiefUsing a Local AI Code Reviewer in a Git Workflow

I recently wrote about the laminar team and this is a bit of a follow-up on the QA part and AI assist in rapid QA as well as for use as support on command line. 

The idea is simple: take one or more source files, apply a configurable instruction set, send the contents to a local Ollama model, and return the review either by email or directly in the terminal.

The useful part is not the model call itself. It is how the tool fits into a wider agentic workflow, particularly when combined with Git hooks.

The basic flow

The application is deliberately independent of Git. It accepts files as command-line parameters:

rapidqa main.go server.go web/app.js

For each file, it checks the extension against a configured allow-list, ignores binary content, reads the source, prepends the configured instructions, and sends the combined prompt to a locally running Ollama instance.

Each response is collected under a clear filename delimiter so multiple files can be reviewed in a single run.

By default, the combined result is sent through SendGrid as a single email.

This keeps the application generic. Git is only one possible way to invoke it.

Local review with -nomail

For development and ad-hoc use, sending an email every time is unnecessary, so I added a -nomail option:

rapidqa -nomail main.go

Instead of using SendGrid, the generated report is printed directly to the console.

This makes the same application useful both as an automated review service and as a normal command-line tool.

For example:

rapidqa -nomail main.go handlers.go database.go

can be used while working on a change without involving Git at all.

Overriding the instructions

The application also supports -in, which allows a different instruction to be supplied for a particular run.

The default behaviour uses the global instruction file stored alongside the application:

rapidqa/ rapidqa config/ config.json instructions.txt 

That instruction might contain a general code-review prompt covering areas such as correctness, security, error handling and unnecessary complexity.

For a specific review, however, the instruction can be replaced from the command line:

rapidqa -nomail -in "Look only for concurrency and locking problems" server.go

This turns the utility into a more general local analysis tool.

The same model can therefore be used for several tasks without changing the source code or global configuration.

Examples might include:

rapidqa -nomail -in "Check this code for SQL injection risks" database.go

or:

rapidqa -nomail -in "Identify duplicated logic and suggest where functions should be extracted" handlers.go routes.go

Adding Git to the workflow

Because the application accepts normal filenames, Git integration can remain outside the application itself.

A global post-commit hook can obtain the files included in the most recent commit and pass them directly to commit-review.

A simplified version looks like this:

#!/usr/bin/env bash

REPO="$(git rev-parse --show-toplevel)"
cd "$REPO" || exit 1

files=()

if git rev-parse --verify HEAD^ >/dev/null 2>&1; then
    mapfile -d '' files < <(
        git diff --name-only --diff-filter=ACMRT -z HEAD^ HEAD
    )
else
    mapfile -d '' files < <(
        git diff-tree --root --no-commit-id --name-only --diff-filter=ACMRT -r -z HEAD
    )
fi

[ "${#files[@]}" -eq 0 ] && exit 0

absolute_files=()

for file in "${files[@]}"; do
    absolute_files+=("$REPO/$file")
done

/opt/rapidqa/rapidqa -- "${absolute_files[@]}"

The hook does the Git-specific work. It determines which files changed and passes them to the application.

The Go program itself does not need to know anything about repositories, branches, commits or Git internals.

That separation is useful because the same binary can still be invoked manually, from a build script, from CI, or from another automation system.

Making the hook global

The hook itself can also be global rather than copied into every repository.

Git supports a global hooks directory:

git config --global core.hooksPath ~/.git-hooks

The resulting setup is roughly:

~/.git-hooks/
    post-commit

/opt/rapidqa/ rapidqa config/ config.json instructions.txt 

Every repository using that Git configuration now runs the same review process after a successful commit.

There is no need to add review scripts, configuration files or binaries to individual projects.

Why use a local model?

Using Ollama means the source does not need to be sent to a remote model provider.

The application reads the source locally, combines it with the instructions, and sends it to the configured Ollama API endpoint, usually something such as:

http://127.0.0.1:11434

The model itself is defined in config.json, along with settings such as timeout, context size and temperature.

This also means the review process can be adapted to the available hardware. A smaller model can be used on a workstation, while a larger coding model can be used on a dedicated local machine.

From automation to an agentic workflow

The interesting part of this approach is that Git becomes the event source rather than the centre of the application.

A commit occurs, the hook identifies the relevant files, and the review agent is given:

  • a set of files
  • a defined instruction
  • a local model
  • an output destination

The agent performs the review and returns the result without requiring another manual step.

At the same time, the command-line interface remains available:

commit-review -nomail -in "Review error handling only" *.go

That makes the same application usable interactively as well as automatically.

The distinction is useful. The tool is not a “Git AI reviewer”. It is a file-processing review agent that Git happens to invoke automatically.

Keeping the boundaries simple

The implementation has ended up with a fairly clean division of responsibility.

  • Git decides which files belong to the event.
  • The Go application decides which files are suitable for processing, reads them, builds the prompts, calls Ollama and formats the results.
  • The configuration controls the model, accepted file extensions and email settings.
  • The instruction file controls what the reviewer is supposed to look for.
  • SendGrid provides asynchronous delivery, while -nomail provides immediate terminal output.
  • The -in option allows the behaviour of the reviewer to be changed for individual runs without changing the global defaults.
  • That separation keeps the application small while making it useful in more situations than the original post-commit use case.

Git repo: 

https://github.com/emberlabstech/RapidQA

License: MIT

Posted on

Do you know the cost of your systems?

Thoughts of the Fractional Chief

Do you even know what systems you have?

Ask that question in most companies and the answer is often less certain than it should be.

Some systems are obvious. Others sit quietly in the background: old applications, internal tools, integrations, databases, spreadsheets and functions that have gradually become part of the furniture, and inside those systems there may be another layer again.

A customer application, for example, is rarely just one system. It may contain a payment module, authentication, customer records, notifications,
reporting, integrations and other subsystems.

Some may be modern and healthy, while others may be carrying years of technical debt.

They all have one thing in common – They cost money, and they carry risk.

What does this solve?

Technical debt is difficult to manage when it remains merely a technical description such as; 

“Legacy payment module.”
“Old integration.”
“Needs refactoring.”
“High maintenance.”

These descriptions may all be correct, but they are difficult to compare objectively.

A simple systems register changes the conversation by attaching cost,
effort, dependency and business impact to the problem.

Instead of saying:

“The payment module is old and needs replacing.”

you can say:

“The payment module costs €3,500 a month to operate and support, consumes 20 hours of engineering time, has generated six incidents this quarter, and an outage would prevent approximately €40,000 of transactions per hour.”

That is a very different management discussion.

The practical simple solution?

Start with a simple register covering your important systems and, where appropriate, their major functional components.

Do not stop at “Customer App”.

Break out the areas that carry their own cost or risk:

  • Customer App
  • Payment Module
  • Authentication
  • Customer Database
  • Notification Service
  • Reporting
  • External Integrations

For each one, capture a small number of meaningful measures.

The format is simple – Put it in a register that holds six fields. 
Update the costs as time goes, and spread them as a cost/month.

System / Function and Owner Cost to retire / replace / fix Operational cost per month Maintenance and incidents Dependencies Impact if unavailable
Customer app / Digital 80,000 5,000 20 hrs, 4 incidents Customers, Support 15,000/h
Payment module / Finance 35,000 3,500 20 hrs, 6 incidents Sales, Finance, Customers 40,000/h
Reporting / Operations 12,000 1,200 5 hrs / 1 incident Management 2,000 / day
…          

The numbers do not have to be perfect on day one – they need to be good enough to start making comparisons.

Update them over time and convert recurring effort into a monthly cost wherever possible.

Why does this work?

Because it exposes something that traditional system inventories usually do not:
The relationship between technical debt, cost and business impact.

A system may be expensive but stable.
Another may be cheap to operate but represent a major business risk.
A third may consume hundreds of engineering hours every year even though its direct infrastructure cost is almost negligible.

Once these factors are visible together, priorities become much clearer.

You can effectively place each system or subsystem into four broad categories:

  Lower business impact Higher business impact
Lower technical debt / cost Maintain:
Acceptable ongoing state
Protect:
Healthy but business-critical,
so keep stable and well-supported
Higher technical debt / cost Simplify or retire:
elevated concern, usually driven
by cost or technical debt
Fix first:
high debt combined with
high business impact

The colours indicate management priority, not simply system health: blue for ongoing maintenance, green for critical systems that should be protected, orange for systems that should be simplified or retired, and red for systems requiring priority remediation.

The bottom-right category is where management attention should naturally go.

High technical debt.
High operating cost.
High dependency.
High impact when unavailable.

These are the systems where doing nothing is itself an expensive decision.

From technical debt to business decisions, the register gives management a way to ask better questions.

What does this system cost us today?
What business functions depend on it?
What happens financially if it fails?
What kind and how much engineering capacity does it consume?
What would it cost to repair or replace?
What is the risk of leaving it untouched for another year?

And importantly:

Which technical debt should we actually fix first?

Not all legacy technology is bad, not all technical debt needs to be repaid, sometimes an old system is inexpensive, stable and “good enough”, carries little risk, and replacing it may create more cost than value.

The purpose of the register is not to create a campaign against legacy systems – It is to make the trade-offs visible.

Once cost, maintenance effort, dependency and outage impact are placed beside one another, technical debt stops being an abstract IT concern, and becomes measurable business exposure, and once it is measurable, management can decide objectively whether to maintain it, fix it, replace it or retire it.

It makes the risks and benefits visible and objectively understandable.