Scan any repo with SAST/SCA tools and patch the vulnerabilities using your AI assistant's LLM. Works as a slash command in Claude Code, Codex, Cursor, Gemini CLI, Aider, OpenCode, and GitHub Copilot CLI.
Project description
yobitsugi
呼継ぎ — "called-in joinery."
AI coding assistant skill that scans a repository with industry SAST/SCA tools
and patches the findings using your assistant's LLM.
A Japanese pottery technique: when a broken vessel can't be repaired with its own fragments, pieces from a different vessel are called in and joined to complete the whole. The repair is honest about its origin — the foreign piece is visible, often a different colour or pattern, and the new vessel is more interesting for it. That's what this tool does. The original code has a crack (a vulnerability). The LLM is the foreign vessel — its patch comes from elsewhere, joined to your code at the seam. Backups, the
applied.jsonlog, the regression test, and the re-scan all keep the join visible and accountable.
Table of Contents
- What is yobitsugi?
- Project focus
- Requirements
- Installing from PyPI
- Installing from source
- Quick start
- Supported AI coding assistants
- Supported scanners
- Installing scanners
- Configure the LLM provider
- Common commands
- Architecture overview
- Safety guarantees
- The unified Finding schema
- Privacy
- Development
- Testing
- Test layout
- Releasing
- Semantic versioning
- Python version lifecycle
- Contributing
- License
What is yobitsugi?
yobitsugi is a CLI and an AI coding assistant skill that:
- Detects the languages in a repo.
- Scans it with real, industry-standard SAST and SCA tools (bandit, semgrep, gosec, brakeman, npm audit, pip-audit, trufflehog, and more).
- Normalises every scanner's output into a single Finding schema.
- Patches each finding by asking an LLM (your provider, your key) to generate a unified diff.
- Applies that diff safely — with backups, a rollback log, and a dirty-tree guard.
- Generates a regression test that would have caught the original bug.
- Re-scans to verify the fix actually closed the finding and didn't introduce a new one.
It ships as a slash command — /yobitsugi . — for Claude Code, Codex, Cursor, Gemini CLI, Aider, OpenCode, and GitHub Copilot CLI. The same yobitsugi CLI also works standalone outside any assistant.
/yobitsugi . # inside any supported assistant
yobitsugi run ./services/api # standalone
yobitsugi run ./services/api --auto # apply fixes without prompting
yobitsugi scan ./services/api # scan-only, no LLM calls
Project focus
- Honest joinery. The LLM patch is a visible repair — every change is logged, backed up, and re-verified.
- No vendor lock-in. Bring your own LLM. Bring your own scanners.
- Offline-first scanning. Only the fix step touches the network.
- Single source of truth. Each finding has a stable
idso re-runs compute truefixed_ids/still_present/newly_introducedsets. - Testable. Every core stage is a pure-ish function with a deliberate boundary. The pipeline runs in-process — no subprocess fork-bombs.
Requirements
| Component | Requirement |
|---|---|
| Python | 3.11+ (tested on 3.11, 3.12, 3.13) |
git |
Required if you want the dirty-tree safety check |
patch or git apply |
Required for diff application |
| Scanner binaries | Optional per language — missing tools are skipped, not fatal |
| LLM provider | OpenAI / Anthropic / Google / Ollama / any OpenAI-compatible endpoint |
Installing from PyPI
pipx install yobitsugi && yobitsugi install
# or
uv tool install yobitsugi && yobitsugi install
# or
pip install yobitsugi && yobitsugi install
Or drop the repo in directly as a Claude Code skill:
git clone https://github.com/FiNiX-GaMmA/yobitsugi ~/.claude/skills/yobitsugi
yobitsugi install auto-detects every supported assistant on your machine and registers the skill for each. Pass --platform <name> to install for one specifically, or --scope project to install into the current repo's config instead of your home dir.
Installing from source
git clone https://github.com/FiNiX-GaMmA/yobitsugi
cd yobitsugi
pip install -e ".[dev]"
yobitsugi version
Quick start
# 1. Make sure you have an LLM key in your environment.
export ANTHROPIC_API_KEY=sk-ant-...
# 2. Scan + fix a repo, prompting before each patch.
yobitsugi run ./my-project
# 3. Or, scan only — no LLM, no edits.
yobitsugi scan ./my-project
# 4. Inspect what was found.
yobitsugi findings ~/.yobitsugi/my-project-20260511-100501
You get a workspace directory:
~/.yobitsugi/<repo>-<timestamp>/
├── languages.json detected languages with file counts
├── scan_report.json per-scanner status (ok / skipped_missing_tool / errored)
├── findings.json unified, deduplicated list of vulnerabilities ← the cracks
├── applied.json rollback log — one entry per applied patch ← the called-in pieces
└── validation.json fixed_ids, still_present, newly_introduced ← did the joins hold?
Supported AI coding assistants
| Platform | Install command |
|---|---|
| Claude Code | yobitsugi install --platform claude |
| Codex | yobitsugi install --platform codex |
| Cursor | yobitsugi install --platform cursor |
| Gemini CLI | yobitsugi install --platform gemini |
| Aider | yobitsugi install --platform aider |
| OpenCode | yobitsugi install --platform opencode |
| GitHub Copilot CLI | yobitsugi install --platform copilot |
Uninstall with yobitsugi uninstall --platform <name>. List everything with yobitsugi list-platforms.
Supported scanners
Auto-detected per language. Missing binaries are skipped, not fatal — and yobitsugi can install most of them for you in an isolated venv (see Installing scanners below).
| Language | Scanners |
|---|---|
| Python | bandit, safety, pip-audit, semgrep |
| JavaScript / TypeScript | eslint (security plugins), npm audit, semgrep |
| Go | gosec, govulncheck, semgrep |
| Java | spotbugs (with FindSecBugs), semgrep |
| Ruby | brakeman, bundler-audit, semgrep |
| PHP | phpstan (security extension), semgrep |
| C / C++ | flawfinder, cppcheck, semgrep |
| Rust | cargo-audit, semgrep |
| Shell | shellcheck, semgrep |
| Cross-language | semgrep, trufflehog (secrets scanning) |
Adding a new scanner is one YAML block in yobitsugi/data/scanners.yaml — no code change needed unless the output format is exotic.
Installing scanners
yobitsugi orchestrates scanners — it doesn't bundle their binaries. When you run yobitsugi scan and a scanner isn't on PATH, that scanner is silently skipped. To check the situation:
yobitsugi list-scanners # every scanner, install method, and whether it's available
To install the Python-based scanners (bandit, safety, pip-audit, semgrep, flawfinder) in one shot, into an isolated venv at ~/.yobitsugi/tools/venv/:
yobitsugi install-scanners # installs only missing ones
yobitsugi install-scanners --all # reinstall/upgrade everything
yobitsugi uninstall-scanners # wipes ~/.yobitsugi/tools/ entirely
After install-scanners succeeds, every subsequent yobitsugi scan or yobitsugi run automatically prepends the venv's bin/ to PATH for scanner subprocesses — you don't need to activate anything yourself, and the venv never pollutes your main Python environment.
Non-Python scanners need their own runtime and yobitsugi will print install hints rather than try to bootstrap six different package managers badly:
| Runtime | Scanners | Install yourself with |
|---|---|---|
| Node | eslint |
npm install -g eslint eslint-plugin-security |
| Go | gosec, govulncheck |
go install github.com/securego/gosec/v2/cmd/gosec@latest etc. |
| Cargo | cargo-audit |
cargo install cargo-audit |
| Ruby | brakeman, bundler-audit |
gem install brakeman bundler-audit |
| System | shellcheck, cppcheck, trufflehog |
brew install … or apt install … |
| Manual | spotbugs, phpstan |
see project release pages |
If you're running yobitsugi via an AI assistant (/yobitsugi .), the assistant will see the "scanner skipped" entries in scan_report.json and ask you whether to run the appropriate install commands.
Configure the LLM provider
Provider config is resolved in this order: --provider flag → environment variables → ~/.yobitsugi/config.yaml → autodetect from any API key in env.
| Provider | Env var | Example model |
|---|---|---|
| OpenAI | OPENAI_API_KEY |
gpt-4o, gpt-4o-mini |
| Anthropic | ANTHROPIC_API_KEY |
claude-opus-4-7, claude-sonnet-4-6 |
GOOGLE_API_KEY |
gemini-1.5-pro |
|
| Ollama (local) | none | llama3.1:70b |
| OpenAI-compatible (Groq, Together, Fireworks, vLLM, LM Studio, OpenRouter) | OPENAI_API_KEY + OPENAI_BASE_URL |
any |
yobitsugi config --init # write a starter ~/.yobitsugi/config.yaml
yobitsugi config # show resolved provider/model/base_url
Common commands
/yobitsugi . # full pipeline, prompt before each fix
/yobitsugi . --auto # apply fixes without confirmation
/yobitsugi . --severity CRITICAL # only critical findings
/yobitsugi . --provider anthropic --model claude-opus-4-7
/yobitsugi . --skip-tests # don't generate regression tests
/yobitsugi . --allow-dirty # run on a dirty git tree
yobitsugi scan ./services/api # scan-only, no fixes, no LLM
yobitsugi findings ~/.yobitsugi/<workspace> # pretty-print existing findings
yobitsugi findings <ws> --severity HIGH --json # JSON output for piping
yobitsugi rollback ~/.yobitsugi/<workspace> # restore all .yobitsugi.bak files
yobitsugi list-platforms # show all supported assistants
yobitsugi detect-platforms # show only the ones installed
yobitsugi list-scanners # every scanner + install status
yobitsugi install-scanners # install missing Python scanners into ~/.yobitsugi/tools/venv/
yobitsugi install-scanners --all # force reinstall/upgrade all of them
yobitsugi uninstall-scanners # wipe the managed venv
yobitsugi summary ~/.yobitsugi/<workspace> # tabular post-run report (runs automatically at the end of `run`/`scan`)
yobitsugi summary <ws> --format markdown # markdown tables — paste into chat
yobitsugi summary <ws> --format json # structured data for tooling
Architecture overview
detect → scan → parse → (loop: fix → apply) → tests → validate
Every stage is also a callable Python function and a standalone CLI entrypoint. The orchestrator at yobitsugi/core/pipeline.py runs them in-process — no subprocess fork between stages. Each stage reads and writes JSON inside the workspace directory, so you can re-run any stage by hand against the previous stage's output.
See ARCHITECTURE.md for the full module map and design notes.
Safety guarantees
- Refuses to run on a dirty git tree unless
--allow-dirtyis set. - Every modified file gets a
.yobitsugi.baksibling before the patch is applied. - An
applied.jsonrollback log records every patch —yobitsugi rollbackrestores all of them in one command. - The model is constrained to return unified diffs or the literal string
# CANNOT_FIX: <reason>. No inline edits, no destructive commands. - Code snippets pulled from your repo are wrapped in
[BEGIN UNTRUSTED USER CODE]/[END UNTRUSTED USER CODE]markers in the LLM prompt to mitigate prompt-injection from malicious comments or strings. - Fixes are never auto-applied unless you explicitly pass
--auto. validatere-runs the full scan after fixes and flagsnewly_introducedfindings — vulnerabilities the patches accidentally created. Exit code 3 if any.
The unified Finding schema
Every scanner's output is normalised to this shape so the LLM, the apply logic, and the test generator all see the same thing:
{
"id": "28b33dd29e127dbf",
"tool": "bandit",
"language": "Python",
"file": "/abs/path/to/file.py",
"line": 6,
"end_line": 6,
"rule_id": "B608",
"type": "SQL_INJECTION",
"severity": "HIGH",
"confidence": "HIGH",
"title": "hardcoded_sql_expressions",
"description": "Possible SQL injection vector through string-based query construction.",
"code_snippet": "5 # SQL injection\n6 query = ...\n7 return ...",
"cwe": ["CWE-89"],
"references": ["https://..."],
"remediation_hint": null,
"package": null,
"fixed_version": null
}
type is one of: SQL_INJECTION, XSS, HARDCODED_SECRET, COMMAND_INJECTION, PATH_TRAVERSAL, WEAK_CRYPTO, INSECURE_DESERIALIZATION, SSRF, OPEN_REDIRECT, VULNERABLE_DEPENDENCY, or OTHER. Auto-classified by the parser when the scanner doesn't say it explicitly.
The id is a stable hash of (tool, file, line, rule_id) — so the same finding gets the same id across runs, which is how validate computes fixed_ids and still_present.
Privacy
- Scanner output stays on your machine. None of it is sent to the LLM unless a fix is being generated.
- Fix generation sends one finding's metadata plus ±12 lines of source context to the LLM, using your own API key.
- No telemetry, no usage tracking, no analytics.
Development
git clone https://github.com/FiNiX-GaMmA/yobitsugi
cd yobitsugi
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Verify the toolchain.
yobitsugi version
ruff check yobitsugi
mypy yobitsugi
pytest
Testing
The test suite is built around pytest and is designed to run hermetically — no real LLM calls, no real scanner binaries, no real network. External dependencies are mocked at well-defined seams (subprocess.run, requests.post, Path.home()).
Running the tests
pip install -e ".[dev]"
pytest # run everything
pytest -v # verbose
pytest tests/test_parse.py # one file
pytest tests/test_parse.py -k bandit # one matching test
pytest --tb=short # shorter tracebacks
pytest -x # stop at first failure
What's covered
| Suite | Module under test | What it asserts |
|---|---|---|
tests/test_detect.py |
yobitsugi.core.detect |
Language counting, directory pruning, large-file skip, symlink handling, CLI output. |
tests/test_parse.py |
yobitsugi.core.parse |
Severity normalisation, vuln-type classification, stable make_id, the unified finding() constructor, bandit/semgrep/safety parsers, deduplication, malformed-input handling. |
tests/test_apply.py |
yobitsugi.core.apply |
The CANNOT_FIX sentinel, diff file extraction, dirty-tree refusal, backup creation, applied.json logging, full rollback round-trip, failure modes. |
tests/test_llm.py |
yobitsugi.core.llm |
Provider resolution precedence (kwargs → env → file → autodetect), missing-key errors, request-shape correctness for OpenAI/Anthropic, error propagation for non-OK / non-JSON responses. |
tests/test_pipeline.py |
yobitsugi.core.pipeline |
Stage ordering, severity filtering, --skip-tests, per-finding LLM-error resilience, empty-diff handling, CLI → run_pipeline plumbing. |
tests/test_cli.py |
yobitsugi.cli |
Every subcommand: version, list-platforms, detect-platforms, config, findings, run, positional shortcut, help fallthrough. |
tests/test_installers.py |
yobitsugi.installers.* |
Registry completeness, get_installer lookup, InstallResult.__str__, install→uninstall round-trip for every supported assistant. |
The test suite runs in ~2 seconds locally and against Python 3.11 / 3.12 / 3.13 in CI on every push and pull request.
Test layout
tests/
├── conftest.py # shared fixtures: tmp_repo, tmp_workspace, fake_home, sample_finding, ...
├── test_detect.py
├── test_parse.py
├── test_apply.py
├── test_llm.py
├── test_pipeline.py
├── test_cli.py
└── test_installers.py
Configuration lives in pyproject.toml under [tool.pytest.ini_options]. Linting + type checking are configured under [tool.ruff] and [tool.mypy].
Writing new tests
- Put pure, parameterised assertions on the smallest unit you can.
- For anything that would call subprocess, network, or the filesystem outside a
tmp_path, use the existing fixtures ormonkeypatch. - New scanner parser? Add a fixture of sample raw input + assert against the unified Finding shape (see
TestBanditParser/TestSemgrepParserfor the pattern). - New LLM provider? Add an analogue of
test_chat_openai_request_shapethat asserts the request URL, headers, and body shape.
Releasing
Release policy: every push to
mainis a release. The workflow finds the latestvX.Y.Ztag, bumps the patch component, creates the new tag, builds, and publishes to both GitHub Releases and PyPI. There is no separate "version bump" step — version metadata lives in git tags, not in source files.
This is wired through hatch-vcs: at build time, the package's version is read from the latest git tag. pyproject.toml declares dynamic = ["version"] rather than a static value; yobitsugi/__init__.py reads __version__ from a generated yobitsugi/_version.py (the file is gitignored — hatch-vcs writes it during pip install / python -m build).
Day-to-day flow
git commit -m "Fix the thing"
git push origin main
That's the whole release procedure. The workflow takes over:
- prep — finds the latest
vX.Y.Ztag (e.g.v0.1.4), computes the next patch (v0.1.5). - tag — creates and pushes
v0.1.5. - build — checks out at
v0.1.5, builds an sdist + wheel; hatch-vcs stamps the artifacts as0.1.5. Validates withtwine check --strict. - release — creates a GitHub Release at
/releaseswith the wheel + sdist attached and notes pulled from the matching## 0.1.5section of CHANGELOG.md (falls back to auto-generated notes from commits). - publish — uploads to https://pypi.org/project/yobitsugi/ through the gated
pypienvironment.
Workflow jobs
| Job | When it runs | What it does |
|---|---|---|
prep |
Always | Computes the next tag (latest vX.Y.Z + 1 patch). |
tag |
Always (skipped only when a v* tag was pushed directly) |
Creates and pushes the new tag. |
build |
After tag succeeds |
sdist + wheel via hatch-vcs, validated with twine check --strict. |
release |
After build (skipped on release: published events) |
Creates the GitHub Release with notes + assets. Tags containing -rc, -alpha, or -beta are marked as pre-releases. |
publish |
After build |
Uploads to PyPI through the pypi environment. |
Bumping minor or major
The workflow only auto-bumps the patch component. To cut a minor or major release, push the tag manually:
git tag v0.2.0
git push origin v0.2.0
The tag job is skipped (tag already exists), prep picks up that exact tag, everything else runs.
Trade-offs of this model
- Every push burns a PyPI version number. Even a typo fix in the README publishes a new release. PyPI versions are immutable.
- Version numbers have no semantic meaning.
0.1.47just means "the 47th push sincev0.1.0," not "47 patch-level fixes." - Every push costs ~2 minutes of CI.
- Upside: zero release ceremony. Push and you've shipped.
If those trade-offs stop making sense, switch the prep job back to "release only when pyproject.toml's version is new" — the prior model. The relevant section is roughly 15 lines.
Required secrets
| Secret | Required? | Value |
|---|---|---|
PYPI_TOKEN |
Yes | A PyPI API token starting with pypi- (create one at https://pypi.org/manage/account/token/) |
RELEASE_PAT |
Optional | A fine-grained Personal Access Token with contents: write on this repo. Only useful if you want the auto-pushed tag to also trigger any other workflows that watch tags. The release flow itself works without it. |
The workflow passes __token__ as the PyPI username, per PyPI's API-token convention — so you only need to manage one secret.
Note — PyPI no longer accepts raw account passwords for uploads.
PYPI_TOKENmust be an API token (the value starts withpypi-), not your account password.
Cut a release:
# Bump the version in pyproject.toml + yobitsugi/__init__.py, commit, then:
git tag v0.1.0
git push origin v0.1.0
Semantic versioning
yobitsugi follows SemVer 2.0.0. The current major (0.x) is the pre-stable line — minor versions may include breaking API changes. From 1.0.0 onwards, breaking changes will only land in major versions.
Python version lifecycle
| Python | Status |
|---|---|
| 3.11 | Supported (oldest supported) |
| 3.12 | Supported |
| 3.13 | Supported |
| ≤ 3.10 | Unsupported — pip install will refuse to install. |
When a Python version reaches end-of-life upstream, support is dropped in the next minor release of yobitsugi.
Contributing
Issues, PRs, and new scanner/installer/provider plug-ins are welcome.
- Fork the repo + create a branch.
pip install -e ".[dev]".- Run
ruff check yobitsugi,mypy yobitsugi, andpytestbefore pushing. - Open a PR against
main. CI will run lint + type-check + tests on Python 3.11 / 3.12 / 3.13.
See ARCHITECTURE.md for where to add new scanners, installers, or LLM providers — most additions are config-only.
License
yobitsugi is distributed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yobitsugi-0.1.3.tar.gz.
File metadata
- Download URL: yobitsugi-0.1.3.tar.gz
- Upload date:
- Size: 67.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec4a0ea796c366905785701fa9eee83fd3ddae23e9a1ac10bcea654e62468562
|
|
| MD5 |
b32f0a69059d4ca6cdba06cfcc934e38
|
|
| BLAKE2b-256 |
2fe3d97f53d6c66ac83abc7fca0dcc61a70d750c2c42d31666fcedf5e1746a5a
|
File details
Details for the file yobitsugi-0.1.3-py3-none-any.whl.
File metadata
- Download URL: yobitsugi-0.1.3-py3-none-any.whl
- Upload date:
- Size: 80.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5dc2d28f154e92e03e096972cf71355946c933c0e19693348aec6994aad547af
|
|
| MD5 |
69ced90029a5e71dbcf94d4709204bc7
|
|
| BLAKE2b-256 |
ee5635ab299b67d4bbedaf3c209b49e35f675017c944fbc17974876a59812d37
|