Skip to main content

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.

PyPI version Python versions License: MIT Stars GitHub followers

CI Release & Publish Code style: ruff Type-checked: mypy Tests: pytest


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.json log, the regression test, and the re-scan all keep the join visible and accountable.


Table of Contents


What is yobitsugi?

yobitsugi is a CLI and an AI coding assistant skill that:

  1. Detects the languages in a repo.
  2. Scans it with real, industry-standard SAST and SCA tools (bandit, semgrep, gosec, brakeman, npm audit, pip-audit, trufflehog, and more).
  3. Normalises every scanner's output into a single Finding schema.
  4. Patches each finding by asking an LLM (your provider, your key) to generate a unified diff.
  5. Applies that diff safely — with backups, a rollback log, and a dirty-tree guard.
  6. Generates a regression test that would have caught the original bug.
  7. 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 id so re-runs compute true fixed_ids / still_present / newly_introduced sets.
  • 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.


Ephemeral tools mode

Both yobitsugi run and yobitsugi scan accept --ephemeral-tools. With that flag:

  1. yobitsugi creates a fresh temp directory (under the OS temp root) and redirects its managed venv there for the duration of the command.
  2. It detects the repo's languages before writing anything to the workspace and installs only the pip scanners the repo actually needs — semgrep won't be pulled in for a Bash-only repo, bandit won't be pulled in for a Go-only repo.
  3. The full pipeline (or scan-only) runs against the throwaway venv. PATH is prepended automatically so the temp scanners shadow anything on the system.
  4. When the command exits — successfully, with an error, or via Ctrl-C — the temp directory is deleted in a finally block. Your home dir's ~/.yobitsugi/tools/ is never touched.

This is the recommended path for slash-command invocations (/yobitsugi . inside any assistant), one-off audits, and CI jobs that don't want to leave state behind. The persistent yobitsugi install-scanners flow is still the right choice for developer workstations where you'd rather pay the install cost once.

yobitsugi scan ./services/api --ephemeral-tools     # one-shot scan, no leftover venv
yobitsugi run ./services/api --ephemeral-tools      # full pipeline, cleaned up at the end

Under the hood:

Piece Where What it does
--ephemeral-tools flag yobitsugi run, yobitsugi scan Opt in to the ephemeral venv path for a single invocation.
tools.ephemeral_tools_dir() yobitsugi/core/tools.py Context manager that swaps TOOLS_DIR / VENV_DIR / MANIFEST_PATH to a fresh tempfile.mkdtemp() and shutil.rmtrees it on exit.
tools.install_missing_pip_scanners(registry, languages=...) yobitsugi/core/tools.py Programmatic equivalent of install-scanners, scoped to the languages the repo actually has.
_with_optional_ephemeral_tools() yobitsugi/cli.py Wraps the body of cmd_run / cmd_scan: detect languages → install only the pip scanners the repo needs → run pipeline → tear down temp venv on the way out.
_quick_detect_languages() yobitsugi/cli.py Calls detect.detect() directly so the pre-install language sniff doesn't touch the real workspace before the pipeline does.

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 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 . --ephemeral-tools                   # install scanners into a temp venv, delete it after the run

yobitsugi scan ./services/api                    # scan-only, no fixes, no LLM
yobitsugi scan ./services/api --ephemeral-tools  # scan in a throwaway venv that's deleted on exit
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-dirty is set.
  • Every modified file gets a .yobitsugi.bak sibling before the patch is applied.
  • An applied.json rollback log records every patch — yobitsugi rollback restores 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.
  • validate re-runs the full scan after fixes and flags newly_introduced findings — 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 or monkeypatch.
  • New scanner parser? Add a fixture of sample raw input + assert against the unified Finding shape (see TestBanditParser / TestSemgrepParser for the pattern).
  • New LLM provider? Add an analogue of test_chat_openai_request_shape that asserts the request URL, headers, and body shape.

Releasing

Release policy: every push to main is a release. The workflow finds the latest vX.Y.Z tag, 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:

  1. prep — finds the latest vX.Y.Z tag (e.g. v0.1.4), computes the next patch (v0.1.5).
  2. tag — creates and pushes v0.1.5.
  3. build — checks out at v0.1.5, builds an sdist + wheel; hatch-vcs stamps the artifacts as 0.1.5. Validates with twine check --strict.
  4. release — creates a GitHub Release at /releases with the wheel + sdist attached and notes pulled from the matching ## 0.1.5 section of CHANGELOG.md (falls back to auto-generated notes from commits).
  5. publish — uploads to https://pypi.org/project/yobitsugi/ through the gated pypi environment.

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.47 just means "the 47th push since v0.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_TOKEN must be an API token (the value starts with pypi-), 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.

  1. Fork the repo + create a branch.
  2. pip install -e ".[dev]".
  3. Run ruff check yobitsugi, mypy yobitsugi, and pytest before pushing.
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yobitsugi-0.1.4.tar.gz (82.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yobitsugi-0.1.4-py3-none-any.whl (86.2 kB view details)

Uploaded Python 3

File details

Details for the file yobitsugi-0.1.4.tar.gz.

File metadata

  • Download URL: yobitsugi-0.1.4.tar.gz
  • Upload date:
  • Size: 82.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for yobitsugi-0.1.4.tar.gz
Algorithm Hash digest
SHA256 89de9e6424bd62529cb042cc79fb0749011cee992377874d0f3f50fa4cfb1397
MD5 84b485f1adc16d07057b21fec1ea176f
BLAKE2b-256 6764601243257c65331b9c7fdf1e7a88a5ad42ad213cafe103f58dd00affde50

See more details on using hashes here.

File details

Details for the file yobitsugi-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: yobitsugi-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 86.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for yobitsugi-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 d8e2655f578f3f42980d306909653e9a83f8543bd9ff1bf99b790adfb7996b23
MD5 aec57aa61f96c63ec4f135ccd2b6bde5
BLAKE2b-256 8aa719b34c012c157653a0463cfb9bbc6de6fd93d0a17321e0bbeda4ffc78b15

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page