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 PyPI downloads Python versions License

CI 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

For JS-native users (still needs Python 3.11+ available somewhere):

npx yobitsugi install
# the npm package is a thin shim that delegates to `uvx yobitsugi`

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.

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.


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 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

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

Auto-tagging, GitHub Release creation, and PyPI publishing are all automated by .github/workflows/publish.yml. The day-to-day flow is:

  1. Bump version in pyproject.toml and __version__ in yobitsugi/__init__.py to the new version.

  2. Move the items under ## Unreleased in CHANGELOG.md into a new ## <version> section.

  3. Commit and push to main.

    git commit -am "Release 0.1.1"
    git push origin main
    

That's it. The workflow takes over from here:

  • Notices the new version in pyproject.toml.
  • Verifies yobitsugi/__init__.py matches.
  • Creates and pushes a v<version> git tag.
  • Builds an sdist + wheel from that tagged commit, validates with twine check --strict.
  • Creates a GitHub Release at /releases with the wheel + sdist attached and notes pulled from the matching ## <version> section of CHANGELOG.md (falls back to auto-generated notes from commits).
  • Uploads the same artifacts to https://pypi.org/project/yobitsugi/ through the gated pypi environment.

Workflow jobs

Job When it runs What it does
prep Always Decides whether this push should release anything. Reads the version from pyproject.toml, compares against existing tags.
tag Only when pyproject.toml's version has no matching tag yet Verifies yobitsugi/__init__.py agrees, then creates and pushes v<version>.
build Whenever prep says to release sdist + wheel + twine check --strict.
release Whenever prep says to release (skipped if a GitHub Release already triggered the run) Creates the GitHub Release with notes + assets. -rc / -alpha / -beta tags are marked as pre-releases.
publish Whenever prep says to release Uploads to PyPI through the pypi environment.

Alternative triggers

You can still cut a release manually if you prefer:

  • Tag push: git tag v0.1.1 && git push origin v0.1.1. The tag job is skipped (tag already exists); everything else runs.
  • GitHub UI release: publish a release via the web UI. tag and release jobs are skipped; publish runs to push to PyPI.
  • workflow_dispatch: trigger it from the Actions tab. Uses the version currently in pyproject.toml.

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.0.tar.gz (53.1 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.0-py3-none-any.whl (65.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: yobitsugi-0.1.0.tar.gz
  • Upload date:
  • Size: 53.1 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.0.tar.gz
Algorithm Hash digest
SHA256 068720a7722d8ec6fb5eeb8301cf9a79df37a53e3de774813e58239f19d2b33f
MD5 b755a698082563c23d338f2d95b668b2
BLAKE2b-256 bd4026bedff1bfa06276b7ae9dbfff61b4b9a328db5177ee47610bb8531165a1

See more details on using hashes here.

File details

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

File metadata

  • Download URL: yobitsugi-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 65.0 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e7ea726dfc4645266850178c6ab9a14953b0f14a4f94c8b2ed872b572838da65
MD5 b2a372c90c71939e7e37ec8daca23a5f
BLAKE2b-256 fdcee1540642763669afca1f0c37b344a2443eeb8ecc7d19a20e0251d7e4a877

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