Learn your coding style from Git commits and share it across agents.
Stylus watches how you revise, narrow, or correct the code that an agent produced. After each commit it compares your changes with the latest recorded agent change on the same branch, extracts coding-style preferences, and writes them into a local skill that agents read.
The result: agents you use — Codex, Cursor, ZCode, Claude — gradually write code that looks more like yours before you have to correct it.
- How it works
- Features
- Requirements
- Installation
- Quick start
- Commands
- Configuration
- Analyzer
- Uninstall
- Development
- Contributing
- License
Agent edits ──▶ stylus record (capture baseline diff)
│
▼
you review & revise the working tree
│
▼
git commit (your correction)
│
▼
post-commit hook ──▶ stylus analyze
│
▼
compare your commit vs. recorded agent change
│
▼
update ~/.stylus skill preferences (shared)
│
▼
next agent session reads the skill and adapts
- After an agent finishes its changes, Stylus automatically runs
stylus record. Stylus captures the working-tree diff as the baseline for that branch. - You review, revise, and
git commit. Thepost-commithook startsstylus analyze --commit HEAD --backgroundand returns without waiting for the analyzer. - Stylus compares your commit against the recorded baseline, asks the configured analyzer to extract reusable preferences, and merges them into the stylus skill under the installed agents.
If your commit reproduces the agent baseline verbatim, Stylus skips analysis
(nothing to learn) and records a skipped result.
- Learns from corrections, not from commands — no manual rule authoring.
- One skill, four agents — a single source of truth synced to Codex, Cursor, ZCode, and Claude.
- OpenAI-compatible — supports the official OpenAI Responses API and any OpenAI-compatible Chat Completions endpoint (DeepSeek, OpenRouter, …).
- Custom analyzer — swap in any program via
STYLUS_ANALYZER_CMD. - Ignore noise — exclude secrets, lock files, and generated artifacts from
learning; reuses your repo's
.gitignoreplus an optional global regex file. - Non-invasive — state lives under
~/.stylus, never inside your repo, so it can never be accidentally committed. The hook is non-blocking and never discards a commit. - Fully uninstallable —
stylus uninstall allcleanly removes the skill, hook, and Git config.
- Python 3.11+
- Git (available on
PATH) - Optional: an
openai-compatible API key for LLM-based analysis
Stylus is a pure-Python package with a single runtime dependency
(openai>=1.40).
# from source
git clone https://github.com/jyz0309/stylus.git
cd stylus
pip install .Verify it is on PATH:
stylus --helpSet up Stylus once per machine:
stylus install skill # install the skill for codex, cursor, zcode, and claude
stylus install hook # install the global post-commit hook
stylus install config # point Git's core.hooksPath at the hookThen use it in any repository:
# 1. An agent finishes editing your working tree (leave changes unstaged).
stylus record --summary "agent changed the helper"
# 2. You review, revise, and commit your correction.
git commit -m "correction"
# ^ the post-commit hook starts analysis in the backgroundThat's it. The next agent session in that repo will read the updated preferences.
stylus <command> [options]
Commands:
init Prepare the current Git repository for Stylus
install skill Install/update the skill for codex, cursor, zcode, and claude
install hook Install the global Stylus Git post-commit hook
install config Configure Git to use the global Stylus hook path
uninstall skill Remove the skill from codex, cursor, zcode, and/or claude
uninstall hook Remove the Stylus block from the global post-commit hook
uninstall config Unset Git's core.hooksPath if it points at Stylus
uninstall all Remove skill + hook + config in one step
record Record the latest agent-produced diff as a baseline
analyze Analyze a committed revision against the baseline
status Show installation, baseline, and learned-preference summary
list Print the learned coding-style preferences
Creates or updates the Stylus skill for codex, cursor, zcode, and claude at once. Codex is the single source of truth (the analyzer only updates it); the others are synced from it on every install.
$ stylus install skill
Installed Stylus skill at:
codex: /home/you/.codex/skills/stylus
cursor: /home/you/.cursor/skills/stylus
zcode: /home/you/.agents/skills/stylus
claude: /home/you/.claude/skills/stylus
Default target directories:
| target | default path | override env var |
|---|---|---|
| codex | ~/.codex/skills |
CODEX_HOME |
| cursor | ~/.cursor/skills |
STYLUS_CURSOR_SKILLS_ROOT |
| zcode | ~/.agents/skills |
STYLUS_ZCODE_SKILLS_ROOT |
| claude | ~/.claude/skills |
STYLUS_CLAUDE_SKILLS_ROOT |
Install only specific targets:
stylus install skill --target cursor --target zcodeinstall hook writes the global hook file at
~/.config/stylus/git-hooks/post-commit. install config sets Git's global
core.hooksPath to that directory. The hook is non-blocking: it only waits
for the background process to start, then returns. Analysis output and failures
are appended to ~/.stylus/analysis.log and never discard the commit.
Captures the current working-tree diff as the agent baseline for the current repository + branch. The agent must leave its edits unstaged — Stylus refuses to record if changes are already staged, so the baseline reflects what the agent actually produced rather than a partial commit.
stylus record --summary "agent changed the helper"
stylus record --summary "refactor api client" --task "migrate to v2 sdk"Compares a commit against the latest recorded baseline and updates preferences. Normally invoked by the hook, but you can run it manually:
stylus analyze --commit HEAD
stylus analyze --commit HEAD --debug # show provider, input sizes, and output
stylus analyze --commit HEAD --background # start analysis and return immediatelyBackground mode resolves HEAD to its commit SHA before starting the detached
process, so a later commit cannot change which revision is analyzed. Its output
is appended to ~/.stylus/analysis.log.
Prints a full status report in one place: which agent targets have the skill
installed, whether the global post-commit hook and core.hooksPath are
configured, the last recorded baseline for the current branch (with analysis
counts), and how many preferences have been learned.
stylus statusRun it any time Stylus seems quiet - it is the fastest way to confirm the learning loop is wired up and to see how many preferences it has accumulated.
Prints the learned coding-style preferences from the codex skill (the single source of truth), grouped by topic with confidence tags. Use it to review what Stylus has learned without opening the skill files manually.
stylus listStylus is configurable entirely through environment variables — no config file required.
| variable | purpose | default |
|---|---|---|
STYLUS_HOME |
Root directory for state, diffs, and the evidence log | ~/.stylus |
OPENAI_API_KEY |
Enable the OpenAI LLM analyzer | (unset → local analyzer) |
STYLUS_OPENAI_MODEL |
Model name for the OpenAI analyzer | gpt-5.5 |
STYLUS_OPENAI_BASE_URL |
OpenAI-compatible base URL | https://api.openai.com/v1 |
STYLUS_ANALYZER_CMD |
External analyzer command (highest priority) | (unset) |
STYLUS_MAX_DIFF_BYTES |
Per-diff byte limit sent to the analyzer | 200000 |
CODEX_HOME |
Override the codex skills root | ~/.codex |
STYLUS_CURSOR_SKILLS_ROOT |
Override the cursor skills root | ~/.cursor/skills |
STYLUS_ZCODE_SKILLS_ROOT |
Override the zcode skills root | ~/.agents/skills |
STYLUS_CLAUDE_SKILLS_ROOT |
Override the claude skills root | ~/.claude/skills |
Some files should never feed Stylus' style learning - secrets, lock files,
generated artifacts, vendored code. Stylus merges two sources of ignore rules
and applies them to both the recorded agent baseline (stylus record) and
the analyzed commit (stylus analyze), so the two sides always compare on the
same footing.
~/.stylus/ignore(global, cross-repository): each non-empty, non-comment line is a Python regular expression (matched withre.search), tested against both the path relative to the repository root (src/pkg/mod.py) and the bare file name (mod.py). Use this for concerns that span repos (e.g.\.env$,\.pem$).- The repository's own
.gitignore: matched by Git itself viagit check-ignore --no-index. You do not configure anything extra - Stylus reuses your existing ignore rules. Because--no-indexis used, patterns apply to already-tracked files too (Git normally exempts tracked files from.gitignore, which would let e.g. a committedapp.logleak into the Stylus diff despite a*.logpattern).
A path matching either source is excluded. Invalid regex lines in
~/.stylus/ignore are skipped with a warning rather than aborting the run. When
neither a global ignore file nor a .gitignore exists, behavior is unchanged -
every file is captured.
Example ~/.stylus/ignore (for rules not already covered by .gitignore):
# secrets not caught by .gitignore
\.pem$
# by filename anywhere
minified\.js$
Stylus picks an analyzer automatically based on the environment. Run
stylus analyze --debug to see which one was selected.
When no OPENAI_API_KEY and no STYLUS_ANALYZER_CMD are set, Stylus uses a
deterministic local analyzer. The learning loop runs end-to-end with no
network access — useful for trying Stylus out or running in air-gapped
environments.
Set OPENAI_API_KEY to enable LLM-based analysis:
export OPENAI_API_KEY="sk-..."
export STYLUS_OPENAI_MODEL="gpt-5.2" # optional
export STYLUS_OPENAI_BASE_URL="https://api.openai.com/v1" # optionalStylus selects the API endpoint automatically:
- OpenAI official (
api.openai.com) → Responses API (/responses) with Structured Outputs (json_schemastrict mode). - Any other provider (DeepSeek, OpenRouter, …) → Chat Completions API
(
/chat/completions) withresponse_format={"type":"json_object"}. The full JSON schema and an example are embedded in the system prompt, so this works with endpoints that only supportjson_objectmode (e.g. DeepSeek) and does not require Structured Outputs.
For a fully custom analyzer, set STYLUS_ANALYZER_CMD. This takes priority
over OPENAI_API_KEY:
export STYLUS_ANALYZER_CMD='python3 /path/to/custom-stylus-analyzer.py'Stylus sends one JSON object on stdin:
{
"repo_id": "/path/to/repo",
"branch": "main",
"commit": "git-sha",
"baseline_change_id": "baseline-id",
"baseline_diff": "...",
"user_diff": "...",
"current_preferences": "..."
}The command must print analyzer JSON on stdout. The built-in OpenAI analyzer uses this same schema:
{
"preferences": [
{
"topic": "change scope",
"instruction": "Prefer small, localized changes when correcting agent output.",
"confidence": "medium",
"evidence": "User commit narrowed the previous agent diff.",
"source_commit": "git-sha"
}
],
"obsolete_preferences": [],
"notes": []
}The SDK-facing response constraint is exported from stylus.analyzer:
from openai import OpenAI
from stylus.analyzer import ANALYZER_RESPONSE_TEXT_FORMAT
client = OpenAI()
response = client.responses.create(
model="gpt-5.2",
input="...",
text={"format": ANALYZER_RESPONSE_TEXT_FORMAT},
)For Chat Completions callers, use ANALYZER_CHAT_RESPONSE_FORMAT as
response_format.
Remove Stylus integration pieces individually or all at once:
stylus uninstall skill # remove skill from codex, cursor, zcode, claude
stylus uninstall skill --target cursor # remove only one target
stylus uninstall hook # remove Stylus block from global post-commit hook
stylus uninstall config # unset Git core.hooksPath if it points at Stylus
stylus uninstall all # remove skill + hook + config in one stepuninstall skilldeletes thestylus/skill directory from each target (or just the ones named with--target).uninstall hookstrips the Stylus block from~/.config/stylus/git-hooks/post-commit, leaving any other hook content intact.uninstall configonly unsetscore.hooksPathif it currently points at the Stylus hooks directory, so unrelated Git config is never touched.~/.stylusstate and evidence are not removed by uninstall; delete that directory manually if you want a full clean slate.
Stylus is a standard Python package using src/ layout.
git clone https://github.com/jyz0309/stylus.git
cd stylus
pip install -e . pytest
pytest # run the full test suiteContributions are welcome! Please open an issue first to discuss any change larger than a typo.
- Fork the repository and create a feature branch.
- Run
pytestand make sure all tests pass. - Keep changes focused and add tests for new behavior.
- Open a pull request with a clear description of the change and the motivation behind it.
By contributing, you agree that your contributions will be licensed under the MIT License.
Released under the MIT License. © Stylus Contributors.