Metadata-Version: 2.4
Name: cbyzt-ycode
Version: 1.0.0
Summary: YCode - a professional text-first coding agent powered by DeepSeek
Author: cbyzt
License-Expression: MIT
Project-URL: Homepage, https://github.com/cbyzt/ycode
Project-URL: Repository, https://github.com/cbyzt/ycode
Keywords: ai,agent,cli,deepseek,coding-assistant
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai>=1.40
Requires-Dist: rich>=13.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: httpx<1,>=0.23
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: mypy>=1.11; extra == "dev"
Requires-Dist: opencc-python-reimplemented>=0.1.7; extra == "dev"
Provides-Extra: frontend
Requires-Dist: playwright>=1.45; extra == "frontend"
Provides-Extra: release
Requires-Dist: build>=1.2; extra == "release"
Requires-Dist: twine>=5; extra == "release"
Provides-Extra: all
Requires-Dist: pytest>=8; extra == "all"
Requires-Dist: ruff>=0.6; extra == "all"
Requires-Dist: mypy>=1.11; extra == "all"
Requires-Dist: opencc-python-reimplemented>=0.1.7; extra == "all"
Requires-Dist: playwright>=1.45; extra == "all"
Requires-Dist: build>=1.2; extra == "all"
Requires-Dist: twine>=5; extra == "all"
Dynamic: license-file

# YCode

YCode is a professional command-line coding agent built for verifiable work:
plan, edit, check, review, diagnose, and roll back from one PowerShell session.
It is optimized for text-first models such as DeepSeek: browser/layout facts are
measured into text reports, not guessed from screenshots.

- **Confined tool loop**: read/search tools run freely; writes, shell commands,
  background processes, and file moves ask first (`y`/`n`/`a`).
- **Plan and critic gate**: non-trivial tasks produce a written plan on the
  stronger `pro` route; a critic reviews it before execution.
- **Verification loop**: after file changes, `.ycode/checks` runs automatically
  and failures are fed back until green or bounded retries are exhausted.
- **Frontend diagnostics for text models**: static HTML and discovered dev
  servers are opened with Playwright, measured, and reported as actionable text
  in `.ycode/frontend-diagnostics.md`.
- **Review, rollback, and reports**: current diffs can be reviewed read-only;
  task snapshots support `/revert` and `/revert-task`; closure artifacts are
  written under `.ycode/`.
- **Doctor and evals**: `/doctor --release` checks packaging, environment, and
  standard gates; `/eval --recorded` scores real task records from
  `.ycode/eval-runs.jsonl`.
- **Release-ready reports**: frontend diagnostics, doctor output, and task
  closure reports use the same Summary / Blocking / Warnings / Evidence / Next
  Action structure.
- **Extensible runtime**: skills, durable memory, subagents, background
  processes, web fetch, and MCP tools can be composed without leaving the CLI.

## Install

Requires Python 3.11+ and a DeepSeek API key.

**For users** - pick one:

```powershell
# Recommended: isolated global install via pipx
pipx install cbyzt-ycode

# Or plain pip
pip install cbyzt-ycode

# Or straight from GitHub (no PyPI needed)
pipx install git+https://github.com/cbyzt/ycode

# Or from a wheel file someone sent you
pip install cbyzt_ycode-1.0.0-py3-none-any.whl
```

**For development** - from a clone of the repo:

```powershell
pip install -e .            # editable install
pip install -e ".[dev]"     # + pytest, ruff, mypy
```

### Checks

The project's checks live in `.ycode/checks` (one shell command per line). Run
them all before pushing:

```powershell
ruff check .     # lint
mypy ycode       # type-check
pytest -q        # tests (the model client is stubbed - no API key or network)
```

YCode also runs these **automatically after it changes files** - the
verification loop feeds any failure back to the model to fix (see below). Drop a
`.ycode/checks` file into any project to enable the same for your own repo; with
no such file, verification simply does not run.

### Set your API key

YCode needs a DeepSeek API key (get one at https://platform.deepseek.com). It is
read from the `DEEPSEEK_API_KEY` environment variable:

```powershell
$env:DEEPSEEK_API_KEY = "sk-..."     # this shell only
setx DEEPSEEK_API_KEY "sk-..."       # persist across shells (reopen the terminal)
```

## Run

```powershell
ycode                      # interactive REPL (or: python -m ycode)
ycode "add a /health route"   # one-shot: run one task, then exit
ycode "fix the failing test" -y   # -y auto-approves file/shell actions
ycode --model pro "..."    # pick the model for this run
```

Type a request in plain language. YCode will read files, propose edits, and run
commands to accomplish it. Read-only actions run automatically; anything that
writes files or runs a shell command asks for confirmation first (`y`/`n`/`a`).

## Commands

| Command            | Description                                   |
| ------------------ | --------------------------------------------- |
| `/help`            | List commands and skills                      |
| `/plan <task>`     | Force the plan-first workflow for a task      |
| `/skip`            | Skip planning for your next task              |
| `/plan-mode on\|off` | Toggle whether tasks plan first by default  |
| `/verify [on\|off]` | Run project checks now, or toggle auto-verify |
| `/review [focus]`  | Read-only review of the current git diff      |
| `/preflight [focus]` | Review + checks + security lint readiness gate |
| `/doctor [--clean [--apply]\|--release]` | Check runtime/repo/release health |
| `/eval [--recorded]` | Score built-in, JSON, or recorded task evals |
| `/frontend-diag [--vision] <url\|file>` | Browser-derived frontend diagnostics (`--vision` adds a visual review) |
| `/resume`          | Resume pending plan steps from `.ycode/tasks.md` |
| `/revert`          | Undo file edits from the last turn                |
| `/revert-task [--dry-run]` | Preview or undo current task edits       |
| `/clear`           | Clear the conversation (keeps the system prompt) |
| `/compact`         | Summarize older turns to free up context      |
| `/memory`          | Show what YCode remembers across sessions     |
| `/forget <name>`   | Delete a stored memory by name                |
| `/mcp`             | Show connected MCP servers and their tools    |
| `/status`          | Show plan/eval/frontend/release/verify/rollback state |
| `/reports [latest\|<kind>\|--json\|--kind K\|--status S]` | List or inspect indexed YCode reports |
| `/diagnostics [--json\|--source S\|--severity S]` | Show non-blocking degradation events |
| `/gate-stats [--json]` | Finding rate per decision layer (critic/routing/verify/review) |
| `/model [flash\|pro]` | Show or switch model                       |
| `/reload`          | Hot-reload tools, skills & prompt (development) |
| `/init`            | Scan the project and generate `YCODE.md`      |
| `/exit`            | Quit                                          |

**Copying output.** Drag with the mouse to select text in the output area and it
is copied on release — drag against the top or bottom edge and the view scrolls,
so a selection can run past a single screenful. For whole-chunk copies there are
two commands: `:copy` takes the entire output, and `:copycmd` takes just the
command a handoff is waiting on (useful when it is wider than the pane).

`/frontend-diag` is built for text-only models: if Playwright is installed, it
opens the page, measures layout/DOM issues such as overflow, clipped content,
small tap targets, mobile input zoom risk, interactive overlap, and console
errors, then writes a text report to `.ycode/frontend-diagnostics.md`. Screenshots
are saved under `.ycode/artifacts/` for human inspection. Frontend tasks attempt
this gate automatically: static HTML is opened directly; package.json dev
servers can be started, probed on local ports, diagnosed, and stopped.

`/frontend-diag --vision` adds what DOM measurement cannot reach: it sends the
screenshots it just captured to a vision-capable model and asks whether the page
actually *looks* right — visual hierarchy, spacing rhythm, typographic scale,
colour harmony, overall polish. Findings come back as ordinary `visual` issues in
the same report. This is opt-in because vision tokens are spent per image, and it
requires a vision-capable second backend: set `YCODE_PRO_*` (see **Models**) plus
`YCODE_PRO_VISION=on`. Without one it says so and reviews nothing — no image is
ever sent to a text-only model.

**Show the agent a picture.** `read_file` on an image (PNG/JPEG/GIF/WEBP/BMP,
detected by content rather than by extension) attaches it to the conversation as a
real image, so the model can look at a screenshot, a mock, a diagram or an error
popup instead of being told about it in words. This needs a backend that accepts
images: declare `vision` in its `caps` (`[providers.<name>]` in `models.toml`, or
`YCODE_PRO_VISION=on`), and route to it (`YCODE_MODEL_PRO=`, or make it the session
model). With no such backend the read still succeeds but says the image was NOT
sent — a text-only model is never handed pixels it cannot see. `encoding=hex` is
unchanged and remains the way to inspect an image's raw bytes without any vision
backend at all.

`/doctor --clean` is dry-run only: it proposes local cleanup actions such as
cache/build directories and inactive YCode runtime artifacts. Add `--apply` only
after reviewing the proposed list. `/doctor --release` writes
`.ycode/doctor-release.md` and checks release metadata, Python/tool environment,
and the standard `ruff`, `mypy`, and `pytest` gates.

`/eval --recorded` reads `.ycode/eval-runs.jsonl`, the local ledger written after
completed file-changing tasks. Use `/eval cases.json --recorded` to score real
task runs against expected files, forbidden files, and required checks.

`/reports` reads `.ycode/reports/index.json`, lists recent report sidecars, and
can show the latest report, a matching kind/path, filtered kind/status lists, or
JSON for automation.

`/diagnostics` reads `.ycode/diagnostics.jsonl` and surfaces best-effort failures
that did not interrupt the turn, such as optional MCP, formatter, or report index
degradations.

## Workflows

**Fix a bug with gates**

```powershell
ycode
> /plan fix the failing auth refresh test
> /status
> /review auth refresh
```

YCode plans read-only, executes one approved step at a time, runs `.ycode/checks`
after file changes, records the task report, and keeps rollback state.

**Build frontend with text diagnostics**

```powershell
ycode "polish the dashboard mobile layout"
> /frontend-diag http://localhost:5173/
> /status
```

For frontend tasks, YCode can discover static HTML or a package.json dev server,
run browser-derived diagnostics, and feed objective DOM/layout issues back to the
model without requiring vision.

**Prepare a release**

```powershell
> /doctor --release
> /preflight release readiness
> /eval --recorded
```

This checks packaging/version drift, environment health, standard gates, current
diff review, security lint, and recent task-run quality.

## Models

- `flash` -> `deepseek-v4-flash` (default: fast, cheap, 1M context)
- `pro` -> `deepseek-v4-pro` (frontier reasoning for hard tasks)

**The alias is stable; the model id behind it is configurable.** When DeepSeek
ships a new generation (`deepseek-v4-*` -> `deepseek-v5-*`) or renames a model,
you edit a file instead of patching the source. Put this at either or both of:

- `./.ycode/models.toml` — this project
- `~/.ycode/models.toml` — everywhere (the layer that follows you across repos)

The project file wins over the global one:

```toml
[models]
flash = "deepseek-v5-flash"
pro   = "deepseek-v5-pro"
```

Rules:

- **Aliases are fixed.** `[models]` may only re-point `flash`/`pro` (or another
  alias a provider declared); an unknown key is an error naming the ones that
  exist, not a silent no-op. To add a *new* backend, use `[providers.*]` below.
- Read at startup, and again on `/reload` — so editing the file and running
  `/reload` is enough; no restart. `ycode` prints `· from project` (or `global` /
  `env`) next to the model when an override is active, and `/model` lists them all.
- With no file and no env, the resolved table is byte-for-byte the built-in
  defaults.

The file can be overridden per shell when you want to try an id without editing:
`YCODE_MODEL_FLASH`, `YCODE_MODEL_PRO` (or the uppercase-id form
`YCODE_MODEL_DEEPSEEK_V5_FLASH`), and the combined
`YCODE_MODEL_OVERRIDES="flash=a,pro=b"`. Precedence, highest last: built-in
defaults -> global file -> project file -> `YCODE_PRO_*` -> env overrides.

### Second backend for hard steps (optional)

By default the "hard" steps - planning (PLAN phase) and verify-loop escalation -
run on `pro` (`deepseek-v4-pro`). You can route them to a **stronger second
model** on any OpenAI-compatible endpoint (OpenAI, OpenRouter, an
OpenAI-compatible gateway, another DeepSeek tier, etc.). Everyday turns stay on
DeepSeek `flash`; only the hard steps switch.

Declare the backend in `models.toml` (this is also the only way to add a *new*
alias beyond `flash`/`pro`), then point the hard steps at it:

```toml
[providers.pro2]
base_url = "https://openrouter.ai/api/v1"
key_env  = "YCODE_PRO_API_KEY"      # optional; implied from the name if omitted
model    = "anthropic/claude-opus-4-8"
caps     = ["text", "tools"]        # optional; the default
```

```powershell
$env:YCODE_PRO_API_KEY = "sk-..."
$env:YCODE_HARD_MODEL  = "pro2"     # route hard steps to it
```

The alias is the provider name (`pro2` here, or any name you choose). The key
comes from `key_env`; without it YCode tries `YCODE_<NAME>_API_KEY`, then
`YCODE_<NAME>_KEY`, then `YCODE_PRO_API_KEY`.

The older all-env form still works and is equivalent:

```powershell
$env:YCODE_PRO_BASE_URL = "https://openrouter.ai/api/v1"
$env:YCODE_PRO_API_KEY  = "sk-..."
$env:YCODE_PRO_MODEL    = "anthropic/claude-opus-4-8"
$env:YCODE_HARD_MODEL   = "pro2"
$env:YCODE_PRO_CAPABILITIES = "text,tools"   # optional; add vision if true
```

Rules:

- Leave the key unset -> that backend is simply not available (no error, and no
  alias appears for it).
- `YCODE_HARD_MODEL=pro2` while `pro2` isn't configured -> silently falls back to
  `pro`. Unset `YCODE_HARD_MODEL` -> hard steps stay on `pro`.
- The default path (no file, no env) is byte-for-byte the old single-DeepSeek
  behavior.
- **A new provider needs a restart** (each provider's client is built once at
  startup). Changing an existing alias's *model id* only needs `/reload`.
- The endpoint must be **OpenAI-compatible** (chat completions + streaming tool
  calls). By default YCode treats both DeepSeek and a second provider as
  text+tools models. Add `vision` to `caps` only when the backend can actually
  inspect images; otherwise frontend quality uses browser-derived text
  diagnostics instead of screenshot judging.

## Evals

`ycode.evals` provides a small deterministic harness for quality-gate regression
cases: expected changed files, forbidden files, required checks, tool-round count,
and token budget. It is intentionally model-agnostic so CI can score recorded or
stubbed runs without a real API key, while richer live-agent evals can build on
the same case/result schema.

Optional extras keep installs focused:

```powershell
pip install -e ".[frontend]"  # Playwright-backed frontend diagnostics
pip install -e ".[release]"   # build + twine release validation
pip install -e ".[all]"       # development, frontend, and release tooling
```

## CI and release checks

GitHub Actions runs lint, type-check, tests, and built-in eval samples on Python
3.11 and 3.12. The release-check workflow also runs the release doctor, builds
sdist/wheel artifacts, and validates them with Twine. See `docs/RELEASE.md` for
the local release gate.

## Skills

A skill is a directory with a `SKILL.md` file:

```yaml
---
name: my-skill
description: One line the model uses to decide relevance.
invocation: both      # slash | auto | both
kind: prompt          # prompt (inject body) | control (run Python)
---
Markdown instructions injected into the conversation when the skill runs.
```

Built-in skills live in `ycode/skills/builtin/`. Project skills are discovered
from `.ycode/skills/` in your working directory. `invocation` controls triggering:
typed `/name`, model-initiated via `load_skill`, or both.

## Tools

`read_file`, `write_file`, `edit_file`, `multi_edit`, `apply_patch` (multi-file
atomic patch), `rename_python_symbol`, `organize_python_imports`,
`list_python_symbols`, `find_references` (find a Python symbol's uses),
`move_file`, `ls`, `glob`, `grep`, `git_status`, `git_diff`, `git_log`,
`run_shell`, `run_tests` (run configured tests mid-task), `web_fetch` (fetch a
URL as text), `web_search` (opt-in; see below), `ask_user` (interactive arrow-key
selector), `load_skill`, `save_memory`, `forget_memory`, `spawn_agent`,
`spawn_agents`.

**Web search** is optional and off by default - the core install needs no extra
package or key for it. To enable it, set an API key for a supported backend and
YCode picks it up automatically (read fresh each call, so a mid-session `export`
works):

```powershell
$env:YCODE_SEARCH_API_KEY = "tvly-..."   # a Tavily key (https://tavily.com)
# optional; defaults to 'tavily' when only the key is set:
$env:YCODE_SEARCH_BACKEND = "tavily"
```

With no key configured, `web_search` returns a one-line setup hint and the model
falls back to `web_fetch` on known URLs. No vendor SDK is added - the backend is
called over the standard library.

**Background processes** - for dev servers, watchers, and anything that does not
exit on its own:

- `run_background` - start a command in the background and return immediately
- `list_processes` - list background processes and their status
- `read_output` - read a background process's captured output
- `stop_process` - stop a background process and its child processes

Background processes are all terminated automatically when YCode exits.

## Memory

YCode keeps durable facts across sessions and loads them into every new
conversation, so you don't repeat yourself. It saves them as one Markdown file
per fact, in two scopes:

- **project** - `.ycode/memory/` in your working directory (constraints and
  preferences specific to this repo)
- **global** - `~/.ycode/memory/` (facts about you that hold across all projects)

The model decides what's worth remembering (a stated preference, feedback on how
you want it to work, a non-obvious constraint) and calls `save_memory`; it won't
store things already in the code, git, or `YCODE.md`. Use `/memory` to see what's
stored and `/forget <name>` (or the `forget_memory` tool) to drop a fact.

## Subagents

For larger tasks, YCode can delegate to **subagents** - fresh agents with their
own context and the same tools, that run autonomously and report back a concise
summary, keeping the main conversation uncluttered.

- `spawn_agent` - one subagent (full tools) for a self-contained subtask.
- `spawn_agents` - several **read-only** research subagents in parallel, for
  independent investigation (inspect many files at once, research several
  questions). Each can only read (`read_file`/`ls`/`glob`/`grep`/`web_fetch`), so
  parallel runs never conflict; their reports come back together.

Subagents can't spawn further subagents or prompt you, so they always terminate.

## MCP servers

YCode is an [MCP](https://modelcontextprotocol.io) client: drop a
`.ycode/mcp.json` in your project and its servers' tools become available to the
model (as `mcp__<server>__<tool>`, gated by the same confirmation prompt).

```json
{
  "mcpServers": {
    "fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }
  }
}
```

Run `/mcp` to see connection status. Servers are started on launch and stopped on
exit.

## Context management

Long sessions never overflow the context window: YCode shows per-turn token usage
and, once the conversation grows past a threshold, automatically **compacts** it -
summarizing the older turns into one note while keeping the system prompt and the
most recent turns. Run `/compact` to trigger it manually.

## Plan-first & verification

Two structural guards bracket every non-trivial task:

- **Plan-first (before)** - the task first enters a read-only *planning* phase on
  the stronger `pro` model: YCode explores the code, writes a plan to
  `.ycode/plan.md`, a critic reviews it, and you approve it (`y`/`n`/`e` to edit)
  - only then does execution begin, with the plan as the spec. It's on by default;
  `/skip` bypasses it for one task and `/plan-mode off` disables it. `/plan <task>`
  forces it.
- **Verification (after)** - once a task has changed files, YCode runs the
  commands in `.ycode/checks` (one per line) and, if any fail, feeds the output
  back to itself to fix - looping until the checks pass or a few attempts are
  spent. With no `.ycode/checks` file it does nothing; `/verify` runs it on demand
  and `/verify off` disables the automatic pass.

Both exist because the model is stronger at bounded coding than at long-horizon
planning or one-shot correctness - so planning gets the stronger model plus your
review, and correctness is enforced by real checks the model can't skip.

## Steering

You don't have to wait for the agent to finish. **Type while it's working** and
your message is folded in at the next turn boundary - steer a long run ("also add a
test for that", "stop, do Y instead") without aborting it. You'll see a
`steering: ...` line when it's picked up. Ctrl-C still interrupts the current turn;
Ctrl-D quits.

## Building & publishing (maintainers)

Cutting a release is one command — it sets the version in every anchor, freezes the
changelog section, runs the full gate, builds, verifies the wheel in an empty
environment, commits and tags:

```powershell
python scripts/release.py 1.0.1            # dry run: reports every change, writes nothing
python scripts/release.py 1.0.1 --apply    # cut it
python scripts/release.py 1.0.1 --apply --upload   # ...and publish to PyPI
```

`--upload` reads `TWINE_PASSWORD` (a PyPI API token, with `TWINE_USERNAME=__token__`).
Dry run is the default, and the script refuses a lower version number, a tag that
already exists, an empty `[Unreleased]` section, and uncommitted changes — the cases
that are not recoverable once pushed. See `docs/RELEASE.md` for the same steps written
out, and for what each refusal is protecting.

To install YCode (users, or verifying a built wheel):

```powershell
python scripts/install.py                  # pip install from PyPI
python scripts/install.py --pipx           # isolated global install (recommended)
python scripts/install.py --from-clone     # editable dev install with extras
python scripts/install.py --wheel dist/cbyzt_ycode-1.0.0-py3-none-any.whl
python scripts/install.py --check          # verify an existing install, change nothing
```

It runs the CLI after installing, because an install that reports success and a `ycode`
that cannot start is the failure worth catching — and it refuses the Windows Store stub
interpreter, which installs fine and produces exactly that.

<details>
<summary>The same steps by hand</summary>

```powershell
# Build sdist + wheel into dist/
ruff check .
mypy ycode
pytest -q
python -m ycode.evals --builtin
python -m ycode.doctor --release
pip install build
python -m build
twine check dist/*

# Publish to PyPI (bump `version` in pyproject.toml first)
pip install twine
twine upload dist/*          # username __token__, password = your PyPI API token
```

Test the upload against TestPyPI first with `twine upload -r testpypi dist/*`.

</details>

### Standalone executable (no Python required)

```powershell
pip install pyinstaller
pyinstaller --onefile --name ycode ^
    --add-data "ycode/skills/builtin;ycode/skills/builtin" ^
    ycode/__main__.py
```

The `--add-data` flag is required - PyInstaller does not bundle the `SKILL.md`
files automatically. The result is `dist/ycode.exe`.
