Desloppify

Published: Mar 5, 2026 by Isaac Johnson

Desloppify is a GenAI skill by Peter Omallet that can easily be run inline to improve code quality and readability. The way it does this is to run a series of checks and ask the AI coding assistent of choice to iterate until it gets the best score possible.

This tool can be a bit time (and token) consuming. As such, I will really focus on using Desloppify in a variety of ways using several tools, OSes and languages.

Usage (Getting started)

The easiest way to use Desloppify, as listed on their readme is to just ask your tool:

I want you to improve the quality of this codebase. To do this, install and run desloppify.
Run ALL of the following (requires Python 3.11+):

pip install --upgrade "desloppify[full]"
desloppify update-skill claude    # IMPORTANT — installs the workflow guide. Pick yours: claude, cursor, codex, copilot, windsurf, gemini
desloppify scan --path .
desloppify next

--path is the directory to scan (use "." for the whole project, or "src/" etc).

Your goal is to get the strict score that Desloppify produces as high as possible. Don't be lazy. Fix things properly
and fix things deep. Large refactors are fine if that's what it takes but also small fixes are great. The scoring is designed
to resist gaming, so the only way to improve it is to actually make the code better. Don't cheat.

Follow `next` — it tells you exactly what to fix, which file, and the resolve command to run
when done. Fix the issue, resolve it, run `next` again. Keep going until done.
Use `plan` to reorder priorities or cluster related issues.
You can scan to refresh things. The scan output includes agent instructions — follow them, don't augment with your own analysis but follow its plan.

I allowed it to install the python libraries and fire off the desloppify process

/content/images/2026/03/deslopify-01.png

It would crank away and find issues

/content/images/2026/03/deslopify-02.png

It gave scores and kept iterating till it finally arrived at it’s best option.

/content/images/2026/03/deslopify-03.png

According to the report, we now have far less duplication and security issues, not to mention better quality and tests.

In total, I used about 383k input tokens and 38k output tokens

/content/images/2026/03/deslopify-04.png

Which, had I not used AI Pro (which covers me), would have cost $0.30. We can see that 2.9m input tokens were from the cache so had I not had that caching, it would have been closer to $1.80

However, with that much change

/content/images/2026/03/deslopify-05.png

I’m going to want to make a branch and review the work first.

There is quite a lot of change:

/content/images/2026/03/deslopify-06.png

My first shot at building showed quite a few issues

 > [frontend-builder 6/6] RUN npm run build:
0.520
0.520 > client@0.0.0 build
0.520 > tsc -b && vite build
0.520
4.217 src/components/LeaveTripModal.tsx(2,10): error TS1484: 'Trip' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.217 src/components/TripCard.tsx(3,10): error TS1484: 'Trip' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.217 src/components/TripEditorModal.tsx(2,10): error TS1484: 'Trip' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.217 src/components/TripEditorModal.tsx(2,16): error TS1484: 'ScheduleEvent' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.217 src/components/TripParticipants.tsx(12,19): error TS6133: 'tripId' is declared but its value is never read.
4.217 src/hooks/useLeaveTrip.ts(2,10): error TS1484: 'Trip' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.217 src/hooks/useMealPlan.ts(95,48): error TS2322: Type 'string | number | undefined' is not assignable to type 'string | number | null'.
4.217   Type 'undefined' is not assignable to type 'string | number | null'.
4.217 src/hooks/useTripEditor.ts(2,10): error TS1484: 'Trip' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.218 src/hooks/useTripEditor.ts(2,16): error TS1484: 'ScheduleEvent' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.218 src/pages/ManageTrips.tsx(1,1): error TS6133: 'React' is declared but its value is never read.
4.218 src/pages/ManageTrips.tsx(4,26): error TS1484: 'Trip' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
4.218 src/pages/ManageTrips.tsx(33,34): error TS2345: Argument of type '(id: number) => Promise<boolean | undefined>' is not assignable to parameter of type '(id: number) => Promise<boolean>'.
4.218   Type 'Promise<boolean | undefined>' is not assignable to type 'Promise<boolean>'.
4.218     Type 'boolean | undefined' is not assignable to type 'boolean'.
4.218       Type 'undefined' is not assignable to type 'boolean'.
4.218 src/pages/TripDetails.tsx(1,8): error TS6133: 'React' is declared but its value is never read.
4.218 src/pages/TripDetails.tsx(2,10): error TS6133: 'Link' is declared but its value is never read.
4.218 src/pages/TripDetails.tsx(35,26): error TS6133: 'setWeatherLocation' is declared but its value is never read.
4.218 src/pages/TripDetails.tsx(119,58): error TS2322: Type '(notes: string) => Promise<boolean | undefined>' is not assignable to type '(notes: string) => Promise<boolean>'.
4.218   Type 'Promise<boolean | undefined>' is not assignable to type 'Promise<boolean>'.
4.218     Type 'boolean | undefined' is not assignable to type 'boolean'.
4.218       Type 'undefined' is not assignable to type 'boolean'.
4.218 src/pages/TripDetails.tsx(126,25): error TS2322: Type 'RefObject<HTMLDivElement | null>' is not assignable to type 'RefObject<HTMLDivElement>'.
4.218   Type 'HTMLDivElement | null' is not assignable to type 'HTMLDivElement'.
4.218     Type 'null' is not assignable to type 'HTMLDivElement'.
4.218 src/pages/TripDetails.tsx(128,25): error TS2322: Type '(content: string) => Promise<boolean | undefined>' is not assignable to type '(content: string) => Promise<boolean>'.
4.218   Type 'Promise<boolean | undefined>' is not assignable to type 'Promise<boolean>'.
4.218     Type 'boolean | undefined' is not assignable to type 'boolean'.
4.218       Type 'undefined' is not assignable to type 'boolean'.

Let’s try again but tweak that prompt a bit:

I want you to improve the quality of this codebase. To do this, install and run desloppify.
Run ALL of the following (requires Python 3.11+):

pip install --upgrade "desloppify[full]"
desloppify update-skill claude    # IMPORTANT — installs the workflow guide. Pick yours: claude, cursor, codex, copilot, windsurf, gemini
desloppify scan --path .
desloppify next

--path is the directory to scan (use "." for the whole project, or "src/" etc).

Your goal is to get the strict score that Desloppify produces as high as possible. Don't be lazy. Fix things properly
and fix things deep. Large refactors are fine if that's what it takes but also small fixes are great. The scoring is designed
to resist gaming, so the only way to improve it is to actually make the code better. Don't cheat.

Each rount of updates should be tested with:  cd /home/builder/Workspaces/wildernesstrip && docker build .

Follow `next` — it tells you exactly what to fix, which file, and the resolve command to run
when done. Fix the issue, resolve it, run `next` again. Keep going until done.
Use `plan` to reorder priorities or cluster related issues.
You can scan to refresh things. The scan output includes agent instructions — follow them, don't augment with your own analysis but follow its plan.

In Linux

I’ll give that a try (on a new host running Linux). It continued to clean things up, but this time ran checks and fixed errors

/content/images/2026/03/deslopify-08.png

Till the score got to the 90s

/content/images/2026/03/deslopify-09.png

Again, this is a very token expensive operation so it’s something to be aware of if you are paying for your credits

/content/images/2026/03/deslopify-10.png

Here is the current card

/content/images/2026/03/scorecard-01.png

And indeed, this time it builds clean

(venv) (base) builder@LuiGi:~/Workspaces/wildernesstrip$ docker build -t harbor.freshbrewed.science/freshbrewedprivate/wildtrip:1.0.7 .
[+] Building 0.5s (16/16) FINISHED                                                                                                                                docker:default
 => [internal] load build definition from Dockerfile                                                                                                                        0.0s
 => => transferring dockerfile: 574B                                                                                                                                        0.0s
 => [internal] load metadata for docker.io/library/node:20-alpine                                                                                                           0.3s
 => [internal] load .dockerignore                                                                                                                                           0.0s
 => => transferring context: 103B                                                                                                                                           0.0s
 => [internal] load build context                                                                                                                                           0.0s
 => => transferring context: 4.74kB                                                                                                                                         0.0s
 => [frontend-builder 1/6] FROM docker.io/library/node:20-alpine@sha256:09e2b3d9726018aecf269bd35325f46bf75046a643a66d28360ec71132750ec8                                    0.0s
 => CACHED [stage-1 2/6] WORKDIR /app/server                                                                                                                                0.0s
 => CACHED [stage-1 3/6] COPY server/package*.json ./                                                                                                                       0.0s
 => CACHED [stage-1 4/6] RUN npm install --production                                                                                                                       0.0s
 => CACHED [stage-1 5/6] COPY server/ .                                                                                                                                     0.0s
 => CACHED [frontend-builder 2/6] WORKDIR /app/client                                                                                                                       0.0s
 => CACHED [frontend-builder 3/6] COPY client/package*.json ./                                                                                                              0.0s
 => CACHED [frontend-builder 4/6] RUN npm install                                                                                                                           0.0s
 => CACHED [frontend-builder 5/6] COPY client/ .                                                                                                                            0.0s
 => CACHED [frontend-builder 6/6] RUN npm run build                                                                                                                         0.0s
 => CACHED [stage-1 6/6] COPY --from=frontend-builder /app/client/dist ../client/dist                                                                                       0.0s
 => exporting to image                                                                                                                                                      0.0s
 => => exporting layers                                                                                                                                                     0.0s
 => => writing image sha256:15d6616fda8cadda714242ff7296da9fdbab4eb16300dea31b755e48b265d7d1                                                                                0.0s
 => => naming to harbor.freshbrewed.science/freshbrewedprivate/wildtrip:1.0.7

Since I’m a bit nervous with all the change, let’s stick with upgrading the beta version

(venv) (base) builder@LuiGi:~/Workspaces/wildernesstrip$ cat myvaluesbeta.yaml | grep tag
  # Overrides the image tag whose default is the chart appVersion.
  tag: "1.0.7"
 (venv) (base) builder@LuiGi:~/Workspaces/wildernesstrip$ helm upgrade wildbeta -n wildbeta -f ./myvaluesbeta.yaml ./helm/wildtrack/
Release "wildbeta" has been upgraded. Happy Helming!
NAME: wildbeta
LAST DEPLOYED: Sun Mar  1 08:03:12 2026
NAMESPACE: wildbeta
STATUS: deployed
REVISION: 2
DESCRIPTION: Upgrade complete
TEST SUITE: None

I did a full test and it worked in beta.

Let’s now PR it back to main as we might in a production flow

/content/images/2026/03/deslopify-11.png

The one difference I’ll make in the PR is to make it a squash commit to minimize churn

/content/images/2026/03/deslopify-12.png

By default, Forgejo doesn’t delete the branch so one needs to remember to do that after a merge

/content/images/2026/03/deslopify-13.png

Though, right after I did that, I checked and indeed there are repo settings to make this the default behavior, so I changed them for future PRs

/content/images/2026/03/deslopify-14.png

Now as I go forward, I can refer to my scorecard to remind myself of the score (and if it changes)

/content/images/2026/03/deslopify-15.png

I then tried a bug fix to see if it would automatically kick in, but it didn’t

/content/images/2026/03/deslopify-16.png

I did more bug fixes and tried again. It seemed to redo the whole codebase again

/content/images/2026/03/deslopify-17.png

That is 1.2M tokens and if paying outright, a couple of bucks to review the codebase. Not to mention how much change is involved

/content/images/2026/03/deslopify-18.png

The tests have improved and the score is higher now by 3:

/content/images/2026/03/deslopify-19.png

But as before, my first test shows it’s not building

                                                                                                      3.1s
------
 > [frontend-builder 6/6] RUN npm run build:
0.346
0.346 > client@0.0.0 build
0.346 > tsc -b && vite build
0.346
2.657 src/components/admin/VersionHistory.tsx(1,1): error TS6133: 'React' is declared but its value is never read.
2.658 src/components/chat/ChatSidebar.tsx(2,10): error TS1484: 'ChatThread' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
2.658 src/components/chat/ChatWindow.tsx(2,10): error TS1484: 'ChatThread' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
2.658 src/components/chat/ChatWindow.tsx(2,22): error TS1484: 'ChatMessage' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
2.658 src/components/inventory/GearListItem.tsx(2,10): error TS1484: 'GearItem' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
2.658 src/components/trip-view/ArchivedTripsTable.tsx(3,10): error TS1484: 'Trip' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
2.658 src/hooks/useInventory.ts(2,10): error TS1484: 'GearItem' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
2.658 src/pages/auth/Login.tsx(1,1): error TS6133: 'React' is declared but its value is never read.
2.658 src/pages/auth/Login.tsx(42,21): error TS2322: Type 'Dispatch<SetStateAction<AuthView>>' is not assignable to type '(view: string) => void'.
2.658   Types of parameters 'value' and 'view' are incompatible.
2.658     Type 'string' is not assignable to type 'SetStateAction<AuthView>'.
2.658 src/pages/auth/Login.tsx(46,21): error TS2322: Type '(field: "email" | "password" | "displayName" | "resetToken", value: string) => void' is not assignable to type '(field: string, value: string) => void'.
2.658   Types of parameters 'field' and 'field' are incompatible.
2.658     Type 'string' is not assignable to type '"email" | "password" | "displayName" | "resetToken"'.
------
Dockerfile:7
--------------------
   5 |     RUN npm install
   6 |     COPY client/ .
   7 | >>> RUN npm run build
   8 |
   9 |     # Stage 2: Runtime
--------------------
ERROR: failed to build: failed to solve: process "/bin/sh -c npm run build" did not complete successfully: exit code: 2

As before, I can have Gemini CLI do a 2nd pass sweep to fix the issues, most of which are just minor type mismatches and errant imports

/content/images/2026/03/deslopify-20.png

(summarized)

/content/images/2026/03/deslopify-21.png

Python

Let’s now try this with a Python app. Perhaps we can get more consistent results here:

/content/images/2026/03/deslopify-22.png

Now I tried for a while to get Aggy to do Python via WSL properly but it just gets so hung up on being in Windows. I’ll need to try on my Linux only box instead

/content/images/2026/03/deslopify-23.png

Copilot also had issues. It properly figured out how to use WSL to install the python packages, but then it went out to lunch installing a skill

It did come back after 5 min, however, and ran desloppify. One of the challenges is we really don’t see output from the agent so there is a lot of sitting and waiting without seeing much output.

That said, it crashed and didn’t finish after a full 30m

In Linux, Antigravity worked great, though all in it took about 30m on back and forth checks and rechecks:

If you look at the diff in Github it’s pretty interesting

Since I checked everything in the repo in (so you could see it), you can see the subagent checks

/content/images/2026/03/deslopify-27.png

And reviewer notes

/content/images/2026/03/deslopify-28.png

While the changes are not drastic, we can see we brought this up to score of 100

/content/images/2026/03/gotifyscorecard.png

Despite the truly massive amount of tokens I have used, I did not exceed that which is allotted with the Google AI Pro subscription.

From Scratch

I had one more idea to try out - involving desploppify at the start

I created a new repo for an idea and did a git init

builder@DESKTOP-QADGF36:~/Workspaces$ mkdir dockerToK8s
builder@DESKTOP-QADGF36:~/Workspaces$ cd dockerToK8s/
builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
Initialized empty Git repository in /home/builder/Workspaces/dockerToK8s/.git/
builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git branch -m main

I then add a good gitignore

builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ cat .gitignore
# Created by https://www.toptal.com/developers/gitignore/api/python,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=python,linux

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml

# ruff
.ruff_cache/

# LSP config files
pyrightconfig.json

# End of https://www.toptal.com/developers/gitignore/api/python,linux
builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git add .gitignore
builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git commit -m first
[main (root-commit) ed8fb14] first
 1 file changed, 191 insertions(+)
 create mode 100644 .gitignore

Before I try building this with Gemini CLI, I’ll get a virt env and add the desploppify binaries

builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ !1567
python -m venv venv
builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ ./venv/bin/p
pip         pip3        pip3.11     python      python3     python3.11
builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ source venv/bin/activate
(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ pip install --upgrade "desloppify[full]"
Collecting desloppify[full]
  Using cached desloppify-0.7.7-py3-none-any.whl.metadata (14 kB)
Collecting tree-sitter-language-pack>=0.3 (from desloppify[full])
  Using cached tree_sitter_language_pack-0.13.0-cp310-abi3-manylinux2014_x86_64.whl.metadata (19 kB)
Collecting bandit>=1.7.8 (from desloppify[full])
  Using cached bandit-1.9.4-py3-none-any.whl.metadata (7.1 kB)
Collecting Pillow>=9.0.0 (from desloppify[full])
  Using cached pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (8.8 kB)
Collecting PyYAML>=5.3.1 (from bandit>=1.7.8->desloppify[full])
  Using cached pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.4 kB)
Collecting stevedore>=1.20.0 (from bandit>=1.7.8->desloppify[full])
  Using cached stevedore-5.7.0-py3-none-any.whl.metadata (2.4 kB)
Collecting rich (from bandit>=1.7.8->desloppify[full])
  Using cached rich-14.3.3-py3-none-any.whl.metadata (18 kB)
Collecting tree-sitter>=0.25.2 (from tree-sitter-language-pack>=0.3->desloppify[full])
  Using cached tree_sitter-0.25.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (10.0 kB)
Collecting tree-sitter-c-sharp>=0.23.1 (from tree-sitter-language-pack>=0.3->desloppify[full])
  Using cached tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.7 kB)
Collecting tree-sitter-embedded-template>=0.25.0 (from tree-sitter-language-pack>=0.3->desloppify[full])
  Using cached tree_sitter_embedded_template-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (2.3 kB)
Collecting tree-sitter-yaml>=0.7.2 (from tree-sitter-language-pack>=0.3->desloppify[full])
  Using cached tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (1.8 kB)
Collecting markdown-it-py>=2.2.0 (from rich->bandit>=1.7.8->desloppify[full])
  Using cached markdown_it_py-4.0.0-py3-none-any.whl.metadata (7.3 kB)
Collecting pygments<3.0.0,>=2.13.0 (from rich->bandit>=1.7.8->desloppify[full])
  Using cached pygments-2.19.2-py3-none-any.whl.metadata (2.5 kB)
Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich->bandit>=1.7.8->desloppify[full])
  Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)
Using cached bandit-1.9.4-py3-none-any.whl (134 kB)
Using cached pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (7.0 MB)
Using cached tree_sitter_language_pack-0.13.0-cp310-abi3-manylinux2014_x86_64.whl (20.0 MB)
Using cached desloppify-0.7.7-py3-none-any.whl (807 kB)
Using cached pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (806 kB)
Using cached stevedore-5.7.0-py3-none-any.whl (54 kB)
Using cached tree_sitter-0.25.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (632 kB)
Using cached tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (402 kB)
Using cached tree_sitter_embedded_template-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (18 kB)
Using cached tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (91 kB)
Using cached rich-14.3.3-py3-none-any.whl (310 kB)
Using cached markdown_it_py-4.0.0-py3-none-any.whl (87 kB)
Using cached pygments-2.19.2-py3-none-any.whl (1.2 MB)
Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)
Installing collected packages: tree-sitter-yaml, tree-sitter-embedded-template, tree-sitter-c-sharp, tree-sitter, stevedore, PyYAML, pygments, Pillow, mdurl, desloppify, tree-sitter-language-pack, markdown-it-py, rich, bandit
Successfully installed Pillow-12.1.1 PyYAML-6.0.3 bandit-1.9.4 desloppify-0.7.7 markdown-it-py-4.0.0 mdurl-0.1.2 pygments-2.19.2 rich-14.3.3 stevedore-5.7.0 tree-sitter-0.25.2 tree-sitter-c-sharp-0.23.1 tree-sitter-embedded-template-0.25.0 tree-sitter-language-pack-0.13.0 tree-sitter-yaml-0.7.2

[notice] A new release of pip is available: 23.3.1 -> 26.0.1
[notice] To update, run: pip install --upgrade pip

I’ll create a keepclean.md so I can just reference it as I go along

(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ cat ./keepclean.md
I want you to improve the quality of this codebase. To do this, install and run desloppify.
Run ALL of the following (requires Python 3.11+):

pip install --upgrade "desloppify[full]"
desloppify update-skill gemini
desloppify scan --path .
desloppify next

--path is the directory to scan (use "." for the whole project, or "src/" etc).

Your goal is to get the strict score that Desloppify produces as high as possible. Don't be lazy. Fix things properly
and fix things deep. Large refactors are fine if that's what it takes but also small fixes are great. The scoring is designed
to resist gaming, so the only way to improve it is to actually make the code better. Don't cheat.

Follow `next` — it tells you exactly what to fix, which file, and the resolve command to run
when done. Fix the issue, resolve it, run `next` again. Keep going until done.
Use `plan` to reorder priorities or cluster related issues.
You can scan to refresh things. The scan output includes agent instructions — follow them, don't augment with your own analysis but follow its plan.

I’ll now fire Gemini CLI up:

I would like a containerized webapp that will take in several form values: image repo, image tag, app port, service port, ingress url, tls (enable/disable), cluster issuer, ingress class, ingress annotations for large payloads (enable/disable). It will then uses these values to create a Kubernetes YAML manifest (single file) with a deployment, service and ingres definition. I would like the app built in python with FastAPI. When you have arrived at a good implementation, run the steps in @keepclean.md

My hope is that by just involving desploppify as part of the flow, I can keep a consistent app layout

Let’s test the app it built

(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ pip install -r requirements.txt
Collecting fastapi (from -r requirements.txt (line 1))
  Downloading fastapi-0.135.1-py3-none-any.whl.metadata (30 kB)
Collecting jinja2 (from -r requirements.txt (line 3))
  Using cached jinja2-3.1.6-py3-none-any.whl.metadata (2.9 kB)
Collecting python-multipart (from -r requirements.txt (line 4))
  Using cached python_multipart-0.0.22-py3-none-any.whl.metadata (1.8 kB)
Requirement already satisfied: PyYAML in ./venv/lib/python3.11/site-packages (from -r requirements.txt (line 5)) (6.0.3)
Collecting uvicorn[standard] (from -r requirements.txt (line 2))
  Downloading uvicorn-0.41.0-py3-none-any.whl.metadata (6.7 kB)
Collecting starlette>=0.46.0 (from fastapi->-r requirements.txt (line 1))
  Using cached starlette-0.52.1-py3-none-any.whl.metadata (6.3 kB)
Collecting pydantic>=2.7.0 (from fastapi->-r requirements.txt (line 1))
  Using cached pydantic-2.12.5-py3-none-any.whl.metadata (90 kB)
Collecting typing-extensions>=4.8.0 (from fastapi->-r requirements.txt (line 1))
  Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Collecting typing-inspection>=0.4.2 (from fastapi->-r requirements.txt (line 1))
  Using cached typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)
Collecting annotated-doc>=0.0.2 (from fastapi->-r requirements.txt (line 1))
  Using cached annotated_doc-0.0.4-py3-none-any.whl.metadata (6.6 kB)
Collecting click>=7.0 (from uvicorn[standard]->-r requirements.txt (line 2))
  Using cached click-8.3.1-py3-none-any.whl.metadata (2.6 kB)
Collecting h11>=0.8 (from uvicorn[standard]->-r requirements.txt (line 2))
  Using cached h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)
Collecting httptools>=0.6.3 (from uvicorn[standard]->-r requirements.txt (line 2))
  Downloading httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (3.5 kB)
Collecting python-dotenv>=0.13 (from uvicorn[standard]->-r requirements.txt (line 2))
  Downloading python_dotenv-1.2.2-py3-none-any.whl.metadata (27 kB)
Collecting uvloop>=0.15.1 (from uvicorn[standard]->-r requirements.txt (line 2))
  Downloading uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (4.9 kB)
Collecting watchfiles>=0.20 (from uvicorn[standard]->-r requirements.txt (line 2))
  Downloading watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)
Collecting websockets>=10.4 (from uvicorn[standard]->-r requirements.txt (line 2))
  Downloading websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.8 kB)
Collecting MarkupSafe>=2.0 (from jinja2->-r requirements.txt (line 3))
  Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.7 kB)
Collecting annotated-types>=0.6.0 (from pydantic>=2.7.0->fastapi->-r requirements.txt (line 1))
  Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
Collecting pydantic-core==2.41.5 (from pydantic>=2.7.0->fastapi->-r requirements.txt (line 1))
  Using cached pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.3 kB)
Collecting anyio<5,>=3.6.2 (from starlette>=0.46.0->fastapi->-r requirements.txt (line 1))
  Using cached anyio-4.12.1-py3-none-any.whl.metadata (4.3 kB)
Collecting idna>=2.8 (from anyio<5,>=3.6.2->starlette>=0.46.0->fastapi->-r requirements.txt (line 1))
  Using cached idna-3.11-py3-none-any.whl.metadata (8.4 kB)
Downloading fastapi-0.135.1-py3-none-any.whl (116 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.0/117.0 kB 3.0 MB/s eta 0:00:00
Using cached jinja2-3.1.6-py3-none-any.whl (134 kB)
Using cached python_multipart-0.0.22-py3-none-any.whl (24 kB)
Using cached annotated_doc-0.0.4-py3-none-any.whl (5.3 kB)
Using cached click-8.3.1-py3-none-any.whl (108 kB)
Using cached h11-0.16.0-py3-none-any.whl (37 kB)
Downloading httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (456 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 456.6/456.6 kB 12.8 MB/s eta 0:00:00
Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
Using cached pydantic-2.12.5-py3-none-any.whl (463 kB)
Using cached pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
Downloading python_dotenv-1.2.2-py3-none-any.whl (22 kB)
Using cached starlette-0.52.1-py3-none-any.whl (74 kB)
Using cached typing_extensions-4.15.0-py3-none-any.whl (44 kB)
Using cached typing_inspection-0.4.2-py3-none-any.whl (14 kB)
Downloading uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (3.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.8/3.8 MB 33.2 MB/s eta 0:00:00
Downloading watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (456 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 456.1/456.1 kB 37.2 MB/s eta 0:00:00
Downloading websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (184 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 184.6/184.6 kB 21.3 MB/s eta 0:00:00
Downloading uvicorn-0.41.0-py3-none-any.whl (68 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 68.8/68.8 kB 6.3 MB/s eta 0:00:00
Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)
Using cached anyio-4.12.1-py3-none-any.whl (113 kB)
Using cached idna-3.11-py3-none-any.whl (71 kB)
Installing collected packages: websockets, uvloop, typing-extensions, python-multipart, python-dotenv, MarkupSafe, idna, httptools, h11, click, annotated-types, annotated-doc, uvicorn, typing-inspection, pydantic-core, jinja2, anyio, watchfiles, starlette, pydantic, fastapi
Successfully installed MarkupSafe-3.0.3 annotated-doc-0.0.4 annotated-types-0.7.0 anyio-4.12.1 click-8.3.1 fastapi-0.135.1 h11-0.16.0 httptools-0.7.1 idna-3.11 jinja2-3.1.6 pydantic-2.12.5 pydantic-core-2.41.5 python-dotenv-1.2.2 python-multipart-0.0.22 starlette-0.52.1 typing-extensions-4.15.0 typing-inspection-0.4.2 uvicorn-0.41.0 uvloop-0.22.1 watchfiles-1.1.1 websockets-16.0

[notice] A new release of pip is available: 23.3.1 -> 26.0.1
[notice] To update, run: pip install --upgrade pip

(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ python main.py
INFO:     Started server process [38533]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

I added some more annotations, then tested the docker file

(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ docker build -t mytest:01 .
[+] Building 23.3s (10/10) FINISHED                                                                                                   docker:default
 => [internal] load build definition from Dockerfile                                                                                            0.1s
 => => transferring dockerfile: 506B                                                                                                            0.0s
 => [internal] load metadata for docker.io/library/python:3.11-slim                                                                             1.7s
 => [auth] library/python:pull token for registry-1.docker.io                                                                                   0.0s
 => [internal] load .dockerignore                                                                                                               0.0s
 => => transferring context: 2B                                                                                                                 0.0s
 => [1/4] FROM docker.io/library/python:3.11-slim@sha256:4057d02a202f69bfbfe10f65300519f612eb00fc595b8499f77d3cfe5b1b9fd4                       7.0s
 => => resolve docker.io/library/python:3.11-slim@sha256:4057d02a202f69bfbfe10f65300519f612eb00fc595b8499f77d3cfe5b1b9fd4                       0.0s
 => => sha256:4057d02a202f69bfbfe10f65300519f612eb00fc595b8499f77d3cfe5b1b9fd4 10.37kB / 10.37kB                                                0.0s
 => => sha256:06fa338add3c2ab8daeff7304a6bb204c68f67743b13553a8999605fc8097c58 1.75kB / 1.75kB                                                  0.0s
 => => sha256:7c68b568387255c3db2b374da329795e7c25520bd232458282d62d606296e719 5.47kB / 5.47kB                                                  0.0s
 => => sha256:206356c42440674ecbdf1070cf70ce8ef7885ac2e5c56f1ecf800b758f6b0419 29.78MB / 29.78MB                                                1.9s
 => => sha256:2067094444255bb5bf0f8b525f10409691c57f48c48e6bebac8c3a85df4f9dd2 1.29MB / 1.29MB                                                  0.3s
 => => sha256:bbf418262cbe98089272fafddcc570e7fa85e6fa28fe7163dc445136042bd183 14.37MB / 14.37MB                                                1.3s
 => => sha256:43272e9c9d4bd6009934139dd3c958777b55bdbe0ccadb5e7a13f847436fabd2 250B / 250B                                                      0.4s
 => => extracting sha256:206356c42440674ecbdf1070cf70ce8ef7885ac2e5c56f1ecf800b758f6b0419                                                       2.4s
 => => extracting sha256:2067094444255bb5bf0f8b525f10409691c57f48c48e6bebac8c3a85df4f9dd2                                                       0.3s
 => => extracting sha256:bbf418262cbe98089272fafddcc570e7fa85e6fa28fe7163dc445136042bd183                                                       1.9s
 => => extracting sha256:43272e9c9d4bd6009934139dd3c958777b55bdbe0ccadb5e7a13f847436fabd2                                                       0.0s
 => [internal] load build context                                                                                                               3.4s
 => => transferring context: 305.65MB                                                                                                           3.3s
 => [2/4] WORKDIR /app                                                                                                                          1.0s
 => [3/4] COPY . /app                                                                                                                           2.4s
 => [4/4] RUN pip install --no-cache-dir -r requirements.txt                                                                                    8.9s
 => exporting to image                                                                                                                          2.2s
 => => exporting layers                                                                                                                         2.1s
 => => writing image sha256:ec912981ad20d20ff7611e939f95a526b8bb1a8d54c37d11d97c0b3447199e46                                                    0.0s
 => => naming to docker.io/library/mytest:01                                                                                                    0.0s
 

(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ docker run -p 8000:8000 mytest:01
INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     172.17.0.1:55542 - "GET / HTTP/1.1" 200 OK

Let’s see how it works:

/content/images/2026/03/deslopify-32.png

Let me push this to Dockerhub for sharing

(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ docker build -t idjohnson/dockertok8s:latest .
[+] Building 0.9s (9/9) FINISHED                                                                                                      docker:default
 => [internal] load build definition from Dockerfile                                                                                            0.0s
 => => transferring dockerfile: 506B                                                                                                            0.0s
 => [internal] load metadata for docker.io/library/python:3.11-slim                                                                             0.3s
 => [internal] load .dockerignore                                                                                                               0.0s
 => => transferring context: 2B                                                                                                                 0.0s
 => [1/4] FROM docker.io/library/python:3.11-slim@sha256:4057d02a202f69bfbfe10f65300519f612eb00fc595b8499f77d3cfe5b1b9fd4                       0.0s
 => [internal] load build context                                                                                                               0.4s
 => => transferring context: 574.07kB                                                                                                           0.3s
 => CACHED [2/4] WORKDIR /app                                                                                                                   0.0s
 => CACHED [3/4] COPY . /app                                                                                                                    0.0s
 => CACHED [4/4] RUN pip install --no-cache-dir -r requirements.txt                                                                             0.0s
 => exporting to image                                                                                                                          0.0s
 => => exporting layers                                                                                                                         0.0s
 => => writing image sha256:ec912981ad20d20ff7611e939f95a526b8bb1a8d54c37d11d97c0b3447199e46                                                    0.0s
 => => naming to docker.io/idjohnson/dockertok8s:latest                                                                                         0.0s
(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ docker push idjohnson/dockertok8s:latest
The push refers to repository [docker.io/idjohnson/dockertok8s]
a04451a11828: Pushed
fbae1722fd86: Pushed
9b12d277a593: Pushed
fa1d322086e6: Mounted from library/python
5184c31ad48f: Mounted from library/python
b0c20bcd44fe: Mounted from library/python
a257f20c716c: Mounted from library/python
latest: digest: sha256:1de95e1fa8d6a07bddb3801bd6f68f3ffd58594b30f4b4b9b8109995807d5aaf size: 1789

I can now create a new public Github repo

/content/images/2026/03/deslopify-33.png

Then add it as an origin and push up my repo

(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git remote add origin git@github.com:idjohnson/dockerToK8s.git
(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git status
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .desloppify/
        AGENTS.md
        Dockerfile
        keepclean.md
        main.py
        requirements.txt
        scorecard.png
        templates/

nothing added to commit but untracked files present (use "git add" to track)
(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git add -A
(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git commit -m "working draft"
[main 9e60775] working draft
 10 files changed, 127664 insertions(+)
 create mode 100644 .desloppify/query.json
 create mode 100644 .desloppify/state-python.json
 create mode 100644 .desloppify/state-python.json.bak
 create mode 100644 AGENTS.md
 create mode 100644 Dockerfile
 create mode 100644 keepclean.md
 create mode 100644 main.py
 create mode 100644 requirements.txt
 create mode 100644 scorecard.png
 create mode 100644 templates/index.html
(venv) builder@DESKTOP-QADGF36:~/Workspaces/dockerToK8s$ git push -u origin --all
Enumerating objects: 17, done.
Counting objects: 100% (17/17), done.
Delta compression using up to 16 threads
Compressing objects: 100% (15/15), done.
Writing objects: 100% (17/17), 160.62 KiB | 5.35 MiB/s, done.
Total 17 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), done.
To github.com:idjohnson/dockerToK8s.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

I can now see the basic app in https://github.com/idjohnson/dockerToK8s

/content/images/2026/03/deslopify-34.png

Summary

We spent a good amount of time diving into Desloppify which is an excellent tool from Peter Omallet.

We tried updating both a NextJS and Python app using Gemini CLI. We also tried building a new app with Desploppify as a final check and cleanup.

Over several iterations, it was clear how Desloppify can improve the readability and security of a variety of languages. However, due to the nature of it being an LLM, it had this habit of totally rewriting the codebase, often equally good, but very different. This would be a real problem over time as a complete rewrite of the directory structure makes tracking changes impossible and any work-in-progress (WIP) branches would have nasty merges.

The other challenge is time and tokens. Because this operates by running long loops over and over in an agentic flow, in some cases it took over 30 minutes on a medium size codebase and millions of tokens. If one has token limits or pays out of pocket, this could get prohibitively expensive, Luckily, I have Gemini AI Pro which might slow down in time, but doesn’t cost me extra.

I think these challenges can be mitigated. We could ask to limit changes to just new files. We could pair Desploppify with tight narrowly defined plans and requirements markdown files that would limit the ‘creativity’ of the underlying LLM.

Additionally, while I focused on Gemini CLI because of Python challenges in my WSL setup on my last windows box, on my daily driver Linux box I had no issues (and I assume a pure Windows setup would be fine and Mac OS would be fine). I will not blame Desloppify here, just Windows 11 and my particular setup.

Going forward I absolutely plan to keep Desloppify in my stack and will use it (at least as a final run and a periodic checker). Seems it might be nice to tie into a PR workflow (to see if we get progressively worse).

GenAI Gemini desloppify copilot GeminiCLI Antigravity Typescript Python

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes