# TAUSIK — public guide for humans and AI systems > TAUSIK is a local open-source governance and verification framework that works on top of AI coding agents. > It turns each change into a task with a goal, scope, acceptance criteria, durable project memory and verification evidence. > Unlike an instruction file alone, TAUSIK keeps machine-readable state and can refuse to start or close a task when its contract is missing. > SENAR defines the delivery discipline. RENAR preserves the link from requirement to interpretation and decision. TAUSIK runs both in the repository. > Canonical site: https://tausik.tech/ --- ## English · /docs/quickstart **English** | [Русский](/ru/docs/quickstart) # Quick Start TAUSIK — **T**ask **A**gent **U**nified **S**upervision, **I**nspection & **K**nowledge. Step-by-step guide: from zero to your first task with an AI agent. Takes 10-15 minutes. No prior experience with AI tools required. > **Are you the agent?** Read [agent-quickstart.md](/docs/agent-quickstart) > instead — the same path as exact calls, with the replies and refusals you > will see, for every host bootstrap scaffolds. --- ## Step 0. What You'll Need Make sure the following are installed on your computer: 1. **Python 3.11 or newer** - Windows: download from [python.org](https://www.python.org/downloads/), check "Add to PATH" during installation. Alternative: `winget install Python.Python.3.13` - macOS: `brew install python@3.13` - Linux (Ubuntu/Debian): `sudo apt install python3.13` · Fedora: `sudo dnf install python3.13` - Verify: open a terminal and type `python --version` (should show 3.11+) - Bootstrap will automatically create an isolated virtual environment (`.tausik/venv/`) and install all required dependencies. Your system Python will not be modified. If a suitable Python is not found, bootstrap will show download instructions. 2. **Git** - Windows: download from [git-scm.com](https://git-scm.com/downloads) - macOS: `brew install git` - Linux: `sudo apt install git` - Verify: `git --version` 3. **Visual Studio Code** - Download from [code.visualstudio.com](https://code.visualstudio.com/) 4. **AI IDE — one of the following:** - **Claude Code** — VSCode extension or CLI: `npm install -g @anthropic-ai/claude-code` - **Cursor** — download from [cursor.com](https://cursor.com) - **Qwen Code (GigaCode)** — install from [qwen.ai/qwencode](https://qwen.ai/qwencode) - **Kilo Code** — VSCode addon; pairs with z.ai GLM models (see [Kilo + z.ai](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/kilo-zai.md)) - **OpenCode** — `npm i -g opencode-ai`; bootstrap with `--ide opencode` - **Codex CLI** — bootstrap with `--ide codex`; its hooks enforce only after you trust them in Codex (see [model-providers.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/model-providers.md#codex-enforcement-matrix)) - **Windsurf** — download from [windsurf.com](https://windsurf.com) (expected; not scaffolded by bootstrap) - You'll need an API key or subscription for your chosen IDE ## Step 1. Create a Repository Go to [github.com](https://github.com) (or GitLab, Bitbucket — any Git hosting). 1. Click **New Repository** 2. Name it, for example: `my-project` 3. Check "Add a README file" 4. Click **Create Repository** Now clone the repository to your computer: ```bash git clone https://github.com/your-username/my-project.git cd my-project ``` > If you already have a project — skip this step and navigate to your project folder. ## Step 2. Connect TAUSIK > **Already have a project with code?** See [Migrating an Existing Project](#migrating-an-existing-project) below — TAUSIK merges into your setup without overwriting anything. There are two ways — tell your AI agent, or do it manually. We recommend the first. ### Option A: Tell your agent (easiest) Open your project in the IDE, open your AI agent (Claude Code, Cursor, Qwen Code, Windsurf) and write: ``` Add https://github.com/Kibertum/tausik-core as a git submodule in .tausik-lib, run python .tausik-lib/bootstrap/bootstrap.py --init, add .tausik/ to .gitignore ``` The agent will execute all three steps — you just confirm its actions. ### Option B: Manually via terminal If you prefer doing everything by hand: ```bash # Connect TAUSIK as a Git submodule # (A submodule is a reference to another Git repository inside yours — # it keeps TAUSIK's code linked but separate from your project) git submodule add https://github.com/Kibertum/tausik-core .tausik-lib # Run bootstrap (one command does everything) # Project name is auto-derived from the directory name python .tausik-lib/bootstrap/bootstrap.py --init # Add working data to .gitignore echo ".tausik/" >> .gitignore ``` ### Restart Your IDE **After bootstrap, restart your IDE window** (Claude Code, Cursor, Qwen Code, Windsurf). Bootstrap generates project MCP configs (`.mcp.json` for Claude ecosystem and `.cursor/mcp.json` for Cursor), but IDEs load them only on startup. Without restart, the agent may fall back to CLI mode instead of using MCP tools. > **Qwen Code users:** Bootstrap also creates `.qwen/settings.json` with MCP config and `QWEN.md` with project instructions. Use `--ide qwen` if you only use Qwen Code, or `--ide all` for multi-IDE setups. ### What Happens After Bootstrap Regardless of which option you chose — the result is the same: - A `.tausik/` folder appears — database, scripts, configuration - A `.claude/` folder appears — skills and settings for Claude Code - A `CLAUDE.md` file appears — instructions for the AI agent - TAUSIK detects your project's stack (Python, React, Go, etc.) and enables appropriate checks **Two directories to understand:** - **`.tausik-lib/`** — the framework source code (git submodule, tracked in version control). This is TAUSIK itself. - **`.tausik/`** — runtime data: database, config, virtual environment. Added to `.gitignore` — this is local working data, never committed. Team members who clone the repo just need to run `git submodule update --init` to get the framework, then `python .tausik-lib/bootstrap/bootstrap.py --init` to set up their local `.tausik/`. `.claude/` and `CLAUDE.md` — keep these under version control. These are instructions for the agent, they should be in the repository. ## Step 3. Verify Installation ```bash .tausik/tausik status ``` > **Windows note:** This command requires Git Bash or WSL. If you're using cmd.exe or PowerShell, run `.tausik/tausik.cmd status` instead. If you see something like: ``` Tasks: 0/0 done Session: none Epics: 0 ``` Everything is working. TAUSIK is ready. ## Step 4. Open the Project in VSCode ```bash code . ``` VSCode will open with your project. In the side panel or at the bottom you'll see Claude Code — a chat with the AI agent. If Claude Code doesn't appear — check that the extension is installed (Ctrl+Shift+X → "Claude Code"). ## Step 5. Start a Session Write in the Claude Code chat: ``` start working ``` The agent will open a session, show the project status, and suggest what to work on. On the first run the project is empty — that's normal. ## Step 6. Create Your First Task Simply describe what needs to be done, in your own words: ``` create a landing page with the title "My Project" and a "Get Started" button ``` The agent will: 1. Create a task in the database 2. Formulate a goal and acceptance criteria (what counts as "done") — **including at least one error or boundary case**. TAUSIK's Context Gate (QG-0) requires it, and the agent supplies it for you (e.g. "the button stays disabled until the title is filled"). You don't have to write it; just know the agent will add one. 3. Start working — write code, create files You'll see the agent working: creating files, writing code, verifying everything works. You can intervene at any time — provide clarification, ask to change the approach. ## Step 7. Complete the Task When the agent finishes (or you decide the result is satisfactory), write: ``` done, ship it ``` The agent will: 1. Run **`tausik verify`** — heavy verification step (pytest, tsc, cargo, phpstan, etc.). May take minutes on large projects. The result is **cached**. 2. Verify that the acceptance criteria are met 3. Close the task via **`task done`** — lightweight step (milliseconds), looks up the fresh verify cache. 4. Offer to commit the changes Answer "yes" to the commit offer — and your first task is complete. If the agent's IDE times out long tool calls (the VS Code extension cuts at about 60 seconds), keep the two steps apart: `tausik verify --task ` first, then `task done`, which reads the cached result in milliseconds. Settings that change this flow — the size of the rules file, a terse output style, the host profile — are in **[Configuration](/docs/configuration#settings-met-in-the-quick-start)**. ## Step 8. Wrap Up When you're done working for the day: ``` that's all for today ``` The agent will save context: what was done, what's unfinished, what decisions were made. Next time you say "start working" — the agent will load this context and continue from where you left off. --- ## Full Work Cycle Here's what typical work with TAUSIK looks like after setup: ``` You: "start working" → agent opens a session You: "fix the bug — form doesn't submit" → agent creates a task, starts ... agent works ... You: "done, ship it" → agent verifies, commits You: "one more — add email validation" → next task ... agent works ... You: "done, ship it" → verify, commit You: "that's all for today" → agent saves context ``` Two or three messages per task. Everything else — automatic. --- ## Working With a Team `.tausik/` is gitignored — the database is yours. What your teammates need is the project's *knowledge*, and that travels in git as text: tasks, decisions and memory are exported to a readable `tausik/` tree that you commit alongside the code. ```bash tausik state export # DB → tausik/ (runs automatically on any durable write) git add tausik/ && git commit ``` After pulling someone else's work, bring it into your database: ```bash git pull tausik sync # tausik/ → DB, files win ``` A teammate who clones the repository gets the project's history rather than an empty database, and no external store has to exist for that to work. `tausik status` tells you when the tree and the database disagree. It reports the counts and names both directions — `tausik sync` (tree → DB) and `tausik state export` (DB → tree) — without recommending either, because the counts prove the two sides *differ*, not which one is newer. Rows the tree has and the database lacks are the one unambiguous signal, and that is what you see after a `git pull`. **[Full round-trip contract →](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/team-state-in-git.md)** --- ## Troubleshooting **"command not found: .tausik/tausik"** - On Windows use Git Bash or WSL, not cmd.exe - Or call directly: `.tausik/tausik.cmd status` **"Python not found"** - Make sure Python is in PATH: `python --version` - On some systems the command is `python3` **"Claude Code doesn't see skills"** - Restart VSCode after bootstrap - Make sure `.claude/` is created and not empty **"Database is locked"** - Delete the file `.tausik/tausik.db-wal` and try again - This happens if a previous process terminated incorrectly **Agent doesn't create tasks, just writes code** - Check that `CLAUDE.md` exists in the project root - Check that it contains a "Constraints" section with the "No code without a task" rule --- ## Migrating an Existing Project If you already have a project with code and want to add TAUSIK: 1. **Navigate to your project root** and connect TAUSIK as a submodule: ```bash git submodule add https://github.com/Kibertum/tausik-core .tausik-lib python .tausik-lib/bootstrap/bootstrap.py --init ``` 2. **Bootstrap auto-detects your environment:** - Recognizes your stack (Python, React, Go, etc.) and enables matching quality gates - Detects existing config files (`.eslintrc`, `pyproject.toml`, etc.) - Preserves your existing `.gitignore`, `CLAUDE.md`, `.mcp.json`, and `.cursor/mcp.json` — merges instead of overwriting 3. **What gets tracked in git:** - `.tausik-lib/` — the framework itself (git submodule, tracked) - `.tausik/` — runtime data (database, config, venv) — added to `.gitignore`, never committed - `.claude/` and `CLAUDE.md` — agent instructions, tracked 4. **Starting fresh vs. importing:** - TAUSIK starts with a clean task database. There is no automatic import from other systems. - If you have existing tasks, create them manually with `task quick "title"` or plan them with `/plan`. - Your code history stays in git — TAUSIK only manages the AI workflow layer on top. After setup, verify with `.tausik/tausik status` and start working normally. --- ## What's Next You've mastered the basic cycle. Next you can learn more about: - **[Workflow](/docs/workflow)** — quick and full modes, quality gates, memory - **[Skills](/docs/skills)** — all phrases the agent understands - **[CLI Commands](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md)** — if you want to call TAUSIK from the terminal directly --- ## English · /docs/workflow **English** | [Русский](/ru/docs/workflow) # Workflow TAUSIK is designed for pair work: the engineer writes in free form, the AI agent interprets and executes. No special commands to memorize — just describe what you want to do. ## Typical Work Day ### Morning: Starting Work Write to the agent: ``` start working ``` The agent will open a session, show what was done last time, which tasks are in progress, and suggest what to work on. If there are unfinished tasks — it will offer to continue. ### Working on a Task For simple tasks — just say what needs to be done: ``` add dark theme to user settings ``` The agent will create a task, formulate acceptance criteria, and start working. For complex tasks it's better to plan first: ``` let's plan the migration from REST to GraphQL ``` The agent will create a task with a detailed plan, break it into steps, estimate complexity, and offer to begin. ### Review and Completion When the work is done: ``` done, review and commit ``` The agent will check the code against a 28-point checklist, run tests and gates, verify that all acceptance criteria are met, and offer to commit. ### End of Day ``` that's all for today ``` The agent will show the summary: what was done, how many tasks were closed, metrics. It will save context for the next session — tomorrow you can continue from where you left off. ## Two Work Modes ### Quick (for small tasks) ``` Engineer: "start working" → /start (opens session) Engineer: "fix the JWT bug" → /plan (creates task, plans) Engineer: "done" → /ship (verifies, commits) ``` ### Full (for complex tasks) ``` Engineer: "start" → /start (context, metrics) Engineer: "plan the API refactoring" → /plan (task + plan + AC) Engineer: "go ahead" → /task (QG-0, begins work) ... work, progress, dead ends ... Engineer: "review the code" → /review (28-item checklist) Engineer: "run tests" → /test Engineer: "close and commit" → /ship (QG-2, gates, commit) Engineer: "that's all for today" → /end (metrics, handoff) ``` ## Quality Gates TAUSIK automatically checks quality at two points: **At task start (QG-0):** - Task goal is formulated - Acceptance criteria are recorded — and say something: templates (`<...>`, `[UPPER]`, `{{...}}`), an unexpanded `$(...)`, TODO/TBD, an empty table and "works as expected" are removed first; under 3 words of goal or 5 of criteria is refused - **Blocks** if criteria don't include a negative scenario (error, failure, invalid input) - Warns for security tasks (auth, payments, PII) without security criteria - Warns if scope is not defined (what to change / what not to touch) **At task completion (QG-2):** - Each acceptance criterion is verified with evidence - All plan steps are completed - Tests pass (pytest, ruff, and other gates per stack) - Warns if knowledge is not documented These gates cannot be bypassed — the agent cannot start work without a goal and cannot close a task without verification. ### When Gates Block You **QG-0 blocks task start:** - Missing goal → add with `task update --goal "..."` - Missing acceptance criteria → add with `task update --acceptance-criteria "..."` - Goal or AC is a placeholder ("the acceptance criteria have 0 word(s) of substance") → write what is checked and how - No negative scenario in AC → add a criterion like "Returns error on invalid input" - A session above the advisory threshold is advice, not a refusal: save state with `/checkpoint` or hand off with `/end` - The host-thread budget is separate from session time. Native Codex response/input/context signals trigger an automatic checkpoint and fresh-window prompt at advisory level; hard level refuses continuation. Reopening TAUSIK inside the same host thread does not reset it. Missing native usage is shown as unavailable and fails open. **QG-2 blocks task completion:** - AC not verified → log evidence: `task log "AC verified: 1. ... ✓ 2. ... ✓"` - Tests failing → fix the code, tests run automatically on next `task done` - Plan steps incomplete → mark done with `task step ` or update the plan The agent handles most of this automatically. If a gate blocks, it will tell you exactly what's missing and how to fix it. ## Hooks — Automatic Control In addition to Quality Gates, TAUSIK uses Claude Code hooks for real-time control: - **No code without a task** — attempting to edit a file without an active task is blocked - **Dangerous command firewall** — `rm -rf`, `DROP TABLE`, `git reset --hard` are blocked - **Git push only via /ship** — direct `git push` is blocked - **Auto-format** — code is automatically formatted after each change (ruff, prettier, gofmt) Details: **[Hooks](/docs/hooks)** ## Project Memory TAUSIK saves knowledge between sessions. The agent automatically: - **Records decisions** — why bcrypt was chosen over argon2 - **Documents dead ends** — what was tried and why it didn't work - **Captures patterns** — API error format, naming conventions - **Passes context** — handoff for the next session This knowledge is loaded at every `/start` and `/task` — the agent doesn't repeat mistakes from previous sessions. ## Metrics TAUSIK automatically tracks: | Metric | What It Shows | |--------|---------------| | Throughput | Tasks per session | | FPSR | Percentage of tasks solved on the first attempt | | DER | Percentage of tasks where a defect was later found | | Dead End Rate | Share of dead ends relative to total tasks | | Lead Time | Average time from creation to task closure | | Cost per Task | Average time by complexity (simple/medium/complex) | Metrics help understand: is the agent working efficiently, or spending time on retries? ## Multi-Agent Work TAUSIK supports multiple AI agents working on the same project simultaneously: - **Task claiming** — `task claim ` locks a task to a specific agent. Other agents see it as taken and pick a different one. `task unclaim ` releases the lock. - **No conflicts** — each agent works on its own claimed task. `task next --agent ` atomically claims and starts the best available task. - **Concurrent writes** — the SQLite database runs in WAL (Write-Ahead Logging) mode, so multiple agents can read and write without blocking each other. - **Shared knowledge** — all agents share the same project memory, decisions, and dead ends. What one agent learns, others see immediately. No special setup is needed. Just run multiple agent sessions in the same project directory. ## What's Next - **[Hooks](/docs/hooks)** — automatic control: blocking, firewall, auto-format - **[Skills](/docs/skills)** — full list of what the agent can do - **[CLI Commands](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md)** — if you want to manage TAUSIK from the terminal - **[Architecture](/docs/architecture)** — how the framework works internally --- ## English · /docs/agent-quickstart **English** | [Русский](/ru/docs/agent-quickstart) # Agent quickstart: connect TAUSIK, then work under it You are an AI agent. This page is written for you, in the order you will need it: how to connect the framework to a project on the host you are running in, how to check that it is actually there, and how to do work once it is — as exact calls, with the replies and refusals you will actually see. Every command, tool name and refusal text here is taken from a live run of this version, not remembered; a test holds them to the code (`tests/test_agent_quickstart.py`). The human version of this walkthrough is [quickstart.md](/docs/quickstart). The rules themselves are in [AGENTS.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/AGENTS.md); this page is the procedure that follows from them. ## 1. Connect TAUSIK is a git submodule plus one bootstrap. From the project root: ```bash git submodule add https://github.com/Kibertum/tausik-core .tausik-lib python .tausik-lib/bootstrap/bootstrap.py --init --ide ``` `` is one of the hosts bootstrap scaffolds — **claude, cursor, qwen, kilo, opencode, codex** — or `all`. Pick the one you are running in; `all` deploys every profile side by side (they share one database in `.tausik/`). | You are running in | `--ide` | What lands | What enforces the rules in real time | |---|---|---|---| | Claude Code / VS Code Claude Extension | `claude` | `.claude/` (skills, hooks, MCP), `.mcp.json`, `CLAUDE.md`, `AGENTS.md` | PreToolUse hooks (`.claude/settings.json`) | | Cursor / Composer | `cursor` | `.cursor/mcp.json`, `.cursorrules`, `AGENTS.md` | nothing at the tool boundary — you self-enforce Rule 1 (see §5) | | Qwen Code | `qwen` | `.qwen/` (skills, settings), `QWEN.md`, `AGENTS.md` | hooks (a subset of Claude's) | | Kilo Code | `kilo` | `.kilo/`, `.kilocode/mcp.json`, `AGENTS.md` | nothing at the tool boundary — MCP gates only | | OpenCode | `opencode` | `.opencode/` (one QG-0 plugin), `opencode.json` | the QG-0 plugin for Rule 1; nothing for Rule 2 | | Codex CLI | `codex` | `.codex/` (config.toml with MCP, skills, agents, hooks.json), `AGENTS.md` | `.codex/hooks.json` — **only after the user trusts the project hooks in Codex**; an untrusted profile enforces nothing (measured live) | After bootstrap: **restart the host** (MCP servers are read at startup), then check. Two equivalent checks — the MCP tool if your host exposes `tausik_*` tools, the CLI otherwise: ``` tausik_status ← MCP .tausik/tausik status ← CLI (Windows cmd/PowerShell: .tausik/tausik.cmd status) ``` Expected on a fresh project: ``` Tasks: 0/0 done Session: none active ``` If the MCP tools are not there after a restart, run `.tausik/tausik doctor` (or `tausik_doctor`) — it names the missing piece; the CLI works regardless. Every `tausik_*` tool has a CLI twin, so nothing below depends on MCP. **Codex specifically.** Codex runs a project's hooks only once the user has trusted them. Until then `bootstrap --check` is clean and the hook file lists every gate, and none of them fires. Do not report Rule 1/Rule 2 as enforced on Codex unless the trusted state has been confirmed; the [enforcement matrix](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/model-providers.md#codex-enforcement-matrix) carries the same condition. ## 2. The cycle, as calls One task at a time, every step recorded. MCP tool on the left, CLI twin on the right; the reply is what you will see. **Open a session.** ``` tausik_session_start .tausik/tausik session start → Session #1 started. ``` **Create the task with a goal AND acceptance criteria.** QG-0 refuses to start a task without them, and one criterion must be a negative (error or boundary) case. ``` tausik_task_quick(title="Demo feature", goal="Show the gate", acceptance="AC-1: the feature returns the answer. AC-2: an empty input is refused with an error.") .tausik/tausik task quick "Demo feature" --goal "Show the gate" --ac "AC-1: … AC-2: an empty input is refused with an error." → Task 'demo-feature' created. ``` **Start it.** This is the moment the write gates open for you. ``` tausik_task_start(slug="demo-feature") .tausik/tausik task start demo-feature → Task 'demo-feature' started (attempt #1). ``` If you skipped a criterion, the refusal is exact and names the fix: ``` Error: QG-0 Context Gate: 'demo-feature' cannot start — missing acceptance_criteria. Fix: .tausik/tausik task update demo-feature --goal '...' --acceptance-criteria '...' Error: QG-0 Start Gate: 'demo-feature' AC has no negative scenario. SENAR requires at least one error/boundary case in acceptance criteria. ``` **Declare what you will touch, then edit.** `relevant_files` is the scope the gates read; paths are space-separated. ``` tausik_task_update(slug="demo-feature", …) .tausik/tausik task update demo-feature --relevant-files src/feature.py tests/test_feature.py ``` **Log as you go.** Every meaningful step, and every criterion's evidence — the closure reads these lines. ``` tausik_task_log(slug, message) .tausik/tausik task log demo-feature "AC-1: ✓ tests/test_feature.py::test_answer AC-2: ✓ tests/test_feature.py::test_empty_is_refused (negative: empty input raises)" ``` **Verify.** Heavy gates (linters, the scoped tests) run once, here, and the green is recorded for the close. ``` tausik_verify(task_slug="demo-feature") .tausik/tausik verify --task demo-feature → [PASS] ruff [PASS] pytest SCOPE: scoped run over 1 of 1 test file(s) mapped from relevant_files … Recorded verification_run #8 (task_slug=demo-feature, exit=0). ``` In a project with a signing key the reply ends with a **verify handle** — pass it to the close. Without a key it says so and gives the close that works: ``` Verify handle: none — no project key, so no signed receipt (`tausik key init` enables them). Close without --verify-handle: `.tausik/tausik task done demo-feature --ac-verified` uses the freshness lookup. ``` **Close.** `--ac-verified` is your statement; the logged evidence is what the gate checks. ``` tausik_task_done(slug="demo-feature", ac_verified=true, verify_handle="") .tausik/tausik task done demo-feature --ac-verified [--verify-handle ] → Task 'demo-feature' completed. ``` Two refusals you will meet if you rush it, both exact: ``` Error: QG-2: 'demo-feature' cannot complete — acceptance criteria not verified. Verify each criterion, then: .tausik/tausik task done demo-feature --ac-verified Error: QG-2: 'demo-feature' has 2 acceptance criteria but no verification evidence in task notes. Log verification: .tausik/tausik task log demo-feature "AC verified: 1. ✓ 2. ✓ ..." ``` **Hand off and end.** A handoff is what the next session starts from. ``` tausik_session_handoff(handoff={...}) .tausik/tausik session handoff '' tausik_session_end .tausik/tausik session end → Session #1 ended. ``` ## 3. What the host enforces and what you enforce Two kinds of rules, two kinds of enforcement — the split is by WHO performs the action, not by host: * **Rules our surface holds, everywhere.** QG-0 (no start without goal + AC), QG-2 (no close without evidence and a fresh verify) and the memory route; session time and capacity are advice there, never a refusal (1.10). These refuse inside `tausik_*` / the CLI, so they hold on a host with no hooks at all. * **Rules that intercept YOUR action.** Rule 1 (no file write without an active task), Rule 2 (no write outside the declared scope), the shell firewall, the push gate. These exist only where a hook runs before the tool: Claude Code, Qwen Code, Codex (once trusted), and Rule 1 alone on OpenCode. On Cursor and Kilo nothing stands between you and the file — you are the hook: start the task before the first edit. Where the hook does run, a write without an active task is refused before the file changes: ``` BLOCKED: No active task. TAUSIK requires a task before code changes (SENAR Rule 1). Create one: /plan (or describe the task and ask to start it) Resume one: .tausik/tausik task list --status planning .tausik/tausik task start ``` `docs/en/enforcement-coverage.md` has the rule-by-rule table, and `.tausik/tausik doctor` prints what is deployed for THIS host. ## 4. Memory: what to write, and where not to * `tausik_memory_add` / `.tausik/tausik memory add` — a pattern, gotcha or convention of THIS project, when you learned something the next agent must not relearn. * `tausik_dead_end` / `.tausik/tausik dead-end "approach" "reason"` — an approach that failed and why. Cheaper than the next agent trying it again. * `tausik_decide` / `.tausik/tausik decide` — a decision with its rationale. * **Do not write to `~/.claude/`** from a non-Claude host, and do not put TAUSIK's own rules there from any host: that directory is a Claude profile, not project memory. Project knowledge goes through the tools above. ## 5. If your host has no slash commands `/start`, `/plan`, `/ship` and the rest are procedures written as Markdown. When the host does not expand them, open `harness/skills//SKILL.md` (deployed under `.claude/skills/`, `.qwen/skills/`, `.codex/skills/`) and run the numbered steps yourself. The tools they call are the ones in §2. ## See also * [AGENTS.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/AGENTS.md) — the rules and the host matrix * [quickstart.md](/docs/quickstart) — the same walkthrough for a person * [mcp.md](/docs/mcp) — every `tausik_*` tool and its parameters * [cli.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md) — every CLI command * [enforcement-coverage.md](/docs/enforcement-coverage) — what is enforced where, by rule * [model-providers.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/model-providers.md) — host notes, including the Codex trust condition --- ## English · /docs/senar **English** | [Русский](/ru/docs/senar) # What is SENAR? SENAR (Supervised Engineering & Normative AI Regulation) is an open engineering standard that defines how AI agents should work on software projects. Think of it as a building code for AI-assisted development — a set of rules that make the agent's work predictable, verifiable, and safe. **Full specification:** [senar.tech](https://senar.tech) | [GitHub](https://github.com/Kibertum/SENAR) TAUSIK (**T**ask **A**gent **U**nified **S**upervision, **I**nspection & **K**nowledge) is the reference implementation of SENAR. Every rule described below is enforced by TAUSIK automatically — you don't need to read the spec to use the framework. --- ## The Problem SENAR Solves AI coding agents are powerful but unreliable. Without structure, they: - Start writing code without defining what "done" means - Claim tasks are complete without testing - Forget everything between sessions - Try the same failed approach three times in a row AGENTS.md and .cursorrules are recommendations — the agent can ignore them. SENAR provides **enforcement**: hard blocks that physically prevent the agent from skipping steps. ## Core Concepts ### Quality Gates Two checkpoints that every task must pass: **QG-0 — Context Gate (before starting work):** - The task must have a clearly defined goal - Acceptance criteria must be written down — what counts as "done" - At least one negative scenario is required (what happens on error, invalid input, etc.) - Security-sensitive tasks (auth, payments, PII) get an additional warning if security criteria are missing **QG-2 — Implementation Gate (before closing a task):** - Every acceptance criterion must have documented evidence (test output, verification steps) - All plan steps must be completed - Automated checks must pass (tests, linters — depending on your stack) These gates are **hard blocks** — the agent literally cannot proceed without meeting the requirements. No `--force` flag, no bypass. > **Why QG-0 and QG-2, not QG-1?** The numbering follows the SENAR specification: QG-0 runs at task start (before any work), QG-2 runs at task completion (after all work). QG-1 is reserved in the spec for a mid-work checkpoint but is not yet implemented. ### Session Management Working sessions have structure: - **Session start** — load context from previous work (what was done, what's blocked, what failed) - **Context pressure** — session time, calls since the last checkpoint and a quiet journal are printed as advice with a measured basis; they never block a task start (1.10). - **Checkpoints** — periodic context snapshots so nothing is lost if the session crashes - **Session end** — save a handoff: what was accomplished, what's unfinished, what decisions were made ### Dead End Tracking When the agent tries an approach and it fails — it records the dead end: what was tried and why it didn't work. Next session, when a similar task comes up, the agent sees the failed approach and avoids repeating it. ### Metrics Six metrics are tracked automatically, with no manual input: | Metric | What It Measures | |--------|------------------| | **Throughput** | Tasks completed per session | | **FPSR** (First-Pass Success Rate) | % of tasks solved on the first attempt | | **DER** (Defect Escape Rate) | % of tasks where a defect was found later | | **Lead Time** | Average time from task creation to completion | | **Dead End Rate** | Share of dead ends relative to total tasks | | **Cost per Task** | Average time by complexity level | These metrics help answer: is the agent working efficiently, or spending time on retries and dead ends? ## What You'll Notice in Practice When using TAUSIK, SENAR manifests as: 1. **The agent always asks "what counts as done?"** before writing code — that's QG-0 2. **The agent can't close a task by just saying "done"** — it must show evidence for each criterion — that's QG-2 3. **Starting a new session loads previous context** — you don't explain the project from scratch 4. **Failed approaches are remembered** — the agent won't try the same broken solution twice 5. **Long sessions get a warning** — after 3 hours the agent suggests taking a checkpoint ## SENAR Rules at a Glance | Rule | What It Says | How TAUSIK Enforces It | |------|-------------|------------------------| | Rule 1 | No code without a task | Hook blocks file edits without an active task | | Rule 2 | Define scope boundaries | `scope_write_gate`/`bash_write_gate` block writes outside `scope_paths`; QG-0 hard-blocks a medium/complex start with no scope | | Rule 3 | Verify against criteria | QG-0 + QG-2 combined enforcement | | Rule 4 | External adversarial review | `risk_l3_trigger` blocks an under-evidenced closure until an L3 review is recorded; reviewer is the separate-model `tausik-external-reviewer` subagent | | Rule 5 | Verification checklist | Hard gate for substantial/deep tiers whose AC cite no existing test; escalating warning below | | Rule 6 | Plan rollback | `rollback_plan` field: QG-0 blocks a medium/complex start without a rollback plan; `task done` warns | | Rule 7 | Find root cause for defects | Warning if defect task has no root cause in notes | | Rule 8 | Capture knowledge | Warning at task close if no decisions/patterns recorded | | Rule 9.2 | Session duration | Signal: advice above `session_max_minutes`, no refusal (1.10) | | Rule 9.3 | Periodic checkpoints | Signal: calls since the last handoff are derived from the ledger, advice above `checkpoint_calls` | | Rule 9.4 | Document dead ends | Dedicated tool + reminders in skills | | Rule 9.5 | Periodic audit | Auto-check at session start | > **A note on Rules 4–6.** As of **v1.5** these are enforced: **Rule 4** (external adversarial review — a separate-model, read-only subagent gates under-evidenced closures), **Rule 5** (verification checklist — a hard gate for substantial/deep planning tiers, advisory below), and **Rule 6** (rollback planning — QG-0 blocks new medium/complex tasks without a documented rollback plan). See the [SENAR Compliance Matrix](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/senar-compliance-matrix.md) for the full enforcement table. **Full compliance matrix:** [SENAR Compliance Matrix](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/senar-compliance-matrix.md) --- ## What's Next - **[Quick Start](/docs/quickstart)** — set up TAUSIK in 10-15 minutes - **[Workflow](/docs/workflow)** — how a typical work day looks - **[SENAR Compliance Matrix](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/senar-compliance-matrix.md)** — detailed implementation status for every rule --- ## English · /docs/reasoning-trace **English** | [Русский](/ru/docs/reasoning-trace) # Reasoning Trace (RENAR) A **reasoning trace** is an ordered, append-only chain of typed steps attached to a task. It records *why* the agent did something — the rationale a fresh agent could not reconstruct from the diff alone. It is the reasoning half of RENAR reproducibility (the other halves are events, verification runs, and receipts). The trace is read on its own via `tausik task show`; `tausik task replay ` produces a fuller chronological timeline that interleaves all four sources. The `/reason` skill is the agent-facing surface; this page is the reference. ## The four kinds (closed list) Steps are one of a closed list of kinds. The four form the canonical cycle and `seq` auto-increments per task: | kind | answers | |------|---------| | `intent` | What am I trying to achieve right now? | | `premise` | What belief/assumption drives the choice? | | `action` | What I'm doing about it | | `verification` | How I confirmed it worked | An invalid kind is rejected twice — once by the service (friendly error) and again by the DB `CHECK` constraint (hard guarantee), so a malformed step is never silently stored. ## Recording steps MCP-first (preferred): ``` tausik_reason_step(slug="my-task", kind="intent", content="…") ``` CLI fallback — arguments are positional ` `: ```bash .tausik/tausik task reason-step my-task intent "…" ``` Read the trace back via CLI — `.tausik/tausik task show my-task` — or MCP — `tausik_task_show(slug="my-task")`. Both print a `Reasoning trace (N)` section after plan and decisions. ## Worked example — a full trace The trace below is from `v16r-reason-skill` itself: shipping a `/reason` skill whose nudge must *not* become a new gate. ```bash .tausik/tausik task reason-step v16r-reason-skill intent \ "Ship a /reason skill + a /task nudge that records reasoning at forks." .tausik/tausik task reason-step v16r-reason-skill premise \ "Reasoning capture is a discipline, not a gate — QG-2 must gain no new blocker." .tausik/tausik task reason-step v16r-reason-skill action \ "Add an escalating SOFT nudge to /task step 6; leave task_done gates untouched." .tausik/tausik task reason-step v16r-reason-skill verification \ "Closed a task with zero reasoning steps end-to-end — task done succeeded, no new gate fired." ``` Rendered by `tausik task show v16r-reason-skill`: ``` Reasoning trace (4): 1. (intent) Ship a /reason skill + a /task nudge that records reasoning at forks. 2. (premise) Reasoning capture is a discipline, not a gate — QG-2 must gain no new blocker. 3. (action) Add an escalating SOFT nudge to /task step 6; leave task_done gates untouched. 4. (verification) Closed a task with zero reasoning steps end-to-end — task done succeeded, no new gate fired. ``` The chain reads as a self-contained argument: the **intent** states the goal, the **premise** names the constraint that shaped the design, the **action** records the concrete choice, and the **verification** closes the loop by testing the very constraint the premise asserted. A correction never edits a prior step — append a new step (often a fresh `premise`) instead. ## When to record — and when not to Record a step at a **fork**: choosing between approaches, adopting a non-obvious premise, or verifying a claim whose rationale would otherwise be lost. Do **not** use it as a per-edit journal — that is `task log`. Three surfaces, three jobs: | Surface | Shape | Use for | |---------|-------|---------| | `reason-step` | typed, ordered, append-only | the *reasoning* behind a fork — replayable | | `task log` | freeform timestamped line | progress journal, crash-safety | | `tausik_decide` | a committed project decision | choices that bind future work | ## Guarantees - **Append-only.** Steps are never edited or deleted; the trace is an audit record. Wrong premise → new step, not a rewrite. - **Advisory, never blocking.** Neither QG-0 (`task start`) nor QG-2 (`task done`) inspects the trace. A task with **zero** reasoning steps starts and closes normally — the `/task` nudge is a soft, escalating reminder only. - **One trace per task.** Steps are scoped to a task slug; there is no global trace. ## See also - **[Skills](/docs/skills)** — `/reason` and the rest of the skill surface - **[Workflow](/docs/workflow)** — how reasoning fits the task lifecycle - **[CLI Commands](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md)** — `task reason-step`, `task replay` - **[MCP Tools](/docs/mcp)** — `tausik_reason_step`, `tausik_task_replay` --- ## English · /docs/enforcement-coverage # Enforcement boundary: what is covered and what is not > Split out of [`../ru/agent-contract.md`](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/agent-contract.md) while closing > `powershell-tool-bypasses-bash-firewall` — the topic stands on its own, and > together with the channel-coverage matrix it no longer fit inside the contract > (filesize gate). RU original: [`../ru/enforcement-coverage.md`](/ru/docs/enforcement-coverage). Full event/coverage reference: [`hooks.md`](/docs/hooks). In short: QG-0 (Rule 1) and the scope ACL (Rule 2) historically hung on `Write|Edit` only, so **a file write through Bash bypassed both**: `cat > f <`, `>>`, `&>`, `N>`, including the heredoc header line), `tee`, `dd of=`, `sed -i` (GNU and the BSD `-i ''` form), `cp`/`mv`/`install` (destination **and** `-t`/`--target-directory=`), `truncate`, `touch`, `curl -o`/`--output`, `wget -O`/`--output-document`, `tar -x … -C DIR`, `unzip … -d DIR`, a literal `open(path, 'w'|'a'|'x')` inside `python`/`perl`/ `ruby -c` — and, on the PowerShell side, `Set-Content`, `Add-Content`, `Clear-Content`, `Out-File`, `New-Item`, `Tee-Object`, `Copy-Item`/`Move-Item` destinations, `Invoke-WebRequest -OutFile`, `Export-Csv`. Heredoc BODIES are not scanned (a `->`/`>` in prose or code inside the body would manufacture a phantom target and block an honest write). Only targets **inside** the project tree are gated, exactly as for Write; the scratchpad, `/tmp`, `/dev/null` and other repos are allowed. The exception is a body the header hands to an interpreter (`python - <$f`, `echo > $SCRATCH/x`, `$env:TEMP\x` — a token carrying `$` is treated as unresolvable and dropped); `base64 -d | sh` and other obfuscation; a command assembled from STDIN (`… | xargs -I{} bash -c '…'`) or executed on another host (`ssh host 'cmd'` — this project's paths mean nothing there); `curl -O`/`wget` without `-O`; `tar`/`unzip` extraction into the current directory with no `-C`/`-d`; arbitrary interpreter code that writes a file by a form outside that declared Python AST catalogue. **The `bash -c` / `sh -c` wrapper — CLOSED.** `bash -c 'echo x > scripts/foo.py'` used to yield NO target at all: the redirection lives inside one quoted argument and the parser never descended into the payload. Rule 1 and the scope ACL were bypassable by a one-liner of the class Decision #162 closed for heredocs — while the residual boundary claimed the cost of a bypass had been raised to "you must deliberately obfuscate". `bash -c` is written every day and is not obfuscation; the claim was overstated. The parser now descends RECURSIVELY into the payload of `bash`/`sh`/`zsh`/`dash`/`ksh`/`ash`/`busybox` under `-c` (including the combined `-lc`, `-ec` forms), with a declared nesting bound (`_MAX_WRAPPER_DEPTH`) so `bash -c "bash -c '…'"` cannot spin. A quoted mention (`echo 'bash -c "x > y"'`), `bash script.sh` without `-c`, and the long option `--color` are not targets. **Transparent prefixes — CLOSED.** `env bash -c '…'` hid the wrapper exactly the way the wrapper hid the redirection: the shell test looked at the first token, and that token was `env`. Found by adversarially reviewing the wrapper FIX (convention #276), not the code it replaced. `env`, `sudo`, `doas`, `nohup`, `nice`, `ionice`, `stdbuf`, `timeout`, `command`, `exec` are now stripped before the command is identified — along with their flags, `VAR=val` assignments and the numeric argument of `timeout`/`nice`. That also closed the "writer behind a wrapper" (`sudo tee`) previously listed as uncaught: the writer was hidden not by `tee` but by the word in front of it. A token that merely STARTS like a prefix (`environment.py`, `timeout_test.sh`) is not one. `TAUSIK_SKIP_HOOKS=1` still disables everything — but that bypass is telemetered (`emit_supervision_bypass`); there is no silent disabling. **An unparseable command.** If a command does not tokenize (unbalanced quote), the parser falls back to a regex that deliberately OVER-detects. `bash_write_gate` (QG-0) accepts that answer: the worst case is being asked for a task the write needed anyway. `memory_pretool_block` does not: its block accuses the agent of leaking knowledge and offers only two exits (a marker that would be a lie, and a permanent config exemption for a one-off command), so a false positive costs more than a miss and trains the bypass. The miss is not silent — `fail_open_unparseable_` is recorded (`…_bash`, `…_powershell`: the reason names the parser that failed rather than filing everything under Bash) — and the in-tree half of the deny-list is judged again by the `memory_route` gate and the pre-commit hook before anything can be committed. ## Coverage BY RULE, not by whole host The notice an agent reads answers two different questions, and the second exists because the first was not enough. "No real-time mechanism is deployed on this host" is true and coarser than the truth: on Kilo every task closure, every knowledge write and every task opening IS refused, because those go through our own tools. One sentence about a whole host cannot carry both answers, and carrying only the first lies to the user in the other direction. The split follows WHO PERFORMS THE ACTION: | What holds the rule | Which rules | Where it works | |---|---|---| | Our surface (`tausik_*` / CLI) | QG-0, QG-2, memory routing (the session limit, 9.2, is a signal since 1.10) | EVERY host: the enforcement point is inside our code, and the MCP server is what an otherwise bare install still has | | Intercepting the host's action | Rule 1 (no code without a task), Rule 2 (scope boundaries), Rule 10.12 (secrets) | ONLY where the artifact carrying that particular rule is deployed | The second column is derived from the deployment, not from a list: each rule names its artifact (`hook:task_gate.py`, `plugin:tausik-qg0.js`), and "is it covered here" is the question "is that artifact in the profile". So OpenCode, which ships one QG-0 plugin, shows as covering Rule 1 and NOT covering Rule 2 or Rule 10.12 — which is the truth, rather than "the host is covered". Measured at the MCP boundary (session #232, AST): 146 declared tools, 44 mutating handlers, and not one reaching the backend directly. The service layer's refusals do travel to the boundary — and that is checked by driving the handlers, not by reasoning about them: `tests/test_rule_coverage.py` closes a task through `tausik_task_done` with no receipt and requires the task to stay open. `.tausik/tausik doctor` prints both lines: coverage by host, and the split by rule. ### Two conditions for an interception, and the scan sees one "Is that artifact in the profile" is the condition a scan can answer. On a host that guards its hook mechanism there is a second one the scan cannot: the host must be WILLING to run the project's hooks. Codex runs a project's `.codex/hooks.json` only after the user has trusted it, and the two states are byte-identical on disk. Measured live in the 1.9 acceptance run (session #251): with the generated profile present but untrusted, the forbidden `Path('outside.txt').write_text(...)` completed and the file existed — no hook ran; the same operation under a trusted profile was refused before the write. So for Rule 1 and Rule 2 on Codex the honest statement is "hard once the hooks are trusted", `doctor` reports only the first condition and says so, and an untrusted generated profile is NOT protected and is not described as such — see the [Codex enforcement matrix](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/model-providers.md#codex-enforcement-matrix). ## Channel-coverage matrix Supervision can be holed not only by RULE but by CHANNEL, and the second kind is harder to notice: every test is green, because every test asks the covered channel. On win32 — the platform this project calls primary — the agent is handed TWO shell tools, `Bash` and `PowerShell`, and until `powershell-tool-bypasses-bash-firewall` the second matched no PreToolUse hook. | Gate | Write/Edit | Bash | PowerShell | |---|---|---|---| | `task_gate` (Rule 1) | ✅ | — ¹ | — ¹ | | `scope_write_gate` (Rule 2) | ✅ | — ¹ | — ¹ | | `bash_write_gate` (Rules 1 + 2 for shells) | n/a | ✅ | ✅ | | `bash_firewall` (dangerous commands) | n/a | ✅ | ✅ | | `git_push_gate` (push ticket) | n/a | ✅ | ✅ | | `memory_pretool_block` (memory-route) | ✅ | ✅ | ✅ | | `secret_scan` (Rule 10.12) | ✅ | ✅ ² | ✅ ² | ¹ Not a gap: the shell vector is judged by `bash_write_gate`, which IMPORTS `scope_write_gate`'s decisions rather than copying them. A shell event carries no `file_path`, so there is nothing for those two hooks to read. ² **Gap closed** (`secret-scan-covers-no-shell-channel`, Decision #178). `secret_scan` is now on `SHELL_MATCHER` in both bootstraps and admits the shell tools via `shell_channel.is_shell_tool` — on both channels at once, because closing it on one shell would put the channels back out of step. The whole command string is scanned (a strict superset of extracting the written value: it catches a heredoc body and `Set-Content -Value`, plus `export KEY=…` — the secret literal in context, which Rule 10.12 also covers) with no new shell parsing. The residual is the same as for paths: a secret passed by variable/env (`--token "$TOKEN"`) is not a literal anywhere, so it is not resolved and not flagged — the correct way to pass it, not a gap. ### PowerShell data-carrying constructs: closed, or named "Is this a command or is this text?" must be asked of EVERY construct, not one. It had been answered for quotes and left unasked for here-strings — and the gate blocked this project's own routine `git commit -m @'...'@`, because a `>` in the message's prose read as a redirection. The audit, one by one: | Construct | Status | How | |---|---|---| | `@'…'@`, `@"…"@` (here-string) | **closed** | The body is ONE token. The opening quote must end its line, and the terminator `'@` is recognised only at the START of a line — as PowerShell itself requires. Relaxing either condition re-exposes the body as live shell. | | `'…'`, `"…"` | closed since the first version | A quoted mention arrives as one token and never forms a statement. | | `{…}` (script block) | **deliberately NOT data** | Braces are separators; the contents are read as ordinary statements. A script block IS executable — `… \| ForEach-Object { Remove-Item -Recurse C:\ }` must stay visible. Prose does not live inside braces. | | `$(…)`, `@(…)` | **residual, named** | Not treated as a nesting level; a token carrying `$` is marked unresolvable and dropped rather than guessed. The same residual the POSIX gate documents for `$VAR`. | **The dialect follows the TOOL; the line is not offered to both judges.** The tempting phrasing — "read it with both tokenizers and block if either sees a push" — sounds like a tightening and is a loosening: each dialect misreads the other's syntax, so of two judges the INCOMPETENT one wins. What actually happened: the POSIX lexer does not know a here-string, broke out at the apostrophe in "PowerShell's", and the `git push --force` in a commit message's prose blocked an ordinary commit. Tokenization goes through `shell_channel` by `tool_name` — the same single table as write targets. Push-gate residual, named explicitly: a push inside a wrapper payload (`powershell -Command 'git push'`) is one quoted token to its dialect and is NOT ticketed. `bash_firewall` does descend into payloads, so a force-push that way is still blocked; an ordinary push that way is not. Closing this via `scan_target` (which joins an interpreter's payload back RAW) is NOT acceptable — it would block an honest `python -c "print('git push')"`, trading one false block for another. The symptom to look for next time: a detector that can tell text from command in one place and not in the place beside it. The cost of that asymmetry is not a miss but a FALSE BLOCK on a routine operation, and that trains the bypass more expensively than the missed command would have cost (#291). **PowerShell-channel residual** (on top of the shell-wide "obfuscated write"): a target arriving by PIPELINE (`Get-ChildItem C:\ | Remove-Item -Recurse`) is not an operand of the deleting cmdlet and is not seen; `-EncodedCommand ` is not decoded; .NET calls (`[IO.File]::Delete`) are not parsed. `Remove-Item` of a project file is not reported as a write — exact parity with Bash, where `rm` is not a writer either; that gap is common to both channels and is closed separately rather than on one side only. A script path carrying a QUOTE character (`python "a'b\helper.py"`) keeps its backslashes and is left unresolved on POSIX — the quote rule that protects a `-c` payload from the separator rewrite covers it too; a token with a quote is read as source, not as a path (review #208, record #22). **Mixed scope (Rule 2, AC3).** As soon as ANY active task declares `scope_paths`, ACL enforcement switches on for ALL co-active tasks: a parallel task with no declared scope loses its legacy freedom and must either declare its own surface or have writes outside the union of declared ACLs blocked (through Write and through both shells). This closes the "keep one undeclared task active to bypass scope" trick. Legacy freedom remains only when NOBODY declared a scope. --- ## English · /docs/receipts **English** | [Русский](/ru/docs/receipts) # Signed verification receipts When `tausik verify` finishes, it writes a small, **ed25519-signed** record of what was checked — a *receipt*. The one-line value: > you can prove a task was actually verified, not just claimed. A receipt is bound to the gates that ran and to the current git `HEAD`. Because it is signed with the project key, a passing ("green") verdict **cannot be forged or replayed** by hand-editing a row or copying an old result onto a new commit. `task done` (QG-2) reads the cached verify run before it lets a task close, so the signature is the evidence behind the green. Receipts are **portable** and **offline-verifiable**: export one to a JSON file, attach it to a PR or CI artifact, and anyone can re-check the signature with no database, no keystore, and no TAUSIK SDK. ## How a receipt is produced ```bash tausik key init # once per project — generates the keypair tausik verify --task my-feature # runs the gates, then signs a receipt ``` `verify` records a `verification_run`, then emits a receipt for it. Emission is best-effort: on a project **without** a key it degrades to "no key" and the verify run still succeeds — you just get no signed evidence. The tail of a `verify` run reports the outcome: ``` Recorded verification_run (task_slug=my-feature, exit=0). Receipt: signed (run #412, key 9f3c1a2b4d5e6f70). ``` The receipt attests only the gates that **actually ran** — a skipped gate proves nothing, so it is left out, even though it counts as "passed" for the verdict. ## The envelope format (`tausik-signed/v1`) A signed receipt is an *envelope* wrapping the canonical receipt plus its signature. The signature is computed over the canonical bytes of the `receipt` object only — never over the envelope. ```json { "envelope": "tausik-signed/v1", "receipt": { "schema": "tausik-receipt/v2", "task_slug": "my-feature", "git_sha": "0123456789abcdef0123456789abcdef01234567", "scope": "standard", "gates": [ {"name": "pytest", "passed": true, "severity": "block"}, {"name": "ruff", "passed": true, "severity": "warn"} ], "passed": true, "ran_at": "2026-06-13T10:42:07Z", "files_hash": "a1b2c3d4...", "key_fingerprint": "9f3c1a2b4d5e6f70", "declared_scope_status": "under-declared", "undeclared_files": ["CHANGELOG.md", "docs/en/receipts.md"], "undeclared_count": 2 }, "signature": { "algorithm": "ed25519", "key_fingerprint": "9f3c1a2b4d5e6f70", "value": "<128 hex chars — the 64-byte ed25519 signature>" } } ``` Field notes: - `git_sha` — the `HEAD` sha at verify time, or `null` outside a git repo. This is what binds a green to a specific commit and blocks replay onto new code. - `gates[]` — reduced to the signable triple `{name, passed, severity}`. Bulky, non-deterministic gate output stays out of the receipt by design. - `passed` — the overall verdict: every non-skipped `block` gate passed and at least one gate ran. - `key_fingerprint` — first 16 hex of the SHA-256 of the public key; the same value appears in `tausik key show`. - `value` — 128 hex chars (64-byte ed25519 signature). ### Scope honesty (schema v2) A receipt claims that gates passed **over some set of files**. The three fields below say whether that set was complete. Without them the receipt stayed silent about the very thing that undermines it: an agent declaring `relevant_files=[README.md]` during a broad edit still received a signed green receipt for `README.md`. - `declared_scope_status` — one of three values: - `complete` — the declared set covers everything git saw change since the task started; - `under-declared` — git saw changes outside the declared set; - `unknown` — the comparison **could not be made**: outside a git repo, with an empty `relevant_files`, without `task_created_at`, or when the git call failed. - `undeclared_files` — sorted list of files git reports as changed that `relevant_files` does not name. Capped at 50 entries. - `undeclared_count` — the full count, **never** truncated. When it exceeds the length of the list, the list is showing only part of the picture. Three properties that matter when reading someone else's receipt: 1. **`unknown` is not `complete`.** A measurement that did not happen is never credited as confirmed coverage. Schema `v1` receipts issued before this change carry no such fields at all — treat their scope as **unverified**, not as complete. 2. **`under-declared` is not an accusation.** The divergence fires on nearly every honest closure: edits to CHANGELOG, docs, generated constants and badges routinely fall outside the declared set. It is therefore recorded but **does not block**. The one exception is an undeclared file matching the security predicate: gates scoped to the declared list would not have checked it at all, so that run fails with status `scope-security-mismatch` and asks for the file to be added to `relevant_files`. 3. **The task's own bookkeeping does not count against it.** Two files are written by the framework rather than by the agent, and both are subtracted before the comparison: - `tausik/tasks/.md`, the task's own export — rewritten between `task start` and the verify by `started_at`, every `task log`, the declared scope and the receipt itself; - `tausik/stories/.md`, the projection of the task's parent story — `task start` flips its `status: open -> active`. Only the CURRENT task's two files. Somebody else's export, and any other story, is a real product of the task that edits it and stays listed. The parent EPIC is not subtracted: a task's lifecycle was measured not to touch it. When the subtraction is the only reason nothing is left, the reason names the files instead of claiming the tree never moved. 4. **A committed sibling is excluded only with commit-local proof.** An active task can overlap a release-accumulation commit, but its timestamp does not make every path in that commit its work. For every commit since `started_at` `verify` reads three tiers of evidence and resolves each changed path on the strongest tier that names anyone — one name owns the path, two or more keep it ambiguous, and a weaker tier is never consulted once a stronger one has spoken: - *same commit* — another task's export committed alongside the path declares it in `relevant_files`, or the export is `active`/`blocked`/`done` and its `scope_paths` ACL matches it; - *parent tree* — an `active`/`blocked`/`done` export already committed before this commit declared the path in `relevant_files` (a task may declare first and commit its implementation later); - *projection* — the path IS another task's export, its parent story, or a DYNAMIC-block-only refresh of `AGENTS.md`/`CLAUDE.md`. A task never holds a work claim on its own export, whichever field spells it. Uncommitted paths, malformed exports, `planning` exports and paths with two claimants on the deciding tier remain in the comparison. The receipt is **canonical** (JCS / RFC 8785 spirit): keys sorted at every level, no whitespace, ASCII-only, floats rejected. The same logical receipt always serializes to the same bytes, so signatures verify identically across machines and platforms. ### What the receipt says about itself (schema v3) Schema `v2` named `files_hash` — an opaque digest you can **compare** but not **read**. Such a receipt could not answer the two questions without which it cannot be presented as proof: "over which files?" and "with which gate set?". Schema `v3` adds three fields, and they are what turn the receipt from a stamp over a row into a document. - `files` — the sorted list of declared paths. The security predicate applies to THIS list, not to the argument of the `task done` call: otherwise a closer could declare a harmless scope while presenting a receipt that covered `auth/`. - `gate_signature` — the same 16 hex that go into `verification_runs.command`. It is **not** recomputed: the gate-set signature is taken from one source, or the receipt could agree with itself and disagree with the row that produced it. - `expires_at` — the shelf life **inside the signature**. A policy that sits beside the document gets edited along with it; a policy inside the signature does not. Receipts of schema `v1` and `v2` remain cryptographically valid, and the old closing path (looking up a fresh run) still accepts them. But such a receipt cannot be **presented** by handle: it reads as **partial**, and the refusal names the missing fields outright. The unknown is reported as unknown — never rounded down to "complete" nor up to "tampered". ## The run handle — presenting instead of searching Decision #218, adopting SEP-2567's "explicit state handles". **How it was.** `task done` searched `verification_runs` for a row: green, same `files_hash`, same gate signature, younger than 600 seconds. The link between "I verified" and "I am closing" was a *search*, not a presentation. Three consequences, all of them defects: a substantive refusal ("the declared scope is already behind the git changes") reached the agent as a **cache miss**; the freshness window belonged to the server and was invisible to the model; and two processes with different in-memory module state judged "freshness" differently. **How it is now.** `tausik verify --task ` prints a handle: ``` Verify handle: 4821.9f3c1a2b4d5e6f7089abcdef01234567 valid until 2026-06-13T11:42:07Z (single use). Present it: tausik task done my-feature --ac-verified --verify-handle 4821.9f3c... ``` The handle is `.`, where the nonce is 128 bits from `secrets` (a SEP-2567 requirement). The server does a **point** lookup by `run_id` rather than a search by freshness. **The shelf life is 3600 seconds (one hour)**, against 600 for the previous cache. That is not a loosening but a consequence of the clock no longer being the main criterion: on redemption `files_hash` is recomputed over the **live** files and the gate signature over the **live** config. A tree that has moved is caught by what actually changed, not by a timer. The clock remains only as the bound on how long an unredeemed handle may hang about. Overridden by the config key `verify_handle_ttl_seconds`. The shelf life is named in the `tausik_verify` tool description, in the CLI output, and in the receipt's own `expires_at` field — on SEP-2567's explicit requirement: "A policy only in server documentation is not visible to the model". ### Verification on presentation — fail-closed Every clause refuses; none passes silently: | Condition | What the refusal says | |---|---| | handle of the wrong shape | `.<32-hex>` was expected | | no such run in this DB | the handle was issued by another project | | nonce mismatch (constant-time compare) | handles are single-use and replaced by every verify | | the run was red | a red run confers no right to close a task | | the run verified a different task | a handle is valid for the task it was issued under | | the run is marked `noncacheable\|` | recorded for audit, not as a certificate | | the handle is already redeemed | single use (replay, SEP-2322) | | expired / unreadable expiry | an unreadable expiry is **not** read as "no expiry" | | the project has no usable public key | the handle path is CLOSED; this is a **named mode**, not a failed check — either `tausik key init`, or close without `--verify-handle` | | no receipt / broken JSON / bad signature | there is nothing to present | | the receipt is signed for a different task than the run row | a substituted receipt | | the receipt's `ran_at` disagrees with the run row | a substituted receipt | | a v1/v2 receipt | the missing v3 fields are listed | | `files` cover a security path | such scopes are re-checked on every close | | `files_hash` over the live files diverged | a **substantive** refusal, not a cache miss | | the receipt's `files_hash` diverged from the run row | document and record describe different file sets | | the gate signature diverged from the row or from the live config | the receipt certifies a gate set that is no longer the one | | git sees a security file changed that the receipt does NOT name | gates scoped by the receipt's list never looked at that file | **And `verify` never hands out what this table would refuse.** A handle is minted only when the receipt it stands for was SIGNED (GitLab #15). Two distinct lines replace the ready-to-copy `--verify-handle` command that `task done` was bound to refuse: in a keyless project, "Verify handle: none — no project key, so no signed receipt (tausik key init enables them)"; when a key is present but signing failed, "Verify handle: none — the receipt could not be signed (see .tausik/tausik.log, tausik key show)". Both end with the close command that works — `task done --ac-verified`, the freshness lookup. The same renderer serves the CLI and `tausik_verify`. One divergence deliberately does NOT block: if git sees changes outside the receipt's scope but none of them is security-sensitive, the handle is honoured and the divergence goes into the verdict text (decision #138 — it fires on almost every honest close: CHANGELOG, docs, generated constants). A divergence nobody sees equals an unmeasured one, so it is stated rather than swallowed. **Compatibility.** `task done` **without** `--verify-handle` works as before, through the fresh-run lookup. A silent tightening is not allowed: the handle changes how a green is **presented**, not which greens count. **What the handle does NOT give you.** It is not authorisation. The private seed lives in the working tree (see "What the signature does NOT prove" below), so an agent is capable of producing a signature the key will accept. The handle's job is to make the link "verified -> closing" explicit and checkable, and replay countable; not to restrain a determined agent. That is precisely why everything the old search checked is **recomputed here against live state** rather than taken on trust from the presented document (SEP-2322: "servers MUST always validate that state, as the client is an untrusted intermediary"). Auditing unredeemed handles: ```sql SELECT id, task_slug, handle_expires_at FROM verification_runs WHERE handle_nonce IS NOT NULL AND handle_redeemed_at IS NULL; ``` ## Key management ```bash tausik key init # generate the project ed25519 keypair (refuses to overwrite) tausik key show # print the public key + fingerprint (never the seed) ``` Keys live under `.tausik/keys/`: | File | Contents | Shareable? | |---|---|---| | `project.key` | private 32-byte seed (`ed25519:<64 hex>`) | **No** — never leaves the machine; `.tausik/` is gitignored | | `project.pub` | public key (`ed25519:<64 hex>`) | Yes — distribute out-of-band for verification | To rotate, run `tausik key init --force`. Be aware: **existing signatures will no longer verify** against the new key. ## Working with receipts ### `tausik receipt show` Print the latest stored envelope for a task (or a specific run) and re-verify its signature against the project key. ```bash tausik receipt show --task my-feature # or: --run 412 tausik receipt show --task my-feature --json # raw envelope JSON ``` Exit codes: `0` valid, `1` signature **invalid** (payload or signature was modified), `2` not found / no key. ### `tausik receipt export` Produce a self-contained, portable artifact — the envelope plus the embedded public key — for a PR or external audit. Export refuses to run if the stored signature does not verify against the current key (tampered row or rotated key). ```bash tausik receipt export --task my-feature # writes to .tausik/receipts/ tausik receipt export --task my-feature --out receipt.json tausik receipt export --task my-feature --stdout # print, don't write ``` The export wraps the envelope in a `tausik-receipt-export/v1` artifact that embeds the public key, so it can be verified anywhere: ```json { "export": "tausik-receipt-export/v1", "envelope": { "...tausik-signed/v1, untouched..." }, "public_key": "ed25519:<64 hex>", "key_fingerprint": "9f3c1a2b4d5e6f70" } ``` ### `tausik receipt verify ` Offline integrity check of an exported artifact — **no database, no keystore, no SDK** required. By default it uses the public key embedded in the file. To distrust that key, pin your own out-of-band: ```bash tausik receipt verify receipt.json tausik receipt verify receipt.json --pub ed25519:7c2f...e0 ``` Exit codes: `0` valid, `1` real artifact with a bad signature, `2` the file is not a valid export artifact. ## Trust model The signature proves **integrity** — the receipt was not modified after it was signed. It does **not**, on its own, prove **origin**: an embedded fingerprint is only as trustworthy as the file it travels in. To anchor origin, compare the `key_fingerprint` against an out-of-band channel — `tausik key show` output, a pinned CI variable, or the PR description — and never trust a fingerprint embedded in the very artifact you are verifying. ### What the signature does NOT prove — the key boundary Be precise about the claim, because the wording is legally load-bearing under the EU AI Act (logging obligations for high-risk systems apply from August 2026). The private seed lives at `.tausik/keys/project.key` **inside the working tree** — the same tree the agent whose work the key signs can read (`mode 0600` is best-effort on POSIX and a no-op on Windows). Therefore: - A valid signature is **tamper-evidence against EXTERNAL edits** to `tausik.db`: nobody rewrote the "green" verdict or replayed an old result onto a new commit *without access to the key*. The same holds for the `events_anchor` signature over the event chain. - It is **NOT attestation against the agent**. An agent (or anything else) with read access to the working tree can read the seed and produce a signature the key would accept. The receipt proves "someone with filesystem access produced this", not "an independent party attests the agent behaved". To obtain attestation the agent cannot forge, the signing key must live **outside the agent's write (and read) zone** — a managed path, an OS keychain, or a separate signer process. That relocation is deliberately deferred (see the `tausik decide` record for `l26-signing-key-boundary`): it is a cross-platform key-custody design of its own, and the immediate integrity win is stating this boundary honestly rather than implying an attestation the current storage cannot deliver. ## Offline / no-SDK verification over HTTP For agents and CI that cannot use the CLI or MCP, `tausik serve` exposes the same signing and verification over a stateless HTTP endpoint — submit gate results and get back the same `tausik-signed/v1` receipt. See [no-sdk-verify.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/no-sdk-verify.md) for the endpoints, a stdlib client, and a GitHub Actions snippet. ## See also - [cli.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md) — full CLI reference (`key`, `verify`, `receipt`). - [mcp.md](/docs/mcp) — the equivalent MCP tools. - [no-sdk-verify.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/no-sdk-verify.md) — HTTP verify endpoint (`tausik serve`). - [senar.md](/docs/senar) — the SENAR verify-first principle behind QG-2. ## Who did the work (`actor`, v4) A receipt is signed with the **project** key and answers which gates passed on which state of the code. Until v4 it did not answer WHO ran them — while the whole quality story rests on separation of duties: an external reviewer on a different model, and with P8 also "the test author is not the implementer". All of it asserted, none of it attestable. The measurement behind the field: 1,686 receipts, not one carrying an actor; `claimed_by` NULL across all 1,574 tasks; of the 29 tasks recording both a starting and a closing model, **none differ**. On the verify path separation of duties does not merely lack attestation — it does not happen. `actor` is **content** of the project-signed receipt, not a second signer. A second key would mean per-agent key material and an identity registry; neither is created, and keys stay local. The field is inside the canonical bytes, so an actor edited after the fact breaks the signature. **Comparing two receipts yields THREE outcomes, not two:** `same`, `different` and `unknown`. A receipt with no `actor` is not a receipt by a different actor, and a check of the form `!= same` would count every one of the 1,686 legacy receipts as separated. Require `different` explicitly. **The boundary.** The receipt records the actor OF THE VERIFICATION RUN. It does not say who wrote the test and who wrote the code — that is P8's subject, and offering this field as evidence for that claim is not allowed. v1–v3 receipts stay valid: the field is added, not required. `actor` is deliberately absent from `V3_REQUIRED_FIELDS`, or 1,686 closures would stop reading at once. --- ## English · /docs/context-economy # Context economy in 1.11 **English** | [Русский](/ru/docs/context-economy) TAUSIK now separates a small invariant contract from task-specific context. New projects use `context_tier: minimal`; existing rules files remain untouched until their owner regenerates or edits them. Measured in this repository on 2026-10-01: | Repeated source | Before | After | Change | |---|---:|---:|---:| | Root `AGENTS.md` static instructions | 17,078 B | 4,602 B | -73.1% | | Claude SessionStart context | 8,670 B | 1,059 B | -87.8% | | Generated rules (`standard` to measured `minimal`) | 15,533 B | about 6,000 B | about -61% | The task package is loaded once when work starts rather than repeated as a global rule. The live 1.11 context task package is 6,007 B against an 8,192 B default budget. It contains goal, acceptance criteria, paths, active decisions, unresolved risk and verification commands. Required fields are never shortened: an oversized contract is returned whole with explicit overflow. Optional memory is admitted line by line and names the omitted range. The task skill requests the package in the successful start result, removing a dependent model turn: ```bash .tausik/tausik task start --package ``` MCP uses `tausik_task_start(slug=..., package=true)`. The previous workflow was `task_start` followed by `task_show(package)`: two model responses and two tool calls on a successful start. The compound route is one response and one call. QG-0 still runs before activation. If the read-only projection fails after the activation commits, the envelope says `started=true, ok=false` and supplies the targeted recovery command; it never reports the partial result as success. For targeted recovery or a later refresh, use MCP `tausik_task_show` with `mode=package`, or: ```bash .tausik/tausik task show --package --max-bytes 8192 ``` The package fingerprint changes when task intent, paths, risk, plan or active decisions change. Superseded decisions are excluded. Tests cover omitted memory, required-field overflow and superseded decisions on the shared path used by Codex, Claude and GLM hosts. Codex native `turn_context` records observed in this environment expose no tool-schema field or explicit `tools_deferred` signal. Tool-schema deferral is therefore **unknown**. TAUSIK does not claim savings from schema deferral. The project does control the surface its own MCP server advertises. On 2026-10-02, a fresh stdio process was measured before and after enabling the existing `mcp.compact_tool_list` project flag. Both measurements used the same 147-tool `tools/list` response and minified UTF-8 JSON; source-file size and a stale already-running MCP process were excluded. | Eager project-controlled source | Before | After | Change | |---|---:|---:|---:| | `AGENTS.md` | 7,518 B | 7,518 B | 0 B | | Canonical skill catalog (15 names + descriptions) | 1,249 B | 1,249 B | 0 B | | MCP names | 3,974 B | 3,974 B | 0 B | | MCP descriptions | 17,722 B | 11,897 B | -5,825 B | | MCP schemas | 32,134 B | 17,145 B | -14,989 B | | MCP JSON structure | 736 B | 736 B | 0 B | | **Eager total** | **63,333 B** | **42,519 B** | **-32.9%** | The rows are an exact non-overlapping partition: MCP field fragments plus JSON structure add to the serialized list, while `AGENTS.md` and the skill catalog are counted once. The 89,315 B of skill bodies are reachable on demand and are reported separately, so they are not added to the eager total. Non-core tool names and short descriptions remain visible; their complete descriptions and schemas are retrieved by exact name through `tausik_tool_schema(name=...)`. Core session, task, verification, memory, search, doctor and recovery operations keep their complete eager definitions. This deterministic server-side reduction does not change the earlier statement about unknown native Codex schema accounting. The one-call count is deterministic on the shared Codex, Claude and GLM skill. Live GLM quota and retry savings remain unmeasured until the release acceptance run; TAUSIK does not translate the saved call into a token or subscription quota claim. Retry cases and measurement boundaries are recorded in the [1.11 compound workflow note](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/research/compound-workflow-111.md). --- ## English · /docs/architecture **English** | [Русский](/ru/docs/architecture) # TAUSIK Architecture Reference **Where it sits.** The field calls this discipline *harness engineering*: a harness is the agent loop, the tool interface, context management and control mechanisms. TAUSIK is not a harness — the loop and the tools belong to Claude Code, Cursor, Codex and the rest. It is the verification and control layer on top of them: what makes it different is that nothing counts as done without evidence. ## Architecture: CLI -> Service -> Backend Three layers with clear boundaries. The Service layer contains business logic, the Backend handles only CRUD and SQL. CLI and MCP are two equal entry points. ``` Engineer (free-form text) | AI Agent (Claude Code / Cursor) | +---------------------------+ | Skills (SKILL.md) | <- instructions for the agent +---------------------------+ v v +---------+ +---------+ | MCP | | CLI | <- two entry points | (tools) | | (bash) | +----+----+ +----+----+ +------+-------+ | +---------------------------+ | Service Layer | <- business logic, QG-0, QG-2 | project_service.py | | + service_task.py | | + service_knowledge.py | +---------------------------+ | +---------------------------+ | Backend Layer | <- SQLite CRUD, FTS5, metrics | project_backend.py | | + backend_queries.py | | + backend_graph.py | | + backend_schema.py | | + backend_migrations.py | +---------------------------+ | +---------------------------+ | SQLite (WAL mode) | <- .tausik/tausik.db | schema v67 | +---------------------------+ ``` ## Key Modules ### Scripts (Business Logic) TAUSIK 1.11 still has a flat deployment layout: 563 files under `scripts/`, 502 at its root. This is historical compatibility, not the 2.0 target. See **[TAUSIK 2.0 package layout](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/package-layout-2.0.md)** for the measured boundary, migration baseline, and rollout. Modules in `scripts/`, each <=500 lines (`filesize` gate; raised from 400 as an interim measure by decision #190, because the tighter cap was deforming the architecture rather than improving it). Line count is not the only size control: the `class_surface` gate separately caps a class's composed public surface after inheritance, which a per-file cap structurally cannot see. Highlights: | File | Purpose | |------|---------| | `project.py` | CLI entry point, dispatch | | `project_parser.py` | argparse command tree | | `project_cli.py` / `_extra.py` / `_ops.py` | CLI handlers (status, task, session, memory, gates, skills, fts, metrics, search, events, explore, audit, run) | | `project_cli_doctor.py` / `_role.py` / `_stack.py` / `_verify.py` | CLI handlers (doctor, roles, stacks, verify) | | `project_service.py` + `service_*.py` mixins | Business logic: tasks, knowledge, skills, gates, cascade, roles, verification | | `service_verification.py` | Scoped pytest gate + verify cache (10 min TTL) | | `service_roles.py` | Hybrid role storage (DB metadata + harness/roles/*.md) | | `service_stack_ops.py` | Stack scaffold, lint, diff, reset | | `project_backend.py` + `backend_*.py` | SQLite + FTS5 backend (WAL mode, schema v67) | | `backend_session_metrics.py` | Gap-based active-time computation | | `backend_tier_metrics.py` | call_budget vs call_actual tier metrics | | `backend_migrations.py` / `_legacy.py` / `_v*.py` | Schema migrations through v67; 34 version modules remain for 1.11 upgrade compatibility | | `project_config.py` + `default_gates.py` | Config loader, gates config, auto-enable | | `gate_runner.py` + `gate_stack_dispatch.py` + `gate_test_resolver.py` | Scoped pytest mapping + dispatch | | `skill_manager.py` + `skill_repos.py` | Skill install/uninstall from repositories | | `knowledge_db.py` + `knowledge_write.py` + `knowledge_read.py` | The shared local store `~/.tausik-knowledge` (`--global` on `decide` / `memory add`; folded into `memory search`) | | `publication_boundary.py` + `knowledge_export.py` | The one place shared-store content leaves the machine (`knowledge export --redacted`) | | `cq_client.py` | Cross-project queue client | | `doc_extract.py` | markitdown integration | | `docs_lint.py` | Warning-only stale-version linter | | `plan_parser.py` | Markdown plan parser for `/run` | | `model_routing.py` | Model selection helper | | `ide_utils.py` | IDE detection, paths, registry | | `tausik_utils.py` + `tausik_version.py` + `project_types.py` | Helpers, version, types | | `gen_doc_constants.py` | Doc-drift entry point: `--check`, `--write`, regenerate `constants.json` | | `doc_drift_common.py` | Shared regex tables, scan targets, text helpers | | `doc_drift_scanners.py` | The six drift scans (versions, MCP counts, closed lists, test and code counts) | | `doc_drift_tables.py` | Numeric cells of markdown table columns, with their subject registry | | `doc_drift_fixes.py` | The auto-fixer `--write` runs | | `code_counts.py` | Counts repo state: hooks, stacks, roles, review agents, skills | | `mcp_tool_counts.py` | Counts the MCP surface each server advertises | | `audit_orphan_files.py` / `audit_stale_docs.py` / `audit_unused_python.py` / `audit_pytest_dedupe.py` | Static audit reports (review-only, v1.5) | | `project_cli_hygiene.py` | `tausik hygiene archive` / `unarchive` (soft-archive old done tasks and the one path back) | | `hooks/check_docs.py` | Pre-commit / CI wrapper for doc-constants drift (v1.5) | ### Bootstrap (Generation) | File | Lines | Purpose | |------|-------|---------| | `bootstrap.py` | ~320 | Orchestration: vendor sync, copy, generate | | `bootstrap_vendor.py` | ~280 | Download vendor skills from GitHub (tarball) | | `bootstrap_copy.py` | ~180 | Copy skills, scripts, MCP into `.claude/` | | `bootstrap_config.py` | ~70 | Configuration, stack detection | | `bootstrap_generate.py` | ~300 | Generate settings.json, CLAUDE.md, skill catalog | | `analyzer.py` | ~260 | Extension-skill detection and tree walking | ### MCP Server | File | Purpose | |------|---------| | `harness/claude/mcp/project/server.py` | JSON-RPC stdio server | | `harness/claude/mcp/project/tools.py` | core tool definitions | | `harness/claude/mcp/project/tools_extra.py` | extended tool definitions (skills, gates, doctor, verify, roles, stacks) | | `harness/claude/mcp/project/handlers.py` | Dispatch only: tool-call counter, `handle_tool`, merge of the per-domain tables | | `harness/claude/mcp/project/handlers_.py` | Handlers by domain: `task`, `session`, `status`, `knowledge`, `hierarchy`, `stack`, `role`, `verification`, `cq`, `skill`, `spec`, `adapt`. Each module exports `_HANDLERS`; `handlers.py` merges them into `_DISPATCH` | | `harness/claude/mcp/project/handlers_render.py` | Shared list rendering (`render_list`) — an empty result must read as "nothing here", not as an empty string | Total MCP surface: **147 project tools** (optional `codebase-rag` adds 7 more; not part of the main count). **THAT SURFACE IS PAID FOR ON EVERY TURN, AND THE PRICE DIFFERS BY HOST.** The MCP protocol sends every tool's name AND schema each turn unless the client defers schemas. Measured in session #229: the project server's serialized definitions are 56,108 bytes (~14,000 tokens); the names alone are 3,201 bytes (~800 tokens) — a 17.5x difference. Claude Code defers and pays the names, which was OBSERVED; a host without deferral pays the whole thing, which follows from the protocol and was not observed here. The figures above are for reading, not the source of truth: that is the `mcp_surface` ratchet in `tausik/gates.json`, which `tests/test_mcp_surface_ratchet.py` re-measures on every run and turns red on growth. It is done that way because the previous version of this number rotted in exactly this spot — the task filed 117 tools and 44,501 bytes in session #178, and by #229 it was 145 and 56,108: 24% and 26% of growth nobody noticed while the number lived in prose. ### Contextual chunk headers (codebase-rag) A chunk cut out of a file stops carrying what the file was about, so a query phrased in the document's terms cannot reach a passage phrased in its own. Each indexed chunk therefore carries a short header built by `harness/claude/mcp/codebase-rag/rag_context.py`: path words, the symbol it defines — or the one it sits inside, for a continuation chunk — and the file's own summary line. This is contextual retrieval with the model taken out. The published technique asks an LLM to write a sentence of context per chunk; here the header comes from metadata the indexer already has, so the same input yields the same bytes and indexing stays reproducible and offline. The header lives in its own indexed column (`rag_chunks.context_prefix`), never in the chunk's content: a word present only in the header still matches, and still does not appear in what search returns. An index built before v1.8 grows into the layout on first open — the column is added and the FTS table rebuilt from the chunks, which are the source of truth. **How that was measured, and how to measure it again.** The headers are not "obviously helpful" — their gain is counted by a reproducible instrument, `scripts/rag_retrieval_bench.py` (`python scripts/rag_retrieval_bench.py [--limit N] [--json]`). It derives its queries MECHANICALLY from the corpus and samples them deterministically, so the set cannot be tuned toward a flattering result and two runs over the same tree ask the same questions. It measures recall@K against the SPECIFIC CHUNK and keeps two query sets at once: `context`, the case the header exists for, and `control`, queries built only from words already inside the chunk, where the header must not help and above all must not hurt. At n=115: recall@3 went 0.4870 → 0.8348 on `context` and 0.5739 → 0.6435 on `control`, with no regression at any K. ### Cross-IDE Support Skills, roles, stacks -- shared across IDEs. So are the MCP servers: `harness/claude/mcp/` is the single canonical tree, and `copy_mcp` hands it to every IDE that has none of its own (all of them, today). A per-IDE copy would be a mirror waiting to drift — one used to exist under `harness/cursor/` and was deleted in v1.7.0. ``` harness/ +-- skills/ # 13 core auto-deployed; external skills installed per skill +-- roles/ # 7 roles (architect, developer, devops, qa, researcher, tech-writer, ui-ux) +-- stacks/ # Stack guides +-- overrides/ # IDE-specific overrides (claude/, cursor/, qwen/) +-- claude/mcp/ # MCP servers (project, codebase-rag) — canonical for ALL IDEs +-- opencode/plugins/ # QG-0 enforcement plugin for OpenCode (tool.execute.before) ``` #### Runtime (IDE) × Model — two orthogonal axes TAUSIK separates *where* it runs from *which model* answers: | Axis | What it controls | `bootstrap --ide` target | Active-model detection | |------|------------------|--------------------------|------------------------| | **claude** | Claude Code (VSCode/CLI) | `.claude/` + `.mcp.json` | JSONL transcript (`model` field) | | **cursor** | Cursor | `.cursor/` + `.cursor/mcp.json` | — | | **qwen** | Qwen Code | `.qwen/settings.json` | — | | **kilo** | Kilo Code (addon + CLI) | `.kilo/kilo.jsonc` **and** `.kilocode/mcp.json` | `KILO_MODEL` env / `.kilo` config | | **opencode** | OpenCode (SST) | `opencode.json` + `.opencode/plugins/` | — | The **model axis** is data, not code: `scripts/model_profiles.py` maps vendor families (`claude`, `glm`/z.ai) × capability ranks → concrete model ids, overridable in `.tausik/config.json` `model_profiles.families`. The routing matrix emits an abstract rank; the active family resolves it to a real model — so a z.ai GLM session routes to GLM models with no code change. See [Kilo + z.ai](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/kilo-zai.md). ## DB: Selected Tables (Schema v67) | Table | Purpose | |-------|---------| | `meta` | Metadata (schema_version) | | `epics` | Epics | | `stories` | Stories (-> epic) | | `tasks` | Tasks (-> story, scope, defect_of, plan, AC) | | `sessions` | Sessions (start, end, summary, handoff) | | `memory` | Project memory (pattern, gotcha, convention, context, dead_end) | | `decisions` | Architectural decisions | | `events` | Audit log (gate_bypass, status_changed, claimed) | | `explorations` | Investigations (time-boxed) | | `memory_edges` | Graph links between memory/decision (Graphiti) | | `fts_tasks` | FTS5 full-text index for tasks | | `fts_memory` | FTS5 index for memory | | `fts_decisions` | FTS5 index for decisions | | `task_logs` | Structured task logs (phase, message) | | `fts_task_logs` | FTS5 index for task logs | | `roles` | Role registry (hybrid: metadata + harness/roles/{slug}.md) | | `session_activity` | Per-tool-call timestamps for gap-based active time | | `verification_runs` | Verify cache: file_hash + timestamp for QG-2 reuse (10 min TTL) | ## Quality Gates ``` gate_registry.py -> GATE_REGISTRY: the one declaration per built-in gate -> GateSpec(name, phase, default_config, impl) -> phase: scoped | post_scope default_gates.py -> DEFAULT_GATES = universal (from registry) ∪ stack-scoped (from stack_registry) ∪ post-scope (from registry) gate_runner.py -> run_gates(trigger, files) [scoped phase only] -> dispatch via GATE_REGISTRY[name].impl, unknown name -> run_command_gate() gate_post_scope.py -> run_post_scope_gates() [post_scope phase] -> verify_first, changelog + one gate_runs row each service_task.py -> _run_quality_gates() (called from task_done) ``` Adding a built-in gate is one `GateSpec`. Before `gate-registry-single-source` it meant four edits, and the two post-scope gates lived in only one of the four: `gates status` did not list them, `gates enable/disable` could not reach them, and they wrote no `gate_runs` row — so nothing could prove a QG-2 gate had run. **Scoped gates** — `(gate_config, files) -> (passed, output)`, run over the task's declared scope. Universal (always on): `filesize`, `class_surface`, `tdd_order`, `ruff`, `mypy`, `bandit`, `bootstrap_drift`, `memory_route`, `renar_drift_schema`, `renar_drift_provenance`, `cross_model_parity`. `cross_model_parity` asks one question: did a capability go host-only without anyone saying so. It RUNS the real mechanism generators into a clean tree and compares hosts that share an extension point — Claude's hooks against Qwen's, plugins against plugins. There is no comparison ACROSS kinds: asking whether Claude is "missing" OpenCode's plugin is a question with no meaning. The gate does NOT demand sameness: Cursor has no extension point at all, so there is nothing for it to be equal to. What it demands is that a difference be NAMED, with a reason — and a declaration that no longer matches any live difference is refused as loudly as an undeclared difference. It fires only on host-layer edits (`bootstrap/`, `scripts/hooks/`, `harness/opencode/`): a gate that asks every task about cross-model parity is a tax, and a tax gets switched off. `bootstrap_drift` checks all three links of the chain "edit → deploy → takes effect": `scripts/` against the deployed profile, `harness/` against its fan-out, and the deployed profile against **the process that runs from it** (`running_source_drift`: a content snapshot at process start, compared at task-done). A long-lived MCP server whose profile was rewritten underneath it executes the old copy; the gate refuses the close and names the fix — restart the server, or close via the CLI, which is a fresh process. **A gate is verified by mutation, not by passing.** A green run proves only that the gate did not object — not that it would object to anything. The rule is held by `tests/test_gates_catch_their_violation.py`: every gate in `gate_registry.GATE_REGISTRY` is in exactly one of two tables. COVERED — the gate is driven through the registry's own `impl_for` on BOTH ends: a real violation built under `tmp_path` must come back failed, a clean input built the same way must come back passed (one end alone is empty: a gate that is red on every input passes a red-only check as well as a correct one). EXCUSED — a gate that cannot be driven from a synthetic tree (service-bound, an external tool's verdict, warn severity), with a reason and the names of a red and a green test in the module that does drive it; the names are checked against that module's AST. The list is closed: a new gate without a row reddens the lane. A mutation cannot stay in the tree by construction — everything is built under `tmp_path`, and a write outside it during the table run is caught by intercepting `open`, `sqlite3.connect` and `subprocess` (the three channels the builders use) — not by a `git status` snapshot, which under xdist loses the race to a neighbouring worker. `class_surface` is the one exception to "run over the declared scope": it ignores the file list and measures the **whole repo** (~0.65s). A class grows past its cap through its *bases*, so a scoped run would never see it — the same blindness that let a module reach 406 lines without blocking anyone. It caps a class's composed public surface after inheritance, which the per-file `filesize` cap structurally cannot see: a god-object built from mixins keeps every file under the line cap. The two **complement** each other — "this class does too much" and "this file is too long to read" are different defects. Counts are a **lower bound** (AST, never `import`, so a gate can measure a branch nobody has read yet), and known oversized classes sit behind a ratchet baseline in `tausik/gates.json` that may only shrink. **Post-scope gates** — take the close context and edit the QG-2 report: `verify_first` (a fresh signed verify green must exist) and `changelog`. `get_gates_for_trigger` excludes them, so `run_gates` never calls one with the wrong signature. Stack-scoped gates: `pytest`, `tsc`, `eslint`, `js-test`, `go-vet`, `go-test`, `golangci-lint`, `cargo-check`, `cargo-test`, `clippy`, `phpstan`, `phpcs`, `phpunit`, `javac`, `ktlint`, `ansible-lint`, `terraform-validate`, `helm-lint`, `kubeconform`, `hadolint`. ## RENAR adoption — advisory-first ("lite") TAUSIK is a lightweight, zero-dependency framework, so it adopts [RENAR](https://renar.tech) (reasoning/governance standard) **advisory-first** rather than as heavyweight mandatory ceremony. Adoption climbs a ladder with explicit entry conditions per rung: | Rung | What | Status | |---|---|---| | 1. Artifacts | SPEC / ADAPT / conformance embedded in the SQLite substrate + one-way `renar/` export | done (RENAR-1) | | 2. Advisory signals | QG-0 surfaces a **non-blocking** nudge when a high-stakes task (tier `substantial`/`deep`, or `complex`) starts with no linked SPEC and no ADAPT — `gate_qg0_renar.renar_qg0_advisory`, toggle `renar.qg0_advisory` (default on) | done (1.5) | | 3. Evidence-based hard-gate | promote a specific advisory to **blocking** only when a real defect traces to its absence (per the #91 coherence audit) | 2.0 | | 4. RENAR-2 signed/immutable ADAPT | sign ADAPT (ed25519) → `tz_immutable=true` + delta-ADAPT — **irreversible, user-directed only** | 2.0 | The philosophy: RENAR strengthens SENAR by making interpretation **visible** at the natural gate (QG-0), while keeping the agent unblocked — fail-soft on advisory, fail-closed only on proven gates. This is a deliberate lightweight-adoption policy, not "unfinished RENAR". ## Orchestrator-worker (model auto-switch via sub-agents) The main session is the **coordinator** (planning, AC, review). A task of complexity ≤ medium can be **delegated** to a **worker sub-agent** spawned with the recommended model, reasoning effort and standard speed. This is the model selection surface exposed by Claude Code and Codex; TAUSIK provides the delegation **scaffolding/state**, while the host agent performs the actual spawn. Kilo/GLM routes are advisory until that host surface is live-verified. | Step | Command / mechanism | |---|---| | Delegate | `tausik task delegate ` — records {recommended model, parent session} in the `meta` kv (no schema migration). **complex tasks are refused** (they stay with the coordinator). | | Handoff contract | `tausik task handoff ` — deterministic JSON {slug, goal, acceptance_criteria, scope, scope_exclude, model, skills}; the trimmed `WORKER_SKILLS` profile (no plan/explore). The orchestrator passes it to the Agent tool; the worker echoes it back (round-trip identity). | | In-session recognition | `task start` on a delegated task surfaces **worker mode** (operating contract) and suppresses the orchestrator-only model-recommendation banner. | | Scope hard-gate | the worker is scope-bounded — `scope_write_gate` blocks edits outside `scope_paths`, and a delegated task with **no** scope is blocked until it declares one (no legacy fail-open for workers). | | Summary-back | `tausik task summary-back "" [--gates …]` — the worker returns a structured result (stored in `meta`, surfaced in `task show`) so the coordinator picks it up **without** the worker transcript. | Delegation state is CLI-first (no MCP surface, to avoid doc-count drift) and lives entirely in the `meta` table (`delegation:`, `worker_summary:`). Route telemetry uses separate outcomes (`selected`, `applied`, `rejected`, `unavailable`). Selection alone is not application: worker-mode recognition at `task start` is the event that records `applied`. The coordinator cannot be switched mid-session and keeps QG-0/QG-2, evidence and task closure. ## Hooks (anti-drift, see [hooks.md](/docs/hooks)) All hook files under `scripts/hooks/` are registered via `bootstrap/bootstrap_generate.py` (Claude Code) and `bootstrap/bootstrap_qwen.py` (Qwen Code). Hook scripts are non-blocking (exit 0); errors go to stderr. Shared helpers live in `scripts/hooks/_common.py`. ## Memory Aggregates `service_knowledge_aggregates.py` holds pure functions for memory re-injection: - `build_memory_block(be, ...)` — compact markdown (decisions + conventions + dead ends), ≤50 lines, called from `/start`, `/checkpoint`, and the SessionStart hook - `build_compact_memory_tail(be)` — the one-line-per-entry recap embedded in the CLAUDE.md dynamic block - `build_memory_compact(be, last_n)` — `task_logs` aggregation: phases + top words + top files Both recaps read the memory graph through `memory_supersedes.live_head`: an entry a LIVE `supersedes` edge has retired is not printed, its line goes to the next live entry, and the surviving entry says `(supersedes #N)` on the line it occupies anyway. Only that relation hides anything, and only when the superseding entry is itself unarchived — otherwise the older entry is the best knowledge left. An unreadable graph retires nothing, so the recap degrades to what it printed before the filter existed. Likewise `scripts/model_routing.py` and `plugin_data.py` are pure modules imported by CLI/MCP handlers. ## Prompt Caching TAUSIK relies on Anthropic's automatic prompt caching to keep agent runs cheap. The framework itself does not call the API — Claude Code does — but the *structure* of what TAUSIK feeds into each turn decides whether the API caches a prefix or re-bills it. Cacheable surface, in priority order: | Surface | Where it lives | Why it caches well | |---|---|---| | System prompt + tool schemas | Injected by Claude Code from `.claude/mcp/project/tools.py` and `tools_extra.py` | Identical across turns within a session — the longest stable prefix | | `CLAUDE.md` | Project root | Read once per session and re-injected; stable unless `tausik_update_claudemd` rewrites the dynamic block | | MCP tool descriptions | Same `tools.py` files | Editing them invalidates the cache — every wording change rewrites the prefix | | Skills (`SKILL.md`) | `harness/skills//SKILL.md` | Loaded only when the skill activates | **What invalidates the cache mid-session.** Editing any of the files above between turns rewrites the prefix and forces the next turn to pay `cache_creation_input_tokens` instead of `cache_read_input_tokens`. The biggest offender is `tausik_update_claudemd` — running it mid-session rewrites the dynamic-state block (session #, task counts, etc.) and the entire `CLAUDE.md` prefix re-caches. Run it at session boundaries (`/start`, `/checkpoint`, `/end`), not between regular tool calls. **Verifying caching is actually active.** Anthropic returns `cache_creation_input_tokens` (the prefix was just laid down) and `cache_read_input_tokens` (a later turn hit the cache) in every response's `usage` block. `scripts/validate_prompt_caching.py` parses a Claude Code transcript JSONL and reports both totals plus a hit-rate: ```bash python scripts/validate_prompt_caching.py --auto # or python scripts/validate_prompt_caching.py path/to/transcript.jsonl ``` Exit code `0` = caching active (`cache_read_input_tokens > 0`); `1` = prefix is unstable (creation > 0 but reads = 0); `2` = API never returned cache fields. See [troubleshooting.md](/docs/troubleshooting) "Prompt caching not active" for common failure modes. ## Testing ```bash pytest tests/ -v # all tests (3844) pytest tests/test_tausik_backend.py # backend CRUD pytest tests/test_tausik_service.py # service logic pytest tests/test_tausik_cli.py # CLI smoke pytest tests/test_gates.py # quality gates + stack auto-enable pytest tests/test_vendor.py # vendor skills + persistence pytest tests/test_graph_memory.py # graph memory edges pytest tests/test_mcp_integration.py # MCP handlers pytest tests/test_senar.py # SENAR compliance pytest tests/test_e2e_workflow.py # E2E workflow ``` When adding or restructuring tests (new module vs extending an existing file, scoped pytest mapping), follow **[Testing principles](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/testing-principles.md)**. --- ## English · /docs/skills **English** | [Русский](/ru/docs/skills) # Skills (v1.4) Skills are intent-based instructions that define agent behaviour. You don't memorize names or syntax — you write what you want, and the agent picks the right skill. Slash-prefix (`/plan`, `/ship`) explicitly invokes one. After bootstrap, **13 core skills** ship with TAUSIK from `harness/skills/`. The official store contains only `docs`, `excel`, and `pdf`. Install one only when needed with `tausik skill install ` so unused instructions add no prompt cost. **Map of repo skills:** [Skill ecosystem (one page)](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/skill-ecosystem.md). > **Token rule since v1.4.** Before v1.4, bootstrap exposed the whole catalog and spent about 1,520 tokens on its reminder list. The default now exposes only core skills. `--include-official` remains a local-checkout compatibility switch, but per-skill installation is the supported economical path. **Multi-host variants:** skills can ship optional **`variants/.md`** overlays — see [Skill profiles & variants](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/skill-profiles.md). ## Core Skills (13) These are always available after bootstrap — the workflow primitives every TAUSIK project needs. ### Workflow | Skill | When | |-------|------| | `/start` | Begin a work session — loads handoff, status, memory block | | `/end` | Wrap up the session — saves metrics + handoff | | `/checkpoint` | Save context without ending the session (when the checkpoint signal says so) | | `/plan` | Plan a task from a free-form description (interview phase + AC) | | `/task` | Work on an existing task with QG-0/QG-2 enforcement | | `/ship` | Wrap up a task: review + test + gates + commit | | `/commit` | Create a standardized git commit | ### Knowledge | Skill | When | |-------|------| | `/explore` | Time-boxed investigation (default 30 min) before committing to an approach | | `/interview` | Socratic Q&A — at most 3 questions to pin down requirements | | `/reason` | Record a structured reasoning trace (intent→premise→action→verification) on a task — see [Reasoning Trace](/docs/reasoning-trace) | ### Quality | Skill | When | |-------|------| | `/review` | Code review against 28-point SENAR checklist (5 parallel agents, iterative) | | `/test` | Run or write tests, track coverage | | `/debug` | Reproduce → isolate root cause → fix | ## Official / Vendor Skills The official catalog is a separate repository and is not part of the core release. It deliberately contains only three neutral document skills: `docs`, `excel`, and `pdf`. Integrations belong in MCP servers or third-party repositories; company-specific roles do not belong in TAUSIK. - Install one capability with `tausik skill install ` and activate it only when needed. - Use `skill bundle` only for a third-party repository that declares one and only when every member is required. - Avoid `--include-official` in normal projects. It exposes every entry found in a local catalog checkout and increases the repeated prompt prefix. - The official repository does not publish a bundle. ## Lifecycle ```bash .tausik/tausik skill list # active + vendored + available .tausik/tausik skill repo add # register a TAUSIK-compatible repo .tausik/tausik skill install # clone + copy + pip deps .tausik/tausik skill activate # copy from harness/skills → .claude/skills .tausik/tausik skill deactivate # remove from .claude/skills (keep vendored copy) .tausik/tausik skill uninstall # remove completely ``` The official vendor repo: `https://github.com/Kibertum/tausik-skills`. Custom repos are supported — see **[Skill Adaptation Guide](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/skill-adaptation.md)**. ### Spec conformance (agentskills.io) The SKILL.md format is now the cross-vendor **agentskills.io** canon. A built-in gate (`skill_spec_conformance`) fails a close/commit when a changed `SKILL.md` breaks the machine-checkable rules: `name` is 1–64 chars of `a-z0-9` with single hyphens (no leading/trailing/doubled hyphen) and **must equal its directory name**; `description` is 1–1024 chars. The gate is inert unless a `SKILL.md` is among the changed files. Local scaffolds whose directory starts with `_` or `.` (e.g. `_profile-demo`) are not published skills and are skipped. > The agentskills.io spec is **not versioned** and contains **no security > provisions** — nothing about trust, sandboxing, or tool permissions. This gate > is a hygiene check that keeps progressive disclosure working; never treat > conformance as a trust signal. ### Bulk install via bundles `tausik skill install ` installs one skill at a time. A repository may publish optional **bundles**; inspect them before bulk installation — see **[Skill Bundles](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/skill-bundles.md)**: ```bash .tausik/tausik skill bundle list # discover bundles .tausik/tausik skill bundle install # install every member of that bundle ``` ## What's Next - **[Workflow](/docs/workflow)** — how skills compose into a work day - **[CLI Commands](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md)** — calling TAUSIK from the terminal directly - **[MCP Tools](/docs/mcp)** — programmatic surface for agents - **[Vendor Skills](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/vendor-skills.md)** — installing and authoring skill packages --- ## English · /docs/hooks **English** | [Русский](/ru/docs/hooks) # Hooks TAUSIK uses Claude Code hooks for automatic quality control. Hooks intercept agent actions **before** and **after** execution — they are gates, not instructions. **23 Python hooks + 1 shell `pre-commit`** ship with TAUSIK — 24 gates in total (v1.4 introduced `secret_scan.py`, `posttool_usage.py`, `tool_output_truncation_nudge.py`, and `task_cost_budget_check.py`; 1.8 added `scope_write_gate.py` and `bash_write_gate.py`; 1.9 added `read_ledger_gate.py`, off by default). ## What Are Hooks Hooks are scripts that run automatically with every agent action. They decide whether an action can be performed (PreToolUse), what to do afterward (PostToolUse), or what to record on session/agent boundaries (SessionStart, Stop, UserPromptSubmit). Shared helpers live in `scripts/hooks/_common.py` (not a hook itself); the regex set in `scripts/hooks/memory_markers.py` is a library imported by `memory_posttool_audit.py` and the brain-scrubbing pipeline. ## PreToolUse — Gates That Run Before an Action | Hook | When | What It Does | |------|------|-------------| | `task_gate.py` | Before every write tool — Write/Edit/MultiEdit/NotebookEdit and the MCP editors listed in `hooks/write_tools.py` (serena, windows-mcp; PR #5) | Blocks file changes if no active task (SENAR Rule 9.1) | | `scope_write_gate.py` | Before every write tool — Write/Edit/MultiEdit/NotebookEdit and the MCP editors listed in `hooks/write_tools.py` (serena, windows-mcp; PR #5) | Scope ACL (SENAR Rule 2, Walko pattern): blocks a write outside the union of the active tasks' declared `scope_paths`. This is the verdict source `bash_write_gate` reuses for shell writes. Conservative adoption: any active task without `scope_paths` → allow (legacy, undeclared = unrestricted); target outside the project root → allow; pre-v30 DB or any DB error → REFUSE unless `TAUSIK_HOOK_FAIL_OPEN=1` (the default flipped in 1.9). | | `memory_pretool_block.py` | Before every write tool — Write/Edit/MultiEdit/NotebookEdit and the MCP editors listed in `hooks/write_tools.py` (serena, windows-mcp; PR #5) **and every shell tool (Bash, PowerShell, `mcp__windows-mcp__PowerShell`)** | Layer 2 of memory-route enforcement: blocks a write into any foreign memory sink from `scripts/memory_sinks.py` (`~/.claude/**/memory/`, `.cursor/rules/`, `.github/copilot-instructions.md`, `.aider*`, …) and redirects to `memory add`. The shell tools are on the matcher because a heredoc — or a `Set-Content` — writes what the Write path refuses. Bypass: `confirm: cross-project` in the prompt, or `gates.memory_route.allow` in config. | | `secret_scan.py` (v1.4) | Before every write tool — Write/Edit/MultiEdit/NotebookEdit and the MCP editors listed in `hooks/write_tools.py` (serena, windows-mcp; PR #5) **and every shell tool (Bash, PowerShell, `mcp__windows-mcp__PowerShell`)** | Scans `tool_input` for likely secrets (AWS/GitHub/Slack/Stripe/OpenAI/Anthropic tokens, JWT, private-key blocks, generic `password`/`api_key` literals). Warns by default; set `TAUSIK_SECRET_SCAN_STRICT=1` to block. (SENAR Rule 10.12). The shell tools are on the matcher: a heredoc or a `Set-Content -Value 'AKIA…'` carries the same secret. | | `bash_firewall.py` | Before Bash **and PowerShell** | Blocks dangerous commands (`rm -rf /`, `Remove-Item -Recurse C:\`, DROP TABLE, `Format-Volume`, force push, etc.). The dialect is chosen by `tool_name`: the POSIX lexer cannot read PowerShell, where `\` is an ordinary path character rather than an escape. | | `bash_write_gate.py` | Before Bash **and PowerShell** | Applies the same QG-0 (Rule 1) and scope-ACL (Rule 2) verdict to a shell write that the Write tool gets — by importing `scope_write_gate`'s decisions, not copying them. Parses redirections, `tee`/`dd`/`sed -i`/`cp`/`mv`, and `Set-Content`/`Add-Content`/`Out-File`/`New-Item`/`Tee-Object`. | | `git_push_gate.py` | Before Bash **and PowerShell** | Blocks unless `.tausik/.push_ticket.json` is fresh, single-use, and bound to HEAD SHA. `/ship` and `/commit` run `tausik push-ok && git push` after your "y" — `push-ok` writes the 60-second ticket; the hook consumes it on the next push. The narrowing `if` clause is gone: it was a second copy of a decision the hook makes itself, and it named only one shell. | ## PostToolUse — Reactions After an Action | Hook | When | What It Does | |------|------|-------------| | `auto_format.py` | After every write tool (MultiEdit was off this hook before PR #5) | Auto-formats with ruff/prettier/gofmt + logs "Modified: X" to task | | `memory_posttool_audit.py` | After every write tool into auto-memory | Audits cross-project leakage (uses `memory_markers.py` regex library) and warns | | `task_done_verify.py` | After `mcp__tausik-project__tausik_task_done`, or a `tausik task done` run in the shell | Audits AC evidence via 5 rule-based checks (Ralph-mode-lite). | | `task_call_counter.py` | After any tool call | Increments per-task `call_actual` counter; warns at 1.5×budget | | `posttool_usage.py` (v1.4) | After any tool call | Records token-usage events to `usage_events` for per-task cost rollup | | `activity_event.py` | After any tool call | Records activity timestamps for **gap-based active-time** session metric (SENAR Rule 9.2) | | `tool_output_truncation_nudge.py` (v1.4) | After Read/Grep/Glob and every shell tool (Bash, PowerShell, `mcp__windows-mcp__PowerShell`) | Coaches the agent to narrow scope when tool output exceeds the configured line threshold (warn-only) | | `rag_grep_context.py` (1.10) | After Grep | Searches the RAG index for the identifiers of the Grep pattern and adds the top 3 chunks (path:lines) as context. Read-only; nothing when the index is absent or has no hit. | | `task_cost_budget_check.py` (v1.4) | After any tool call | Compares the active task's `cost_actual` / `tokens_actual` against budget; emits WARN at 1.5× and BLOCKER at 2× (throttled) | ## SessionStart | Hook | When | What It Does | |------|------|-------------| | `session_start.py` | On session start | Auto-injects status + Memory Block + rebuilds skill profiles — no manual `/start` needed | ## UserPromptSubmit | Hook | When | What It Does | |------|------|-------------| | `user_prompt_submit.py` | On user prompt | Detects coding-intent (EN+RU) → nudges if no active task; scores the answer just read and, over `answer_budget_words` or without a verdict first, adds one line with the numbers. The code-discovery nudge toward `search_code` was removed in 1.10 (decision #390: 0 search_code calls with it in a paired replay). Ignores slash-command bodies and hook-generated text | ## Stop | Hook | When | What It Does | |------|------|-------------| | `keyword_detector.py` | On agent stop | Catches "I'll implement"/"сейчас напишу" drift phrases → blocks stop | | `session_cleanup_check.py` | On agent stop | Warns about open exploration / review tasks / session timeout | ## SessionEnd | Hook | When | What It Does | |------|------|-------------| | `session_metrics.py` | On session end | Records session metrics (active vs wall, throughput) to DB | ## The session on every host (1.10) The TAUSIK session is the host session: the host opens and closes it, not an agent's ritual. Measured on the host binary, not its documentation, session #266. | Host | Open | Close | Provided by | Measured | |---|---|---|---|---| | claude | hook | hook | SessionStart / SessionEnd from the shared hook declaration | Claude Code hook protocol | | qwen | hook | hook | SessionStart / SessionEnd from the shared hook declaration | bootstrap profile | | codex | hook | hook | SessionStart / SessionEnd from the shared hook declaration | codex 0.153.4: the hook event list carries SessionStart and SessionEnd | | opencode | plugin | plugin | `session.created` / `session.deleted` in the `tausik-qg0.js` plugin | opencode 1.1.42: plugin events in the binary | | kilo | cli | cli | `tausik session start` / `session end` by hand | the profile has no session events | | cursor | cli | cli | `tausik session start` / `session end` by hand | the profile carries no hooks | Where it says `cli`, what closing by a hook does is lost: recording the transcript's metrics and the generated handoff at exit. `session end` by hand does both. ## Git pre-commit | Hook | When | What It Does | |------|------|-------------| | `pre-commit` (shell) | Before `git commit` | Runs `python -m mypy` against `scripts/` (uses `pyproject.toml` config). On exit ≠ 0 — **blocks the commit**. Optionally runs an incremental `codebase-rag` reindex (warn-only, capped at 5s); never blocks the commit because of RAG. | This is **not** "scoped quality gates" — those run via `tausik verify` (heavy stack: pytest/tsc/cargo/phpstan/…) and are decoupled from `git commit` since the v1.4 Verify-First Contract. ### Install (one-time) ```bash # Option A: copy the file cp scripts/hooks/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit # Option B (recommended): point git at the in-repo hooks dir so updates are picked up automatically git config core.hooksPath scripts/hooks ``` > **Windows caveat.** `pre-commit` is a Bash script with `timeout(1)` and POSIX `[ -f … ]`. Plain `cmd.exe` will fail to execute it. Use Git Bash, WSL, or a terminal that ships Bash + `timeout` on `PATH`. If your team runs Windows-only, replace the script with a `pre-commit.cmd` wrapper that calls `python -m mypy` directly and accepts the same exit contract. ### Disable / bypass - One-off: `git commit --no-verify` (skips `core.hooksPath` entirely). - Temporarily: `git config --unset core.hooksPath`. - For CI without mypy: keep `core.hooksPath` unset on CI runners; the heavy verification runs via `tausik verify` regardless. ## How It Works ``` You: "add a button to the homepage" Agent wants to edit index.html → task_gate.py checks: is there an active task? No → BLOCKED → Agent creates a task via /plan, starts → task_gate.py checks again: task exists → ALLOWED Agent edits index.html → auto_format.py: formats with prettier → auto_format.py: logs "Modified: index.html" to the task → task_call_counter.py: bumps call_actual; warns at 1.5×budget → activity_event.py: stamps activity timestamp (active-time) Agent: tausik task done my-button --ac-verified → task_done_verify.py: 5-check AC audit ``` ## Exit Codes | Code | Meaning | Behavior | |------|---------|----------| | 0 | Success | Action allowed | | 1 | Warning | Action allowed; warning logged | | 2 | Block | Action **cancelled**; agent receives the reason | ## What `bash_firewall` Blocks - `rm -rf /` and `rm -rf .` — filesystem deletion - `DROP TABLE`, `DROP DATABASE`, `TRUNCATE TABLE` — data deletion - `git reset --hard` — loss of local changes - `git push --force` — overwriting remote history - `git clean -fd` — deleting untracked files - `dd if=/dev/zero`, `mkfs.` — disk formatting - Fork bombs ## Disabling Hooks For testing or debugging: set `TAUSIK_SKIP_HOOKS=1`. In `.claude/settings.json` hooks are generated automatically during bootstrap. To disable a specific hook, remove it from the `hooks` section. To re-generate the file, run `python .tausik-lib/bootstrap/bootstrap.py --refresh`. ## What's Next - **[Workflow](/docs/workflow)** — how hooks fit the work cycle - **[Session Active Time](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/session-active-time.md)** — what `activity_event.py` powers - **[CLI Commands](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md)** — managing tasks from the terminal --- ## English · /docs/mcp **English** | [Русский](/ru/docs/mcp) # TAUSIK MCP — Tool Reference **147 tools** for AI agents (current actual count, asserted via `len(TOOLS)`). The MCP surface covers everything an agent does day-to-day. A few CLI-only commands have no MCP equivalent — they are operator / maintenance verbs that don't belong in an agent loop: `skill rebuild`, `skill bundle`, `fts optimize`, `db prune`, `audit vendors`/`research`, `config set`/`show`, `push-ok`, `run`, `doc extract`/`constants`, `hud`, `suggest-model`, `hygiene archive --confirm`. For the agent's working set, prefer MCP tools over shell calls — they are atomic, return structured data, and keep your context cleaner. > **Optional `codebase-rag` server** adds 7 tools (search_code, find_symbol, …). It is enabled separately during bootstrap and is NOT part of the main 152 count - total with it is 154 tools. Two MCP servers live in this project: - `tausik-project` — project-scoped tools (147): tasks, sessions, knowledge, stacks, roles, gates, skills, exploration, audit, doctor, verify, usage logging. There is also an optional `codebase-rag` server documented at the bottom. ## Verify-First Contract (v1.5) Heavy quality gates (pytest, tsc, cargo, phpstan, javac, js-test, terraform-validate, helm-lint, kubeconform, hadolint, ansible-lint) live on a dedicated `verify` trigger. The MCP workflow: ``` tausik_task_start(slug=…, package=true) # QG-0 + bounded context in one result … work on code … tausik_verify(task_slug=…) # heavy: subprocess gates → caches green tausik_task_done(slug=…, ac_verified=True) # lightweight: cache lookup ``` `tausik_task_done` will refuse to close the task if the verify cache is missing or stale — it returns a structured failure with explicit remediation. Opt-out for CI: set `{"task_done": {"auto_verify": true}}` in `.tausik/config.json` so the heavy gates fire inside `task_done` like in pre-v1.5 releases. **Terminology:** [Verify / QG glossary](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/verify-glossary.md) distinguishes *supported opt-out*, *QG bypass* (not available for `task_done`), *verify-cache bypass*, and the pytest **test shim**. ## Status, Health, Metrics | Tool | Description | Required Parameters | |---|---|---| | `tausik_health` | Health check: version, DB, tables | — | | `tausik_self_check` | MCP-server freshness: startup time, watched-module mtime snapshot vs current on-disk mtimes, `drift_detected` flag, stale modules with `delta_seconds`, sibling MCP project server count. Call from `/start` to catch silent-hang precursors (gotchas #77/#79/#80). | — | | `tausik_status` | Project overview: tasks, session, epics. Optional `compact: true` → one-line JSON (default text unchanged). | `compact` (optional) | | `tausik_doctor` | 4-group health (venv + DB + MCP + skills + drift) | — | | `tausik_metrics` | SENAR metrics, or compact native usage with `host=codex\|kilo` | `host?` | | `tausik_usage_event_log` | Append manual row to `usage_events` (does not update session aggregates) | `tokens_input`, `tokens_output`, `tokens_total`, `cost_usd` | | `tausik_search` | Full-text search across tasks, memory, decisions | `query` | ## Tasks | Tool | Description | Required Parameters | |---|---|---| | `tausik_task_add` | Create task (optionally in a story) | `slug`, `title` | | `tausik_task_quick` | Quick creation with auto-slug | `title` | | `tausik_task_start` | Start work; optionally return bounded context in the same result | `slug`, optional `package` | | `tausik_task_done` | Complete (QG-2: `ac_verified=true`, scoped pytest, verify cache). Returns structured JSON: `blocking_failures`, per-gate results, cache status. | `slug` | | `tausik_task_show` | Full task information, or bounded context with `mode=package` | `slug`; optional `mode` | | `tausik_task_list` | List tasks with filters (status enum: `planning,active,blocked,review,done`) | — | | `tausik_task_update` | Update fields (title/goal/AC/scope/notes/stack/complexity/role/tier/call_budget) | `slug` | | `tausik_task_plan` | Set plan steps | `slug`, `steps[]` | | `tausik_task_step` | Mark step as completed | `slug`, `step_num` | | `tausik_task_log` | Append journal entry | `slug`, `message` | | `tausik_task_logs` | Read structured logs (filter by phase) | `slug` | | `tausik_reason_step` | RENAR reasoning step (intent\|premise\|action\|verification) | `slug`, `kind`, `content` | | `tausik_task_replay` | Chronological task timeline (logs + reasoning + events + verification) | `slug` | | `tausik_task_block` | Block task | `slug` | | `tausik_task_unblock` | Unblock | `slug` | | `tausik_task_review` | Move to review | `slug` | | `tausik_task_delete` | Delete task | `slug` | | `tausik_task_move` | Move to another story | `slug`, `new_story_slug` | | `tausik_task_next` | Pick next task by score | — | | `tausik_task_claim` | Claim task (multi-agent) | `slug`, `agent_id` | | `tausik_task_unclaim` | Release task | `slug` | ### `tausik_task_done` parameters - `ac_verified` — **required** for QG-2 - `evidence` — inline AC verification log (replaces a separate `task_log` call) - `no_knowledge` — confirm no knowledge to capture (suppresses warning) - `relevant_files[]` — files modified; drives **scoped** pytest gate (basename match → `tests/test_.py`). Empty list with non-empty original → gate skipped (no false-positive on full suite). Verify cache (10 min TTL) skips re-runs with same `files_hash`. There is **no `--force`** on `task_done` — QG-2 cannot be bypassed. `task_start` does have `--force` to bypass session capacity, with audit trail. ### `tausik_task_done` structured response `tausik_task_done` returns JSON for agent workflows: - stage flags (`plan_complete`, `ac_verified`, `gates_passed`) - per-gate results (`gates[]`) - `blocking_failures[]` with gate, files, output, and remediation hints - `warnings[]`, `cache_status`, and final `ok` Pre-1.4 there was a parallel `tausik_task_done_v2` alias for the structured-JSON variant. **v14b-task-done-rename-drop-v2 consolidated both into the single `tausik_task_done` returning the structured JSON above** — there is no `_v2` suffix. The Verify-First Contract is honoured on every path. ## Sessions | Tool | Description | Required Parameters | |---|---|---| | `tausik_session_start` | Start session | — | | `tausik_session_end` | End session | — | | `tausik_session_extend` | Raise the active-time advisory threshold (advice, not a gate) | — | | `tausik_session_current` | Current active session | — | | `tausik_session_list` | List sessions | — | | `tausik_session_handoff` | Save handoff data | `handoff` (object) | | `tausik_session_last_handoff` | The live handoff, or session `session_id`'s | `session_id` (opt.) | | `tausik_session_open` (v1.5) | Compound RPC: host-context policy + session start + status + handoff + active/blocked tasks + self_check in one envelope. Powers `/start` Phase 1. `host_context` reports same-thread reopen state, native usage availability, advisory/hard pressure and the fresh-window prompt. Session and self_check are projected; large handoffs use a 4096-byte UTF-8 summary with explicit omissions and a full-recovery call. If `_projection.requires_full_handoff` is true, read the full handoff before resuming work. The bound applies to the handoff section, not the whole envelope. Full telemetry: `tausik_self_check`; full handoff: `tausik_session_last_handoff`. | — | Session time is gap-based **active time** (paused after 10-min idle gap), not wall clock, and it is advice, never a refusal (1.10). See `session-active-time.md`. ## Hierarchy (Epics and Stories) | Tool | Description | Required Parameters | |---|---|---| | `tausik_epic_add` | Create epic | `slug`, `title` | | `tausik_epic_list` | List epics; `(stale: N)` — tasks created since the description was last edited (a report, not a gate) | — | | `tausik_epic_update` | Change an epic's title and/or description — the group's intent; at least one field | `slug` | | `tausik_epic_done` | Complete epic | `slug` | | `tausik_epic_delete` | Delete (cascade: stories + tasks) | `slug` | | `tausik_story_add` | Create story in epic | `epic_slug`, `slug`, `title` | | `tausik_story_list` | List stories; `(stale: N)` as for epics | — | | `tausik_story_update` | Change a story's title and/or description; at least one field | `slug` | | `tausik_story_done` | Complete story | `slug` | | `tausik_story_delete` | Delete (cascade: tasks) | `slug` | | `tausik_roadmap` | Tree: epic → story → task | — | ## RENAR substrate — SPEC + ADAPT (17 tools) The RENAR substrate: formal requirements (**SPEC**) and requirement interpretation (**ADAPT**, §7) with forward interpretations, backward findings and the architect's signature (§7.5). Used by QG-0 for substantial/deep tasks and by `tausik renar export` / `conformance`. See also `tausik_reason_step` (RENAR trace) under "Tasks". ### SPEC (8) | Tool | Description | Required Parameters | |---|---|---| | `tausik_spec_add` | Create a SPEC artifact. `type` is a closed list (ARCH/API/DATA/INT/PROC/UI/AI/SEC/OPS/TEST/DOC/UC; UC needs a body with `role` and a statement ref on every step); a new type is an amendment to the standard, not free text | `slug`, `type`, `title`, `version` | | `tausik_spec_list` | List SPECs, optionally filtered by type (JSON) | — | | `tausik_spec_show` | SPEC + linked tasks (JSON) | `slug` | | `tausik_spec_update` | Patch mutable fields (title/version/content_ref/status); `type` and `slug` are immutable | `slug` | | `tausik_spec_delete` | Delete a SPEC (cascade: task links) | `slug` | | `tausik_spec_link` | Link a task to a SPEC (both must exist — no silent dangling links) | `task_slug`, `spec_slug` | | `tausik_spec_unlink` | Unlink task ↔ SPEC | `task_slug`, `spec_slug` | | `tausik_spec_search` | FTS5 over slug/title/content_ref (JSON) | `query` | ### ADAPT (9) | Tool | Description | Required Parameters | |---|---|---| | `tausik_adapt_create` | Create an ADAPT header (§7); `tz_ref` (the source requirement doc) is mandatory; starts in `draft` | `slug`, `title`, `tz_ref` | | `tausik_adapt_interpret` | Forward interpretation (§7.4.3); tz_ref/citation/interpretation/scope_in/scope_out all required | `tz_ref`, `citation`, `interpretation`, `scope_in`, `scope_out` (+ adapt) | | `tausik_adapt_finding` | Backward finding; `category` is a closed list of 7 (contradiction/gap/hidden-assumption/feasibility/regulatory/terminology/scope) | `adapt_slug`, `category`, `description` | | `tausik_adapt_sign` | Architect signature (§7.5): signs the body with the project's ed25519 key ⇒ `approved` (§13.3.3 p.77 — status and signature are separate facts). `role=client` is REFUSED — ADR-011 withdrew the client signature under ADAPT; what the client approves belongs in an ACTZ | `adapt_slug`, `role`, `signed_by` | | `tausik_adapt_show` | ADAPT + forward interpretations, findings, signatures, links (JSON) | `slug` | | `tausik_adapt_list` | List ADAPTs, optionally filtered by status (§7.8.1 closed list: draft/review/asked/answered/approved/frozen/superseded) | — | | `tausik_adapt_delta` | Delta-ADAPT superseding its parent (§7.6); the parent becomes `superseded`, and a later link to it is a FATAL dangling link (§7.6.4) | `parent_slug`, `new_slug`, `title`, `tz_ref` | | `tausik_adapt_link` | Link an ADAPT to a task/SPEC; the target must exist; a link to a superseded ADAPT is FATAL (§7.6.4) | `adapt_slug`, `target_type`, `target_slug` | | `tausik_adapt_search` | FTS5 over slug/title/tz_ref (JSON) | `query` | ### ACTZ (15) The contractual clarification protocol (§5A, ADR-011) — client-facing, unlike ADAPT: what the client approves belongs here. Lifecycle `draft` → `sent` → `signed` → `superseded`, computed from signature-role coverage. One project ed25519 key exists (not one per party): `architect` signs for real; `client` records `signed_by`+`signed_at` only, no simulated independent signature. `final_tz`/`orphans` (§5A.4) are read-only projections over the signed points below — the derived acceptance reference, never a third copy of the text. | Tool | Description | Required Parameters | |---|---|---| | `tausik_actz_create` | Create an ACTZ header (§5A); `tz_ref` mandatory; starts in `draft` | `slug`, `title`, `tz_ref` | | `tausik_actz_point` | Add a numbered point; only while `draft` (frozen once any signature is recorded). `tz_ref` names which clause of the original ТЗ (or a prior ACTZ point) this point clarifies | `actz_slug`, `point_no`, `tz_ref`, `text` | | `tausik_actz_sign` | Record a signature (§5.5.3): `architect` signs the canonical body with the project ed25519 key; `client` records `signed_by`+`signed_at` only. First signature ⇒ `sent`; both roles ⇒ `signed` | `actz_slug`, `role`, `signed_by` | | `tausik_actz_verify` | Verify the architect ed25519 signature against the current body | `slug` | | `tausik_actz_show` | ACTZ + points, signatures, links (JSON) | `slug` | | `tausik_actz_list` | List ACTZ headers, optionally filtered by status (draft/sent/signed/superseded) | — | | `tausik_actz_delta` | Delta-ACTZ superseding its parent; the parent becomes `superseded`, a later link to it is refused | `parent_slug`, `new_slug`, `title`, `tz_ref`, `supersession_rationale` | | `tausik_actz_link` | Link an ACTZ to a task/SPEC; target must exist; a link to a superseded ACTZ is refused | `actz_slug`, `target_type`, `target_slug` | | `tausik_actz_unlink` | Remove an ACTZ↔task/SPEC link | `actz_slug`, `target_type`, `target_slug` | | `tausik_actz_delete` | Delete an ACTZ (cascades points/signatures/links/decided-in edges) | `slug` | | `tausik_actz_search` | FTS5 over slug/title/tz_ref (JSON) | `query` | | `tausik_actz_decided_in` | Record that an ADAPT backward finding was decided-in a point of a SIGNED ACTZ, with provenance (`linked_by`); refuses an unsigned target | `adapt_slug`, `finding_id`, `actz_slug`, `actz_point_no`, `linked_by` | | `tausik_actz_decided_in_remove` | Remove a decided-in edge | `adapt_slug`, `finding_id`, `actz_slug`, `actz_point_no` | | `tausik_actz_final_tz` | The derived acceptance reference (§5A.4): per ТЗ clause, the latest both-role-signed point, naming what it overrode. `as_of` (ISO-8601) shows it at a past moment | — | | `tausik_actz_orphans` | Signed points no ADAPT reflects — an obligation outside requirements (§5A.4, fatal), found by query | — | ### AT (9) Acceptance Test artifacts (§8A, ADR-012) — the one check traceability (TC → SR → ADAPT → ТЗ) structurally cannot provide, because a wrong interpretation makes every TC pass. These tools RECORD the result of the isolated-generation procedure (docs/en/at-generation-procedure.md) — none of them generate anything. `tz_text` (verbatim contract quote) and `generated_by` are mandatory on create; `check_freshness` compares against the LIVE `final_tz_snapshot` for each AT's `tz_ref` and names what changed (§8A property 2 — regenerate before every trial). See also the `at_freshness` warn gate. `record_result`/`diagnose`/`release_readiness` implement §8A.4/§10.4.3's routing matrix. TAUSIK has no first-class TC artifact yet (a separate, open task) — `diagnose` never reads pytest/verification_runs itself; the caller supplies `tc_outcome` explicitly. `release_readiness` needs no TC at all: it is ready only when every AT's latest outcome is green and fresh — distinct from QG-4, which is optional and measures business outcome. | Tool | Description | Required Parameters | |---|---|---| | `tausik_at_create` | Record an AT — the result of the isolated-generation procedure, not a generator | `slug`, `tz_ref`, `tz_text`, `scenario`, `source_as_of`, `generated_by` | | `tausik_at_show` | Show an AT record (JSON) | `slug` | | `tausik_at_list` | List AT records, optionally filtered by tz_ref (JSON) | — | | `tausik_at_delete` | Delete an AT record | `slug` | | `tausik_at_search` | FTS5 over slug/tz_ref/tz_text/scenario (JSON) | `query` | | `tausik_at_check_freshness` | Which AT records are stale against the current final-TZ (§8A.2); omit slug to check all | — | | `tausik_at_record_result` | Record one observed trial outcome (append-only — a re-run is a new row) | `slug`, `outcome` | | `tausik_at_diagnose` | Route an AT's latest outcome against a caller-supplied `tc_outcome` (§8A.4/§10.4.3) | `slug`, `tc_outcome` | | `tausik_at_release_readiness` | §8A.4 release gate: ready only when every AT is green and fresh | — | ## Knowledge | Tool | Description | Required Parameters | |---|---|---| | `tausik_memory_add` | Save to project memory; `provenance` observed / inferred (default) / told | `type`, `title`, `content` | | `tausik_memory_search` | Full-text search in memory | `query` | | `tausik_memory_list` | List entries (filter by type) | — | | `tausik_memory_show` | Show entry by ID | `id` | | `tausik_memory_delete` | Delete entry | `id` | | `tausik_memory_block` | Compact markdown: recent decisions + conventions + dead ends (for /start re-injection) | — | | `tausik_memory_compact` | Aggregate recent task_logs (phases + top words + top files) | — | | `tausik_memory_archive` (v1.5) | Soft-archive memory rows older than a duration (90d / 12w / 2m / 1y). Dry-run unless `confirm: true`. | `before` (string), `confirm` (bool, optional) | | `tausik_memory_dedupe` (v1.5) | List near-duplicate memory pairs above a similarity threshold (read-only). | `threshold` (float, optional), `limit` (int, optional) | | `tausik_decide` | Record a decision; optional `rejected` (list of "option :: why") and `supersedes` (id, reason in `rationale`) | `decision` | | `tausik_decisions_list` | List decisions | — | Memory types: `pattern`, `gotcha`, `convention`, `context`, `dead_end`. ## Graph Memory | Tool | Description | Required Parameters | |---|---|---| | `tausik_memory_link` | Create edge between nodes | `source_type`, `source_id`, `target_type`, `target_id`, `relation` | | `tausik_memory_unlink` | Soft-invalidate edge (never deletes) | `edge_id` | | `tausik_memory_related` | Find related nodes (1–3 hops) | `node_type`, `node_id` | | `tausik_memory_graph` | List edges with filters | — | Relation types: `supersedes`, `caused_by`, `relates_to`, `contradicts`. ## Dead Ends and Explorations | Tool | Description | Required Parameters | |---|---|---| | `tausik_dead_end` | Document a failed approach | `approach`, `reason` | | `tausik_explore_start` | Start time-boxed investigation | `title` | | `tausik_explore_end` | End investigation | — | | `tausik_explore_current` | Current investigation | — | ## Quality Gates and Verification | Tool | Description | Required Parameters | |---|---|---| | `tausik_gates_status` | Status of all gates (by stack) | — | | `tausik_gates_enable` | Enable gate | `name` | | `tausik_gates_disable` | Disable gate | `name` | | `tausik_verify` | v1.5 Verify-First: run heavy gates (pytest, tsc, …) and cache green in `verification_runs`. After that `tausik_task_done` reads the cache and closes instantly. | `task_slug` | Available gates: `pytest`, `ruff`, `mypy`, `bandit`, `tsc`, `eslint`, `go-vet`, `golangci-lint`, `cargo-check`, `clippy`, `phpstan`, `phpcs`, `javac`, `ktlint`, `filesize`, `class_surface`, `tdd_order`. Stack-scoped gates auto-enable based on detected stack; universal gates (`filesize`, `class_surface`, `tdd_order`) apply to all stacks. `class_surface` is repo-wide rather than scoped: it caps a class's composed public surface after inheritance, which a per-file line cap cannot see. `tdd_order` is disabled by default. Enable with `tausik_gates_enable name=tdd_order`. ## Stacks | Tool | Description | Required Parameters | |---|---|---| | `tausik_stack_list` | List built-in + custom stacks | — | | `tausik_stack_show` | Resolved stack: gates per language + override info | `stack` | | `tausik_stack_export` | Export resolved declaration as JSON | `stack` | | `tausik_stack_diff` | Diff between built-in and user override | `stack` | | `tausik_stack_reset` | Remove user override at `.tausik/stacks//` | `stack` | | `tausik_stack_lint` | Validate user-override `stack.json` files | — | | `tausik_stack_scaffold` | Create `.tausik/stacks//{stack.json,guide.md}` skeleton | `name` | DEFAULT_STACKS: 25 entries (python, fastapi, django, flask, react, next, vue, nuxt, svelte, typescript, javascript, go, rust, java, kotlin, swift, flutter, laravel, php, blade, ansible, terraform, helm, kubernetes, docker). Custom stacks via `.tausik/config.json` → `custom_stacks`. ## Roles | Tool | Description | Required Parameters | |---|---|---| | `tausik_role_list` | List roles | — | | `tausik_role_show` | Show role profile | `slug` | | `tausik_role_create` | Create role (optionally `extends` a base profile) | `slug`, `title` | | `tausik_role_update` | Update role metadata | `slug` | | `tausik_role_delete` | Delete role | `slug` | | `tausik_role_seed` | Bootstrap rows from `harness/roles/*.md` + existing task usage | — | Role storage is hybrid: SQLite metadata + `harness/roles/{role}.md` profile markdown. Roles on tasks remain free-text. ## Periodic Audit (SENAR Rule 9.5) | Tool | Description | Required Parameters | |---|---|---| | `tausik_audit_check` | Check whether audit is overdue | — | | `tausik_audit_mark` | Mark audit as completed | — | ## Skills | Tool | Description | Required Parameters | |---|---|---| | `tausik_skill_list` | List skills: active, vendored, available | — | | `tausik_skill_install` | Install skill from repo (clone + copy + deps) | `name` | | `tausik_skill_uninstall` | Uninstall skill completely | `name` | | `tausik_skill_activate` | Activate installed skill | `name` | | `tausik_skill_deactivate` | Deactivate skill (keep files) | `name` | | `tausik_skill_repo_add` | Add TAUSIK-compatible skill repo (third-party URLs need `force`) | `url`, optional `force` | | `tausik_skill_repo_remove` | Remove skill repo | `name` | | `tausik_skill_repo_list` | List repos and available skills | — | | `tausik_skill_catalog` | Discovery: list skills offered by configured/cloned repos (name, category, description) | optional `repo`, optional `as_json` | ## Cross-Project Queue (CQ) | Tool | Description | Required Parameters | |---|---|---| | `tausik_cq_publish` | Publish a cross-project event | `payload` | | `tausik_cq_query` | Query cross-project queue | — | ## Multi-agent and Maintenance | Tool | Description | Required Parameters | |---|---|---| | `tausik_team` | Tasks grouped by agent | — | | `tausik_events` | Audit log (events) | — | | `tausik_update_claudemd` | Update dynamic section in CLAUDE.md | — | | `tausik_fts_optimize` | Optimize FTS5 indexes | — | ## Codebase RAG (separate optional MCP server) | Tool | Description | Required Parameters | |---|---|---| | `search_code` | Search project code via RAG index | `query` | | `search_knowledge` | Search project knowledge base | `query` | | `reindex` | Reindex the codebase | `mode` (incremental/full), `max_seconds` (soft limit, full only). v1.5: stderr progress every 100 files; truncated=true on timeout. | | `rag_status` | RAG index status | — | | `archive_done` | Archive completed tasks | — | | `cache_web_result` | Cache web search result for reuse | `query`, `content` | | `search_web_cache` | Search cached web results | `query` | These are not part of the main 147 count — they belong to the optional `codebase-rag` server. ## Scoped tool surface (`mcp.scope_tools_exposure`) Off by default. When you set `mcp.scope_tools_exposure: true` in `config.json`, the server narrows the advertised tool-list to what the **active task** is allowed to use: the union of the task's declared `scope_tools` (SENAR Rule 2 ACL) and an always-safe core — the whole `tausik_task_*` and `tausik_session_*` families plus `tausik_status`, `tausik_verify`, `tausik_doctor`, `tausik_self_check`, `tausik_update_claudemd` and the `*_search` tools. Every other tool is hidden from the list, cutting both the token cost of the tool definitions and the attack surface. It is **fail-open**: all tools are exposed whenever no task is active, no active task declared a non-empty `scope_tools`, or the scope cannot be resolved — so turning it on never strands a project that never declared a scope. Hiding is a UX/token optimization, **not** the security barrier: a hidden tool called directly still passes the existing scope enforcement, and the write-gate is untouched. The scoped list is recomputed each time the host fetches `list_tools` — i.e. on every server connect with a task already active. **Measured cost.** The full authored surface is 147 tools ≈ 62 KB of tool definitions (~15.9k estimated tokens; `tests/test_mcp_tool_token_cost.py` pins this and ratchets it). Under Claude Code deferred loading (`ENABLE_TOOL_SEARCH`) only tool names load eagerly and each description is truncated to 2 KB — a ratchet test keeps every TAUSIK description under that limit so none is silently cut, and asserts names stay unique and searchable so name-based dispatch still resolves. ## Launching the Tausik MCP Server The bootstrap step generates IDE-specific MCP launchers under `harness//mcp/`. Claude Code reads `.claude/settings.json` (auto-generated). To regenerate IDE assets and MCP wiring, run `python bootstrap/bootstrap.py` from your TAUSIK checkout (or `python .tausik-lib/bootstrap/bootstrap.py` when using the submodule layout). Use **`python bootstrap/bootstrap.py --refresh`** only to rewrite `.tausik/config.json` (e.g. after setting **`TAUSIK_MODEL_PROFILE`**) without copying skills/scripts — it does **not** regenerate `.mcp.json` files. ## Context economy: schemas on demand `mcp.compact_tool_list` in `.tausik/config.json` is enabled by bootstrap unless the project explicitly sets it to `false`. Without generated config, the server fails open to the full list. When on, full schemas stay with a 23-tool core; the other 124 appear as a name and the first 60 characters of their description. Any of those schemas is one call away: `tausik_tool_schema(name=...)`, with `query` matching a substring and an empty argument returning the name list. Measured on a fresh 147-tool stdio surface on 2026-10-02, the minified UTF-8 list drops from 54,566 to 33,752 bytes (-38.1%). The 60-character cut is also a measurement rather than a preference: name-only is smaller, but removes the cue an agent uses to decide whose schema to ask for and can spend the saving on an extra turn. The complete project-controlled inventory is in [context-economy.md](/docs/context-economy). Set the flag to `false` to opt out after checking turns-per-task in your own logs. Missing, malformed or unreadable config always returns the full list. --- ## English · /docs/configuration **English** | [Русский](/ru/docs/configuration) # TAUSIK Configuration Reference All knobs live in `.tausik/config.json` at project root. Anything not set falls back to the documented default. To override, add the key under the top-level object (NOT under `bootstrap` — that section is bootstrap-managed). See also: [environment.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/environment.md) — env vars, [permissions.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/permissions.md) — permission modes. ## Session signals (SENAR Rule 9.2 — advice, not gates since 1.10) | Key | Default | Purpose | |---|---|---| | `session_max_minutes` | `180` | Advisory threshold on session ACTIVE minutes: above it `task start`, `status` and the Stop hook print advice; nothing refuses. `0` switches it off. | | `session_idle_threshold_minutes` | `10` | Gap (in minutes) above which a pause between `events` rows is treated as AFK (excluded from active-time sum). | | `session_warn_threshold_minutes` | `150` | Stop-hook reminder threshold in `session_cleanup_check.py`. Should be < `session_max_minutes`. | | `session_capacity_calls` | `200` | Per-session tool-call budget. A task `call_budget` above what remains is an advisory line in `task start`, not a refusal. | | `answer_budget_words` | `200` | Word budget for the agent's final answer. On the next prompt, an answer over it (or without a verdict in its first line) gets one advisory line with the numbers; nothing is blocked. Measure with `tausik metrics answers`. | | `checkpoint_calls` | `40` | Calls since the last handoff before checkpoint advice (derived from the ledger). `0` switches it off. | | `journal_freshness_calls` | `40` | Calls since an active task's last log entry before journal advice. `0` switches it off. | | `audit_every_closures` | `17` | Task closures since the last audit mark before the SENAR 9.5 audit is overdue. | ## Host context budget `host_context_budget` guards the real host thread. Each threshold has three positive integer fields: `responses`, cumulative `input_tokens`, and the latest response's `context_tokens`. Crossing any advisory field writes one checkpoint and returns a copy-ready new-window prompt. Crossing any hard field sets `allow_continue=false`; an old thread without a bound TAUSIK session is not opened. Defaults are advisory `{24, 2500000, 140000}` and hard `{32, 4000000, 180000}` in that field order. Every advisory value must be below its hard value. Codex supplies native thread identity and usage from its local journal. A host without those signals returns `level=unavailable` and a reason, then fails open to the existing time/call advice. Ending and reopening a TAUSIK session in the same host thread never makes `fresh_context` true. ## Code search (RAG) languages | Key | Default | Meaning | |-----|---------|---------| | `rag.extra_extensions` | `{}` | Extra file types to index, `{".unity": "unity-scene"}`. A built-in extension cannot be overridden. Godot (`.gd`, `.gdshader`, `.tscn`, `.tres`, `.godot`) is built in since 1.10. | | `rag.boundaries` | `{}` | Where to cut a language into chunks, `{"unity-scene": "^--- !u!"}` (a multiline regex). A bad entry is skipped and named in `rag_status` under `language_config.problems`. | ## Update check | Key | Default | Meaning | |-----|---------|---------| | `updates.check` | `true` | At most once a day, SessionStart runs `tausik update-check` detached: one anonymous GET to GitHub's `releases/latest` of `Kibertum/tausik-core`, nothing about the project in it. `status` then names a newer release. `false` switches it off; `tausik doctor` shows the state. | ## Verification cache (SENAR Rule 5) | Key | Default | Purpose | |---|---|---| | `verify_cache_ttl_seconds` | `600` | How long a green verify run is reused before re-running gates. Lower for security-critical projects. | ## Settings met in the quick-start All keys sit at the root of `.tausik/config.json` unless a path says otherwise. | Key | Default | Purpose | |---|---|---| | `context_tier` | `"minimal"` | Size of the generated rules file: `"minimal"` (default, bounded), `"standard"`, `"full"` (extended pointers). Bootstrap refuses an unknown value; `tausik doctor` checks drift against the saved tier. | | `output_mode` | `"off"` | `"caveman"` adds terse prose. Every mode ships the same controlled-prose rules: name actor/action, prefer active voice where natural, keep one action per sentence and one term per concept. This is multilingual guidance, not ASD-STE100 compliance. Code, tool output and SENAR evidence stay whole. A bad value falls back to `"off"`. | | `model_profile` | unset | Host profile slug (`a-z`, digits, hyphens), e.g. `claude`, `codex`. Bootstrap writes it when `TAUSIK_MODEL_PROFILE` is set; an invalid value aborts bootstrap. `python bootstrap/bootstrap.py --refresh` refreshes config only. | | `mcp.compact_tool_list` | `true` after bootstrap | Advertise complete definitions for the core workflow and short definitions for other tools; retrieve a full deferred schema with `tausik_tool_schema`. Explicit `false` is preserved. Missing or malformed config fails open to the full list. | | `task_done.auto_verify` | `false` | `true` makes `task done` run the gates inline and close **without a signed receipt**. It is not a bypass of the gates — they still run — but it gives up the receipt, so the config trust check treats it as a weakening and a trusted tier sets it back (`scripts/config_trust.py`). Prefer `task done --ac-verified --verify`: one call, with the receipt. | For explanation-shaped prompts, the prompt hook adds a conditional format matrix. Concise prose remains the fallback; tables serve repeated exact comparisons, Mermaid serves connected structure, and HTML requires an explicit or named interactive need. The router never selects video. Ordinary prompts do not pay for this extra instruction. **Rules files are preserved once written.** `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `QWEN.md` and the OpenCode rules file are not rewritten by bootstrap when they exist, so changing `context_tier` or `output_mode` later does not reach them. Bootstrap says so; delete the generated file and run bootstrap again, or edit it by hand. **The MCP close returns structure.** `tausik_task_done` answers with `stage`, `gate_results` and `blocking_failures`, so an agent can fix what failed without parsing prose. ## Stacks | Key | Default | Purpose | |---|---|---| | `custom_stacks` | `[]` | List of custom stack slugs accepted by `task add --stack X`. | ## Gates | Key | Default | Purpose | |---|---|---| | `gates` | `{}` | Per-gate overrides: `{ "pytest": { "enabled": true }, "filesize": { "max_lines": 600 } }`. Merges over `default_gates.py`. | ## Agent-instruction files (the DYNAMIC block) `tausik update-claudemd` rewrites the block between `` and `` in CLAUDE.md and, when one sits beside it, in AGENTS.md. AGENTS.md is tracked in most projects, so what goes into it is a policy, not an accident (GitLab #14): | Key | Default | Purpose | |---|---|---| | `claudemd.sibling_dynamic` | `true` | `true`: AGENTS.md is refreshed with the block **minus** "Shared knowledge — from other projects" — other projects' knowledge never enters this repository's history, and a memory tail left with nothing under it is dropped. `false`: AGENTS.md is not written at all; CLAUDE.md is refreshed as before. Only the JSON boolean `false` turns it off — the string `"false"` or `0` is read as on. The key is read from the `.tausik/` beside the file being written. | The `claudemd_state` commit gate judges each file against the same plan the writer follows: a sibling the policy does not write is not judged, and a sibling whose only knowledge would be foreign owes no tail. ## Publication (what a redacted export hides) The shared store (`~/.tausik-knowledge`) needs no configuration: `--global` on `decide` / `memory add` writes to it, `$TAUSIK_HOME` moves it. These keys feed `knowledge export --redacted` only (see [knowledge-store.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/knowledge-store.md)). | Key | Default | Purpose | |---|---|---| | `publication.project_names` | `[]` | Project names to replace with `[REDACTED:project]`, in addition to this project's directory name. | | `publication.private_url_patterns` | `[]` | Regex strings; a URL matching one becomes `[REDACTED:url]`. | | `brain.local_mirror_path` | `~/.tausik-brain/brain.db` | Only read by the one-off `knowledge import-brain`: where the retired Notion transport left its local mirror. | ## Codex subscription-credit rates `codex_subscription_credit_rates` is optional and has no framework default. Set a dated card only when `metrics tokens --host codex` should add a credit-weighted accepted-task view to its raw counters: ```json { "codex_subscription_credit_rates": { "source": "https://developers.openai.com/docs/pricing", "as_of": "2026-10-02", "unit": "credits_per_million_tokens", "models": { "gpt-5.6-sol": {"input": 100, "cached_input": 10, "output": 500} } } } ``` Use the values applicable to your account and date. These are subscription credits, not API USD rates or a conversion of remaining included allowance. ## Example ```json { "session_max_minutes": 240, "session_idle_threshold_minutes": 15, "host_context_budget": { "advisory": {"responses": 20, "input_tokens": 2000000, "context_tokens": 120000}, "hard": {"responses": 28, "input_tokens": 3500000, "context_tokens": 170000} }, "verify_cache_ttl_seconds": 1200, "custom_stacks": ["ruby", "elixir"], "gates": { "filesize": { "max_lines": 500 }, "ruff": { "enabled": false } }, "publication": { "project_names": ["acme"], "private_url_patterns": ["acme\\.internal"] }, "claudemd": { "sibling_dynamic": false } } ``` ## Health check `tausik doctor` (v1.3+) verifies that resolved config + venv + DB + skills are coherent and surfaces actionable next steps. --- ## English · /docs/security **English** | [Русский](/ru/docs/security) # Security Rules See also: [security-checklist.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/security-checklist.md) — OWASP Top 10 checklist. ## Core principles 1. **Never trust user input** — validate everything 2. **Least privilege** — grant only what's needed 3. **Defense in depth** — multiple layers of protection 4. **Fail safe** — errors must not leak information 5. **Audit everything** — log security events --- ## OWASP Top 10 (brief) ### A01: Broken Access Control - Check authorization on EVERY endpoint - Don't trust client-side data - Verify resource ownership ### A02: Cryptographic Failures - Use argon2/bcrypt for passwords (NOT MD5/SHA1) - Secrets via environment variables - HTTPS required in production ### A03: Injection - Parameterized SQL queries - `textContent` instead of `innerHTML` - Escape output for XSS prevention ### A05: Security Misconfiguration - Safe error messages - Configured security headers - Specific CORS (not `*`) ### A07: Authentication Failures - Rate limiting on auth endpoints - Secure cookie flags (httpOnly, secure, sameSite) - Strong password requirements --- ## Input validation ### Required checks - Data type - Length / size - Format (email, URL, etc) - Allowed values (whitelist) ### Where to validate - At system boundaries (API, CLI, MCP) — always - Inside trusted code paths — only at the boundary - Client-side validation = UX only, not security --- ## Authentication ### Password requirements - At least 12 characters - Mix of uppercase, lowercase, digits, symbols - Check against well-known breached-password lists ### Cookie security ``` httpOnly: true — no JS access secure: true — HTTPS only sameSite: strict — CSRF protection ``` --- ## Secrets management ### Never - Hardcode secrets in source - Commit `.env` files - Log secret values ### Do this instead - Environment variables for local dev (gitignored `.env`) - Production: secret manager (AWS Secrets Manager, HashiCorp Vault) - Rotate keys periodically ### .gitignore for secrets ``` .env .env.* !.env.example secrets/ *.pem *.key ``` --- ## Audit logging ### What to log - All authentication events (success + failure) - Permission changes - Sensitive data access - Configuration / administrative actions ### What NOT to log - Passwords (even hashed) - API keys - Session tokens - Card numbers - PII in cleartext Logs must contain: timestamp, actor, action, resource. --- ## Checklists ### Pre-commit - [ ] No hardcoded secrets - [ ] Input validation on all endpoints - [ ] Authorization checks present - [ ] Error messages don't leak information - [ ] No SQL / command injection - [ ] Rate limiting on sensitive endpoints ### Pre-deploy - [ ] Dependencies scanned for vulnerabilities - [ ] Security headers configured - [ ] HTTPS enforced - [ ] Secrets in proper storage - [ ] Logging configured correctly --- ## TAUSIK-specific guards - `bash_firewall.py` blocks `rm -rf /`, `Remove-Item -Recurse -Force C:\`, `git reset --hard origin`, force-push — **on both shell channels**, Bash and PowerShell. Full channel-coverage matrix: [`enforcement-coverage.md`](/docs/enforcement-coverage) - `git_push_gate.py` requires a fresh, single-use ticket at `.tausik/.push_ticket.json`, written by `tausik push-ok` (60s TTL, bound to HEAD SHA). `/ship` and `/commit` run `tausik push-ok && git push` after user "y". The historical `TAUSIK_ALLOW_PUSH=1` env path was broken-by-design (inline env never reached harness-level hooks) and was removed in v1.4. `TAUSIK_SKIP_PUSH_HOOK=1` remains as a debug-only bypass. - `memory_pretool_block.py` blocks Write/Edit/MultiEdit **and Bash/PowerShell** writes to any foreign memory sink — `~/.claude/**/memory/`, `.cursor/rules/`, `.windsurf/rules/`, `.github/copilot-instructions.md`, `.clinerules`, `.roo/rules/`, `.continue/rules/`, `.aider*` (deny-list: `scripts/memory_sinks.py`) - `memory_route` gate + the `pre-commit` hook enforce the same deny-list IDE-agnostically over the working tree; the litmus block in the generated rule files covers hosts whose memory is cloud-side and writes no file at all - `publication_boundary.py` (over `brain_scrubbing`'s detectors) redacts paths, e-mails, private URLs and project names in `knowledge export --redacted` - Slug validation in role/stack scaffold blocks path traversal - A default gate's command cannot be pointed at a different tool: `.tausik/config.json` travels with the repository, so an override of `gates..command` must keep invoking the default's tool. Arguments, paths and runner wrappers stay free (`vendor/bin/phpstan analyse --level=8` is accepted, and so is `eslint {files}` against a default of `npx eslint {files}` — wrappers are seen through); the tool does not (`python -c pass` in place of `ruff` is refused and the default command is kept) - Skill installs are gated on **both** paths into the activated tree — `install` (`skill_manager.copy_skill`) and `activate` (`service_skills.skill_activate`): publisher signature (ed25519, per-repo pinned key), path-traversal and symlink guards, and a shared **invisible-Unicode content scan** (`skill_content_scan.assert_skill_tree_clean`) that refuses a skill hiding agent-directed instructions in zero-width / bidi / U+E0000 tag-block characters across every prose/config/script file it ships. Full model: [`skill-supply-chain-threat-model.md`](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/skill-supply-chain-threat-model.md) ### Limits of these guarantees Supervision is machine-checked where a machine can check it. What remains is written down here honestly rather than left implied. - **Inert arguments to the same tool are NOT detected.** The check compares the tool being invoked, not the meaning of the invocation, so `ruff --version` or `pytest --collect-only` in an override will pass: the tool is the right one, the gate is nominally enabled, and it is green forever. There is no machine-checkable definition of "this command does real work", and a rule requiring the command to start with the default prefix would break both the vendored-path case and the legitimate dropping of an `npx` wrapper. Read gate command overrides in a foreign repository with your own eyes — a gate that goes green suspiciously fast deserves a look at its command. - **Built-in gates** (`filesize`, `class_surface`, `tdd_order`, `renar_drift_*`) have no command at all; an attempt to add one is refused. --- ## English · /docs/known-limitations **English** | [Русский](/ru/docs/known-limitations) # Deliberate gaps, open defects, and what is out of scope Three different things that looked identical before this page: "we decided not to do this", "we have not finished this", and "this is broken". A guard whose blind spot is undocumented reads as total — so the gaps are declared here rather than inferred by the reader from silence. **What marks a gap as deliberate is a stated reason why closing it now costs more than living with it.** An entry without that reason is not a gap but a defect, and it belongs in the task list. So every line below carries both the reason and a reference to whatever holds the boundary. ## Deliberate gaps Each entry answers one question: **what exactly is NOT guaranteed** — not "works partially", but what gets through and in which case. ### The knowledge-routing gate cannot see a gitignored sink **Not guaranteed:** a write of knowledge into a git-ignored file will be stopped at a close or a commit. A path outside the repository (`~/.claude/**/memory/`) is invisible to it as well. **Why we live with it:** the gate's input is `git status --porcelain`, and `--ignored` would sweep in `.tausik/`, `node_modules/` and every build artifact. An ignored file cannot reach a commit anyway, and a leak into one is caught by another layer — the `PreToolUse` hook. The gate's question is "does the change this project is about to record carry knowledge off-project"; the hook's is "is this write leaking", and only the second can see an ignored path. **Holds the boundary:** the docstring of `scripts/gate_memory_route.py` (both limits are stated where the code is), `tests/test_memory_sinks.py`. ### The filesize gate looks only at the task's files **Not guaranteed:** a file already over the cap but untouched by the current task will be named. The gate answers "did the file you are editing grow", not "is any file in the tree over the cap". **Why we live with it:** it is a ratchet on the change, not an audit of the tree. An audit would require declaring an exemption for every historical file at once, or it would block every task for someone else's debt. The whole tree is checked by a separate lane. **Holds the boundary:** `run_filesize_gate(gate, files)` takes the task's scope; `tests/test_gates.py`. ### The projection round-trip gate is opt-in and fail-open **Not guaranteed:** a divergence between the `tausik/` tree and the database will be noticed in a project that has no database (the gate skips — on a fresh clone that is the right answer), and an internal failure of the gate itself will not fail the commit. **Why we live with it:** a gate that blocked a project without a database would be enforcing "have a database", not "keep the projection in sync". And a watchman's own failure is not evidence of divergence. **Holds the boundary:** the docstring of `scripts/gate_state_roundtrip.py`, `tests/test_state_roundtrip_gate.py`. ### The historical-citation marker checks the SHAPE of a sha, not that the commit exists **Not guaranteed:** `path (removed in )` stops counting as a stale reference even when no such commit exists. Seven or more hex characters in parentheses right after the path are sufficient grounds. **Why we live with it:** asking git would let a broken git silently switch off the whole stale-reference detector. That costs more than a forged marker, which has to be typed by hand and hides nothing beyond the one reference it is attached to. **Holds the boundary:** `_REMOVED_CITATION` in `scripts/memory_cleanup.py`, `tests/test_repo_hygiene_ratchet.py`. ### The orphan-module scan cannot see a name built by an expression **Not guaranteed:** a module reached only as `__import__(f"mod_{kind}")` will not be called an orphan. A string literal (`__import__("mod")`) is visible; a computed name is not. **Why we live with it:** a computed name names no SINGLE module, and guessing one would trade this false positive for a false negative — the detector would start claiming a reference it never saw. **Holds the boundary:** `_dynamic_import_name` in `scripts/audit_orphan_files.py`, `tests/test_repo_hygiene_ratchet.py`. ### The stale-reference check requires the DIRECTORY to exist **Not guaranteed:** a reference to a file inside a directory that was deleted whole will be reported as stale. **Why we live with it:** prose is full of slashes that are not paths (`lru_cache/functools.cache`, `release/1.8`). Requiring an existing parent removes that noise; without it the finding list becomes unreadable and the reader stops looking at the real ones. **Holds the boundary:** `_parent_dir_exists` in `scripts/memory_cleanup.py`. ### Archiving a task is irreversible and does not shrink the tree **Not guaranteed:** `archived_at` can be cleared by a command — no such command exists; and the task's projection file stays in `tausik/tasks/` after archiving, because the exporter selects tasks with no filter on that column (memory has one). **Why we live with it:** archiving was designed as "hide from `task list`", and in that role it works. Both halves are filed as tasks, and reversibility is declared a precondition of shrinking the projection. **Holds the boundary:** `docs/en/task-archive-spec.md`, the caveats in `docs/en/cli.md`, `tests/test_hygiene_cli.py`. ### The MCP server holds old code until the IDE restarts **Not guaranteed:** an edit in `scripts/` reaches the MCP tools immediately. Until the host restarts, the server may execute the previous modules. **Why we live with it:** reloading modules in a live server breaks connection state for more than immediacy is worth. The divergence is DECLARED instead: `self_check.drift_detected` raises the flag, and from then on MCP answers in that session cannot be trusted — the CLI, which re-reads disk on every call, can. **Holds the boundary:** `tausik_self_check`, the "MCP Health" section of the `/start` skill. ### `/rewind` does not undo what the agent did through the shell **Not guaranteed:** Claude Code's `/rewind` restores the files an agent changed. Its checkpoints track only edits made by its own file tools; anything written through Bash, a script or another process is not in the snapshot and is not rolled back. **Why we live with it:** it is the host's mechanism, not ours, and TAUSIK agents do much of their work through the shell. Pretending otherwise would hand the next agent a safety net that is not there. **Holds the boundary:** git. Commit or stash before a risky step; `git diff` shows what a rewind would miss. ### The rules file keeps its changing part last **Not guaranteed:** that everything the agent is given stays in the prompt cache. The host caches the longest unchanged prefix, so whatever changes invalidates what follows it. **Why we live with it:** some context has to change — the session state and the memory tail. It is kept in one block at the very end of the rules file (``), after every stable rule, so a change there costs only itself. Per-prompt hook text arrives after the conversation prefix for the same reason. **Holds the boundary:** `tests/test_context_order.py` fails if anything static follows the dynamic block in the generated rules file or in this repository's `CLAUDE.md`. ### Time windows have SECOND granularity **Not guaranteed:** an event written in the same second in which a window bounded by `now` is read will fall inside the selection. **Why we live with it:** `utcnow_iso()` writes seconds across the whole project, and moving to milliseconds would touch every timestamp in the schema and in the projection. For an open interval the upper bound is simply not set — it is meaningless there. **Holds the boundary:** the memory record about the event window, `tests/test_med_findings_fix.py`. ### Doctor checks are best-effort **Not guaranteed:** a failure of one check will be distinguished from its negative result: an exception inside a check becomes a WARN carrying the error text, not a FAIL. **Why we live with it:** `doctor` is an overview, not a gate. One broken check must not hide the other nine. **Holds the boundary:** `scripts/project_cli_doctor.py` (every check in its own `try`), `tests/test_doctor_*.py`. ## Open defects **They are not listed here by hand, and that is a decision rather than an omission.** A hand-maintained defect list falls behind silently; that is exactly how a document that called itself the map of the project's direction ended up asserting a state two releases old. Open defects live in the task database, and that is where to read them: ```bash tausik task list --status planning # what is filed and waiting tausik roadmap # the release composition with counters ``` A defect found inside a task is filed as its own task rather than fixed in passing — so "none currently open" here would mean only that nobody looked. ## Out of scope What TAUSIK does not do by design, rather than by omission: * **It does not fix a failing gate for you.** A gate names the check, the cause and the remediation command; a human or an agent applies it. A gate that repaired itself would teach everyone to stop reading the cause. * **It gives an agent no direct database access.** MCP and the CLI only. Raw SQL would bypass the projection, the triggers and the receipts — everything reproducibility rests on. * **It does not replace code review.** Gates check discipline and boundaries, not intent. An L3 separation of duties requires a different model from the author's. * **It does not serve several projects from one daemon.** The MCP server is bound to a project at spawn; a systemwide mode is a separate body of work, not a gap. --- ## English · /docs/troubleshooting **English** | [Русский](/ru/docs/troubleshooting) # Troubleshooting Reference Machine-readable guide: error → diagnosis → fix. ## Prompt caching not active Symptom: token cost per session grows faster than expected; the LLM bill shows almost all input as plain `input_tokens` rather than `cache_read_input_tokens`. Check with `python scripts/validate_prompt_caching.py --auto` (or pass a JSONL path). Exit code 2 = API never returns cache fields; exit code 1 = `cache_creation > 0` but `cache_read = 0` (every turn re-caches the prefix). | Symptom | Diagnosis | Fix | |---|---|---| | `validate_prompt_caching.py` exit=2 (no cache fields) | Current client / endpoint does not request caching | Use the official Claude Code (caching on by default). For third-party wrappers, verify they send `cache_control` on at least system prompt + tools. | | Exit=1 (creation > 0, reads = 0) | Prefix unstable — something is changing between turns | See the invalidator list below. | | `cache_read_input_tokens` drops to zero mid-session | `tausik_update_claudemd` rewrote the dynamic CLAUDE.md block | Do not run `update_claudemd` between tool calls; keep it on session boundaries (`/start`, `/checkpoint`, `/end`). | | Low hit-rate (<50%) even without CLAUDE.md edits | A `SKILL.md` or MCP `tools.py` was edited in the worktree between turns | After editing any agent artifact, restart MCP / IDE — the prior prefix is already invalidated; continuing the session is pointless cache-wise. | | Want a live hit-rate read | — | Run `python scripts/validate_prompt_caching.py --auto` after a long session. `usage` blocks are already in `~/.claude/projects//*.jsonl`. | See [architecture.md](/docs/architecture) "Prompt Caching" section for the cacheable-surface list and which edits rewrite the prefix. ## Stale MCP modules (silent hangs) Symptom: `tausik_verify` or `tausik_task_done` never returns. CLI works fine. The MCP server is running stale Python modules — usually because the user (or bootstrap) edited service code AFTER the IDE opened, and the IDE never respawned the MCP child. Multiple MCP project servers for the same project are also a strong signal (each prior IDE window leaks one). | Symptom | Diagnosis | Fix | |---|---|---| | MCP tool hangs > 60 s but CLI of the same op completes instantly | `tausik_self_check` likely shows `drift_detected=true` or `sibling_mcp_count > 0` — stale modules in memory | Restart the IDE so the MCP project server respawns. Until then use `.tausik/tausik` CLI. | | `/start` warns `⚠ MCP Health` with stale module list | Watched module mtime advanced after MCP startup | Restart IDE; re-run `/start`. | | `sibling_mcp_count > 0` reported | Multiple MCP project servers on the same project (window leak) | Close stale IDE windows, then `Get-Process python` (Windows) / `pgrep -f mcp/project/server.py` (POSIX) and kill the older PIDs. | | `tausik_self_check` returns `error: self_check unavailable` | The running MCP server predates this diagnostic (older than v1.4 polish) | Restart IDE so the new server boots; the diagnostic is registered on fresh startups only. | Companion gotchas in `.tausik/tausik.db`: #77 (verify hang after editing `service_verification.py`/`gate_runner.py`), #79 (`task_done` hang on large evidence), #80 (root cause = stale modules + sibling MCP servers). The Verify-First Contract's 60 s envelope timeout (`verify_pipeline_timeout_seconds`) catches new servers; stale ones loaded their code BEFORE that timer was added and ignore it. ## RAG (FTS5) | Error Pattern | Diagnosis | Fix Command | |---|---|---| | `search_code` returns empty | RAG index empty or stale | Reindex via MCP `reindex` tool or restart RAG server | | RAG DB missing | Never indexed | Run bootstrap: `python bootstrap/bootstrap.py` | | `reindex` hangs / times out on large monorepo | `mode=full` walks every tracked file; on 50k+ files this can exceed MCP per-tool timeout | Pass `max_seconds=N` (soft limit — partial result with `truncated=true` is returned) or use `mode=incremental` (default; only re-indexes files changed since `last_commit`). v1.4 also writes `[rag] indexed X/Y files, N chunks, ZZs elapsed` to stderr every 100 files so the host renders progress instead of "frozen". | ## CLI & Project | Error Pattern | Diagnosis | Fix Command | |---|---|---| | `No config found` / `config.json missing` | Project not initialized | `.tausik/tausik init --name "Project"` | | `Task 'X' blocked by unfinished dependencies` | Task has unresolved deps | `.tausik/tausik task show X` → finish the blockers it lists, then `.tausik/tausik task unblock X` | | `No active session` | Session not started | `.tausik/tausik session start` | | `Task 'X' already claimed by agent Y` | Multi-agent conflict | `.tausik/tausik team status` → wait or override | | `unrecognized arguments` | CLI syntax error | Read [`docs/en/cli.md`](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/cli.md) for correct syntax | | `ModuleNotFoundError` in project.py | Wrong Python or missing sys.path | Run with system Python, not venv: `.tausik/tausik` | ## Failed verify-gate → tausik-gate-fixer (auto-helper) When `tausik verify` or `tausik task done` returns a blocking failure (filesize / ruff / mypy / pytest), invoke the **`tausik-gate-fixer`** sub-agent instead of decoding the stderr by hand. It reads the stderr + `docs/en/troubleshooting.md` + `docs/en/architecture.md`, then returns a 1-3 step JSON fix plan. ``` Agent( subagent_type="tausik-gate-fixer", prompt="gate_name=ruff; stderr=; relevant_files=[...]; task_slug=; goal=", ) ``` Response: ```json {"gate":"ruff","family":"style","plan":[{"step":1,"action":"edit","target":"scripts/foo.py:42","change":"...","why":"..."}],"meta":{...}} ``` **Action vocabulary** (closed set — the agent picks from these, never invents): `edit`, `extract_module`, `add_test`, `move_file`, `delete_dead_code`, `re_run_gate`. Apply the plan, then `.tausik/tausik verify --task ` again. The sub-agent is read-only — it never edits code itself. ## Bootstrap | Error Pattern | Diagnosis | Fix Command | |---|---|---| | `.claude/` is stale / skills missing | Bootstrap not run after update | `python bootstrap/bootstrap.py` | | `FileNotFoundError` on skill | Skill not in core/extension list | Edit `.tausik/config.json` → add the skill name under `bootstrap.core_skills` or `bootstrap.extension_skills`, then re-run `python bootstrap/bootstrap.py` | | `charmap codec can't encode` (Windows) | Unicode in output on non-UTF8 terminal | Set `PYTHONIOENCODING=utf-8` or use `chcp 65001` | ## MCP Servers | Error Pattern | Diagnosis | Fix Command | |---|---|---| | MCP tool not found / unavailable | `.mcp.json` missing or wrong paths | Re-run `python bootstrap/bootstrap.py` to regenerate `.mcp.json` | | MCP server crashes on start | Wrong Python path in `.mcp.json` | Re-run `python bootstrap/bootstrap.py` to regenerate `.mcp.json` with correct paths | | `task_done` hangs / times out in VS Code Claude Extension | Heavy gates (pytest, tsc) ran inline, host killed call at the per-tool timeout | v1.4 Verify-First Contract: call `tausik_verify` first (streams progress, host can interrupt), then `tausik_task_done` reads cache and closes in milliseconds. See [Host limits](#host-limits-task_done-ux). | | `task_done` returns generic timeout error, no traceback | Old MCP server (pre 1.4) swallowed exceptions to single-line text | Update MCP server: `python bootstrap/bootstrap.py` (1.4 prints traceback to stderr). | | Agent runs heavy gates inline on task close | MCP server bundled in project predates 1.4 (the Verify-First Contract) | Update bootstrap: `python bootstrap/bootstrap.py`. v1.4 splits verify from close — `tausik_verify` streams progress, `tausik_task_done` reads cache. | ## Host limits & `task_done` UX The TAUSIK MCP servers run inside an IDE host (VS Code Claude Extension, JetBrains, Cursor, Claude Code, etc.). Every host applies a per-tool timeout (~60s in current builds). If `task_done` triggered the heavy verification stack inline, large monorepos would push past the timeout, the host would kill the call, and the agent would see a generic transport error — not a usable failure report. **Workflow (preferred):** 1. `tausik_verify(task_slug=…)` — runs heavy gates (pytest, tsc, cargo, phpstan, …), streams progress, the user can interrupt cleanly. Result is cached for 10 minutes. 2. `tausik_task_done(slug=…, ac_verified=True, relevant_files=[…])` — reads the cache, closes the task in milliseconds. Returns structured JSON (`stage`, `gate_results`, `blocking_failures`). **Opt-out (CI / batch runs):** add `{ "task_done": { "auto_verify": true } }` to `.tausik/config.json`. `task_done` will run the heavy gates inline like in 1.3 — fine outside an interactive host where there is no per-tool timeout. **`tausik_task_done` is the single QG-2 entrypoint.** Earlier docs referenced a `task_done_v2 vs task_done` migration; v1.4 ships only `tausik_task_done` (structured JSON response with `stage`, `gate_results`, `blocking_failures`). It honours the Verify-First Contract — the heavy gates must already have run via `tausik_verify`, the result is cached for 10 minutes, and `task_done` reads that cache. **Streaming progress (v1.4):** when `task done` runs gates inline (`auto_verify=true` or interactive `tausik task done`), `gate_runner` emits a `run_start` progress event up-front with `total` (gate count) and `max_seconds` (sum of per-gate timeouts) so MCP hosts can render an ETA before pytest blocks the channel. The CLI handler maps this to one stderr line per event: ``` [gates] Running 2 gate(s) (trigger=task-done, max ~125s). [gates] 1/2 filesize ... [gates] 1/2 filesize PASS (8 ms) [gates] 2/2 pytest ... [gates] 2/2 pytest PASS (1062 ms) ``` Set `TAUSIK_QUIET=1` to suppress these lines (CI / scripted runs). MCP servers receive the same event payload and can surface it as a structured progress message. ## VS Code Claude Extension — full reference (v1.4) The VS Code Claude Extension is the strictest MCP host for TAUSIK because it (1) imposes a hard per-tool timeout that cannot be configured from inside the tool, (2) has no hooks payload generated for it by TAUSIK, and (3) renders MCP tool results as a single line. v1.4 ships behavior tuned for this host explicitly. This section consolidates the full picture so you don't have to chase it across `host-limits`, `Host limits`, and the streaming-progress note. ### Hooks status This table names THREE of the five scaffolded hosts, and it is kept by hand. The authoritative answer is measured: `.tausik/tausik doctor` prints, per host, the hook commands and plugins actually deployed in its profile, and each host's own rules file opens with the same statement derived the same way. Where a cell says TAUSIK deploys none, that is a fact about TAUSIK, not a claim about the host — whether the host would accept a hooks payload is a separate question this table does not answer. | Hook category | Claude Code (CLI) | Cursor | VS Code Claude Ext. | Qwen Code | |---|---|---|---|---| | PreToolUse / PostToolUse / SessionStart / SessionEnd | ✅ Real, enforced | ❌ TAUSIK deploys none | ❌ TAUSIK deploys none | ✅ Real, enforced (full parity since v1.4) | | `task_gate.py` (Rule 9.1) | Hard block | Instruction-only | Instruction-only | Hard block | | `secret_scan.py` (Rule 10.12) | Warn / strict-block | Instruction-only | Instruction-only | Warn / strict-block | | `git_push_gate.py` | Hard block | Instruction-only | Instruction-only | Hard block | Practical implication: the VS Code extension cannot block file writes when there is no active TAUSIK task. The agent *should* honour the rule (it's spelled out in `CLAUDE.md` and the multi-model onboarding block of `AGENTS.md`), but you don't get the safety net. Treat the rules as conventions, not gates, in this host. ### MCP per-tool timeout The current extension build kills any single MCP tool call that runs longer than ~60 seconds. There is no client-side knob to bump it. Affected tools and the v1.4 mitigation: | Tool | Pre-1.4 behavior | v1.4 mitigation | |---|---|---| | `tausik_task_done` | Ran pytest/tsc/cargo inline → 60s+ on big repos → killed → generic timeout error | **Verify-First Contract**: refuses to close until `tausik_verify` ran. Verify streams stderr progress, the user can stop it, and its result is cached for 10 min. Then `task done` reads cache and finishes in <100ms. Returns structured JSON (`stage`, `gate_results`, `blocking_failures`) instead of a single error string. | | `codebase-rag.reindex` (full) | Walked every file silently → host killed at 60s | Accepts `max_seconds` soft limit; emits `[rag] indexed X/Y files...` to stderr every 100 files. Default mode is `incremental` — only files changed since `last_commit`. | | `tausik_verify` | (introduced 1.4) | The intended foreground heavy-work entrypoint. Streams progress; the user sees what's happening; the host doesn't time out as long as we keep emitting bytes within its idle threshold. | ### Recommended workflow For routine task closure in VS Code Claude Extension: 1. Skill or agent calls `tausik_verify(task_slug=...)` **first**. This is the only place pytest/tsc/etc. are allowed to run inline. 2. The user sees streaming progress and can intervene (Ctrl-C is delivered to the MCP process). 3. On green, `tausik_verify` writes a `verification_runs` row. 4. Skill calls `tausik_task_done(task_slug=..., relevant_files=[...], ac_verified=true)`. 5. `task_done` checks the cache (≤10 min old, same `files_hash`, same gate signature), confirms green, and closes within ~100ms. 6. If the cache lookup fails, `task_done` returns a structured error explaining exactly which gate is missing — *not* a transport timeout. For CI / batch / non-interactive runs (where there is no per-tool timeout), set `{ "task_done": { "auto_verify": true } }` in `.tausik/config.json` to restore the legacy 1.3 behaviour where `task_done` runs gates inline. ### Diagnostics Use these to confirm the extension is configured correctly: | Symptom | Check | |---|---| | Skill calls a legacy `task_done` shape | v1.4 ships a single `tausik_task_done`. If the agent's bundled SKILL.md still says "call v2", re-run `python bootstrap/bootstrap.py` so the `.claude/skills/` (and equivalents) reflect the current contract. | | Verify never returns | Run the same verify command from a regular terminal (`.tausik/tausik verify --task `) — if it works there but hangs through the extension, the issue is host-side, not TAUSIK. | | Hooks not firing on `Write` | Expected — TAUSIK deploys no hooks for the VS Code extension. The agent must obey rules without enforcement; consider running task-critical work through Claude Code CLI or Qwen Code if hard blocks matter. | --- ## English · /docs/upgrade **English** | [Русский](/ru/docs/upgrade) # Upgrade Safety What bootstrap touches, what it leaves alone, and how to upgrade without losing your work. ## The two trees ``` / ├── stacks/ ← framework-owned. Bootstrap rewrites this. ├── scripts/ ← framework-owned. ├── bootstrap/ ← framework-owned. ├── harness/ ← framework-owned. ├── .claude/ ← bootstrap output (ignore-able; regenerable). └── .tausik/ ← YOUR data. Bootstrap NEVER touches this. ├── tausik.db ├── config.json ├── stacks/ ← your stack overrides └── venv/ ``` ## Bootstrap-owned vs user-owned | Path | Owner | Survives upgrade? | |---|---|---| | `stacks//stack.json` | Framework | No — overwritten on every bootstrap. | | `harness/stacks/*.md` (legacy) | Framework | Replaced on bootstrap; legacy fallback only. | | `.claude/` | Bootstrap output | Regenerated each run. | | `.tausik/stacks//stack.json` | **You** | **Yes — bootstrap never touches it.** | | `.tausik/config.json` | **You** | **Yes.** | | `.tausik/tausik.db` | **You** | **Yes.** | The `tests/test_bootstrap_non_destructive.py` suite enforces this contract. Every bootstrap path is checked to make sure it never writes inside `.tausik/`. ## Upgrade workflow 1. `git pull` (or update the framework however you installed it). 2. `python bootstrap/bootstrap.py --no-detect` — refreshes `.claude/`, copies new `stacks/`, regenerates the MCP `_STACKS_ENUM` block. 3. `tausik stack lint` — validate that your overrides still parse against the new schema. 4. `tausik stack diff ` — for each stack you've overridden, sanity-check that the built-in didn't change in a way that breaks your assumptions. ## What can break a stack override A non-trivial framework upgrade *can* invalidate an override even though the file isn't touched: - The schema gained a required field. `tausik stack lint` will report it. - A field was renamed (e.g. `detect[].kind` → `detect[].type`). `lint` will report unknown-field errors. - An `extends: "builtin:NAME"` target was renamed or removed. `lint` will surface "extends target not found". - A gate name moved between stacks. Your override referencing it via `null` (to disable) becomes a no-op silently — review with `stack export`. The framework prefers additive changes; breaking changes ship in a major version with a migration note in the changelog. ## When something goes wrong | Symptom | What to do | |---|---| | Bootstrap fails or warns about `.tausik/stacks/` | Run `tausik stack lint` to spot validation errors. | | `tausik task add --stack X` rejects a stack you defined | Confirm `.tausik/stacks/X/stack.json` exists and parses; check `tausik stack list`. | | A gate stops firing after upgrade | `tausik stack info ` to see active gates; `stack export ` to confirm merge order. | | You want to start over | `tausik stack reset ` removes a single override; `rm -rf .tausik/stacks/` blows them all away. | ## Disaster recovery If `.tausik/` itself is corrupted or you want to nuke local state, only the database (`.tausik/tausik.db`) is irreplaceable — everything else can be regenerated by re-running bootstrap. Back up `.tausik/tausik.db` and `.tausik/stacks/` before doing anything destructive. ## Version Policy - **Patch (x.y.Z)** — bug fixes, no API or schema changes. - **Minor (x.Y.0)** — new features, additive DB migrations, API extends without breaking changes. - **Major (X.0.0)** — breaking API/schema changes; a migration note in `CHANGELOG.md` is mandatory. `tausik doctor` after an upgrade reports drift or config issues. ## See Also - [Customization](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/customization.md) — how to override without touching framework files. - [Architecture](/docs/architecture) — layer and ownership model. --- ## Русский · /ru/docs/quickstart [English](/docs/quickstart) | **Русский** # Быстрый старт TAUSIK — **Т**ехнический **А**гент **У**нифицированного **С**опровождения, **И**нспекции и **К**онтроля. Пошаговая инструкция: от нуля до первой задачи с ИИ-агентом. Занимает 10-15 минут. Никакого опыта с ИИ-инструментами не требуется. > **Вы — агент?** Читайте [agent-quickstart.md](/ru/docs/agent-quickstart) — тот > же путь точными вызовами, с ответами и отказами, которые вы увидите, для > каждого хоста, который разворачивает bootstrap. --- ## Шаг 0. Что вам понадобится Убедитесь, что на вашем компьютере установлены: 1. **Python 3.11 или новее** - Windows: скачайте с [python.org](https://www.python.org/downloads/), при установке поставьте галочку "Add to PATH". Альтернатива: `winget install Python.Python.3.13` - macOS: `brew install python@3.13` - Linux (Ubuntu/Debian): `sudo apt install python3.13` · Fedora: `sudo dnf install python3.13` - Проверка: откройте терминал и введите `python --version` (должно показать 3.11+) - Bootstrap автоматически создаст изолированное виртуальное окружение (`.tausik/venv/`) и установит все необходимые зависимости. Ваш системный Python не будет изменён. Если подходящий Python не найден, bootstrap покажет инструкцию по установке. 2. **Git** - Windows: скачайте с [git-scm.com](https://git-scm.com/downloads) - macOS: `brew install git` - Linux: `sudo apt install git` - Проверка: `git --version` 3. **Visual Studio Code** - Скачайте с [code.visualstudio.com](https://code.visualstudio.com/) 4. **ИИ-среда разработки — одна из следующих:** - **Claude Code** — расширение VSCode или CLI: `npm install -g @anthropic-ai/claude-code` - **Cursor** — скачайте с [cursor.com](https://cursor.com) - **Qwen Code (GigaCode)** — установите с [qwen.ai/qwencode](https://qwen.ai/qwencode) - **Kilo Code** — дополнение VSCode; работает с моделями z.ai GLM (см. [Kilo + z.ai](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/kilo-zai.md)) - **OpenCode** — `npm i -g opencode-ai`; bootstrap с `--ide opencode` - **Codex CLI** — bootstrap с `--ide codex`; его хуки принуждают только после того, как вы доверили их в Codex (см. [model-providers.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/model-providers.md#матрица-принуждения-codex)) - **Windsurf** — скачайте с [windsurf.com](https://windsurf.com) (ожидается; bootstrap его не разворачивает) - Вам понадобится API-ключ или подписка для выбранной среды ## Шаг 1. Создайте репозиторий Перейдите на [github.com](https://github.com) (или GitLab, Bitbucket — любой Git-хостинг). 1. Нажмите **New Repository** 2. Назовите, например: `my-project` 3. Поставьте галочку "Add a README file" 4. Нажмите **Create Repository** Теперь склонируйте репозиторий к себе на компьютер: ```bash git clone https://github.com/your-username/my-project.git cd my-project ``` > Если у вас уже есть проект — пропустите этот шаг и перейдите в папку вашего проекта. ## Шаг 2. Подключите TAUSIK > **Уже есть проект с кодом?** Смотрите [Миграция существующего проекта](#миграция-существующего-проекта) ниже — TAUSIK встраивается в ваш проект, ничего не перезаписывая. Есть два способа — сказать ИИ-агенту или сделать вручную. Рекомендуем первый. ### Вариант А: скажите агенту (проще всего) Откройте проект в IDE, откройте вашего ИИ-агента (Claude Code, Cursor, Qwen Code, Windsurf) и напишите: ``` Добавь https://github.com/Kibertum/tausik-core как git submodule в .tausik-lib, запусти python .tausik-lib/bootstrap/bootstrap.py --init, добавь .tausik/ в .gitignore ``` Агент выполнит все три шага — вам нужно только подтверждать его действия. ### Вариант Б: вручную через терминал Если предпочитаете делать всё руками: ```bash # Подключить TAUSIK как Git-подмодуль # (Подмодуль — это ссылка на другой Git-репозиторий внутри вашего. # Код TAUSIK остаётся связанным, но отделённым от вашего проекта) git submodule add https://github.com/Kibertum/tausik-core .tausik-lib # Запустить bootstrap (одна команда делает всё) # Имя проекта определяется автоматически из названия папки python .tausik-lib/bootstrap/bootstrap.py --init # Добавить рабочие данные в .gitignore echo ".tausik/" >> .gitignore ``` ### Перезапустите IDE **После bootstrap перезапустите окно IDE** (Claude Code, Cursor, Qwen Code, Windsurf). Bootstrap генерирует project MCP-конфиги (`.mcp.json` для Claude-экосистемы и `.cursor/mcp.json` для Cursor), а IDE считывают их только при запуске. Без перезапуска агент может уйти в CLI-режим вместо MCP-инструментов. > **Пользователям Qwen Code:** Bootstrap также создаёт `.qwen/settings.json` с MCP-конфигом и `QWEN.md` с инструкциями проекта. Используйте `--ide qwen` если работаете только в Qwen Code, или `--ide all` для мультисредового сетапа. ### Что произойдёт после bootstrap Неважно какой вариант вы выбрали — результат одинаковый: - Появится папка `.tausik/` — база данных, скрипты, конфигурация - Появится папка `.claude/` — навыки и настройки для Claude Code - Появится файл `CLAUDE.md` — инструкции для ИИ-агента - TAUSIK определит технологический стек вашего проекта (Python, React, Go и т.д.) и включит подходящие проверки **Две директории, которые важно понимать:** - **`.tausik-lib/`** — исходный код фреймворка (git submodule, отслеживается в git). Это сам TAUSIK. - **`.tausik/`** — рабочие данные: база, конфиг, виртуальное окружение. Добавлена в `.gitignore` — это локальные данные, они не коммитятся. Коллеги, склонировавшие репозиторий, выполняют `git submodule update --init` для получения фреймворка, затем `python .tausik-lib/bootstrap/bootstrap.py --init` для настройки локальной `.tausik/`. `.claude/` и `CLAUDE.md` — оставьте под версионным контролем. Это инструкции для агента, они должны быть в репозитории. ## Шаг 3. Проверьте установку ```bash .tausik/tausik status ``` > **Windows:** эта команда требует Git Bash или WSL. Если используете cmd.exe или PowerShell, выполните `.tausik/tausik.cmd status`. Если видите что-то вроде: ``` Tasks: 0/0 done Session: none Epics: 0 ``` Всё работает. TAUSIK готов. ## Шаг 4. Откройте проект в VSCode ```bash code . ``` Откроется VSCode с вашим проектом. В боковой панели или внизу вы увидите Claude Code — чат с ИИ-агентом. Если Claude Code не появился — проверьте что расширение установлено (Ctrl+Shift+X → «Claude Code»). ## Шаг 5. Начните сессию Напишите в чат Claude Code: ``` начинай работу ``` Агент откроет сессию, покажет статус проекта и предложит чем заняться. При первом запуске проект пустой — это нормально. ## Шаг 6. Поставьте первую задачу Просто опишите что нужно сделать, своими словами: ``` создай главную страницу с заголовком "Мой проект" и кнопкой "Начать" ``` Агент сам: 1. Создаст задачу в базе данных 2. Сформулирует цель и критерии приёмки (что считается «готово») — **включая хотя бы один сценарий ошибки или граничный случай**. Этого требует Context Gate (QG-0), и агент добавит его за вас (например, «кнопка неактивна, пока не заполнен заголовок»). Писать его вручную не нужно — просто знайте, что агент его добавит. 3. Начнёт работу — напишет код, создаст файлы Вы увидите как агент работает: создаёт файлы, пишет код, проверяет что всё работает. Можете вмешаться в любой момент — дать уточнение, попросить изменить подход. ## Шаг 7. Завершите задачу Когда агент закончит (или вы решите что результат устраивает), напишите: ``` готово, отправляй ``` Агент: 1. Запустит **`tausik verify`** — это шаг тяжёлой проверки (pytest, tsc, cargo, phpstan и т.д.). Может идти минуты на больших проектах. Результат **кешируется**. 2. Убедится что критерии приёмки выполнены 3. Закроет задачу через **`task done`** — это лёгкий шаг (миллисекунды), ищет свежий verify-кеш. 4. Предложит сохранить изменения в репозитории Ответьте «да» на предложение — и ваша первая задача завершена. Если IDE агента обрывает долгие вызовы инструментов (расширение VS Code — примерно через 60 секунд), держите два шага раздельно: сначала `tausik verify --task `, затем `task done`, который берёт результат из кэша за миллисекунды. Настройки, меняющие этот процесс, — размер файла правил, сжатый стиль ответа, профиль хоста — в **[Конфигурации](/ru/docs/configuration#настройки-из-быстрого-старта)**. ## Шаг 8. Закончите работу Когда работать больше не планируете: ``` на сегодня всё ``` Агент сохранит контекст: что сделано, что не закончено, какие решения приняли. В следующий раз, когда вы скажете "начинай работу" — агент загрузит этот контекст и продолжит с того же места. --- ## Полный рабочий цикл Вот как выглядит типичная работа с TAUSIK после настройки: ``` Вы: "начинай работу" → агент открывает сессию Вы: "исправь баг — форма не отправляется" → агент создаёт задачу, начинает ... агент работает ... Вы: "готово, отправляй" → агент проверяет, фиксирует Вы: "ещё одна — добавь валидацию email" → следующая задача ... агент работает ... Вы: "готово, отправляй" → проверка, фиксация Вы: "на сегодня всё" → агент сохраняет контекст ``` Два-три сообщения на задачу. Всё остальное — автоматически. --- ## Работа в команде `.tausik/` лежит в gitignore — база ваша. Коллегам нужны не файлы базы, а *знания* проекта, и они едут в git текстом: задачи, решения и память выгружаются в читаемое дерево `tausik/`, которое вы коммитите рядом с кодом. ```bash tausik state export # БД → tausik/ (выполняется автоматически при любой durable-записи) git add tausik/ && git commit ``` После того как забрали чужую работу, внесите её в свою базу: ```bash git pull tausik sync # tausik/ → БД, файлы побеждают ``` Коллега, склонировавший репозиторий, получает историю проекта, а не пустую базу, и никакого внешнего хранилища для этого держать не нужно. `tausik status` сообщает, когда дерево и база расходятся. Он показывает количества и называет **оба** направления — `tausik sync` (дерево → БД) и `tausik state export` (БД → дерево), — не рекомендуя ни одного, потому что количества доказывают, что стороны *различаются*, а не какая из них свежее. Единственный однозначный сигнал — строки, которые есть в дереве и которых нет в базе; именно его вы видите после `git pull`. **[Полный контракт round-trip →](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/team-state-in-git.md)** --- ## Если что-то пошло не так **"command not found: .tausik/tausik"** - На Windows используйте Git Bash или WSL, не cmd.exe - Или вызывайте напрямую: `.tausik/tausik.cmd status` **"Python not found"** - Убедитесь что Python в PATH: `python --version` - На некоторых системах команда называется `python3` **"Claude Code не видит навыки"** - Перезапустите VSCode после bootstrap - Убедитесь что `.claude/` создана и не пуста **"База данных заблокирована"** - Удалите файл `.tausik/tausik.db-wal` и попробуйте снова - Это случается если предыдущий процесс завершился некорректно **Агент не создаёт задачи, а просто пишет код** - Проверьте что `CLAUDE.md` существует в корне проекта - Проверьте что в нём есть раздел «Ограничения» с правилом «Нет кода без задачи» --- ## Миграция существующего проекта Если у вас уже есть проект с кодом и вы хотите добавить TAUSIK: 1. **Перейдите в корень проекта** и подключите TAUSIK как подмодуль: ```bash git submodule add https://github.com/Kibertum/tausik-core .tausik-lib python .tausik-lib/bootstrap/bootstrap.py --init ``` 2. **Bootstrap автоматически определяет окружение:** - Распознаёт стек (Python, React, Go и т.д.) и включает соответствующие quality gates - Находит существующие конфиги (`.eslintrc`, `pyproject.toml` и т.д.) - Сохраняет ваши `.gitignore`, `CLAUDE.md`, `.mcp.json` и `.cursor/mcp.json` — объединяет, а не перезаписывает 3. **Что попадает в git:** - `.tausik-lib/` — сам фреймворк (git submodule, отслеживается) - `.tausik/` — рабочие данные (база, конфиг, venv) — добавляется в `.gitignore`, не коммитится - `.claude/` и `CLAUDE.md` — инструкции агента, отслеживаются 4. **Начать с нуля или импортировать:** - TAUSIK начинает с чистой базы задач. Автоматического импорта из других систем нет. - Существующие задачи можно создать вручную через `task quick "title"` или спланировать через `/plan`. - История кода остаётся в git — TAUSIK управляет только слоем AI-workflow поверх неё. После настройки проверьте `.tausik/tausik status` и начинайте работу. --- ## Что дальше Вы освоили базовый цикл. Дальше можно разобраться подробнее: - **[Рабочий процесс](/ru/docs/workflow)** — быстрый и полный режимы, quality gates, память - **[Навыки](/ru/docs/skills)** — все фразы, которые понимает агент - **[CLI-команды](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/cli.md)** — если хотите вызывать TAUSIK из терминала напрямую --- ## Русский · /ru/docs/workflow [English](/docs/workflow) | **Русский** # Рабочий процесс TAUSIK рассчитан на работу в паре: инженер пишет в свободной форме, ИИ-агент интерпретирует и выполняет. Никаких специальных команд запоминать не нужно — просто описывайте что хотите сделать. ## Типичный рабочий день ### Утро: начало работы Напишите агенту: ``` начинай работу ``` Агент откроет сессию, покажет что было сделано в прошлый раз, какие задачи в работе, и предложит чем заняться. Если есть незавершённые задачи — предложит продолжить. ### Работа над задачей Для простых задач — просто скажите что нужно сделать: ``` добавь тёмную тему в настройки пользователя ``` Агент создаст задачу, сформулирует критерии приёмки и начнёт работу. Для сложных задач лучше сначала спланировать: ``` давай спланируем миграцию с REST на GraphQL ``` Агент создаст задачу с детальным планом, разобьёт на шаги, оценит сложность и предложит начать. ### Проверка и завершение Когда работа сделана: ``` готово, проверь и сохрани ``` Агент проверит код по 28-пунктному контрольному списку, прогонит тесты и quality gates, убедится что все критерии приёмки выполнены, и предложит зафиксировать изменения. ### Конец дня ``` на сегодня всё ``` Агент покажет итоги: что сделано, сколько задач закрыто, какие метрики. Сохранит контекст для следующей сессии — завтра можно продолжить с того же места. ## Два режима работы ### Быстрый (для мелких задач) ``` Инженер: "начинай работу" → /start (открывает сессию) Инженер: "исправь ошибку с JWT" → /plan (создаёт задачу, планирует) Инженер: "готово" → /ship (проверяет, фиксирует) ``` ### Полный (для сложных задач) ``` Инженер: "начинай" → /start (контекст, метрики) Инженер: "спланируй рефакторинг API" → /plan (задача + план + AC) Инженер: "приступай" → /task (QG-0, начало работы) ... работа, прогресс, тупики ... Инженер: "проверь код" → /review (28-пунктный контрольный список) Инженер: "прогони тесты" → /test Инженер: "закрывай и сохраняй" → /ship (QG-2, quality gates, фиксация) Инженер: "на сегодня всё" → /end (метрики, передача контекста) ``` ## Quality Gates TAUSIK автоматически проверяет качество в двух точках: **При старте задачи (QG-0):** - Цель задачи сформулирована - Критерии приёмки записаны — и что-то говорят: шаблоны (`<...>`, `[UPPER]`, `{{...}}`), нераскрытый `$(...)`, TODO/TBD, пустая таблица и «работает корректно» удаляются до подсчёта; меньше 3 слов цели или 5 слов критериев — отказ - **Блокирует** если в критериях нет негативного сценария (ошибка, отказ, некорректный ввод) - Предупреждает для задач безопасности (авторизация, оплата, персональные данные) без критериев безопасности - Предупреждает если не определена область изменений (что менять / что не трогать) **При завершении задачи (QG-2):** - Каждый критерий приёмки проверен с доказательством - Все шаги плана выполнены - Тесты проходят (pytest, ruff, и другие проверки по стеку) - Предупреждает если не задокументированы знания Эти ворота нельзя обойти — агент не сможет начать работу без цели и не сможет закрыть задачу без проверки. ### Когда quality gates блокируют **QG-0 блокирует старт задачи:** - Нет цели → добавьте: `task update --goal "..."` - Нет критериев приёмки → добавьте: `task update --acceptance-criteria "..."` - Цель или AC — заглушка («the acceptance criteria have 0 word(s) of substance») → напишите, что проверяется и как - Нет негативного сценария в AC → добавьте критерий вроде «Возвращает ошибку при невалидном вводе» - Сессия выше порога совета — это совет, а не отказ: сохраните состояние через `/checkpoint` или передайте работу через `/end` - Бюджет host thread отделён от времени сессии. Native-сигналы Codex по ответам/input/context на advisory автоматически сохраняют checkpoint и дают промпт нового окна; hard запрещает продолжение. Повторное открытие TAUSIK в том же host thread не сбрасывает бюджет. Отсутствующий native usage показывается как unavailable и работает fail-open. **QG-2 блокирует закрытие задачи:** - AC не проверены → залогируйте: `task log "AC verified: 1. ... ✓ 2. ... ✓"` - Тесты падают → исправьте код, тесты запустятся автоматически при следующем `task done` - Шаги плана не завершены → отметьте: `task step ` или обновите план Агент обрабатывает большинство ситуаций автоматически. Если quality gate блокирует — агент сообщит, что именно не хватает и как исправить. ## Хуки — автоматический контроль Помимо quality gates, TAUSIK использует хуки Claude Code для контроля в реальном времени: - **Нет кода без задачи** — попытка редактировать файл без активной задачи блокируется - **Фильтр опасных команд** — `rm -rf`, `DROP TABLE`, `git reset --hard` блокируются - **Отправка в репозиторий только через /ship** — прямой `git push` блокируется - **Автоформатирование** — код форматируется автоматически после каждого изменения (ruff, prettier, gofmt) Подробнее: **[Хуки](/ru/docs/hooks)** ## Память проекта TAUSIK сохраняет знания между сессиями. Агент автоматически: - **Записывает решения** — почему выбрали bcrypt, а не argon2 - **Документирует тупики** — что пробовали и почему не сработало - **Фиксирует закономерности** — формат ошибок API, соглашения об именовании - **Передаёт контекст** — передача для следующей сессии Эти знания загружаются при каждом `/start` и `/task` — агент не повторяет ошибки предыдущих сессий. ## Метрики TAUSIK автоматически считает: | Метрика | Что показывает | |---------|----------------| | Производительность | Сколько задач за сессию | | Доля успеха с первой попытки | Процент задач, решённых без повторных заходов | | Уровень пропущенных дефектов | Процент задач, в которых потом нашли дефект | | Доля тупиков | Доля тупиков от общего числа задач | | Время выполнения | Среднее время от создания до закрытия задачи | | Стоимость задачи | Среднее время по сложности (простая/средняя/сложная) | Метрики помогают понять: агент работает эффективно, или тратит время на повторные попытки? ## Работа нескольких агентов TAUSIK поддерживает одновременную работу нескольких ИИ-агентов над одним проектом: - **Захват задач** — `task claim ` блокирует задачу за конкретным агентом. Другие видят её как занятую и берут другую. `task unclaim ` снимает блокировку. - **Без конфликтов** — каждый агент работает над своей захваченной задачей. `task next --agent ` атомарно захватывает и запускает лучшую свободную задачу. - **Параллельная запись** — база SQLite работает в режиме WAL (Write-Ahead Logging), поэтому несколько агентов могут читать и писать одновременно без блокировок. - **Общие знания** — все агенты используют одну память проекта, решения и тупики. Что узнал один агент — сразу видят остальные. Специальной настройки не требуется. Просто запустите несколько сессий агентов в одной директории проекта. ## Что дальше - **[Хуки](/ru/docs/hooks)** — автоматический контроль: блокировка, фильтр команд, автоформатирование - **[Навыки](/ru/docs/skills)** — полный список того, что умеет агент - **[CLI-команды](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/cli.md)** — если хотите управлять TAUSIK из терминала - **[Архитектура](/ru/docs/architecture)** — как устроен фреймворк внутри --- ## Русский · /ru/docs/agent-quickstart [English](/docs/agent-quickstart) | **Русский** # Быстрый старт для агента: подключить TAUSIK и работать под ним Ты — AI-агент. Эта страница написана для тебя и в том порядке, в каком она понадобится: как подключить фреймворк к проекту на том хосте, в котором ты работаешь, как убедиться, что он действительно подключён, и как делать работу после этого — точными вызовами, с ответами и отказами, которые ты реально увидишь. Каждая команда, имя инструмента и текст отказа здесь сняты с живого прогона этой версии, а не вспомнены; тест держит их в согласии с кодом (`tests/test_agent_quickstart.py`). Человеческая версия того же пути — [quickstart.md](/ru/docs/quickstart). Сами правила — в [AGENTS.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/AGENTS.md); эта страница — процедура, которая из них следует. ## 1. Подключить TAUSIK — это git-сабмодуль плюс один bootstrap. Из корня проекта: ```bash git submodule add https://github.com/Kibertum/tausik-core .tausik-lib python .tausik-lib/bootstrap/bootstrap.py --init --ide ``` `` — один из хостов, которые bootstrap умеет разворачивать: **claude, cursor, qwen, kilo, opencode, codex** — или `all`. Выбери тот, в котором работаешь; `all` кладёт все профили рядом (у них одна база в `.tausik/`). | Ты работаешь в | `--ide` | Что появляется | Что принуждает к правилам в реальном времени | |---|---|---|---| | Claude Code / VS Code Claude Extension | `claude` | `.claude/` (навыки, хуки, MCP), `.mcp.json`, `CLAUDE.md`, `AGENTS.md` | PreToolUse-хуки (`.claude/settings.json`) | | Cursor / Composer | `cursor` | `.cursor/mcp.json`, `.cursorrules`, `AGENTS.md` | ничего на границе инструмента — Rule 1 исполняешь ты сам (см. §3) | | Qwen Code | `qwen` | `.qwen/` (навыки, настройки), `QWEN.md`, `AGENTS.md` | хуки (подмножество хуков Claude) | | Kilo Code | `kilo` | `.kilo/`, `.kilocode/mcp.json`, `AGENTS.md` | ничего на границе инструмента — только гейты MCP | | OpenCode | `opencode` | `.opencode/` (один плагин QG-0), `opencode.json` | плагин QG-0 для Rule 1; для Rule 2 — ничего | | Codex CLI | `codex` | `.codex/` (config.toml с MCP, навыки, агенты, hooks.json), `AGENTS.md` | `.codex/hooks.json` — **только после того, как пользователь доверил хуки проекта в Codex**; недоверенный профиль не принуждает ничего (замерено живьём) | После bootstrap — **перезапусти хост** (MCP-серверы читаются при старте), затем проверь. Две равнозначные проверки — инструмент MCP, если хост показывает `tausik_*`, иначе CLI: ``` tausik_status ← MCP .tausik/tausik status ← CLI (Windows cmd/PowerShell: .tausik/tausik.cmd status) ``` Ожидаемо на свежем проекте: ``` Tasks: 0/0 done Session: none active ``` Если после перезапуска инструментов MCP нет — `.tausik/tausik doctor` (или `tausik_doctor`) назовёт недостающее; CLI работает в любом случае. У каждого инструмента `tausik_*` есть CLI-двойник, поэтому ничего ниже от MCP не зависит. **Отдельно про Codex.** Codex исполняет хуки проекта только после того, как пользователь им доверил. До этого `bootstrap --check` чист, файл хуков перечисляет все гейты — и ни один не срабатывает. Не сообщай Rule 1/Rule 2 как принуждаемые на Codex, пока доверенное состояние не подтверждено; [матрица принуждения](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/model-providers.md#матрица-принуждения-codex) несёт то же условие. ## 2. Цикл вызовами Одна задача за раз, каждый шаг записан. Слева инструмент MCP, справа CLI-двойник; ответ — то, что ты увидишь. **Открыть смену.** ``` tausik_session_start .tausik/tausik session start → Session #1 started. ``` **Создать задачу с целью И критериями приёмки.** QG-0 отказывает в старте без них, и один критерий обязан быть негативным (ошибка или граница). ``` tausik_task_quick(title="Demo feature", goal="Show the gate", acceptance="AC-1: the feature returns the answer. AC-2: an empty input is refused with an error.") .tausik/tausik task quick "Demo feature" --goal "Show the gate" --ac "AC-1: … AC-2: an empty input is refused with an error." → Task 'demo-feature' created. ``` **Стартовать.** С этого момента гейты записи для тебя открыты. ``` tausik_task_start(slug="demo-feature") .tausik/tausik task start demo-feature → Task 'demo-feature' started (attempt #1). ``` Если критерий пропущен, отказ точен и называет починку: ``` Error: QG-0 Context Gate: 'demo-feature' cannot start — missing acceptance_criteria. Fix: .tausik/tausik task update demo-feature --goal '...' --acceptance-criteria '...' Error: QG-0 Start Gate: 'demo-feature' AC has no negative scenario. SENAR requires at least one error/boundary case in acceptance criteria. ``` **Объявить, что будешь трогать, затем править.** `relevant_files` — область, которую читают гейты; пути через пробел. ``` tausik_task_update(slug="demo-feature", …) .tausik/tausik task update demo-feature --relevant-files src/feature.py tests/test_feature.py ``` **Журналировать по ходу.** Каждый значимый шаг и доказательство по каждому критерию — закрытие читает эти строки. ``` tausik_task_log(slug, message) .tausik/tausik task log demo-feature "AC-1: ✓ tests/test_feature.py::test_answer AC-2: ✓ tests/test_feature.py::test_empty_is_refused (negative: empty input raises)" ``` **Проверить.** Тяжёлые гейты (линтеры, scoped-тесты) бегут один раз, здесь, и зелёное записывается для закрытия. ``` tausik_verify(task_slug="demo-feature") .tausik/tausik verify --task demo-feature → [PASS] ruff [PASS] pytest SCOPE: scoped run over 1 of 1 test file(s) mapped from relevant_files … Recorded verification_run #8 (task_slug=demo-feature, exit=0). ``` В проекте с ключом подписи ответ кончается **verify handle** — передай его в закрытие. Без ключа он так и говорит и даёт рабочее закрытие: ``` Verify handle: none — no project key, so no signed receipt (`tausik key init` enables them). Close without --verify-handle: `.tausik/tausik task done demo-feature --ac-verified` uses the freshness lookup. ``` **Закрыть.** `--ac-verified` — твоё утверждение; проверяет гейт записанное доказательство. ``` tausik_task_done(slug="demo-feature", ac_verified=true, verify_handle="<из verify>") .tausik/tausik task done demo-feature --ac-verified [--verify-handle ] → Task 'demo-feature' completed. ``` Два отказа, которые встретишь, если поспешить, — оба точные: ``` Error: QG-2: 'demo-feature' cannot complete — acceptance criteria not verified. Verify each criterion, then: .tausik/tausik task done demo-feature --ac-verified Error: QG-2: 'demo-feature' has 2 acceptance criteria but no verification evidence in task notes. Log verification: .tausik/tausik task log demo-feature "AC verified: 1. ✓ 2. ✓ ..." ``` **Передать и закончить.** Handoff — то, с чего начнёт следующая смена. ``` tausik_session_handoff(handoff={...}) .tausik/tausik session handoff '' tausik_session_end .tausik/tausik session end → Session #1 ended. ``` ## 3. Что принуждает хост, а что — ты Два рода правил, два рода принуждения — граница проходит по тому, КТО совершает действие, а не по хосту: * **Правила, которые держит наша поверхность, везде.** QG-0 (нет старта без цели и AC), QG-2 (нет закрытия без доказательства и свежего verify), лимит смены, маршрут памяти. Они отказывают внутри `tausik_*` / CLI и держатся на хосте вовсе без хуков. * **Правила, перехватывающие ТВОЁ действие.** Rule 1 (нет записи файла без активной задачи), Rule 2 (нет записи вне объявленной области), shell-файрвол, push-гейт. Они есть только там, где хук бежит перед инструментом: Claude Code, Qwen Code, Codex (после доверия), и один Rule 1 на OpenCode. На Cursor и Kilo между тобой и файлом никого нет — хук это ты: стартуй задачу до первой правки. Там, где хук бежит, запись без активной задачи отклоняется до изменения файла: ``` BLOCKED: No active task. TAUSIK requires a task before code changes (SENAR Rule 1). Create one: /plan (or describe the task and ask to start it) Resume one: .tausik/tausik task list --status planning .tausik/tausik task start ``` `docs/ru/enforcement-coverage.md` — таблица по правилам, а `.tausik/tausik doctor` печатает, что развёрнуто для ЭТОГО хоста. ## 4. Память: что писать и куда не писать * `tausik_memory_add` / `.tausik/tausik memory add` — паттерн, ловушка или конвенция ЭТОГО проекта, когда узнал то, что следующий агент не должен узнавать заново. * `tausik_dead_end` / `.tausik/tausik dead-end "подход" "причина"` — подход, который не сработал, и почему. Дешевле, чем следующий агент попробует снова. * `tausik_decide` / `.tausik/tausik decide` — решение с обоснованием. * **Не пиши в `~/.claude/`** с не-Claude хоста и не клади туда правила самого TAUSIK ни с какого хоста: это профиль Claude, а не память проекта. Знание проекта идёт через инструменты выше. ## 5. Если у хоста нет slash-команд `/start`, `/plan`, `/ship` и остальные — процедуры, записанные Markdown-ом. Когда хост их не разворачивает, открой `harness/skills//SKILL.md` (развёрнуто в `.claude/skills/`, `.qwen/skills/`, `.codex/skills/`) и выполни нумерованные шаги сам. Инструменты, которые они зовут, — те же, что в §2. ## См. также * [AGENTS.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/AGENTS.md) — правила и матрица хостов * [quickstart.md](/ru/docs/quickstart) — тот же путь для человека * [mcp.md](/ru/docs/mcp) — каждый инструмент `tausik_*` и его параметры * [cli.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/cli.md) — каждая команда CLI * [enforcement-coverage.md](/ru/docs/enforcement-coverage) — что принуждается где, по правилам * [model-providers.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/model-providers.md) — заметки по хостам, включая условие доверия Codex --- ## Русский · /ru/docs/senar [English](/docs/senar) | **Русский** # Что такое SENAR? SENAR (Supervised Engineering & Normative AI Regulation) — открытый инженерный стандарт, определяющий как AI-агенты должны работать над программными проектами. Думайте о нём как о строительном кодексе для AI-разработки — набор правил, которые делают работу агента предсказуемой, проверяемой и безопасной. **Полная спецификация:** [senar.tech](https://senar.tech) | [GitHub](https://github.com/Kibertum/SENAR) TAUSIK (**Т**ехнический **А**гент **У**нифицированного **С**опровождения, **И**нспекции и **К**онтроля) — эталонная реализация SENAR. Каждое правило, описанное ниже, применяется TAUSIK автоматически — вам не нужно читать спецификацию, чтобы использовать фреймворк. --- ## Какую проблему решает SENAR ИИ-агенты для программирования мощные, но ненадёжные. Без структуры они: - Начинают писать код, не определив что значит «готово» - Заявляют что задача выполнена, не запуская тесты - Забывают всё между сессиями - Пробуют один и тот же неработающий подход три раза подряд AGENTS.md и .cursorrules — это рекомендации, которые агент может проигнорировать. SENAR обеспечивает **принудительный контроль**: жёсткие блокировки, которые физически не дают агенту пропустить шаги. ## Ключевые концепции ### Quality Gates Два контрольных пункта, которые должна пройти каждая задача: **QG-0 — Context Gate (перед началом работы):** - Задача должна иметь чётко сформулированную цель - Критерии приёмки должны быть записаны — что считается «готово» - Обязателен хотя бы один негативный сценарий (что происходит при ошибке, некорректном вводе и т.д.) - Для задач, связанных с безопасностью (авторизация, платежи, персональные данные), выводится предупреждение, если нет критериев безопасности **QG-2 — Implementation Gate (перед закрытием задачи):** - Каждый критерий приёмки должен иметь задокументированное подтверждение (вывод тестов, шаги проверки) - Все шаги плана должны быть выполнены - Автоматические проверки должны пройти (тесты, линтеры — в зависимости от стека) Эти quality gates — **жёсткие блокировки**: агент буквально не может продолжить, не выполнив требования. Никакого `--force`, никакого обхода. > **Почему QG-0 и QG-2, а не QG-1?** Нумерация следует спецификации SENAR: QG-0 срабатывает при старте задачи (до начала работы), QG-2 — при завершении (после всей работы). QG-1 зарезервирован в спецификации для промежуточной проверки, но пока не реализован. ### Управление сессиями Рабочие сессии имеют структуру: - **Старт сессии** — загрузка контекста из предыдущей работы (что сделано, что заблокировано, что не сработало) - **Давление контекста** — время сессии, вызовы с последнего чекпоинта и тишина журнала печатаются советом с измеренным основанием; старт задачи они не блокируют (1.10). - **Контрольные точки** — периодические снимки контекста, чтобы ничего не потерялось при сбое сессии - **Конец сессии** — сохранение передачи: что выполнено, что не закончено, какие решения приняты ### Отслеживание тупиков Когда агент пробует подход и он не работает — он записывает тупик: что пробовали и почему не сработало. В следующей сессии, когда возникает похожая задача, агент видит неудачный подход и не повторяет его. ### Метрики Шесть метрик отслеживаются автоматически, без ручного ввода: | Метрика | Что измеряет | |---------|--------------| | **Производительность** | Задач завершено за сессию | | **Доля успеха с первой попытки** | % задач, решённых без повторных заходов | | **Уровень пропущенных дефектов** | % задач, в которых потом нашли дефект | | **Время выполнения** | Среднее время от создания задачи до завершения | | **Доля тупиков** | Доля тупиков относительно общего числа задач | | **Стоимость задачи** | Среднее время по уровню сложности | Эти метрики помогают ответить: работает ли агент эффективно или тратит время на повторы и тупики? ## Что вы заметите на практике При использовании TAUSIK SENAR проявляется так: 1. **Агент всегда спрашивает "что считается готовым?"** перед написанием кода — это QG-0 2. **Агент не может закрыть задачу, просто сказав "готово"** — он должен показать подтверждение по каждому критерию — это QG-2 3. **Начало новой сессии загружает предыдущий контекст** — вы не объясняете проект с нуля 4. **Неудачные подходы запоминаются** — агент не будет пробовать одно и то же сломанное решение дважды 5. **Длинные сессии получают предупреждение** — после 3 часов агент предложит сохранить контрольную точку ## Правила SENAR кратко | Правило | Что говорит | Как TAUSIK применяет | |---------|------------|----------------------| | Rule 1 | Нет кода без задачи | Хук блокирует редактирование файлов без активной задачи | | Rule 2 | Определить границы задачи | `scope_write_gate`/`bash_write_gate` блокируют запись вне `scope_paths`; QG-0 жёстко блокирует старт medium/complex без scope | | Rule 3 | Проверять по критериям | QG-0 + QG-2 совместный контроль | | Rule 4 | Внешнее состязательное ревью | `risk_l3_trigger` блокирует закрытие с тонким доказательством до записи L3-ревью; ревьюер — сепаратный субагент `tausik-external-reviewer` | | Rule 5 | Контрольный список верификации | Жёсткий гейт для тиров substantial/deep, чьи AC не ссылаются на существующий тест; ниже — эскалирующий warning | | Rule 6 | Планировать откат | Поле `rollback_plan`: QG-0 блокирует старт medium/complex без плана отката; `task done` предупреждает | | Rule 7 | Найти первопричину дефектов | Предупреждение если в задаче-дефекте нет первопричины | | Rule 8 | Фиксировать знания | Предупреждение при закрытии если не записаны решения и закономерности | | Rule 9.2 | Длительность сессии | Сигнал: совет выше порога `session_max_minutes`, без отказа (1.10) | | Rule 9.3 | Периодические контрольные точки | Сигнал: счёт вызовов с последнего handoff выводится из журнала, совет выше `checkpoint_calls` | | Rule 9.4 | Документировать тупики | Специальный инструмент + напоминания в скиллах | | Rule 9.5 | Периодический аудит | Автопроверка при старте сессии | > **О правилах 4–6.** С **v1.5** они применяются как жёсткие блокировки: **Rule 4** (внешнее состязательное ревью — сепаратный по модели, read-only субагент гейтит закрытия с тонким доказательством), **Rule 5** (контрольный список верификации — жёсткий гейт для планировочных тиров substantial/deep, совещательный ниже) и **Rule 6** (планирование отката — QG-0 блокирует новые medium/complex-задачи без задокументированного плана отката). Полная таблица enforcement — в [Матрице соответствия SENAR](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/senar-compliance-matrix.md). **Полная матрица соответствия:** [Матрица соответствия SENAR](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/senar-compliance-matrix.md) --- ## Что дальше - **[Быстрый старт](/ru/docs/quickstart)** — настроить TAUSIK за 10-15 минут - **[Рабочий процесс](/ru/docs/workflow)** — как выглядит типичный рабочий день - **[Матрица соответствия SENAR](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/senar-compliance-matrix.md)** — детальный статус по каждому правилу --- ## Русский · /ru/docs/reasoning-trace [English](/docs/reasoning-trace) | **Русский** # Трейс рассуждений (RENAR) **Трейс рассуждений** — упорядоченная append-only цепочка типизированных шагов, привязанная к задаче. Он фиксирует, *почему* агент что-то сделал — обоснование, которое свежий агент не восстановит из одного диффа. Это «рассуждательная» половина воспроизводимости RENAR (остальные — events, прогоны верификации и receipts). Сам трейс читается через `tausik task show`; `tausik task replay ` собирает более полный хронологический таймлайн, сшивающий все четыре источника. Скилл `/reason` — агентская поверхность; эта страница — справочник. ## Четыре вида (закрытый список) Шаг относится к одному из закрытого списка видов. Четыре образуют канонический цикл; `seq` автоинкрементируется в рамках задачи: | вид | отвечает на вопрос | |-----|--------------------| | `intent` | Чего я сейчас пытаюсь достичь? | | `premise` | Какое убеждение/допущение определяет выбор? | | `action` | Что я для этого делаю | | `verification` | Как я подтвердил, что сработало | Неверный вид отклоняется дважды — сервисом (дружелюбная ошибка) и ограничением БД `CHECK` (жёсткая гарантия), поэтому некорректный шаг не сохраняется тихо. ## Запись шагов MCP-first (предпочтительно): ``` tausik_reason_step(slug="my-task", kind="intent", content="…") ``` CLI как fallback — аргументы позиционные ` `: ```bash .tausik/tausik task reason-step my-task intent "…" ``` Прочитать трейс через CLI — `.tausik/tausik task show my-task` — или MCP — `tausik_task_show(slug="my-task")`. Секция `Reasoning trace (N)` печатается после плана и решений. ## Разобранный пример — полный трейс Трейс ниже — из самой задачи `v16r-reason-skill`: выпуск скилла `/reason`, nudge которого *не* должен стать новым гейтом. ```bash .tausik/tausik task reason-step v16r-reason-skill intent \ "Ship a /reason skill + a /task nudge that records reasoning at forks." .tausik/tausik task reason-step v16r-reason-skill premise \ "Reasoning capture is a discipline, not a gate — QG-2 must gain no new blocker." .tausik/tausik task reason-step v16r-reason-skill action \ "Add an escalating SOFT nudge to /task step 6; leave task_done gates untouched." .tausik/tausik task reason-step v16r-reason-skill verification \ "Closed a task with zero reasoning steps end-to-end — task done succeeded, no new gate fired." ``` Вывод `tausik task show v16r-reason-skill`: ``` Reasoning trace (4): 1. (intent) Ship a /reason skill + a /task nudge that records reasoning at forks. 2. (premise) Reasoning capture is a discipline, not a gate — QG-2 must gain no new blocker. 3. (action) Add an escalating SOFT nudge to /task step 6; leave task_done gates untouched. 4. (verification) Closed a task with zero reasoning steps end-to-end — task done succeeded, no new gate fired. ``` Цепочка читается как самодостаточный аргумент: **intent** задаёт цель, **premise** называет ограничение, сформировавшее дизайн, **action** фиксирует конкретный выбор, а **verification** замыкает петлю, проверяя ровно то ограничение, что утверждал premise. Исправление никогда не редактирует прошлый шаг — добавляется новый (часто свежий `premise`). ## Когда записывать — и когда нет Записывайте шаг на **развилке**: выбор между подходами, принятие неочевидного допущения, верификация утверждения, обоснование которого иначе потеряется. **Не** используйте как журнал на каждую правку — для этого есть `task log`. Три поверхности, три задачи: | Поверхность | Форма | Для чего | |-------------|-------|----------| | `reason-step` | типизированный, упорядоченный, append-only | *рассуждение* за развилкой — воспроизводимо | | `task log` | свободная строка с таймстампом | журнал прогресса, crash-safety | | `tausik_decide` | зафиксированное проектное решение | выборы, связывающие будущую работу | ## Гарантии - **Append-only.** Шаги не редактируются и не удаляются; трейс — аудит-запись. Неверный premise → новый шаг, не переписывание. - **Advisory, не блокирует.** Ни QG-0 (`task start`), ни QG-2 (`task done`) не смотрят на трейс. Задача с **нулём** шагов рассуждений стартует и закрывается как обычно — nudge в `/task` лишь мягкое эскалирующее напоминание. - **Один трейс на задачу.** Шаги привязаны к slug задачи; глобального трейса нет. ## См. также - **[Скиллы](/ru/docs/skills)** — `/reason` и остальная поверхность скиллов - **[Рабочий процесс](/ru/docs/workflow)** — как рассуждение встроено в жизненный цикл задачи - **[CLI-команды](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/cli.md)** — `task reason-step`, `task replay` - **[MCP-инструменты](/ru/docs/mcp)** — `tausik_reason_step`, `tausik_task_replay` --- ## Русский · /ru/docs/enforcement-coverage # Граница принуждения хуков: что покрыто и что нет > Вынесено из [`agent-contract.md`](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/agent-contract.md) при закрытии > `powershell-tool-bypasses-bash-firewall` — тема самостоятельная и вместе с > матрицей покрытия каналов перестала помещаться в контракт (filesize-гейт). > EN-зеркало: [`../en/enforcement-coverage.md`](/docs/enforcement-coverage). Полный разбор событий и покрытия — [`hooks-events.md`](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/hooks-events.md). Кратко: QG-0 (Rule 1) и scope-ACL (Rule 2) исторически висели только на `Write|Edit`, поэтому **запись файла через Bash обходила обе проверки**: `cat > f <`, `>>`, `&>`, `N>`, включая heredoc-заголовок), `tee`, `dd of=`, `sed -i` (GNU и BSD-форма `-i ''`), `cp`/`mv`/`install` (назначение **и** `-t`/`--target-directory=`), `truncate`, `touch`, `curl -o`/`--output`, `wget -O`/`--output-document`, `tar -x … -C DIR`, `unzip … -d DIR`, а также литеральный `open(path, 'w'|'a'|'x')` внутри `python`/`perl`/`ruby -c`. Тело heredoc'а разбором **не** сканируется (иначе `->`/`>` в прозе или коде тела давали бы фантомную цель и блокировали честную запись). Исключение — тело, которое заголовок отдаёт интерпретатору (`python - <$f`, `echo > $SCRATCH/x` — токен с `$` считается неразрешимым и пропускается); `base64 -d | sh` и прочая обфускация; команда, собираемая из STDIN (`… | xargs -I{} bash -c '…'`) или исполняемая на другом хосте (`ssh host 'cmd'` — там пути этого проекта ничего не значат); `curl -O`/`wget` без `-O` (имя из URL); `tar`/`unzip` распаковка в текущий каталог без `-C`/`-d`; `base64 -o`, `xxd -r`; код интерпретатора, пишущий файл формой вне объявленного каталога Python AST — и то же самое ВНУТРИ скрипта, который команда запускает, если скрипт написан не на Python (shell, Node): `python_source_writes` читает Python, и парсер, не умеющий читать субстрат, не должен заявлять, что его проверил. Разрез здесь не тот, каким его называли раньше. До смены #201 остаток описывался как «литеральный `open()` против вычисленного», из чего читалось, что `python helper.py` с литеральным `open()` ЛОВИТСЯ. Не ловился: парсер читал только ТЕКСТ КОМАНДЫ, а код лежал в файле. Замер #200: `cp x .claude/...` отклонён с печатью ACL, `python helper.py`, пишущий тот же путь, вернул ноль и правку записал. Настоящий разрез — ВСТРОЕННЫЙ КОД против КОДА В ФАЙЛЕ, и запуск скрипта из файла есть обычный способ запустить код, а не обфускация. Закрыто `bash_write_parse._script_file_writes` для формы `python [опции] скрипт.py`. Умышленно узко — `python -m pytest tests/test_x.py` не должен блокироваться литералами `open(..., "w")` из тестового файла, которых сама команда не выполняет. **Форма закрыта целиком, а не одним написанием (смена #203).** Первая версия закрыла ту форму, которую ЗАМЕРИЛА, — `python скрипт.py`, — и оставила соседние, отличающиеся одним токеном. Замер через реальный хук: из 17 обычных написаний той же команды 10 проходили мимо ОБОИХ правил гейта разом (и scope-ACL по SENAR Rule 2, и запрет записи без активной задачи), а два написания давали ЛОЖНЫЙ БЛОК. Сейчас: * **имя интерпретатора выводится правилом, а не перечнем.** `python`, `python3`, `python3.11`, `python2`, `pythonw`, `py`, `py -3`, с `.exe` и без. Перечень уже разошёлся с жизнью однажды — `python3.11` есть штатный способ вызова, — поэтому правило описывает ФОРМУ имени; * **позиция флага решает, а не его присутствие.** `-m`/`-c` ДО первого позиционного означают «файла-скрипта нет»; те же `-m`/`-c` ПОСЛЕ него суть аргументы скрипта и о запуске не говорят ничего. Прежняя проверка сканировала весь список аргументов, поэтому `python helper.py -m foo` снимал защиту — а `-c` есть обычный флаг обычной программы, так что обфускация для этого не требовалась; * **`-m`/`-c` распознаются как БУКВЫ,** покрывая слитную (`-mpytest`) и кластерную (`-um`) записи. Перечень точных токенов их не покрывал и давал на них ложный блок; * **скрипт — ПЕРВЫЙ позиционный,** а не первый попавшийся аргумент на `.py`: точка входа без расширения (наш собственный CLI `.tausik/tausik` — ровно такая) не должна приводить к чтению файла, переданного ей аргументом; * **связь с `_INTERPRETERS` из `bash_cmd_scan` снята.** Раньше чтение скрипта стояло за проверкой из ДРУГОГО гейта, где нет ни `py`, ни `python2`, — и эти имена были мёртвыми ветвями: константа заявляла покрытие, которого вызывающий не допускал. **Ложный блок В ЭТОМ ЖЕ МЕСТЕ — ЗАКРЫТ для Python, который парсер читает (смена #207).** Четыре смены детектор искал по ТЕКСТУ файла и не отличал исполняемый код от текста: литерал `open(path, 'w')` внутри строки, докстринга или даже КОММЕНТАРИЯ засчитывался как запись, которой команда не делает. Замерено в #203 дважды — гейт отказал измерительному стенду самой задачи и файлу, у которого единственный `open(` стоял в комментарии, — и в #207 по всему репозиторию: из 878 файлов `.py` текстовое чтение называло цель в шести, и все шесть были фантомами; исполняемого литерального `open(..., "w")` с константным путём в дереве нет ни одного. Теперь `python_source_writes` РАЗБИРАЕТ исходник (`ast`) и считает целью только настоящий узел вызова `open` с константным путём и константным режимом записи — комментарий, докстринг и строка не дают цели по построению, у них нет такого узла. Формы, а не примеры: голый `open` и атрибутный `io.open`/`codecs.open`; режим позиционный и `mode=`; путь позиционный и `file=`; raw-строка и неявная склейка литералов; режимы `w`/`a`/`x` и `+` (прежнее чтение `r+` не видело); внутри `def`/`with`/`class`/аргумента. Вычисленный путь (f-строка, имя, конкатенация) и нелитеральный режим — по-прежнему не цель, это объявленный остаток. Встроенный payload `python -c CODE` читается тем же разбором, и ЧИТАЕТСЯ ТОЛЬКО КОД: аргументы после него — `sys.argv` программы, данные, и `python -m pytest -k "open('x','w')"` больше не называет файл, которого команда не пишет. Что осталось от фантома, ограничено и названо: (1) исходник, который парсер не читает (синтаксическая ошибка, NUL, глубина вложенности), деградирует к ПРЕЖНЕМУ текстовому чтению, а не к тишине — такой файл может исполнить интерпретатор с другой грамматикой, и «не смог проверить» не должно читаться как «проверил и чисто»; (2) текст команды вокруг НЕ-python интерпретатора (`ruby -e`, `xargs python -c`) по-прежнему читается как текст, потому что другого чтения для этого субстрата нет, и там сверх-обнаружение объявлено направлением. Стоимость: 1.9 мс на файл по репозиторию, 10 мс на самом большом тестовом модуле, при пределе 256 КБ — десятки миллисекунд однократно. **Обёртка `bash -c` / `sh -c` — ЗАКРЫТА.** `bash -c 'echo x > scripts/foo.py'` раньше не давал НИ ОДНОЙ цели: перенаправление живёт внутри одного закавыченного аргумента, и разбор внутрь payload'а не шёл. То есть Rule 1 и scope-ACL обходились однострочником того же класса, который Решение #162 закрыло для heredoc'ов, — а остаточная граница при этом утверждала, что цена обхода поднята до «нужно намеренно обфусцировать». `bash -c` пишут каждый день, обфускацией он не является; формулировка была завышена. Теперь парсер РЕКУРСИВНО разбирает payload у `bash`/`sh`/`zsh`/`dash`/`ksh`/`ash`/`busybox` при флаге `-c` (включая слитные формы `-lc`, `-ec`), с объявленным пределом вложенности (`_MAX_WRAPPER_DEPTH`), чтобы `bash -c "bash -c '…'"` не уходил в бесконечность. Упоминание в кавычках (`echo 'bash -c "x > y"'`), `bash script.sh` без `-c` и длинная опция `--color` целями не считаются. **Прозрачные префиксы — ЗАКРЫТЫ.** `env bash -c '…'` прятал обёртку ровно так же, как обёртка прятала перенаправление: проверка оболочки смотрела на первый токен, а им был `env`. Найдено адверсариальным ревью САМОГО ФИКСА обёртки (конвенция #276), а не заменённого кода. Теперь перед определением команды снимаются `env`, `sudo`, `doas`, `nohup`, `nice`, `ionice`, `stdbuf`, `timeout`, `command`, `exec` — вместе с их флагами, присваиваниями `VAR=val` и числовым аргументом `timeout`/`nice`. Побочно это закрыло и «писателя за обёрткой» (`sudo tee`), который раньше стоял в списке непойманного: писателя прятал не `tee`, а слово перед ним. Токен, лишь НАЧИНАЮЩИЙСЯ как префикс (`environment.py`, `timeout_test.sh`), префиксом не считается. `TAUSIK_SKIP_HOOKS=1` по-прежнему всё отключает — но этот обход телеметрируется (`emit_supervision_bypass`), молчаливого выключения надзора нет. **Непарсящаяся команда.** Если команда не токенизируется (несбалансированная кавычка), парсер падает в регулярное выражение, которое намеренно ПЕРЕобнаруживает. `bash_write_gate` (QG-0) этот ответ принимает: худший случай — попросят задачу, которая и так нужна. `memory_pretool_block` — нет: его блок обвиняет агента в утечке знаний, а выходов только два (маркер, который был бы неправдой, и постоянное исключение в конфиге ради разовой команды), поэтому ложное срабатывание там дороже пропуска и тренирует обход. Пропуск в этом хуке не молчаливый: пишется `fail_open_unparseable_<канал>` (`…_bash`, `…_powershell` — причина называет тот парсер, который не справился, а не сваливает всё на Bash), а in-tree половину deny-list всё равно пересудят гейт `memory_route` и pre-commit до коммита. ## Покрытие ПО ПРАВИЛАМ, а не по хостам целиком Уведомление, которое читает агент, отвечает на два разных вопроса, и второй появился потому, что первого оказалось мало. «На этом хосте механизма реального времени нет» — правда, но грубее правды: на Kilo каждое закрытие задачи, каждая запись знания и каждое открытие задачи ОТКАЗЫВАЕТСЯ, потому что идут через наши собственные инструменты. Одно предложение про хост целиком не может сказать и то и другое, а сказать только первое — значит соврать пользователю в обратную сторону. Разрез проходит по тому, КТО СОВЕРШАЕТ ДЕЙСТВИЕ: | Что держит правило | Какие правила | Где работает | |---|---|---| | Наша поверхность (`tausik_*` / CLI) | QG-0, QG-2, лимит смены (9.2), маршрутизация памяти | НА ЛЮБОМ хосте: точка принуждения внутри нашего кода, а MCP-сервер есть даже там, где нет ничего другого | | Перехват действий хоста | Rule 1 (нет кода без задачи), Rule 2 (границы scope), Rule 10.12 (секреты) | ТОЛЬКО там, где развёрнут артефакт, несущий именно это правило | Второй столбец выводится из развёрнутого, а не из списка: правило названо артефактом (`hook:task_gate.py`, `plugin:tausik-qg0.js`), и вопрос «покрыто ли оно здесь» есть вопрос «лежит ли этот артефакт в профиле». Поэтому OpenCode, у которого один плагин QG-0, показывается как покрывающий Rule 1 и НЕ покрывающий Rule 2 и Rule 10.12 — что и есть правда, а не «хост покрыт». Замер MCP-границы (смена #232, разбор AST): 146 объявленных инструментов, 44 обработчика меняют состояние, и ни один не обращается к бэкенду напрямую. Отказы служебного слоя доезжают до границы — и это проверяется прогоном самих обработчиков, а не рассуждением: `tests/test_rule_coverage.py` закрывает задачу через `tausik_task_done` без квитанции и требует, чтобы задача осталась открытой. `.tausik/tausik doctor` печатает обе строки: покрытие по хостам и разрез по правилам. ### У перехвата два условия, а скан видит одно «Есть ли артефакт в профиле» — условие, на которое скан ответить может. У хоста, охраняющего свой механизм хуков, есть второе, которое скану недоступно: хост должен быть СОГЛАСЕН исполнять хуки проекта. Codex исполняет проектный `.codex/hooks.json` только после того, как пользователь ему доверил, и на диске два состояния неотличимы байт в байт. Замерено живьём в приёмке 1.9 (смена #251): при сгенерированном, но недоверенном профиле запрещённая `Path('outside.txt').write_text(...)` выполнилась, файл появился — ни один хук не сработал; та же операция под доверенным профилем была отклонена до записи. Поэтому честная формулировка для Rule 1 и Rule 2 на Codex — «hard, как только хуки доверены», `doctor` сообщает только первое условие и говорит об этом, а недоверенный сгенерированный профиль НЕ защищён и защищённым не называется — см. [матрицу принуждения Codex](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/model-providers.md#матрица-принуждения-codex). ### Матрица покрытия каналов Надзор бывает дырявым не только по ПРАВИЛУ, но и по КАНАЛУ, и второе заметить труднее: все тесты зелёные, потому что все тесты спрашивают покрытый канал. На win32 — первичной платформе проекта — агенту доступны ДВА инструмента-оболочки, `Bash` и `PowerShell`, и до задачи `powershell-tool-bypasses-bash-firewall` второй не матчился ни одним PreToolUse-хуком. | Гейт | Write/Edit | Bash | PowerShell | |---|---|---|---| | `task_gate` (Rule 1) | ✅ | — ¹ | — ¹ | | `scope_write_gate` (Rule 2) | ✅ | — ¹ | — ¹ | | `bash_write_gate` (Rule 1 + 2 для оболочек) | н/п | ✅ | ✅ | | `bash_firewall` (опасные команды) | н/п | ✅ | ✅ | | `git_push_gate` (push-тикет) | н/п | ✅ | ✅ | | `memory_pretool_block` (memory-route) | ✅ | ✅ | ✅ | | `secret_scan` (Rule 10.12) | ✅ | ✅ ² | ✅ ² | ¹ Не пробел: оболочечный вектор судит `bash_write_gate`, который импортирует решения `scope_write_gate`, а не копирует их. У события оболочки нет `file_path`, читать этим хукам там нечего. ² **Пробел закрыт** (`secret-scan-covers-no-shell-channel`, Решение #178). `secret_scan` теперь на `SHELL_MATCHER` в обоих bootstrap и пропускает оболочечные инструменты через `shell_channel.is_shell_tool` — на обоих каналах сразу, потому что закрывать на одной оболочке значило бы снова развести каналы. Сканируется ВСЯ командная строка (строгое надмножество извлечения записываемого значения: ловит heredoc-тело и `Set-Content -Value`, плюс `export KEY=…` — литерал секрета в контексте, что Rule 10.12 тоже покрывает), без нового shell-парсинга. Остаток тот же, что у путей: секрет, переданный переменной/env (`--token "$TOKEN"`), не резолвится и не флагается — это корректный способ передачи, а не пробел. ### Конструкции-данные PowerShell: что закрыто, что названо Вопрос «это команда или это текст?» надо задавать КАЖДОЙ конструкции, а не одной. Здесь он был отвечен для кавычек и не задан для here-string — и гейт заблокировал обычный `git commit -m @'...'@` собственного проекта, потому что `>` в прозе сообщения прочитался как редирект. Аудит по одному: | Конструкция | Статус | Как именно | |---|---|---| | `@'…'@`, `@"…"@` (here-string) | **закрыто** | Тело — ОДИН токен. Открывающая кавычка обязана заканчивать строку, терминатор `'@` признаётся только В НАЧАЛЕ строки — как требует сам PowerShell. Ослабить любое из двух условий значит снова выпустить тело как живой shell. | | `'…'`, `"…"` | закрыто с первой версии | Упоминание в кавычках приходит одним токеном и не образует инструкции. | | `{…}` (script block) | **сознательно НЕ данные** | Скобки — разделители, содержимое читается как обычные инструкции. Script block ИСПОЛНЯЕМ: `... \| ForEach-Object { Remove-Item -Recurse C:\ }` обязан остаться видимым. Проза внутри фигурных скобок не живёт. | | `$(…)`, `@(…)` | **остаточная граница** | Уровнем вложенности не считаются; токен с `$` помечается неразрешимым и отбрасывается, а не угадывается. Тот же остаток, что POSIX-гейт объявляет для `$VAR`. | **Диалект следует за ИНСТРУМЕНТОМ, а не проверяется обоими.** Соблазнительная формулировка «читаем обоими токенизаторами и блокируем, если push увидел любой» звучит как усиление и является ослаблением: каждый диалект неверно читает синтаксис другого, поэтому из двух судей выигрывает НЕКОМПЕТЕНТНЫЙ. Реально случилось: POSIX-лексер не знает here-string, ломается на апострофе в слове «PowerShell's», и `git push --force` из прозы commit-сообщения блокирует обычный коммит. Токенизация идёт через `shell_channel` по `tool_name` — той же единственной картой, что и цели записи. Остаточная граница push-гейта, названная явно: push внутри payload'а обёртки (`powershell -Command 'git push'`) для своего диалекта — один закавыченный токен и гейтом тикета НЕ ловится. `bash_firewall` в payload'ы спускается, поэтому force-push этим путём по-прежнему блокируется; обычный push этим путём тикета не требует. Закрывать это через `scan_target` (который склеивает payload интерпретатора сырьём) НЕЛЬЗЯ — он заблокирует честный `python -c "print('git push')"`, то есть обменяет один ложный блок на другой. Симптом для поиска в будущем: детектор, который в одном месте умеет отличать текст от команды, а в соседнем — нет. Цена такой асимметрии не «пропуск», а ЛОЖНЫЙ БЛОК на рутинной операции, и он тренирует обход дороже, чем стоила бы пропущенная команда (конв. #291). **Остаточная граница PowerShell-канала** (сверх общей для оболочек «обфусцированной записи»): цель, пришедшая по КОНВЕЙЕРУ (`Get-ChildItem C:\ | Remove-Item -Recurse`), не является операндом удаляющего командлета и не видна; `-EncodedCommand ` не декодируется; вызовы .NET (`[IO.File]::Delete`) не разбираются. `Remove-Item` файла проекта не считается записью — это ТОЧНЫЙ паритет с Bash, где `rm` тоже не писатель; пробел общий для обоих каналов и закрывается отдельно, а не на одной стороне. Путь скрипта с КАВЫЧКОЙ в токене (`python "a'b\helper.py"`) сохраняет обратные косые и на POSIX остаётся неразрешённым — правило о кавычках, защищающее payload `-c` от замены разделителя, накрывает и его; токен с кавычкой читается как исходник, а не как путь (ревью #208, запись #22). **Смешанный scope (Rule 2, AC3).** Как только ЛЮБАЯ активная задача объявила `scope_paths`, ACL-энфорсмент включается для ВСЕХ co-active задач: параллельная задача без объявленного scope теряет legacy-свободу и должна либо объявить свою область, либо её запись вне union'а объявленных ACL будет заблокирована (и через Write, и через обе оболочки). Это закрывает трюк «держать одну необъявленную задачу активной, чтобы обойти scope». Legacy-свобода остаётся только когда scope не объявил НИКТО. --- ## Русский · /ru/docs/receipts [English](/docs/receipts) | **Русский** # Подписанные чеки верификации После завершения `tausik verify` записывает компактный, **подписанный ed25519** документ о том, что именно было проверено, — *чек* (receipt). Ценность в одной строке: > можно доказать, что задача действительно была проверена, а не просто заявлена > проверенной. Чек привязан к гейтам, которые отработали, и к текущему git `HEAD`. Поскольку он подписан проектным ключом, «зелёный» вердикт **нельзя подделать или переиграть** ручной правкой строки в БД или переносом старого результата на новый коммит. `task done` (QG-2) перед закрытием задачи читает кэшированный verify-прогон, поэтому подпись и есть доказательство за «зелёным». Чеки **переносимы** и **проверяемы офлайн**: выгрузите его в JSON-файл, приложите к PR или артефакту CI — и кто угодно перепроверит подпись без базы данных, без хранилища ключей и без SDK TAUSIK. ## Как появляется чек ```bash tausik key init # один раз на проект — создаёт пару ключей tausik verify --task my-feature # прогоняет гейты и подписывает чек ``` `verify` записывает `verification_run`, а затем выпускает для него чек. Выпуск — best-effort: на проекте **без** ключа он деградирует до «нет ключа», и verify-прогон всё равно успешен — просто без подписанного доказательства. В конце прогона `verify` сообщает результат: ``` Recorded verification_run (task_slug=my-feature, exit=0). Receipt: signed (run #412, key 9f3c1a2b4d5e6f70). ``` Чек удостоверяет только **фактически отработавшие** гейты — пропущенный (skipped) гейт ничего не доказывает, поэтому в чек не попадает, даже если для вердикта он считается «passed». ## Формат конверта (`tausik-signed/v1`) Подписанный чек — это *конверт* (envelope), оборачивающий канонический чек и его подпись. Подпись вычисляется по каноническим байтам **только** объекта `receipt`, а не самого конверта. ```json { "envelope": "tausik-signed/v1", "receipt": { "schema": "tausik-receipt/v4", "task_slug": "my-feature", "git_sha": "0123456789abcdef0123456789abcdef01234567", "scope": "standard", "gates": [ {"name": "pytest", "passed": true, "severity": "block"}, {"name": "ruff", "passed": true, "severity": "warn"} ], "passed": true, "ran_at": "2026-06-13T10:42:07Z", "files_hash": "a1b2c3d4...", "key_fingerprint": "9f3c1a2b4d5e6f70", "declared_scope_status": "under-declared", "undeclared_files": ["CHANGELOG.md", "docs/ru/receipts.md"], "undeclared_count": 2, "files": ["scripts/verify_handle.py", "tests/test_verify_handle.py"], "gate_signature": "9c1f0b7a4e2d6538", "expires_at": "2026-06-13T11:42:07Z", "actor": { "session_id": "239", "model_id": "claude-opus-5", "model_version": null, "role": "developer" } }, "signature": { "algorithm": "ed25519", "key_fingerprint": "9f3c1a2b4d5e6f70", "value": "<128 hex-символов — 64-байтная подпись ed25519>" } } ``` Пояснения к полям: - `git_sha` — sha `HEAD` на момент verify, либо `null` вне git-репозитория. Именно это привязывает «зелёный» к конкретному коммиту и блокирует перенос на новый код. - `gates[]` — сведены к подписываемой тройке `{name, passed, severity}`. Объёмный и недетерминированный вывод гейтов в чек намеренно не входит. - `passed` — общий вердикт: все непропущенные гейты со `severity: "block"` прошли и хотя бы один гейт отработал. - `key_fingerprint` — первые 16 hex от SHA-256 публичного ключа; то же значение показывает `tausik key show`. - `value` — 128 hex-символов (64-байтная подпись ed25519). ### Честность области (схема v2) Чек утверждает, что гейты прошли **на некотором наборе файлов**. Три поля ниже говорят, был ли этот набор полон. Без них чек умалчивал ровно о том, что подрывает его ценность: агент, объявивший `relevant_files=[README.md]` при широкой правке, получал подписанный зелёный чек на `README.md`. - `declared_scope_status` — одно из трёх: - `complete` — объявленный набор покрывает всё, что git видит изменённым с момента старта задачи; - `under-declared` — git видит изменения вне объявленного набора; - `unknown` — сравнение **не удалось провести**: вне git-репозитория, при пустом `relevant_files`, без `task_created_at` или при сбое вызова git. - `undeclared_files` — отсортированный список файлов, которые git считает изменёнными, а `relevant_files` их не называет. Список ограничен 50 записями. - `undeclared_count` — полное число таких файлов, **без** усечения. Если оно больше длины списка, значит список показан частично. Три свойства, важных при чтении чужого чека: 1. **`unknown` — это не `complete`.** Отсутствие измерения никогда не засчитывается как подтверждённая полнота. Чеки схемы `v1`, выпущенные до этого изменения, полей не имеют — их область следует считать **непроверенной**, а не полной. 2. **`under-declared` — не признак обмана.** Расхождение возникает практически при каждом добросовестном закрытии: правки CHANGELOG, доки, сгенерированных констант и бейджей выходят за объявленный набор. Поэтому оно фиксируется, но **не блокирует**. Единственное исключение — недообъявленный файл, попадающий под security-предикат: гейты со scope по объявленному списку не проверили бы его вовсе, поэтому такой прогон падает со статусом `scope-security-mismatch` и просит добавить файл в `relevant_files`. 3. **Собственная бухгалтерия задачи против неё не считается.** Два файла пишет фреймворк, а не агент, и оба вычитаются до сравнения: - `tausik/tasks/.md`, собственный экспорт задачи — его переписывают `started_at`, каждый `task log`, объявленная область и сам чек; - `tausik/stories/<родитель>.md`, проекция родительской истории — `task start` переводит её `status: open -> active`. Только эти два файла ТЕКУЩЕЙ задачи. Чужой экспорт и любая другая история — настоящий продукт той задачи, которая их правит, и остаются в списке. Родительский ЭПИК не вычитается: замерено, что жизненный цикл задачи его не трогает. Если после вычитания не остаётся ничего, причина называет файлы, а не утверждает, что дерево не двигалось. 4. **Чужой закоммиченный результат вычитается только при доказательстве в том же коммите.** Активная задача может пересекаться с накопительным release-коммитом, но временная метка не приписывает ей все пути. Для каждого коммита после `started_at` `verify` читает три яруса доказательств и решает каждый изменённый путь на самом сильном ярусе, где есть хоть один претендент: один — владелец, два и больше — неоднозначность; более слабый ярус не читается, если сильный уже ответил: - *тот же коммит* — экспорт другой задачи, закоммиченный вместе с путём, объявляет его в `relevant_files`, либо экспорт в статусе `active`/`blocked`/`done` и его ACL `scope_paths` покрывает путь; - *родительское дерево* — экспорт в статусе `active`/`blocked`/`done`, закоммиченный раньше, уже объявлял путь в `relevant_files` (задача вправе объявить сначала, а реализацию закоммитить позже); - *проекция* — сам путь ЕСТЬ экспорт другой задачи, её родительская история или обновление только DYNAMIC-блока в `AGENTS.md`/`CLAUDE.md`. Задача никогда не держит рабочую претензию на собственный экспорт, каким бы полем он ни был назван. Незакоммиченные пути, повреждённые экспорты, экспорты в `planning` и пути с двумя претендентами на решающем ярусе остаются в сравнении. ### Что чек говорит о себе (схема v3) Схема `v2` называла `files_hash` — непрозрачный отпечаток, который можно **сравнить**, но нельзя **прочитать**. Такой чек не мог ответить на два вопроса, без которых его нельзя предъявить как доказательство: «над какими файлами?» и «каким набором гейтов?». Схема `v3` добавляет три поля, и именно они превращают чек из печати поверх строки в документ. - `files` — отсортированный список объявленных путей. Именно к нему, а не к аргументу вызова `task done`, применяется security-предикат: иначе закрывающий мог бы объявить безобидную область, предъявив чек, покрывший `auth/`. - `gate_signature` — те же 16 hex, что уходят в `verification_runs.command`. Это **не** повторное вычисление: подпись набора гейтов берётся из одного источника, иначе чек мог бы сойтись сам с собой и разойтись со строкой, которая его породила. - `expires_at` — срок годности **внутри подписи**. Политика, лежащая рядом с документом, редактируется вместе с ним; политика внутри подписи — нет. Чеки схем `v1` и `v2` остаются криптографически валидными, и старый путь закрытия (поиск свежего прогона) принимает их по-прежнему. Но **предъявить** такой чек по хендлу нельзя: он читается как **частичный**, и отказ прямо перечисляет недостающие поля. Неизвестное сообщается как неизвестное — не округляется ни до «полного», ни до «подделки». ## Хендл прогона — предъявление вместо поиска Решение #218, приём SEP-2567 «explicit state handles». **Как было.** `task done` искал в `verification_runs` строку: зелёную, с тем же `files_hash`, с той же подписью гейтов и моложе 600 секунд. Связь между «я проверил» и «я закрываю» была *поиском*, а не предъявлением. Три следствия, и все три — дефекты: содержательный отказ («объявленная область уже изменений по git») доходил до агента как **промах кэша**; окно свежести принадлежало серверу и модели видно не было; два процесса с разным состоянием модулей в памяти считали «свежесть» по-разному. **Как стало.** `tausik verify --task ` печатает хендл: ``` Verify handle: 4821.9f3c1a2b4d5e6f7089abcdef01234567 valid until 2026-06-13T11:42:07Z (single use). Present it: tausik task done my-feature --ac-verified --verify-handle 4821.9f3c... ``` Хендл — это `.`, где nonce — 128 бит из `secrets` (требование SEP-2567). Сервер делает **точечный** lookup по `run_id`, а не поиск по свежести. **Срок годности — 3600 секунд (1 час)**, против 600 у прежнего кэша. Это не ослабление, а следствие того, что часы перестали быть основным критерием: при погашении `files_hash` пересчитывается по **живым** файлам, а подпись гейтов — по **живому** конфигу. Дерево, которое сдвинулось, ловится тем, что действительно изменилось, а не таймером. Часы остались лишь границей того, сколько непогашенный хендл может висеть. Переопределяется ключом конфига `verify_handle_ttl_seconds`. Срок назван в описании инструмента `tausik_verify`, в выводе CLI и в поле `expires_at` самого чека — по прямому требованию SEP-2567: «A policy only in server documentation is not visible to the model». ### Проверка при предъявлении — fail-closed Каждый пункт отказывает, ни один не проходит молча: | Условие | Отказ говорит | |---|---| | хендл не той формы | ожидался `.<32-hex>` | | прогона нет в этой БД | хендл выпущен другим проектом | | nonce не совпал (сверка constant-time) | хендлы одноразовы и заменяются каждым verify | | прогон красный | красный прогон не даёт права закрыть задачу | | прогон верифицировал другую задачу | хендл действителен для задачи, под которую выпущен | | прогон помечен `noncacheable\|` | записан для аудита, а не как сертификат | | хендл уже погашен | одноразовость (replay, SEP-2322) | | срок истёк / срок нечитаем | нечитаемый срок **не** трактуется как «без срока» | | у проекта нет пригодного публичного ключа | путь через хендл ЗАКРЫТ; это **именованный режим**, а не провалившаяся проверка — либо `tausik key init`, либо закрывать без `--verify-handle` | | чека нет / битый JSON / плохая подпись | предъявлять нечего | | чек подписан на другую задачу, чем строка прогона | подменённый чек | | `ran_at` чека не совпадает со строкой прогона | подменённый чек | | чек схемы v1/v2 | перечисляются недостающие поля v3 | | `files` покрывают security-путь | такие области перепроверяются при каждом закрытии | | `files_hash` по живым файлам разошёлся | **содержательный** отказ, а не промах кэша | | `files_hash` чека разошёлся со строкой прогона | документ и запись описывают разные наборы файлов | | подпись гейтов разошлась со строкой или с живым конфигом | чек удостоверяет уже не тот набор гейтов | | git видит изменённым security-файл, которого чек НЕ называет | гейты со scope по списку чека этот файл не смотрели вовсе | **И `verify` никогда не выдаёт того, что эта таблица отвергнет.** Хендл чеканится только тогда, когда чек, за который он стоит, ПОДПИСАН (GitLab #15). Две разные строки заменяют готовую к копированию команду `--verify-handle`, которую `task done` обязан был отвергнуть: в проекте без ключа — «Verify handle: none — no project key, so no signed receipt (tausik key init enables them)»; при ключе, которым не удалось подписать, — «Verify handle: none — the receipt could not be signed (see .tausik/tausik.log, tausik key show)». Обе заканчиваются рабочей командой закрытия — `task done --ac-verified`, поиск по свежести. CLI и `tausik_verify` рисуют это одним рендером. Одно расхождение НЕ блокирует и это намеренно: если git видит изменения вне области чека, но ни одно из них не security-чувствительно, хендл засчитывается, а расхождение попадает в текст вердикта (решение #138 — оно срабатывает почти при каждом добросовестном закрытии: CHANGELOG, доки, сгенерированные константы). Расхождение, которого никто не видит, равно неизмеренному, поэтому оно проговаривается вслух. Погашение атомарно: `UPDATE ... WHERE handle_redeemed_at IS NULL` с проверкой `rowcount`, поэтому два одновременных закрытия не могут оба увидеть непогашенный хендл. **Проверяется на гейте, ТРАТИТСЯ при закрытии.** Хендл списывается внутри той же транзакции, что пишет `status='done'`, а не в момент проверки. Разница видна, когда закрытие проходит Verify-First и падает на ПОЗДНЕЙШЕЙ проверке: раньше хендл уже был потрачен, и полутораминутный прогон verify сгорал ради задачи, которая не закрылась. Ничего при этом не удостоверялось, значит и безопасности трата не покупала. Одноразовость нужна для того, чтобы один зелёный не закрыл две задачи, — а это свойство ЗАКРЫТИЯ, не прохождения гейта. Откат транзакции возвращает хендл в непогашенное состояние вместе со всем остальным. **Совместимость.** `task done` **без** `--verify-handle` работает как раньше — через поиск свежего прогона. Тихое ужесточение недопустимо: хендл меняет способ **предъявления** зелёного, а не то, какие зелёные засчитываются. **Чего хендл не даёт.** Это не авторизация. Приватный seed лежит в рабочем дереве (см. «Что подпись НЕ доказывает» ниже), поэтому агент способен выпустить подпись, которую ключ примет. Задача хендла — сделать связь «проверил → закрываю» явной и проверяемой, а replay — счётным; не удержать решительного агента. Именно поэтому всё, что проверял прежний поиск, здесь **пересчитывается заново по живому состоянию**, а не берётся на веру из предъявленного документа (SEP-2322: «servers MUST always validate that state, as the client is an untrusted intermediary»). Аудит непогашенных хендлов: ```sql SELECT id, task_slug, handle_expires_at FROM verification_runs WHERE handle_nonce IS NOT NULL AND handle_redeemed_at IS NULL; ``` Чек **каноничен** (в духе JCS / RFC 8785): ключи отсортированы на каждом уровне, без пробелов, только ASCII, числа с плавающей точкой отвергаются. Один и тот же логический чек всегда сериализуется в одни и те же байты, поэтому подпись проверяется одинаково на любой машине и платформе. ## Управление ключами ```bash tausik key init # создать проектную пару ed25519 (не перезапишет существующую) tausik key show # показать публичный ключ и отпечаток (никогда — seed) ``` Ключи лежат в `.tausik/keys/`: | Файл | Содержимое | Можно делиться? | |---|---|---| | `project.key` | приватный 32-байтный seed (`ed25519:<64 hex>`) | **Нет** — не покидает машину; `.tausik/` в gitignore | | `project.pub` | публичный ключ (`ed25519:<64 hex>`) | Да — раздавайте по доверенному каналу для проверки | Ротация — `tausik key init --force`. Учтите: **существующие подписи перестанут проверяться** против нового ключа. ## Работа с чеками ### `tausik receipt show` Показать последний сохранённый конверт для задачи (или конкретного прогона) и перепроверить его подпись против проектного ключа. ```bash tausik receipt show --task my-feature # либо: --run 412 tausik receipt show --task my-feature --json # сырой JSON конверта ``` Коды выхода: `0` — валидна, `1` — подпись **невалидна** (изменены данные или подпись), `2` — не найдено / нет ключа. ### `tausik receipt export` Сформировать самодостаточный переносимый артефакт — конверт плюс встроенный публичный ключ — для PR или внешнего аудита. Экспорт откажется работать, если сохранённая подпись не проверяется против текущего ключа (испорченная строка или сменённый ключ). ```bash tausik receipt export --task my-feature # пишет в .tausik/receipts/ tausik receipt export --task my-feature --out receipt.json tausik receipt export --task my-feature --stdout # вывести, а не записывать ``` Экспорт оборачивает конверт в артефакт `tausik-receipt-export/v1`, встраивающий публичный ключ, — его можно проверить где угодно: ```json { "export": "tausik-receipt-export/v1", "envelope": { "...tausik-signed/v1, без изменений..." }, "public_key": "ed25519:<64 hex>", "key_fingerprint": "9f3c1a2b4d5e6f70" } ``` ### `tausik receipt verify ` Офлайн-проверка целостности экспортированного артефакта — **без базы данных, без хранилища ключей, без SDK**. По умолчанию используется публичный ключ, встроенный в файл. Чтобы не доверять этому ключу, передайте свой по доверенному каналу: ```bash tausik receipt verify receipt.json tausik receipt verify receipt.json --pub ed25519:7c2f...e0 ``` Коды выхода: `0` — валидна, `1` — настоящий артефакт с плохой подписью, `2` — файл не является корректным артефактом экспорта. ## Модель доверия Подпись доказывает **целостность** — чек не изменялся после подписания. Сама по себе она **не** доказывает **происхождение**: встроенный отпечаток доверия ровно столько, сколько файл, в котором он едет. Чтобы привязать происхождение, сверьте `key_fingerprint` с доверенным каналом — выводом `tausik key show`, зафиксированной переменной CI или описанием PR — и никогда не доверяйте отпечатку, встроенному в тот же артефакт, который вы проверяете. ### Что подпись НЕ доказывает — граница ключа Формулировка здесь юридически значима с учётом EU AI Act (обязательства по логированию для high-risk-систем действуют с августа 2026), поэтому важна точность. Приватный seed лежит в `.tausik/keys/project.key` **внутри рабочего дерева** — того самого, которое читает агент, чью работу этот ключ подписывает (`mode 0600` — best-effort на POSIX и no-op на Windows). Отсюда: - Валидная подпись — это **tamper-evidence против ВНЕШНИХ правок** `tausik.db`: никто не переписал «зелёный» вердикт и не перенёс старый результат на новый коммит *без доступа к ключу*. То же верно для подписи `events_anchor` над цепочкой событий. - Это **НЕ attestation против агента**. Агент (или что угодно) с доступом на чтение рабочего дерева может прочитать seed и выпустить подпись, которую ключ примет. Чек доказывает «это произвёл кто-то с доступом к файловой системе», а не «независимая сторона свидетельствует, что агент вёл себя честно». Чтобы получить attestation, который агент не может подделать, ключ подписи обязан лежать **вне зоны записи (и чтения) агента** — managed-путь, OS-keychain или отдельный процесс-подписант. Этот вынос осознанно отложен (см. запись `tausik decide` по `l26-signing-key-boundary`): это самостоятельный кросс-платформенный дизайн key-custody, а немедленный выигрыш по целостности — честно назвать эту границу, а не подразумевать attestation, который текущее хранилище дать не может. ## Офлайн / проверка без SDK по HTTP Для агентов и CI, которым недоступны CLI или MCP, `tausik serve` отдаёт то же подписание и проверку через stateless HTTP-эндпоинт — отправьте результаты гейтов и получите тот же чек `tausik-signed/v1`. Эндпоинты, клиент на stdlib и сниппет для GitHub Actions — в [no-sdk-verify.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/no-sdk-verify.md). ## Смотрите также - [cli.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/cli.md) — полный справочник CLI (`key`, `verify`, `receipt`). - [mcp.md](/ru/docs/mcp) — эквивалентные MCP-инструменты. - [no-sdk-verify.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/no-sdk-verify.md) — HTTP-эндпоинт верификации (`tausik serve`). - [senar.md](/ru/docs/senar) — принцип verify-first из SENAR, стоящий за QG-2. ## Кто делал работу (`actor`, v4) Чек подписывается ключом **проекта** и отвечает, какие гейты прошли на каком состоянии кода. До v4 он не отвечал, КТО их прогнал, — а весь контроль качества стоит на разделении обязанностей: внешний ревьюер на другой модели, а с приходом P8 ещё и «автор теста не есть исполнитель». Всё это было объявлено и ничем не предъявлено. Замер, из-за которого поле появилось: 1686 чеков, ни одного поля о деятеле; `claimed_by` пуст у всех 1574 задач; задач с известными стартовой и закрывающей моделью — 29, и модели **не различаются ни в одной**. То есть на пути verify разделения не просто не засвидетельствовано — его там нет. `actor` есть **содержимое** подписанного чека, а не второй подписант. Второй ключ означал бы управление ключами агентов и реестр идентичностей; ни того, ни другого не заводится, ключи остаются локальными. Поле входит в канонические байты, поэтому подменить деятеля, не сломав подпись, нельзя. **Сравнение двух чеков даёт ТРИ исхода, а не два:** `same`, `different` и `unknown`. Чек без поля `actor` — это не чек другого деятеля, и проверка вида `!= same` засчитала бы каждый из 1686 старых чеков как разделённый. Требовать надо `different` явно. **Граница.** Чек записывает деятеля **прогона проверки**. Он не говорит, кто писал тест, а кто код: это предмет P8. Предъявлять это поле как доказательство такого утверждения нельзя. Чеки v1–v3 остаются валидными: поле добавляется, а не требуется. `actor` намеренно не входит в `V3_REQUIRED_FIELDS` — иначе 1686 закрытий разом перестали бы читаться. --- ## Русский · /ru/docs/context-economy # Экономия контекста в 1.11 [English](/docs/context-economy) | **Русский** TAUSIK разделяет короткий неизменный контракт и контекст конкретной задачи. Новые проекты получают `context_tier: minimal`; существующие файлы правил не перезаписываются без явной регенерации или ручной правки владельца. Замер в этом репозитории 01.10.2026: | Повторяемый источник | Было | Стало | Изменение | |---|---:|---:|---:| | Статическая часть корневого `AGENTS.md` | 17 078 Б | 4 602 Б | -73,1% | | Контекст Claude SessionStart | 8 670 Б | 1 059 Б | -87,8% | | Генерируемые правила (`standard` → измеренный `minimal`) | 15 533 Б | около 6 000 Б | около -61% | Пакет задачи загружается один раз при начале работы. Пакет текущей задачи 1.11 занимает 6 007 Б при бюджете 8 192 Б. В нём есть цель, AC, пути, активные решения, нерешённые риски и команды проверки. Обязательные поля не обрезаются: переполнение возвращается явно. Опциональная память добавляется целыми строками, а пропущенный диапазон называется в ответе. Навык задачи запрашивает пакет сразу в успешном результате старта и убирает один зависимый ход модели: ```bash .tausik/tausik task start --package ``` Для MCP используется `tausik_task_start(slug=..., package=true)`. Раньше успешный путь состоял из `task_start`, затем `task_show(package)`: два ответа модели и два вызова инструмента. Теперь это один ответ и один вызов. QG-0 по-прежнему выполняется до активации. Если проекция контекста падает уже после успешной активации, envelope явно возвращает `started=true, ok=false` и команду точечного восстановления; частичный результат не выдаётся за успех. Для восстановления или последующего обновления контекста используйте `tausik_task_show` с `mode=package`; CLI: ```bash .tausik/tausik task show --package --max-bytes 8192 ``` Отпечаток меняется при изменении задачи, путей, риска, плана или активных решений. Заменённые решения исключаются. Общий путь Codex, Claude и GLM покрыт негативными тестами на пропуск памяти, переполнение обязательных полей и superseded-решения. Нативные записи Codex `turn_context` в этой среде не содержат схем инструментов или явного сигнала `tools_deferred`. Поэтому отложенная загрузка схем имеет статус **unknown**; экономия от неё не заявляется. Проект контролирует поверхность, которую объявляет его MCP-сервер. 02.10.2026 свежий stdio-процесс замерен до и после включения существующего проектного флага `mcp.compact_tool_list`. В обоих замерах использован одинаковый ответ `tools/list` из 147 инструментов в minified UTF-8 JSON; размер исходников и устаревший уже запущенный MCP не учитывались. | Источник в eager-контексте проекта | До | После | Изменение | |---|---:|---:|---:| | `AGENTS.md` | 7 518 Б | 7 518 Б | 0 Б | | Канонический каталог skills (15 имён и описаний) | 1 249 Б | 1 249 Б | 0 Б | | Имена MCP | 3 974 Б | 3 974 Б | 0 Б | | Описания MCP | 17 722 Б | 11 897 Б | -5 825 Б | | Схемы MCP | 32 134 Б | 17 145 Б | -14 989 Б | | Структура MCP JSON | 736 Б | 736 Б | 0 Б | | **Всего eager** | **63 333 Б** | **42 519 Б** | **-32,9%** | Строки не пересекаются: фрагменты полей MCP и структура JSON складываются в сериализованный список, `AGENTS.md` и каталог skills учтены по одному разу. 89 315 Б тел skills доступны по требованию и показаны отдельно, поэтому не входят в eager-итог. Имена и короткие описания неядерных инструментов остаются видимы; полные описания и схемы добираются по точному имени через `tausik_tool_schema(name=...)`. Операции сессии, задачи, проверки, памяти, поиска, doctor и восстановления сохраняют полные eager-определения. Этот детерминированный замер сервера не меняет статус unknown для нативного учёта схем Codex. Один вызов следует из общего навыка Codex, Claude и GLM. Экономия живой квоты и повторов GLM остаётся незамеренной до release acceptance run; число сэкономленных вызовов не пересчитывается в токены или лимит подписки. Сценарии повторов и границы замера записаны в [исследовании compound workflow 1.11](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/research/compound-workflow-111.md). --- ## Русский · /ru/docs/architecture [English](/docs/architecture) | **Русский** # Архитектура TAUSIK **Где это стоит.** Поле называет эту дисциплину *harness engineering*: харнесс — это цикл агента, интерфейс инструментов, управление контекстом и механизмы контроля. TAUSIK — не харнесс: цикл и инструменты принадлежат Claude Code, Cursor, Codex и другим. Это слой проверки и контроля поверх них, и его отличие в том, что без доказательства ничто не считается сделанным. ## Три слоя: CLI → Сервис → Хранилище Три слоя с чёткими границами. Сервисный слой содержит бизнес-логику, хранилище — только CRUD и SQL. CLI и MCP — два равноправных входа. ``` Инженер (свободный текст) ↓ ИИ-агент (Claude Code / Cursor) ↓ ┌─────────────────────────┐ │ Навыки (SKILL.md) │ ← инструкции для агента └─────────────────────────┘ ↓ ↓ ┌─────────┐ ┌─────────┐ │ MCP │ │ CLI │ ← два входа │ (tools) │ │ (bash) │ └────┬────┘ └────┬────┘ └──────┬───────┘ ↓ ┌─────────────────────────┐ │ Сервисный слой │ ← бизнес-логика, QG-0, QG-2 │ project_service.py │ │ + service_task.py │ │ + service_knowledge.py │ └─────────────────────────┘ ↓ ┌─────────────────────────┐ │ Слой хранилища │ ← SQLite CRUD, FTS5, метрики │ project_backend.py │ │ + backend_queries.py │ │ + backend_graph.py │ │ + backend_schema.py │ │ + backend_migrations.py │ └─────────────────────────┘ ↓ ┌─────────────────────────┐ │ SQLite (WAL mode) │ ← .tausik/tausik.db │ схема v67 │ └─────────────────────────┘ ``` ## Ключевые модули ### Скрипты (бизнес-логика) В TAUSIK 1.11 пока сохраняется плоская поставка: 563 файла в `scripts/`, из них 502 — в корне. Это историческая совместимость, а не цель 2.0. Измеренные границы, baseline миграций и порядок перехода описаны в **[пакетной архитектуре TAUSIK 2.0](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/package-layout-2.0.md)**. Модули в `scripts/`, каждый ≤500 строк (гейт `filesize`; поднято с 400 как промежуточная мера решением #190 — более тесный лимит деформировал архитектуру, а не улучшал её). Число строк — не единственный контроль размера: гейт `class_surface` отдельно ограничивает составную публичную поверхность класса после наследования, которую пофайловый лимит видеть структурно не может. Хайлайты: | Файл | Назначение | |------|------------| | `project.py` | Точка входа CLI, диспетчеризация | | `project_parser.py` | Дерево команд argparse | | `project_cli.py` / `_extra.py` / `_ops.py` | CLI-обработчики (статус, задачи, сессии, память, шлюзы, навыки, FTS, метрики, поиск, события, исследования, аудит, run) | | `project_cli_doctor.py` / `_role.py` / `_stack.py` / `_verify.py` | CLI-обработчики (doctor, roles, stacks, verify) | | `project_service.py` + миксины `service_*.py` | Бизнес-логика: задачи, знания, навыки, шлюзы, каскады, роли, верификация | | `service_verification.py` | Scoped pytest gate + verify cache (10 min TTL) | | `service_roles.py` | Гибридное хранение ролей (DB-метаданные + harness/roles/*.md) | | `service_stack_ops.py` | Stack scaffold, lint, diff, reset | | `project_backend.py` + `backend_*.py` | SQLite + FTS5 backend (WAL mode, schema v67) | | `backend_session_metrics.py` | Gap-based active-time computation | | `backend_tier_metrics.py` | call_budget vs call_actual tier-метрики | | `backend_migrations.py` / `_legacy.py` / `_v*.py` | Миграции схемы до v67; 34 version-модуля остаются для совместимости обновлений 1.11 | | `project_config.py` + `default_gates.py` | Загрузчик конфигурации, настройка шлюзов, автовключение | | `gate_runner.py` + `gate_stack_dispatch.py` + `gate_test_resolver.py` | Scoped pytest mapping + dispatch | | `skill_manager.py` + `skill_repos.py` | Установка/удаление навыков из репозиториев | | `knowledge_db.py` + `knowledge_write.py` + `knowledge_read.py` | Общее локальное хранилище `~/.tausik-knowledge` (`--global` у `decide` / `memory add`; входит в `memory search`) | | `publication_boundary.py` + `knowledge_export.py` | Единственное место, где содержимое общего хранилища покидает машину (`knowledge export --redacted`) | | `cq_client.py` | Cross-project queue клиент | | `doc_extract.py` | markitdown интеграция | | `docs_lint.py` | Warning-only stale-version линтер | | `plan_parser.py` | Парсер markdown-планов для `/run` | | `model_routing.py` | Helper выбора модели | | `ide_utils.py` | Определение IDE, пути, реестр | | `tausik_utils.py` + `tausik_version.py` + `project_types.py` | Хелперы, версия, типы | | `gen_doc_constants.py` | Точка входа доковых проверок: `--check`, `--write`, перегенерация `constants.json` | | `doc_drift_common.py` | Общие таблицы регулярных выражений, цели сканов, текстовые помощники | | `doc_drift_scanners.py` | Шесть сканов дрейфа: версии, счётчики MCP, закрытые перечни, числа тестов и кода | | `doc_drift_tables.py` | Числовые ячейки колонок таблиц markdown и реестр их предметов | | `doc_drift_fixes.py` | Автопочинщик, который запускает `--write` | | `code_counts.py` | Считает состояние репозитория: хуки, стеки, роли, агенты ревью, скиллы | | `mcp_tool_counts.py` | Считает поверхность MCP, которую объявляет каждый сервер | | `audit_orphan_files.py` / `audit_stale_docs.py` / `audit_unused_python.py` / `audit_pytest_dedupe.py` | Static audit reports (review-only, v1.5) | | `project_cli_hygiene.py` | `tausik hygiene archive` / `unarchive` (мягкий архив старых done-задач и единственный путь обратно) | | `hooks/check_docs.py` | Pre-commit / CI wrapper для drift-проверки doc-constants (v1.5) | ### Начальная настройка (генерация) | Файл | Строк | Назначение | |------|-------|------------| | `bootstrap.py` | ~320 | Оркестрация: vendor sync, copy, generate | | `bootstrap_vendor.py` | ~280 | Скачивание внешних навыков из GitHub (tarball) | | `bootstrap_copy.py` | ~180 | Копирование навыков, скриптов, MCP в `.claude/` | | `bootstrap_config.py` | ~70 | Конфигурация, стек-детекция | | `bootstrap_generate.py` | ~300 | Генерация settings.json, CLAUDE.md, каталога навыков | | `analyzer.py` | ~260 | Детекция расширяющих скиллов и обход дерева | ### MCP-сервер | Файл | Назначение | |------|------------| | `harness/claude/mcp/project/server.py` | JSON-RPC stdio-сервер | | `harness/claude/mcp/project/tools.py` | core tool definitions | | `harness/claude/mcp/project/tools_extra.py` | расширенные tool definitions (skills, gates, doctor, verify, roles, stacks) | | `harness/claude/mcp/project/handlers.py` | Только диспетчеризация: счётчик вызовов, `handle_tool`, слияние доменных таблиц | | `harness/claude/mcp/project/handlers_<домен>.py` | Обработчики по доменам: `task`, `session`, `status`, `knowledge`, `hierarchy`, `stack`, `role`, `verification`, `cq`, `skill`, `spec`, `adapt`. Каждый модуль экспортирует `_HANDLERS`, `handlers.py` сливает их в `_DISPATCH` | | `harness/claude/mcp/project/handlers_render.py` | Общий рендер списков (`render_list`) — пустой результат обязан читаться как «ничего нет», а не как пустая строка | Полный MCP-surface: **147 project-инструментов** (опциональный `codebase-rag` добавляет ещё 7; не в основном счёте). **ЦЕНА ЭТОЙ ПОВЕРХНОСТИ ПЛАТИТСЯ НА КАЖДОМ ХОДУ, И ОНА РАЗНАЯ ПО ХОСТАМ.** Протокол MCP пересылает имя И схему каждого инструмента на каждом ходу, если клиент не откладывает схемы. Замер смены #229: сериализованные определения project-сервера весят 56 108 байт (порядка 14 000 токенов), одни имена — 3 201 байт (около 800 токенов), разница в 17.5 раза. Claude Code схемы откладывает и платит имена — это НАБЛЮДАЛОСЬ; хост без отложенной загрузки платит всё — это следует из протокола и здесь не наблюдалось. Числа выше даны для чтения, а не как источник истины: источник — храповик `mcp_surface` в `tausik/gates.json`, который `tests/test_mcp_surface_ratchet.py` пересчитывает на каждом прогоне и краснит при росте. Так сделано потому, что предыдущая редакция этого числа сгнила ровно здесь: задача записала 117 инструментов и 44 501 байт в сессии #178, а к #229 стало 145 и 56 108 — рост на 24% и 26%, которого никто не заметил, пока число жило в тексте. ### Контекстные заголовки чанков (codebase-rag) Чанк, вырезанный из файла, перестаёт нести то, о чём файл был, и запрос, сформулированный в терминах документа, до такого пассажа не дотягивается. Поэтому каждый индексируемый чанк несёт короткий заголовок, который строит `harness/claude/mcp/codebase-rag/rag_context.py`: слова пути, символ, который чанк определяет, — или тот, внутри которого он находится, если это чанк-продолжение, — и строку-сводку самого файла. Это contextual retrieval с изъятой из него моделью. Опубликованная техника просит LLM написать по предложению контекста на чанк; здесь заголовок берётся из метаданных, которые у индексатора уже есть, поэтому один и тот же вход даёт одни и те же байты, а индексация остаётся воспроизводимой и офлайновой. Заголовок живёт в собственной индексируемой колонке (`rag_chunks.context_prefix`), а не в содержимом чанка: слово, присутствующее только в заголовке, находится — и в выдаче поиска не появляется. Индекс, собранный до v1.8, дорастает до новой раскладки при первом открытии: колонка добавляется, а FTS-таблица перестраивается из чанков, которые и есть источник истины. **Как это измерено, и чем измерить снова.** Заголовки не «выглядят полезными» — их выигрыш посчитан воспроизводимым прибором `scripts/rag_retrieval_bench.py` (`python scripts/rag_retrieval_bench.py [--limit N] [--json]`). Он выводит запросы МЕХАНИЧЕСКИ из корпуса и выбирает их детерминированно, поэтому набор нельзя подогнать под лестный результат, а два прогона по одному дереву задают одни и те же вопросы. Меряет recall@K по КОНКРЕТНОМУ ЧАНКУ и держит два набора сразу: `context` — случай, ради которого заголовок заведён, и `control` — запросы из слов, уже лежащих в теле чанка, где заголовок помогать не должен и особенно не должен вредить. Результат при n=115: recall@3 в наборе context 0.4870 → 0.8348, в control 0.5739 → 0.6435; регрессии нет ни на одном K. ### Поддержка разных сред разработки Навыки, роли, стеки — общие для всех сред. MCP-серверы тоже: `harness/claude/mcp/` — единственное каноническое дерево, и `copy_mcp` отдаёт его каждой среде, у которой нет своего (сегодня — всем). Отдельная копия под IDE была бы зеркалом, обречённым разъехаться: такое лежало в `harness/cursor/` и удалено в v1.7.0. ``` harness/ ├── skills/ # 13 core auto-deployed; внешние ставятся по одному ├── roles/ # 7 ролей (architect, developer, devops, qa, researcher, tech-writer, ui-ux) ├── stacks/ # Руководства по стекам ├── overrides/ # Переопределения для конкретных сред (claude/, cursor/, qwen/) ├── claude/mcp/ # MCP-серверы (project, codebase-rag) — канон для ВСЕХ сред └── opencode/plugins/ # Плагин дисциплины QG-0 для OpenCode (tool.execute.before) ``` #### Среда (IDE) × Модель — две ортогональные оси TAUSIK разделяет *где* он работает и *какая модель* отвечает: | Ось | Что задаёт | Цель `bootstrap --ide` | Определение активной модели | |-----|------------|------------------------|-----------------------------| | **claude** | Claude Code (VSCode/CLI) | `.claude/` + `.mcp.json` | JSONL-транскрипт (поле `model`) | | **cursor** | Cursor | `.cursor/` + `.cursor/mcp.json` | — | | **qwen** | Qwen Code | `.qwen/settings.json` | — | | **kilo** | Kilo Code (аддон + CLI) | `.kilo/kilo.jsonc` **и** `.kilocode/mcp.json` | env `KILO_MODEL` / конфиг `.kilo` | | **opencode** | OpenCode (SST) | `opencode.json` + `.opencode/plugins/` | — | **Ось модели — это данные, а не код**: `scripts/model_profiles.py` отображает семейства вендоров (`claude`, `glm`/z.ai) × ранги способностей → конкретные id моделей; переопределяется в `.tausik/config.json`, ключ `model_profiles.families`. Матрица маршрутизации выдаёт абстрактный ранг, активное семейство резолвит его в реальную модель — поэтому сессия на z.ai GLM уезжает к GLM-моделям без единой правки кода. См. [Kilo + z.ai](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/kilo-zai.md). ## БД: Избранные таблицы (Schema v67) | Таблица | Назначение | |---------|------------| | `meta` | Метаданные (schema_version) | | `epics` | Эпики | | `stories` | Стори (→ epic) | | `tasks` | Задачи (→ story, scope, defect_of, plan, AC) | | `sessions` | Сессии (start, end, summary, handoff) | | `memory` | Память проекта (pattern, gotcha, convention, context, dead_end) | | `decisions` | Архитектурные решения | | `events` | Аудит-лог (gate_bypass, status_changed, claimed) | | `explorations` | Исследования (time-boxed) | | `memory_edges` | Графовые связи между записями памяти и решениями | | `fts_tasks` | FTS5 полнотекстовый индекс по задачам | | `fts_memory` | FTS5 индекс по памяти | | `fts_decisions` | FTS5 индекс по решениям | | `task_logs` | Структурированные логи задач (phase, message) | | `fts_task_logs` | FTS5 индекс по логам задач | | `roles` | Реестр ролей (гибрид: метаданные + harness/roles/{slug}.md) | | `session_activity` | Per-tool-call таймстемпы для gap-based active time | | `verification_runs` | Verify cache: file_hash + timestamp для QG-2 reuse (10 min TTL) | ## Шлюзы качества ``` gate_registry.py → GATE_REGISTRY: одно объявление на встроенный гейт → GateSpec(name, phase, default_config, impl) → phase: scoped | post_scope default_gates.py → DEFAULT_GATES = универсальные (из реестра) ∪ stack-scoped (из stack_registry) ∪ post-scope (из реестра) gate_runner.py → run_gates(trigger, files) [только фаза scoped] → диспетч через GATE_REGISTRY[name].impl, имя вне реестра → run_command_gate() gate_post_scope.py → run_post_scope_gates() [фаза post_scope] → verify_first, changelog + по строке в gate_runs service_task.py → _run_quality_gates() (вызывается из task_done) ``` Добавить встроенный гейт — это один `GateSpec`. До `gate-registry-single-source` требовалось четыре правки, а два post-scope гейта жили лишь в одном из четырёх мест: `gates status` их не перечислял, `gates enable/disable` до них не доставал, и они не писали строку в `gate_runs` — то есть НИЧТО не могло доказать, что QG-2-гейт отработал. **Scoped-гейты** — `(gate_config, files) -> (passed, output)`, судят объявленный скоуп задачи. Универсальные (всегда включены): `filesize`, `class_surface`, `tdd_order`, `ruff`, `mypy`, `bandit`, `bootstrap_drift`, `memory_route`, `renar_drift_schema`, `renar_drift_provenance`, `cross_model_parity`. `cross_model_parity` спрашивает одно: не уехала ли возможность к одному хосту молча. Он ЗАПУСКАЕТ настоящие генераторы механизмов в чистое дерево и сравнивает хосты, делящие одну точку расширения: хуки claude против хуков qwen, плагины против плагинов. Сравнения ПОПЕРЁК форм нет — спрашивать, «не потерял ли Claude плагин OpenCode», значит задавать вопрос без смысла. Гейт НЕ требует одинакового поведения: у Cursor точки расширения нет вовсе, и равняться там не на что. Требуется, чтобы различие было НАЗВАНО, с причиной; объявление, которому больше не соответствует ни одно живое различие, отвергается так же громко, как необъявленное различие. Срабатывает только на правках слоя хостов (`bootstrap/`, `scripts/hooks/`, `harness/opencode/`) — гейт, спрашивающий про кроссмодельность на каждой задаче, становится налогом, а налог выключают. `bootstrap_drift` проверяет три звена цепи «правка → развёртывание → вступает в силу»: `scripts/` против развёрнутого профиля, `harness/` против его разворота, и развёрнутый профиль против **процесса, который из него работает** (`running_source_drift`: снимок содержимого при старте процесса, сравнение на task-done). Долгоживущий MCP-сервер, под которым перезаписали профиль, исполняет старую копию; гейт отказывает закрытию и называет лекарство — перезапустить сервер или закрыть через CLI, который есть свежий процесс. **Гейт проверяется мутацией, а не тем, что он зелёный.** Зелёный прогон доказывает лишь, что гейт не возразил, — не то, что он возразил бы хоть чему-то. Правило держит `tests/test_gates_catch_their_violation.py`: каждый гейт из `gate_registry.GATE_REGISTRY` стоит ровно в одной из двух таблиц. COVERED — гейт прогоняется через собственный `impl_for` реестра на ОБОИХ концах: настоящее нарушение, собранное под `tmp_path`, обязано вернуть failed, чистый вход, собранный так же, — passed (один конец без другого пуст: гейт, красный на любом входе, красную проверку проходит не хуже верного). EXCUSED — гейт, который из синтетического дерева не завести (привязан к сервису, вердикт внешнего инструмента, severity warn), с причиной и именами красного и зелёного теста в модуле, который его гоняет; имена сверяются с AST того модуля. Список закрыт: новый гейт без строки краснит ленту. Мутация не может остаться в дереве по построению — всё строится под `tmp_path`, и запись за его пределы во время прогона таблицы ловится перехватом `open`, `sqlite3.connect` и `subprocess` (три канала, которыми билдеры пользуются), а не снимком `git status`, который под xdist гонку с соседним воркером проигрывает. `class_surface` — единственное исключение из «судят объявленный скоуп»: он игнорирует список файлов и мерит **весь репозиторий** (~0.65 с). Класс уезжает за лимит через свои *базы*, поэтому пофайловый прогон этого не увидит никогда — та же слепота, из-за которой модуль дорос до 406 строк, никого не заблокировав. Гейт ограничивает составную публичную поверхность класса после наследования, которую пофайловый `filesize` структурно видеть не может: god-объект, собранный из миксинов, держит каждый файл ниже строкового лимита. Они **дополняют** друг друга — «этот класс делает слишком много» и «этот файл слишком длинный, чтобы его читать» суть разные дефекты. Счёт объявлен **нижней границей** (AST, никогда `import`, чтобы гейт мог измерить ветку, которую ещё никто не читал), а известные превышающие классы держит baseline-храповик в `tausik/gates.json`, который может только сокращаться. **Post-scope гейты** — принимают контекст закрытия и правят QG-2-отчёт: `verify_first` (обязателен свежий подписанный зелёный verify) и `changelog` (конвенция #275). `get_gates_for_trigger` их отфильтровывает, поэтому `run_gates` никогда не вызовет их с чужой сигнатурой. Stack-scoped гейты: `pytest`, `tsc`, `eslint`, `js-test`, `go-vet`, `go-test`, `golangci-lint`, `cargo-check`, `cargo-test`, `clippy`, `phpstan`, `phpcs`, `phpunit`, `javac`, `ktlint`, `ansible-lint`, `terraform-validate`, `helm-lint`, `kubeconform`, `hadolint`. ## Адаптация RENAR — advisory-first («лайт») TAUSIK — лёгкий zero-dep фреймворк, поэтому [RENAR](https://renar.tech) (стандарт рассуждения/управления) внедряется **advisory-first**, а не тяжёлой обязательной церемонией. Адаптация поднимается по лестнице с явными условиями входа на каждую ступень: | Ступень | Что | Статус | |---|---|---| | 1. Артефакты | SPEC / ADAPT / conformance в SQLite-субстрате + one-way экспорт `renar/` | done (RENAR-1) | | 2. Advisory-сигналы | QG-0 выдаёт **неблокирующий** нудж, когда high-stakes задача (tier `substantial`/`deep` или `complex`) стартует без связанного SPEC и без ADAPT — `gate_qg0_renar.renar_qg0_advisory`, тоггл `renar.qg0_advisory` (по умолчанию вкл) | done (1.5) | | 3. Хардгейт по доказательству | повысить конкретный advisory до **блокирующего** только когда реальный дефект упрётся в его отсутствие (аудит #91) | 2.0 | | 4. RENAR-2 подписанный/неизменяемый ADAPT | подпись ADAPT (ed25519) → `tz_immutable=true` + delta-ADAPT — **необратимо, только по команде пользователя** | 2.0 | Философия: RENAR усиливает SENAR, делая интерпретацию **видимой** на естественном гейте (QG-0), не блокируя агента — fail-soft на advisory, fail-closed только на доказанных гейтах. Это осознанная политика лёгкой адаптации, а не «недоделанный RENAR». ## Orchestrator-worker (авто-переключение модели через сабагентов) Главная сессия — **координатор** (планирование, AC, ревью). Задачу complexity ≤ medium можно **делегировать** **воркеру-сабагенту** с рекомендованными моделью, reasoning effort и standard speed. Такую поверхность выбора модели дают Claude Code и Codex; TAUSIK хранит **scaffolding/state** делегирования, а сам spawn делает агент хоста. Маршруты Kilo/GLM остаются advisory, пока поверхность этого хоста не подтверждена живой проверкой. | Шаг | Команда / механизм | |---|---| | Делегировать | `tausik task delegate ` — пишет {рекоменд. модель, parent session} в `meta` kv (без миграции). **complex отвергается** (остаётся у координатора). | | Handoff-контракт | `tausik task handoff ` — детерминированный JSON {slug, goal, acceptance_criteria, scope, scope_exclude, model, skills}; trimmed профиль `WORKER_SKILLS` (без plan/explore). Оркестратор передаёт его в Agent tool; воркер возвращает обратно (round-trip identity). | | Распознавание in-session | `task start` делегированной задачи показывает **worker mode** (operating contract) и подавляет orchestrator-only баннер модели. | | Scope hard-gate | воркер ограничен scope — `scope_write_gate` блокирует edits вне `scope_paths`, а делегированная задача **без** scope блокируется до объявления (нет legacy fail-open для воркеров). | | Summary-back | `tausik task summary-back "" [--gates …]` — воркер возвращает структурный результат (в `meta`, виден в `task show`), чтобы координатор взял его **без** транскрипта воркера. | Состояние делегирования — CLI-first (без MCP, чтобы избежать doc-count drift) и целиком в таблице `meta` (`delegation:`, `worker_summary:`). Телеметрия маршрута отдельно хранит `selected`, `applied`, `rejected` и `unavailable`. Сам выбор ещё не означает применение: `applied` записывается, когда воркер входит в worker mode через `task start`. Работающая сессия координатора программно не переключается; QG-0/QG-2, доказательства и закрытие задачи остаются у координатора. ## Hooks (anti-drift, см. [hooks.md](/ru/docs/hooks)) Все hook-файлы в `scripts/hooks/` регистрируются через `bootstrap/bootstrap_generate.py` (Claude Code) и `bootstrap/bootstrap_qwen.py` (Qwen Code). Hook-скрипты non-blocking (exit 0), ошибки в stderr. Общие helper'ы в `scripts/hooks/_common.py`. ## Memory Aggregates `service_knowledge_aggregates.py` содержит чистые функции для re-injection памяти: - `build_memory_block(be, ...)` — компактный markdown (decisions + conventions + dead ends) ≤50 строк, вызывается из `/start`, `/checkpoint`, SessionStart hook - `build_compact_memory_tail(be)` — построчная выжимка, встраиваемая в динамический блок CLAUDE.md - `build_memory_compact(be, last_n)` — агрегация `task_logs`: фазы + топ-слова + топ-файлы Обе выжимки спрашивают граф памяти через `memory_supersedes.live_head`: запись, которую отменило ЖИВОЕ ребро `supersedes`, не печатается, её строка достаётся следующей живой записи, а выжившая запись говорит `(supersedes #N)` на строке, которую занимает в любом случае. Скрывает только это отношение и только пока заменившая запись сама не заархивирована — иначе старая есть лучшее из оставшегося знания. Нечитаемый граф не отменяет ничего: выжимка вырождается в то, что печаталось до фильтра. Аналогично `scripts/model_routing.py` + `plugin_data.py` — чистые модули, импортируемые из CLI/MCP handlers. ## Prompt caching TAUSIK опирается на автоматический prompt caching от Anthropic — это удерживает стоимость агентских прогонов в разумных границах. Сам фреймворк не делает API-вызовов (это делает Claude Code), но *структура* того, что TAUSIK кладёт в каждый ход, определяет: попадёт префикс в кеш или перебиллится заново. Кешируемая поверхность по приоритету: | Поверхность | Где живёт | Почему кешируется хорошо | |---|---|---| | System prompt + схемы инструментов | Инжектится Claude Code'ом из `.claude/mcp/project/tools.py` и `tools_extra.py` | Идентично между ходами в рамках сессии — самый длинный стабильный префикс | | `CLAUDE.md` | Корень проекта | Читается раз за сессию и реинжектится; стабилен пока `tausik_update_claudemd` не перепишет dynamic-блок | | Описания MCP-инструментов | Те же `tools.py` | Любая правка инвалидирует кеш — изменение формулировки переписывает весь префикс | | Skills (`SKILL.md`) | `harness/skills//SKILL.md` | Подгружаются только при активации скилла | **Что инвалидирует кеш в середине сессии.** Любая правка перечисленных файлов между ходами переписывает префикс и заставляет следующий ход платить `cache_creation_input_tokens` вместо `cache_read_input_tokens`. Главный нарушитель — `tausik_update_claudemd`: его прогон в середине сессии переписывает dynamic-state блок (номер сессии, счётчики задач и т.д.), и весь префикс `CLAUDE.md` перекешируется. Зови его на границах сессии (`/start`, `/checkpoint`, `/end`), а не между рядовыми tool-вызовами. **Как проверить, что caching реально работает.** Anthropic возвращает `cache_creation_input_tokens` (префикс только что записан) и `cache_read_input_tokens` (последующий ход попал в кеш) в `usage`-блоке каждого ответа. `scripts/validate_prompt_caching.py` парсит транскрипт Claude Code (JSONL) и выдаёт обе суммы + hit-rate: ```bash python scripts/validate_prompt_caching.py --auto # или python scripts/validate_prompt_caching.py path/to/transcript.jsonl ``` Exit code `0` = caching активен (`cache_read_input_tokens > 0`); `1` = префикс нестабилен (creation > 0, reads = 0); `2` = API вообще не вернул cache-поля. См. [troubleshooting.md](/ru/docs/troubleshooting) секцию «Prompt caching не активен» — типовые причины. ## Тестирование ```bash pytest tests/ -v # все тесты (6096) pytest tests/test_tausik_backend.py # backend CRUD pytest tests/test_tausik_service.py # service logic pytest tests/test_tausik_cli.py # CLI smoke pytest tests/test_gates.py # quality gates + stack auto-enable pytest tests/test_vendor.py # vendor skills + persistence pytest tests/test_graph_memory.py # graph memory edges pytest tests/test_mcp_integration.py # MCP handlers pytest tests/test_senar.py # SENAR compliance pytest tests/test_e2e_workflow.py # E2E workflow ``` См. **[Принципы тестирования](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/testing-principles.md)** — когда добавлять тесты, маппинг scoped pytest, анти-паттерны (в т.ч. копипаста без нового поведения). --- ## Русский · /ru/docs/skills [English](/docs/skills) | **Русский** # Навыки (v1.4) Skill'ы — intent-based инструкции, определяющие поведение агента. Не нужно запоминать имена или синтаксис — пишете, что хотите, и агент подбирает подходящий skill. Slash-префикс (`/plan`, `/ship`) явно вызывает один. После bootstrap идут **13 core skills** из `harness/skills/`. В официальном магазине остаются только `docs`, `excel` и `pdf`. Ставьте нужный навык через `tausik skill install `, чтобы неиспользуемые инструкции не расходовали контекст. **Карта репо-скиллов:** [Экосистема скиллов (one-pager)](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/skill-ecosystem.md). > **Правило экономии с v1.4.** Раньше bootstrap раскрывал весь каталог и тратил около 1 520 токенов на список в system reminder. Теперь по умолчанию раскрываются только core-навыки. `--include-official` оставлен для совместимости с локальным checkout, но экономичный поддерживаемый путь — установка по одному навыку. **Варианты под разные хосты:** у skill может быть каталог **`variants/.md`** — см. [Профили skills и variants](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/skill-profiles.md). ## Core skill'ы (13) Всегда доступны после bootstrap — workflow-примитивы, без которых TAUSIK не работает. ### Workflow | Skill | Когда | |-------|-------| | `/start` | Начать рабочую сессию — загружает handoff, status, memory block | | `/end` | Завершить сессию — сохраняет метрики + handoff | | `/checkpoint` | Сохранить контекст без завершения сессии (когда об этом говорит сигнал чекпоинта) | | `/plan` | Спланировать задачу из свободного описания (interview phase + AC) | | `/task` | Работать над существующей задачей с QG-0/QG-2 enforcement | | `/ship` | Завершить задачу: review + test + gates + commit | | `/commit` | Создать стандартизированный git-коммит | ### Знания | Skill | Когда | |-------|-------| | `/explore` | Time-boxed исследование (default 30 мин) перед коммитом к подходу | | `/interview` | Сократическая Q&A — макс. 3 вопроса для пиннинга требований | | `/reason` | Записать структурированный трейс рассуждений (intent→premise→action→verification) на задаче — см. [Трейс рассуждений](/ru/docs/reasoning-trace) | ### Качество | Skill | Когда | |-------|-------| | `/review` | Code review против 28-point SENAR checklist (5 параллельных агентов, итеративно) | | `/test` | Запуск/написание тестов, отслеживание coverage | | `/debug` | Reproduce → isolate root cause → fix | ## Official / Vendor skills Официальный каталог живёт в отдельном репозитории и не входит в релиз core. В нём намеренно только три нейтральных навыка для документов: `docs`, `excel` и `pdf`. Интеграции должны жить в MCP-серверах или сторонних репозиториях; корпоративным ролям в TAUSIK не место. - Ставьте одну возможность через `tausik skill install ` и активируйте её только при необходимости. - Используйте `skill bundle` только для стороннего репозитория, который его объявляет, и только если проекту нужны все участники. - Не используйте `--include-official` в обычных проектах: он раскрывает все записи локального checkout и увеличивает повторяемый префикс промпта. - Официальный репозиторий не публикует bundle. ## Жизненный цикл ```bash .tausik/tausik skill list # активные + vendored + доступные .tausik/tausik skill repo add # зарегистрировать TAUSIK-совместимый репо .tausik/tausik skill install # clone + copy + pip deps .tausik/tausik skill activate # копирует из harness/skills → .claude/skills .tausik/tausik skill deactivate # убрать из .claude/skills (vendored copy остаётся) .tausik/tausik skill uninstall # удалить полностью ``` Официальный vendor-репо: `https://github.com/Kibertum/tausik-skills`. Custom-репозитории поддерживаются — см. **[Skill Adaptation Guide](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/skill-adaptation.md)**. ### Соответствие спеке (agentskills.io) Формат SKILL.md теперь — кросс-вендорный канон **agentskills.io**. Встроенный гейт (`skill_spec_conformance`) фейлит close/commit, когда изменённый `SKILL.md` нарушает машинно-проверяемые правила: `name` — 1–64 символа `a-z0-9` с одиночными дефисами (без ведущего/замыкающего/сдвоенного дефиса) и **обязан совпадать с именем каталога**; `description` — 1–1024 символа. Гейт инертен, пока среди изменённых файлов нет `SKILL.md`. Локальные scaffold'ы, чей каталог начинается с `_` или `.` (например `_profile-demo`), не являются публикуемыми скиллами и пропускаются. > Спека agentskills.io **не версионирована** и не содержит **ни одного положения > по безопасности** — ничего про доверие, песочницы или права на инструменты. > Этот гейт — проверка гигиены, держащая прогрессивное раскрытие рабочим; никогда > не трактуйте соответствие как сигнал доверия. ### Массовая установка через бандлы `tausik skill install ` ставит по одному навыку. Репозиторий может публиковать необязательные **bundles**; проверьте состав перед массовой установкой — см. **[Skill Bundles](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/skill-bundles.md)**: ```bash .tausik/tausik skill bundle list # посмотреть доступные бандлы .tausik/tausik skill bundle install # поставить всех участников bundle ``` ## Что дальше - **[Workflow](/ru/docs/workflow)** — как skill'ы композятся в рабочий день - **[CLI команды](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/cli.md)** — вызов TAUSIK из терминала напрямую - **[MCP инструменты](/ru/docs/mcp)** — программный surface для агентов - **[Vendor skill'ы](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/vendor-skills.md)** — установка и авторинг skill-пакетов --- ## Русский · /ru/docs/hooks [English](/docs/hooks) | **Русский** # Хуки TAUSIK использует хуки Claude Code для автоматического контроля качества. Хуки перехватывают действия агента **до** и **после** выполнения — это шлюзы, не инструкции. **23 Python-хука + 1 shell `pre-commit`** идут с TAUSIK — всего 24 шлюза (v1.4 добавил `secret_scan.py`, `posttool_usage.py`, `tool_output_truncation_nudge.py`, `task_cost_budget_check.py`; 1.8 добавил `scope_write_gate.py` и `bash_write_gate.py`; 1.9 добавил `read_ledger_gate.py`, выключенный по умолчанию). ## Что такое хуки Хуки — скрипты, запускающиеся автоматически на каждое действие агента. Они решают, можно ли действие выполнять (PreToolUse), что делать после (PostToolUse) или что записать на границах сессии/агента (SessionStart, Stop, UserPromptSubmit). Общие хелперы живут в `scripts/hooks/_common.py` (сам по себе не хук); regex-набор `scripts/hooks/memory_markers.py` — библиотека, импортируемая `memory_posttool_audit.py` и pipeline'ом скраббинга brain. ## PreToolUse — шлюзы перед действием | Хук | Когда | Что делает | |------|-------|-----------| | `task_gate.py` | Перед каждым пишущим инструментом — Write/Edit/MultiEdit/NotebookEdit и MCP-редакторами из `hooks/write_tools.py` (serena, windows-mcp; PR #5) | Блокирует изменения файлов, если нет активной задачи (SENAR Rule 9.1) | | `scope_write_gate.py` | Перед каждым пишущим инструментом — Write/Edit/MultiEdit/NotebookEdit и MCP-редакторами из `hooks/write_tools.py` (serena, windows-mcp; PR #5) | Scope-ACL (SENAR Rule 2, Walko-паттерн): блокирует запись вне объединения объявленных `scope_paths` активных задач. Именно этот вердикт переиспользует `bash_write_gate` для оболочечных записей. Консервативно: хоть одна активная задача без `scope_paths` → доступ открыт (legacy, необъявленное = без ограничений); цель вне корня проекта → открыт; pre-v30 БД или любая ошибка БД → ОТКАЗ, кроме `TAUSIK_HOOK_FAIL_OPEN=1` (умолчание перевёрнуто в 1.9). | | `memory_pretool_block.py` | Перед каждым пишущим инструментом — Write/Edit/MultiEdit/NotebookEdit и MCP-редакторами из `hooks/write_tools.py` (serena, windows-mcp; PR #5) **и каждым shell-инструментом (Bash, PowerShell, `mcp__windows-mcp__PowerShell`)** | Слой 2 memory-route: блокирует запись в любой чужой memory-сток из `scripts/memory_sinks.py` (`~/.claude/**/memory/`, `.cursor/rules/`, `.github/copilot-instructions.md`, `.aider*`, …) и перенаправляет на `memory add`. Оболочки в matcher, потому что heredoc и `Set-Content` пишут ровно то, что запрещает Write. Обход: `confirm: cross-project` в промпте или `gates.memory_route.allow` в конфиге. | | `secret_scan.py` (v1.4) | Перед каждым пишущим инструментом — Write/Edit/MultiEdit/NotebookEdit и MCP-редакторами из `hooks/write_tools.py` (serena, windows-mcp; PR #5) **и каждым shell-инструментом (Bash, PowerShell, `mcp__windows-mcp__PowerShell`)** | Сканирует `tool_input` на типичные секреты (AWS/GitHub/Slack/Stripe/OpenAI/Anthropic токены, JWT, блоки приватного ключа, generic `password`/`api_key`). По умолчанию warning; `TAUSIK_SECRET_SCAN_STRICT=1` — блокировка. (SENAR Rule 10.12). **Оболочечные каналы не покрывает** — ни Bash, ни PowerShell; см. матрицу покрытия в [`enforcement-coverage.md`](/ru/docs/enforcement-coverage). | | `bash_firewall.py` | Перед Bash **и PowerShell** | Блокирует опасные команды (`rm -rf /`, `Remove-Item -Recurse C:\`, DROP TABLE, `Format-Volume`, force push, …). Диалект выбирается по `tool_name`: POSIX-лексер не умеет читать PowerShell, где `\` — обычный символ пути, а не экранирование. | | `bash_write_gate.py` | Перед Bash **и PowerShell** | Применяет к записи через оболочку те же QG-0 (Rule 1) и scope-ACL (Rule 2), что и Write — переиспользуя решения `scope_write_gate`, а не копируя их. Разбирает редиректы, `tee`/`dd`/`sed -i`/`cp`/`mv` и `Set-Content`/`Add-Content`/`Out-File`/`New-Item`/`Tee-Object`. | | `git_push_gate.py` | Перед Bash **и PowerShell** | Блокирует push без свежего, одноразового тикета `.tausik/.push_ticket.json`, привязанного к SHA HEAD. `/ship` и `/commit` запускают `tausik push-ok && git push` после вашего "y" — `push-ok` пишет 60-секундный тикет, хук съедает его на следующем push. Сужающей клаузы `if` больше нет: она была второй копией решения, которое хук принимает сам, и называла только одну оболочку. | ## PostToolUse — реакции после действия | Хук | Когда | Что делает | |------|-------|-----------| | `auto_format.py` | После каждого пишущего инструмента (MultiEdit не был на этом хуке до PR #5) | Авто-форматирование через ruff/prettier/gofmt + лог "Modified: X" в задачу | | `memory_posttool_audit.py` | После каждого пишущего инструмента в auto-memory | Аудитит cross-project leakage (использует regex-библиотеку `memory_markers.py`) и предупреждает | | `task_done_verify.py` | После `mcp__tausik-project__tausik_task_done` или `tausik task done`, запущенного в оболочке | Аудитит AC evidence через 5 правило-base проверок (Ralph-mode-lite). | | `task_call_counter.py` | После любого tool call | Инкрементирует per-task `call_actual` счётчик; warning'ит на 1.5×budget | | `posttool_usage.py` (v1.4) | После любого tool call | Записывает token-usage события в `usage_events` для per-task cost rollup | | `activity_event.py` | После любого tool call | Записывает activity-таймстемпы для **gap-based active-time** метрики (SENAR Rule 9.2) | | `tool_output_truncation_nudge.py` (v1.4) | После Read/Grep/Glob и каждым shell-инструментом (Bash, PowerShell, `mcp__windows-mcp__PowerShell`) | Подсказывает агенту сузить scope, когда вывод превышает порог по строкам (warn-only) | | `rag_grep_context.py` (1.10) | После Grep | Ищет идентификаторы шаблона Grep в RAG-индексе и добавляет 3 лучших куска (путь:строки) в контекст. Только чтение; ничего, если индекса нет или совпадений нет. | | `task_cost_budget_check.py` (v1.4) | После любого tool call | Сравнивает `cost_actual` / `tokens_actual` активной задачи с бюджетом; WARN на 1.5×, BLOCKER на 2× (с throttle) | ## SessionStart | Хук | Когда | Что делает | |------|-------|-----------| | `session_start.py` | На старте сессии | Авто-инжектит status + Memory Block + ребилдит skill-профили — без ручного `/start` | ## UserPromptSubmit | Хук | Когда | Что делает | |------|-------|-----------| | `user_prompt_submit.py` | На пользовательском промпте | Распознаёт coding-intent (EN+RU) → подталкивает, если нет активной задачи; меряет только что прочитанный ответ и сверх `answer_budget_words` или без вердикта первой строкой добавляет одну строку с числами. Подсказка к `search_code` на поисковый интент убрана в 1.10 (решение #390: в парном прогоне с ней 0 вызовов search_code). Игнорирует тела слэш-команд и текст, порождённый хуками | ## Stop | Хук | Когда | Что делает | |------|-------|-----------| | `keyword_detector.py` | На остановке агента | Ловит "I'll implement"/"сейчас напишу" drift-фразы → блокирует stop | | `session_cleanup_check.py` | На остановке агента | Предупреждает об открытом exploration / review-задачах / session timeout | ## SessionEnd | Хук | Когда | Что делает | |------|-------|-----------| | `session_metrics.py` | На завершении сессии | Записывает session metrics (active vs wall, throughput) в БД | ## Сессия на каждом хосте (1.10) Сессия TAUSIK — это сессия хоста: её открывает и закрывает сам хост, а не ритуал агента. Замеры — по бинарю хоста, а не по документации (конвенция #686), смена #266. | Хост | Открытие | Закрытие | Чем обеспечено | Замер | |---|---|---|---|---| | claude | hook | hook | SessionStart / SessionEnd из общего объявления хуков | протокол хуков Claude Code | | qwen | hook | hook | SessionStart / SessionEnd из общего объявления хуков | профиль bootstrap | | codex | hook | hook | SessionStart / SessionEnd из общего объявления хуков | codex 0.153.4: перечень событий содержит SessionStart и SessionEnd | | opencode | plugin | plugin | `session.created` / `session.deleted` в плагине `tausik-qg0.js` | opencode 1.1.42: события плагина в бинаре | | kilo | cli | cli | `tausik session start` / `session end` руками | событий сессии у профиля нет | | cursor | cli | cli | `tausik session start` / `session end` руками | профиль без хуков | Где стоит `cli`, теряется то, что делает закрытие хуком: запись метрик транскрипта и порождённый handoff в момент выхода. `session end` руками делает и то и другое. ## Git pre-commit | Хук | Когда | Что делает | |------|-------|-----------| | `pre-commit` (shell) | Перед `git commit` | Запускает `python -m mypy` против `scripts/` (конфиг из `pyproject.toml`). На exit ≠ 0 — **блокирует commit**. Опционально гонит инкрементальный `codebase-rag` reindex (warn-only, лимит 5с); никогда не блокирует commit из-за RAG. | Это **не** «scoped quality gates» — те запускаются через `tausik verify` (тяжёлый стек: pytest/tsc/cargo/phpstan/…) и развязаны с `git commit` начиная с v1.4 Verify-First Contract. ### Установка (один раз) Путь зависит от того, ГДЕ вы находитесь. В репозитории самого TAUSIK `scripts/` принадлежит харнессу и хук лежит там. В проекте, который ПОДКЛЮЧИЛ TAUSIK, `scripts/` принадлежит проекту и никакого `pre-commit` в нём нет — команды ниже указали бы git на каталог без хука, а git считает отсутствующий хук не ошибкой, а отсутствием: установка молча не сработала бы. ```bash # --- в репозитории TAUSIK --- # Вариант A: скопировать файл cp scripts/hooks/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit # Вариант B (рекомендуется): указать git на in-repo hooks-папку, чтобы обновления подхватывались автоматически git config core.hooksPath scripts/hooks # --- в проекте, подключившем TAUSIK сабмодулем --- git config core.hooksPath .tausik-lib/scripts/hooks # Если сабмодуль не инициализирован (обычный клон без --recurse-submodules), # каталог пуст. Сперва: git submodule update --init --recursive ``` Сам хук ищет файлы движка шире, чем каталог, из которого запущен: развёрнутый профиль IDE, затем `.tausik-lib`, затем дерево проекта. Поэтому гейт `memory_route` находится и в потребительской раскладке, а если не находится — говорит об этом в stderr, а не молчит. > **Windows caveat.** `pre-commit` — bash-скрипт с `timeout(1)` и POSIX-ом `[ -f … ]`. Чистый `cmd.exe` его не запустит. Используй Git Bash, WSL или терминал, в котором есть Bash + `timeout` в `PATH`. Если команда работает только под Windows — замени скрипт на `pre-commit.cmd`-обёртку, которая зовёт `python -m mypy` напрямую и держит тот же контракт по exit code. ### Отключение / bypass - Разово: `git commit --no-verify` (полностью пропускает `core.hooksPath`). - Временно: `git config --unset core.hooksPath`. - На CI без mypy: на CI-раннере не выставляй `core.hooksPath`; тяжёлая верификация всё равно идёт через `tausik verify`. ## Как это работает ``` Вы: "добавь кнопку на главную" Агент хочет редактировать index.html → task_gate.py проверяет: есть ли активная задача? Нет → ЗАБЛОКИРОВАНО → Агент создаёт задачу через /plan, стартует → task_gate.py проверяет снова: задача есть → РАЗРЕШЕНО Агент редактирует index.html → auto_format.py: форматирует prettier'ом → auto_format.py: пишет "Modified: index.html" в задачу → task_call_counter.py: бампит call_actual; warning на 1.5×budget → activity_event.py: штампует activity-таймстемп (active-time) Агент: tausik task done my-button --ac-verified → task_done_verify.py: 5-проверочный AC-аудит ``` ## Коды возврата | Код | Значение | Поведение | |------|---------|----------| | 0 | Успех | Действие разрешено | | 1 | Warning | Действие разрешено; warning записан | | 2 | Block | Действие **отменено**; агент получает причину | ## Что блокирует `bash_firewall` - `rm -rf /` и `rm -rf .` — удаление файловой системы - `DROP TABLE`, `DROP DATABASE`, `TRUNCATE TABLE` — удаление данных - `git reset --hard` — потеря локальных изменений - `git push --force` — перезапись remote-истории - `git clean -fd` — удаление untracked-файлов - `dd if=/dev/zero`, `mkfs.` — форматирование диска - Fork bombs ## Отключение хуков Для тестирования или дебага: установите `TAUSIK_SKIP_HOOKS=1`. В `.claude/settings.json` хуки генерируются автоматически на bootstrap. Чтобы отключить конкретный хук, удалите его из секции `hooks`. Для регенерации файла запустите `python .tausik-lib/bootstrap/bootstrap.py --refresh`. ## См. также - **[Workflow](/ru/docs/workflow)** — как хуки вписываются в рабочий цикл - **[Session Active Time](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/session-active-time.md)** — что питает `activity_event.py` - **[CLI команды](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/cli.md)** — управление задачами из терминала --- ## Русский · /ru/docs/mcp [English](/docs/mcp) | **Русский** # TAUSIK MCP — Справочник инструментов **147 инструмента** для ИИ-агентов (актуальный счёт, проверено `len(TOOLS)`). MCP-surface покрывает всё, что агент делает день за днём. Несколько CLI-only команд намеренно не имеют MCP-аналога — это оператор/maintenance verbs, которым не место в agent-loop: `skill rebuild`, `skill bundle`, `fts optimize`, `db prune`, `audit vendors`/`research`, `config set`/`show`, `push-ok`, `run`, `doc extract`/`constants`, `hud`, `suggest-model`, `hygiene archive --confirm`. Для рабочего набора агента предпочитайте MCP-инструменты shell-вызовам — они атомарны, возвращают структурированные данные и держат контекст чище. > **Опциональный сервер `codebase-rag`** добавляет 7 инструментов (search_code, find_symbol, etc.). Он включается отдельно через bootstrap и НЕ входит в основной счёт 152 — итого с ним 154 инструментов. В проекте живут два MCP-сервера: - `tausik-project` — project-scoped инструменты (147): tasks, sessions, knowledge, stacks, roles, gates, skills, exploration, audit, doctor, verify, usage logging, RENAR substrate (specs + adapts). Опционально доступен `codebase-rag` сервер (документирован в конце). ## Verify-First Contract (v1.5) Тяжёлые quality gates (pytest, tsc, cargo, phpstan, javac, js-test, terraform-validate, helm-lint, kubeconform, hadolint, ansible-lint) живут на отдельном триггере `verify`. MCP workflow: ``` tausik_task_start(slug=…, package=true) # QG-0 + bounded context одним результатом … работа над кодом … tausik_verify(task_slug=…) # тяжёлое: subprocess-гейты → кеш green tausik_task_done(slug=…, ac_verified=True) # лёгкое: lookup в кеше ``` `tausik_task_done` откажется закрывать задачу, если verify-кеш отсутствует или устарел — возвращает структурированный failure с явной remediation. Opt-out для CI: установите `{"task_done": {"auto_verify": true}}` в `.tausik/config.json` — тогда heavy гейты выполнятся внутри `task_done` как в релизах до v1.5. **Терминология:** [Глоссарий verify / QG](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/verify-glossary.md) — *поддерживаемый opt-out*, *обход QG* (для `task_done` недоступен), *обход verify-кеша* и pytest **test shim**. ## Status, Health, Metrics | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_health` | Health check: версия, DB, таблицы | — | | `tausik_self_check` | Свежесть MCP-сервера: время старта, snapshot mtime watched-модулей vs текущие mtime на диске, флаг `drift_detected`, список stale-модулей с `delta_seconds`, число sibling MCP project-серверов. Вызывать из `/start` чтобы поймать предвестники тихих зависаний (gotchas #77/#79/#80). | — | | `tausik_status` | Обзор проекта: задачи, сессия, эпики. `compact: true` → один JSON без изменения текстового режима по умолчанию. | `compact` (опционально) | | `tausik_doctor` | 4-group health (venv + DB + MCP + skills + drift) | — | | `tausik_metrics` | Метрики SENAR или компактный нативный расход при `host=codex\|kilo` | `host?` | | `tausik_usage_event_log` | Ручная запись в `usage_events` (агрегаты сессии не трогает) | `tokens_input`, `tokens_output`, `tokens_total`, `cost_usd` | | `tausik_search` | Полнотекстовый поиск по задачам, памяти, решениям | `query` | ## Задачи | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_task_add` | Создать задачу (опционально в стори) | `slug`, `title` | | `tausik_task_quick` | Быстрое создание с auto-slug | `title` | | `tausik_task_start` | Начать работу; опционально вернуть bounded context тем же результатом | `slug`, опционально `package` | | `tausik_task_done` | Завершить (QG-2: `ac_verified=true`, scoped pytest, verify cache). Возвращает structured JSON: `blocking_failures`, per-gate results, cache status. | `slug` | | `tausik_task_show` | Полная информация или ограниченный контекст с `mode=package` | `slug`; опционально `mode` | | `tausik_task_list` | Список с фильтрами (status enum: `planning,active,blocked,review,done`) | — | | `tausik_task_update` | Обновить поля (title/goal/AC/scope/notes/stack/complexity/role/tier/call_budget) | `slug` | | `tausik_task_plan` | Задать шаги плана | `slug`, `steps[]` | | `tausik_task_step` | Отметить шаг выполненным | `slug`, `step_num` | | `tausik_task_log` | Добавить запись в журнал | `slug`, `message` | | `tausik_task_logs` | Чтение структурированных логов (фильтр по фазе) | `slug` | | `tausik_reason_step` | RENAR шаг рассуждения (intent\|premise\|action\|verification) | `slug`, `kind`, `content` | | `tausik_task_replay` | Хронологический таймлайн задачи (logs + reasoning + events + verification) | `slug` | | `tausik_task_block` | Заблокировать | `slug` | | `tausik_task_unblock` | Разблокировать | `slug` | | `tausik_task_review` | Перевести в review | `slug` | | `tausik_task_delete` | Удалить | `slug` | | `tausik_task_move` | Переместить в другую стори | `slug`, `new_story_slug` | | `tausik_task_next` | Выбрать следующую задачу по score | — | | `tausik_task_claim` | Занять задачу (мульти-агент) | `slug`, `agent_id` | | `tausik_task_unclaim` | Освободить | `slug` | ### `tausik_task_done` параметры - `ac_verified` — **обязательно** для QG-2 - `evidence` — inline AC verification log (заменяет отдельный `task_log` вызов) - `no_knowledge` — подтвердить отсутствие знаний для фиксации (подавляет warning) - `relevant_files[]` — изменённые файлы; драйвят **scoped** pytest gate (basename match → `tests/test_.py`). Empty list при non-empty original → gate skipped (нет ложных срабатываний на full suite). Verify cache (10 min TTL) пропускает re-run при том же `files_hash`. `task_done` **не имеет `--force`** — QG-2 нельзя байпаснуть. У `task_start` `--force` есть для байпаса session capacity, с audit trail. ### `tausik_task_done` structured response `tausik_task_done` возвращает JSON для агентных сценариев: - stage-флаги (`plan_complete`, `ac_verified`, `gates_passed`) - результаты по каждому gate (`gates[]`) - `blocking_failures[]` с `gate`, `files`, `output`, `remediation` - `warnings[]`, `cache_status` и итоговый `ok` До v1.5 был параллельный alias `tausik_task_done_v2` для structured-JSON варианта. **v14b-task-done-rename-drop-v2 объединил оба в один `tausik_task_done` со structured JSON выше** — суффикса `_v2` больше нет. Verify-First Contract соблюдается на всех путях. ## Сессии | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_session_start` | Начать сессию | — | | `tausik_session_end` | Завершить сессию | — | | `tausik_session_extend` | Поднять порог совета по active-time (совет, не ворота) | — | | `tausik_session_current` | Текущая активная сессия | — | | `tausik_session_list` | Список сессий | — | | `tausik_session_handoff` | Сохранить handoff data | `handoff` (object) | | `tausik_session_last_handoff` | Живой handoff или handoff сессии `session_id` | `session_id` (опц.) | | `tausik_session_open` (v1.5) | Compound RPC: host-context policy + session start + status + handoff + active/blocked задачи + self_check в одном envelope. Питает Phase 1 в `/start`. `host_context` показывает повтор того же thread, доступность native usage, advisory/hard pressure и промпт нового окна. Session и self_check спроецированы; большой handoff — сводка до 4096 UTF-8 байт с явными пропусками и вызовом полного чтения. При `_projection.requires_full_handoff=true` сначала прочитайте полный handoff. Ограничение относится к секции handoff, а не ко всему envelope. Полная телеметрия: `tausik_self_check`; полный handoff: `tausik_session_last_handoff`. | — | Лимит сессии — gap-based **active time** (паузится после 10-min idle gap), не wall clock. См. `session-active-time.md`. ## Иерархия (эпики и стори) | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_epic_add` | Создать эпик | `slug`, `title` | | `tausik_epic_list` | Список эпиков; `(stale: N)` — задач создано после последней правки описания (отчёт, не гейт) | — | | `tausik_epic_update` | Изменить title и/или description эпика — замысел группы задач; хотя бы одно поле | `slug` | | `tausik_epic_done` | Завершить эпик | `slug` | | `tausik_epic_delete` | Удалить (cascade: стори + задачи) | `slug` | | `tausik_story_add` | Создать стори в эпике | `epic_slug`, `slug`, `title` | | `tausik_story_list` | Список стори; `(stale: N)` как у эпиков | — | | `tausik_story_update` | Изменить title и/или description стори; хотя бы одно поле | `slug` | | `tausik_story_done` | Завершить стори | `slug` | | `tausik_story_delete` | Удалить (cascade: задачи) | `slug` | | `tausik_roadmap` | Дерево: epic → story → task | — | ## RENAR substrate — SPEC + ADAPT (17 инструментов) RENAR-подложка: формальные требования (**SPEC**) и интерпретация ТЗ (**ADAPT**, §7) с forward-интерпретациями, backward-findings и подписью архитектора (§7.5). Используется QG-0 для substantial/deep задач и `tausik renar export`/`conformance`. См. также `tausik_reason_step` (RENAR trace) в разделе «Задачи». ### SPEC (8) | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_spec_add` | Создать SPEC-артефакт. `type` — закрытый список (ARCH/API/DATA/INT/PROC/UI/AI/SEC/OPS/TEST/DOC/UC; UC требует тела с `role` и ссылкой на утверждение в каждом шаге); новый тип = поправка к стандарту, не free-text | `slug`, `type`, `title`, `version` | | `tausik_spec_list` | Список SPEC, опц. фильтр по типу (JSON) | — | | `tausik_spec_show` | SPEC + связанные задачи (JSON) | `slug` | | `tausik_spec_update` | Патч изменяемых полей (title/version/content_ref/status); `type`+`slug` иммутабельны | `slug` | | `tausik_spec_delete` | Удалить SPEC (cascade: task-линки) | `slug` | | `tausik_spec_link` | Связать задачу со SPEC (оба должны существовать — нет тихих dangling-линков) | `task_slug`, `spec_slug` | | `tausik_spec_unlink` | Снять связь задача↔SPEC | `task_slug`, `spec_slug` | | `tausik_spec_search` | FTS5 по slug/title/content_ref (JSON) | `query` | ### ADAPT (9) | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_adapt_create` | Создать заголовок ADAPT (§7); `tz_ref` (исходное ТЗ) обязателен; старт в `draft` | `slug`, `title`, `tz_ref` | | `tausik_adapt_interpret` | Forward-интерпретация (§7.4.3); tz_ref/citation/interpretation/scope_in/scope_out обязательны | `tz_ref`, `citation`, `interpretation`, `scope_in`, `scope_out` (+ adapt) | | `tausik_adapt_finding` | Backward-finding; `category` — закрытый список 7 (contradiction/gap/hidden-assumption/feasibility/regulatory/terminology/scope) | `adapt_slug`, `category`, `description` | | `tausik_adapt_sign` | Подпись architect (§7.5): подписывает тело ed25519-ключом проекта ⇒ `approved` (§13.3.3 стр.77 — статус и подпись разные факты). `role=client` ОТКЛОНЯЕТСЯ — ADR-011 отозвал подпись клиента под ADAPT; то, что одобряет клиент, теперь живёт в ACTZ | `adapt_slug`, `role`, `signed_by` | | `tausik_adapt_show` | ADAPT + forward-интерпретации, findings, подписи, линки (JSON) | `slug` | | `tausik_adapt_list` | Список ADAPT, опц. фильтр по статусу (закрытый перечень §7.8.1: draft/review/asked/answered/approved/frozen/superseded) | — | | `tausik_adapt_delta` | Delta-ADAPT, замещающий родителя (§7.6); родитель → `superseded`, поздний линк к нему = FATAL dangling (§7.6.4) | `parent_slug`, `new_slug`, `title`, `tz_ref` | | `tausik_adapt_link` | Связать ADAPT с задачей/SPEC; target должен существовать; линк к superseded ADAPT = FATAL (§7.6.4) | `adapt_slug`, `target_type`, `target_slug` | | `tausik_adapt_search` | FTS5 по slug/title/tz_ref (JSON) | `query` | ### ACTZ (15) Контрактный протокол уточнения ТЗ (§5A, ADR-011) — в отличие от ADAPT обращён к клиенту: то, что клиент утверждает, живёт здесь. Жизненный цикл `draft` → `sent` → `signed` → `superseded`, вычисляется по покрытию ролей подписи. В проекте один ed25519-ключ, не по сторонам: `architect` подписывает по-настоящему; `client` записывает только `signed_by`+`signed_at`, без имитации независимой подписи. `final_tz`/`orphans` (§5A.4) — read-only проекции над подписанными пунктами ниже: производный эталон приёмки, а не третья копия текста. | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_actz_create` | Создать заголовок ACTZ (§5A); `tz_ref` обязателен; старт в `draft` | `slug`, `title`, `tz_ref` | | `tausik_actz_point` | Добавить нумерованный пункт; только пока `draft` (заморожено после первой подписи). `tz_ref` называет пункт исходного ТЗ (или предыдущего пункта ACTZ), который уточняется | `actz_slug`, `point_no`, `tz_ref`, `text` | | `tausik_actz_sign` | Записать подпись (§5.5.3): `architect` подписывает тело ed25519-ключом проекта; `client` — только `signed_by`+`signed_at`. Первая подпись ⇒ `sent`; обе роли ⇒ `signed` | `actz_slug`, `role`, `signed_by` | | `tausik_actz_verify` | Проверить подпись architect (ed25519) против текущего тела | `slug` | | `tausik_actz_show` | ACTZ + пункты, подписи, линки (JSON) | `slug` | | `tausik_actz_list` | Список ACTZ, опц. фильтр по статусу (draft/sent/signed/superseded) | — | | `tausik_actz_delta` | Delta-ACTZ, замещающий родителя; родитель → `superseded`, поздний линк к нему отклоняется | `parent_slug`, `new_slug`, `title`, `tz_ref`, `supersession_rationale` | | `tausik_actz_link` | Связать ACTZ с задачей/SPEC; target должен существовать; линк к superseded ACTZ отклоняется | `actz_slug`, `target_type`, `target_slug` | | `tausik_actz_unlink` | Снять связь ACTZ↔задача/SPEC | `actz_slug`, `target_type`, `target_slug` | | `tausik_actz_delete` | Удалить ACTZ (cascade: пункты/подписи/линки/decided-in) | `slug` | | `tausik_actz_search` | FTS5 по slug/title/tz_ref (JSON) | `query` | | `tausik_actz_decided_in` | Записать: backward-finding ADAPT решён в пункте ПОДПИСАННОГО ACTZ, с provenance (`linked_by`); отклоняет неподписанную цель | `adapt_slug`, `finding_id`, `actz_slug`, `actz_point_no`, `linked_by` | | `tausik_actz_decided_in_remove` | Удалить decided-in ребро | `adapt_slug`, `finding_id`, `actz_slug`, `actz_point_no` | | `tausik_actz_final_tz` | Производный эталон приёмки (§5A.4): по каждому пункту ТЗ — последний обеими сторонами подписанный пункт, с указанием, что он перекрыл. `as_of` (ISO-8601) — на прошлый момент | — | | `tausik_actz_orphans` | Подписанные пункты, которые не отражены ни в одном ADAPT — обязательство вне требований (§5A.4, fatal), находится запросом | — | ### AT (9) Приёмочные тесты (§8A, ADR-012) — единственная проверка, которую трассируемость (TC → SR → ADAPT → ТЗ) структурно дать не может: неверная интерпретация проходит все TC. Эти инструменты ЗАПИСЫВАЮТ результат процедуры изолированной генерации (docs/en/at-generation-procedure.md) — ни один не генерирует ничего сам. `tz_text` (дословная цитата контракта) и `generated_by` обязательны при создании; `check_freshness` сравнивает с ЖИВЫМ `final_tz_snapshot` по `tz_ref` каждой записи и называет, что изменилось (§8A.2 — перегенерировать перед каждым испытанием). См. также warn-гейт `at_freshness`. `record_result`/`diagnose`/`release_readiness` реализуют матрицу маршрутизации §8A.4/§10.4.3. У TAUSIK нет TC как самостоятельной сущности (открытая отдельная задача) — `diagnose` сам pytest/verification_runs не читает; `tc_outcome` передаёт вызывающий явно. `release_readiness` не требует TC вовсе: готовность наступает только когда исход каждого AT зелёный и свежий — отдельно от QG-4, который необязателен и меряет бизнес-результат. | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_at_create` | Записать AT — результат процедуры изолированной генерации, не генератор | `slug`, `tz_ref`, `tz_text`, `scenario`, `source_as_of`, `generated_by` | | `tausik_at_show` | Показать запись AT (JSON) | `slug` | | `tausik_at_list` | Список AT, опц. фильтр по tz_ref (JSON) | — | | `tausik_at_delete` | Удалить запись AT | `slug` | | `tausik_at_search` | FTS5 по slug/tz_ref/tz_text/scenario (JSON) | `query` | | `tausik_at_check_freshness` | Какие AT устарели против текущего итогового ТЗ (§8A.2); без slug — проверка всех | — | | `tausik_at_record_result` | Записать один наблюдённый исход испытания (только добавление — повтор — новая строка) | `slug`, `outcome` | | `tausik_at_diagnose` | Маршрутизировать последний исход AT против переданного вызывающим `tc_outcome` (§8A.4/§10.4.3) | `slug`, `tc_outcome` | | `tausik_at_release_readiness` | Релизный гейт §8A.4: готовность только когда каждый AT зелёный и свежий | — | ## Знания | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_memory_add` | Сохранить в проектную память; `provenance` observed / inferred (по умолчанию) / told | `type`, `title`, `content` | | `tausik_memory_search` | Полнотекстовый поиск | `query` | | `tausik_memory_list` | Список (фильтр по типу) | — | | `tausik_memory_show` | Показать запись по ID | `id` | | `tausik_memory_delete` | Удалить запись | `id` | | `tausik_memory_block` | Compact markdown: recent decisions + conventions + dead ends (для /start re-injection) | — | | `tausik_memory_compact` | Aggregate recent task_logs (phases + top words + top files) | — | | `tausik_memory_archive` (v1.5) | Soft-archive памяти старше duration (90d / 12w / 2m / 1y). Dry-run если нет `confirm: true`. | `before` (string), `confirm` (bool, опционально) | | `tausik_memory_dedupe` (v1.5) | Список near-duplicate memory-пар выше порога similarity (read-only). | `threshold` (float, опц.), `limit` (int, опц.) | | `tausik_decide` | Записать решение; необязательные `rejected` (список «вариант :: почему») и `supersedes` (id, причина в `rationale`) | `decision` | | `tausik_decisions_list` | Список решений | — | Типы памяти: `pattern`, `gotcha`, `convention`, `context`, `dead_end`. ## Графовая память | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_memory_link` | Создать связь между узлами | `source_type`, `source_id`, `target_type`, `target_id`, `relation` | | `tausik_memory_unlink` | Soft-invalidate связь (никогда не удаляет) | `edge_id` | | `tausik_memory_related` | Найти связанные узлы (1–3 hops) | `node_type`, `node_id` | | `tausik_memory_graph` | Список связей с фильтрами | — | Типы связей: `supersedes`, `caused_by`, `relates_to`, `contradicts`. ## Тупики и исследования | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_dead_end` | Документировать неудачный подход | `approach`, `reason` | | `tausik_explore_start` | Начать time-boxed исследование | `title` | | `tausik_explore_end` | Завершить исследование | — | | `tausik_explore_current` | Текущее исследование | — | ## Шлюзы качества и верификация | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_gates_status` | Статус всех gates (по стеку) | — | | `tausik_gates_enable` | Включить gate | `name` | | `tausik_gates_disable` | Выключить gate | `name` | | `tausik_verify` | v1.5 Verify-First: запустить heavy gates (pytest, tsc, …) и закешировать green в `verification_runs`. После этого `tausik_task_done` использует кеш и закрывается мгновенно. | `task_slug` | Доступные gates: `pytest`, `ruff`, `mypy`, `bandit`, `tsc`, `eslint`, `go-vet`, `golangci-lint`, `cargo-check`, `clippy`, `phpstan`, `phpcs`, `javac`, `ktlint`, `filesize`, `class_surface`, `tdd_order`. Stack-scoped gates авто-включаются по обнаруженному стеку; universal gates (`filesize`, `class_surface`, `tdd_order`) применяются ко всем стекам. `class_surface` работает по всему репозиторию, а не по скоупу: он ограничивает составную публичную поверхность класса после наследования, которую пофайловый строковый лимит видеть не может. `tdd_order` отключён по умолчанию. Включите через `tausik_gates_enable name=tdd_order`. ## Стеки | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_stack_list` | Список встроенных + custom стеков | — | | `tausik_stack_show` | Резолвленный стек: gates per language + override info | `stack` | | `tausik_stack_export` | Экспорт резолвленной декларации как JSON | `stack` | | `tausik_stack_diff` | Diff между built-in и user override | `stack` | | `tausik_stack_reset` | Удалить user override в `.tausik/stacks//` | `stack` | | `tausik_stack_lint` | Валидировать user-override `stack.json` | — | | `tausik_stack_scaffold` | Создать `.tausik/stacks//{stack.json,guide.md}` skeleton | `name` | DEFAULT_STACKS: 25 записей (python, fastapi, django, flask, react, next, vue, nuxt, svelte, typescript, javascript, go, rust, java, kotlin, swift, flutter, laravel, php, blade, ansible, terraform, helm, kubernetes, docker). Custom-стеки через `.tausik/config.json` → `custom_stacks`. ## Роли | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_role_list` | Список ролей | — | | `tausik_role_show` | Профиль роли | `slug` | | `tausik_role_create` | Создать роль (опционально `extends` базовый профиль) | `slug`, `title` | | `tausik_role_update` | Обновить метаданные | `slug` | | `tausik_role_delete` | Удалить роль | `slug` | | `tausik_role_seed` | Bootstrap из `harness/roles/*.md` + использования в задачах | — | Хранение ролей гибридное: SQLite-метаданные + markdown-профиль `harness/roles/{role}.md`. Роли в задачах остаются свободным текстом. ## Периодический аудит (SENAR Rule 9.5) | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_audit_check` | Просрочен ли аудит | — | | `tausik_audit_mark` | Отметить аудит выполненным | — | ## Навыки | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_skill_list` | Список навыков: активные, vendored, доступные | — | | `tausik_skill_install` | Установить из репо (clone + copy + deps) | `name` | | `tausik_skill_uninstall` | Удалить полностью | `name` | | `tausik_skill_activate` | Активировать установленный | `name` | | `tausik_skill_deactivate` | Деактивировать (файлы остаются) | `name` | | `tausik_skill_repo_add` | Добавить TAUSIK-совместимый репо (сторонний URL — `force`) | `url`, опционально `force` | | `tausik_skill_repo_remove` | Удалить репо | `name` | | `tausik_skill_repo_list` | Список репозиториев и доступных skills | — | | `tausik_skill_catalog` | Discovery: список skills из настроенных/клонированных repos (name, category, description) | опц. `repo`, опц. `as_json` | ## Cross-Project Queue (CQ) | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_cq_publish` | Опубликовать cross-project event | `payload` | | `tausik_cq_query` | Query cross-project queue | — | ## Мульти-агент и обслуживание | Инструмент | Описание | Обязательные параметры | |---|---|---| | `tausik_team` | Задачи сгруппированные по агентам | — | | `tausik_events` | Audit log (events) | — | | `tausik_update_claudemd` | Обновить динамическую секцию в CLAUDE.md | — | | `tausik_fts_optimize` | Оптимизировать FTS5 индексы | — | ## Codebase RAG (отдельный опциональный MCP-сервер) | Инструмент | Описание | Обязательные параметры | |---|---|---| | `search_code` | Поиск кода через RAG-индекс | `query` | | `search_knowledge` | Поиск в knowledge base | `query` | | `reindex` | Реиндексация кодбазы | `mode` (incremental/full), `max_seconds` (soft limit, только для full). v1.5: stderr-прогресс каждые 100 файлов; truncated=true при таймауте. | | `rag_status` | Статус RAG-индекса | — | | `archive_done` | Архивировать выполненные задачи | — | | `cache_web_result` | Кешировать web-результат | `query`, `content` | | `search_web_cache` | Поиск кешированных web-результатов | `query` | Эти не входят в основной счёт 147 — принадлежат опциональному `codebase-rag` серверу. ## Область tool-поверхности (`mcp.scope_tools_exposure`) Выключено по умолчанию. Когда вы ставите `mcp.scope_tools_exposure: true` в `config.json`, сервер сужает рекламируемый tool-list до того, что разрешено **активной задаче**: объединение объявленных задачей `scope_tools` (ACL SENAR Rule 2) и всегда-безопасного ядра — целиком семейства `tausik_task_*` и `tausik_session_*` плюс `tausik_status`, `tausik_verify`, `tausik_doctor`, `tausik_self_check`, `tausik_update_claudemd` и тулы `*_search`. Прочие тулы скрыты из списка, что сокращает и токен-стоимость определений тулов, и поверхность атаки. Это **fail-open**: все тулы экспонируются, когда нет активной задачи, ни одна активная задача не объявила непустой `scope_tools`, или область не резолвится — включение никогда не оставляет без тулов проект, который не объявлял область. Сокрытие — это UX/токен-оптимизация, а **не** барьер безопасности: скрытый тул, вызванный напрямую, всё равно проходит существующий scope-энфорсмент, а write-гейт не тронут. Область пересчитывается каждый раз, когда хост запрашивает `list_tools` — то есть при каждом подключении к серверу с уже активной задачей. **Замер стоимости.** Полная авторская поверхность — 147 тула ~ 62 КБ определений (~15.9k оценочных токенов; `tests/test_mcp_tool_token_cost.py` фиксирует это и держит храповиком). При отложенной загрузке Claude Code (`ENABLE_TOOL_SEARCH`) эагерно грузятся только имена, а каждое описание обрезается до 2 КБ — храповой тест держит каждое описание TAUSIK под этим лимитом, чтобы ничего не срезалось молча, и проверяет, что имена остаются уникальными и искомыми, чтобы диспетчер по имени по-прежнему находил нужный тул. ## Запуск Tausik MCP-сервера Bootstrap-шаг генерирует IDE-specific MCP-launchers под `harness//mcp/`. Claude Code читает `.claude/settings.json` (auto-generated). Для регенерации запустите `python .tausik-lib/bootstrap/bootstrap.py --refresh`. ## Экономия контекста: схемы по требованию `mcp.compact_tool_list` в `.tausik/config.json` включается bootstrap, если проект не задал явное `false`. Без порождённого конфига сервер безопасно отдаёт полный список. Включённый флаг оставляет полные схемы только у ядра из 23 инструментов, а остальным 124 — имя и первые 60 символов описания. Схема любого из них добирается одним вызовом `tausik_tool_schema(name=...)`; `query` ищет по подстроке, пустой аргумент отдаёт перечень имён. На свежей stdio-поверхности из 147 инструментов 02.10.2026 minified UTF-8 список уменьшился с 54 566 до 33 752 байт (-38,1%). Выбор «60 символов» тоже основан на замере: только имя ещё меньше, но лишает модель признака, по которому выбирают, чью схему просить, и может потратить экономию на лишний ход. Полная раскладка проектного контекста приведена в [context-economy.md](/ru/docs/context-economy). Чтобы отказаться от сокращения, задайте `false` после проверки числа ходов на задачу. При отсутствующем, повреждённом или нечитаемом конфиге сервер всегда возвращает полный список. --- ## Русский · /ru/docs/configuration [English](/docs/configuration) | **Русский** # Справочник конфигурации TAUSIK Все настройки в `.tausik/config.json` в корне проекта. Что не указано — берёт документированный дефолт. Override — добавь ключ в top-level объект (НЕ под `bootstrap` — там bootstrap управляет). См. также: [environment.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/environment.md) — env-переменные, [permissions.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/permissions.md) — режимы permissions. ## Сигналы сессии (SENAR Rule 9.2 — с 1.10 совет, а не ворота) | Ключ | Дефолт | Назначение | |---|---|---| | `session_max_minutes` | `180` | Порог совета по АКТИВНЫМ минутам сессии: выше него `task start`, `status` и Stop-хук печатают совет; ничто не отказывает. `0` выключает. | | `session_idle_threshold_minutes` | `10` | Промежуток (в минутах), после которого пауза считается AFK и исключается из active-time. | | `session_warn_threshold_minutes` | `150` | Порог напоминания stop-хука в `session_cleanup_check.py`. Должен быть < `session_max_minutes`. | | `session_capacity_calls` | `200` | Бюджет tool-calls на сессию. `call_budget` задачи выше остатка — строка совета в `task start`, не отказ. | | `answer_budget_words` | `200` | Бюджет слов итогового ответа агента. На следующем запросе ответ сверх него (или без вердикта в первой строке) получает одну строку-совет с числами; ничего не блокируется. Замер — `tausik metrics answers`. | | `checkpoint_calls` | `40` | Вызовы с последнего handoff до совета о чекпоинте (выводится из журнала). `0` выключает. | | `journal_freshness_calls` | `40` | Вызовы с последней записи журнала активной задачи до совета. `0` выключает. | | `audit_every_closures` | `17` | Закрытия задач с последней отметки аудита до просрочки аудита SENAR 9.5. | ## Бюджет контекста host thread `host_context_budget` ограничивает реальный thread хоста. В каждом пороге три положительных целых поля: `responses`, накопленные `input_tokens` и `context_tokens` последнего ответа. Пересечение любого advisory-порога один раз сохраняет checkpoint и возвращает готовый промпт нового окна. Пересечение любого hard-порога задаёт `allow_continue=false`; старый thread без привязанной TAUSIK-сессии не открывается. Дефолты: advisory `{24, 2500000, 140000}` и hard `{32, 4000000, 180000}` в указанном порядке полей. Каждый advisory-порог должен быть ниже hard. Codex отдаёт native identity и usage из локального журнала. Хост без таких сигналов возвращает `level=unavailable` с причиной и fail-open использует прежние советы по времени и вызовам. Завершение и повторное открытие TAUSIK-сессии в том же host thread не делает `fresh_context=true`. ## Языки поиска по коду (RAG) | Ключ | По умолчанию | Смысл | |------|--------------|-------| | `rag.extra_extensions` | `{}` | Дополнительные типы файлов для индекса, `{".unity": "unity-scene"}`. Встроенное расширение не переопределяется. Godot (`.gd`, `.gdshader`, `.tscn`, `.tres`, `.godot`) встроен с 1.10. | | `rag.boundaries` | `{}` | Где резать язык на чанки, `{"unity-scene": "^--- !u!"}` (многострочная регулярка). Кривая запись пропускается и называется в `rag_status` в `language_config.problems`. | ## Проверка обновления | Ключ | По умолчанию | Смысл | |------|--------------|-------| | `updates.check` | `true` | Не чаще раза в сутки SessionStart отсоединённо запускает `tausik update-check`: один анонимный GET к `releases/latest` репозитория `Kibertum/tausik-core` на GitHub, без данных о проекте. `status` называет вышедшую версию. `false` выключает; состояние — в `tausik doctor`. | ## Кэш верификации (SENAR Rule 5) | Ключ | Дефолт | Назначение | |---|---|---| | `verify_cache_ttl_seconds` | `600` | Сколько секунд зелёный verify-run переиспользуется до перезапуска gates. Уменьши для security-critical проектов. | ## Настройки из быстрого старта Все ключи лежат в корне `.tausik/config.json`, если путь не говорит иного. | Ключ | По умолчанию | Назначение | |---|---|---| | `context_tier` | `"minimal"` | Размер порождаемого файла правил: `"minimal"` (по умолчанию, с лимитом), `"standard"`, `"full"` (расширенные указатели). Неизвестное значение bootstrap отклоняет; `tausik doctor` сверяет дрифт с сохранённым уровнем. | | `output_mode` | `"off"` | `"caveman"` добавляет сжатую прозу. В любом режиме действуют единые правила: называть исполнителя и действие, предпочитать активный залог, давать одно действие на предложение и один термин на понятие. Это многоязычная рекомендация, а не заявление соответствия ASD-STE100. Код, вывод инструментов и SENAR-доказательства остаются целиком. Ошибочное значение откатывается к `"off"`. | | `model_profile` | не задан | Слаг профиля хоста (`a-z`, цифры, дефисы), например `claude`, `codex`. Bootstrap записывает его, когда задана `TAUSIK_MODEL_PROFILE`; неверное значение прерывает bootstrap. `python bootstrap/bootstrap.py --refresh` обновляет только конфигурацию. | | `mcp.compact_tool_list` | `true` после bootstrap | Объявляет полные определения ядра workflow и короткие определения остальных инструментов; полная отложенная схема доступна через `tausik_tool_schema`. Явное `false` сохраняется. Отсутствующий или повреждённый конфиг безопасно возвращает полный список. | | `task_done.auto_verify` | `false` | `true` заставляет `task done` прогонять гейты внутри вызова и закрывать задачу **без подписанной квитанции**. Это не обход гейтов — они работают, — но квитанция теряется, поэтому проверка доверия конфигурации считает ключ ослаблением, и доверенный тир возвращает его назад (`scripts/config_trust.py`). Лучше `task done --ac-verified --verify`: один вызов и квитанция. | Для запросов на объяснение prompt-hook добавляет условную матрицу формата. Краткая проза остаётся fallback; таблица нужна для повторяющихся точных сравнений, Mermaid — для связанной структуры, HTML — только для явной или названной интерактивной потребности. Видео роутер не выбирает. Обычные запросы не платят за эту добавку. **Файлы правил сохраняются после первой записи.** `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `QWEN.md` и файл правил OpenCode bootstrap не перезаписывает, если они уже есть, поэтому поздняя смена `context_tier` или `output_mode` до них не доходит. Bootstrap об этом говорит; удалите порождённый файл и запустите bootstrap заново или поправьте руками. **MCP-закрытие возвращает структуру.** `tausik_task_done` отвечает полями `stage`, `gate_results` и `blocking_failures`, чтобы агент чинил упавшее, не разбирая прозу. ## Стеки | Ключ | Дефолт | Назначение | |---|---|---| | `custom_stacks` | `[]` | Список custom stack slug'ов, принимаемых `task add --stack X`. | ## Gates | Ключ | Дефолт | Назначение | |---|---|---| | `gates` | `{}` | Per-gate overrides: `{ "pytest": { "enabled": true }, "filesize": { "max_lines": 600 } }`. Мержится поверх `default_gates.py`. | ## Файлы инструкций агента (блок DYNAMIC) `tausik update-claudemd` переписывает блок между `` и `` в CLAUDE.md и, если он лежит рядом, в AGENTS.md. AGENTS.md в большинстве проектов версионируется, поэтому его содержимое — политика, а не случайность (GitLab #14): | Ключ | Умолчание | Назначение | |---|---|---| | `claudemd.sibling_dynamic` | `true` | `true`: AGENTS.md обновляется блоком **без** «Shared knowledge — from other projects» — знания чужих проектов не попадают в историю этого репозитория, а хвост памяти, под которым ничего не осталось, опускается. `false`: AGENTS.md не пишется вовсе; CLAUDE.md обновляется как раньше. Выключает только JSON-булево `false` — строка `"false"` или `0` читаются как «включено». Ключ читается из `.tausik/` рядом с записываемым файлом. | Гейт коммита `claudemd_state` судит каждый файл по тому же плану, которому следует писатель: сиблинг, который политика не пишет, не судится, а сиблинг, чьё единственное знание было бы чужим, хвоста не должен. ## Публикация (что скрывает вычищенный экспорт) Общее хранилище (`~/.tausik-knowledge`) не требует настройки: `--global` у `decide` / `memory add` пишет в него, `$TAUSIK_HOME` переносит. Эти ключи читает только `knowledge export --redacted` (см. [knowledge-store.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/knowledge-store.md)). | Ключ | Дефолт | Назначение | |---|---|---| | `publication.project_names` | `[]` | Имена проектов, заменяемые на `[REDACTED:project]`, в дополнение к имени каталога этого проекта. | | `publication.private_url_patterns` | `[]` | Regex-строки; URL, подходящий под одну из них, становится `[REDACTED:url]`. | | `brain.local_mirror_path` | `~/.tausik-brain/brain.db` | Читается только разовым `knowledge import-brain`: где отставленный транспорт Notion оставил локальное зеркало. | ## Ставки кредитов подписки Codex `codex_subscription_credit_rates` необязателен; встроенной таблицы нет. Датированная таблица добавляет к исходным счётчикам `metrics tokens --host codex` стоимость принятой задачи в кредитах: ```json { "codex_subscription_credit_rates": { "source": "https://developers.openai.com/docs/pricing", "as_of": "2026-10-02", "unit": "credits_per_million_tokens", "models": { "gpt-5.6-sol": {"input": 100, "cached_input": 10, "output": 500} } } } ``` Указывайте ставки, действующие для вашего аккаунта и даты. Это кредиты подписки, а не API-доллары и не пересчёт остатка включённой квоты. ## Пример ```json { "session_max_minutes": 240, "session_idle_threshold_minutes": 15, "host_context_budget": { "advisory": {"responses": 20, "input_tokens": 2000000, "context_tokens": 120000}, "hard": {"responses": 28, "input_tokens": 3500000, "context_tokens": 170000} }, "verify_cache_ttl_seconds": 1200, "custom_stacks": ["ruby", "elixir"], "gates": { "filesize": { "max_lines": 500 }, "ruff": { "enabled": false } }, "publication": { "project_names": ["acme"], "private_url_patterns": ["acme\\.internal"] }, "claudemd": { "sibling_dynamic": false } } ``` ## Health check `tausik doctor` (v1.3+) проверяет согласованность config + venv + DB + skills и выдаёт actionable next steps. --- ## Русский · /ru/docs/security [English](/docs/security) | **Русский** # Правила безопасности См. также: [security-checklist.md](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/en/security-checklist.md) — OWASP Top 10 чек-лист. ## Основные принципы 1. **Никогда не доверяй пользовательскому вводу** — валидируй всё 2. **Минимальные привилегии** — давай только необходимое 3. **Эшелонированная защита** — множество слоёв защиты 4. **Безопасный отказ** — ошибки не должны раскрывать информацию 5. **Аудит всего** — логируй события безопасности --- ## OWASP Top 10 (кратко) ### A01: Нарушение контроля доступа - Проверяй авторизацию на КАЖДОМ endpoint - Не доверяй client-side данным - Проверяй ownership ресурсов ### A02: Криптографические сбои - Используй argon2/bcrypt для паролей (НЕ MD5/SHA1) - Secrets через переменные окружения - HTTPS обязателен в production ### A03: Инъекции - Параметризованные SQL запросы - textContent вместо innerHTML - Экранируй вывод для XSS prevention ### A05: Неправильная конфигурация - Безопасные сообщения об ошибках - Настроенные security headers - Специфичный CORS (не *) ### A07: Сбои аутентификации - Rate limiting на auth endpoints - Secure cookie flags (httpOnly, secure, sameSite) - Сильные требования к паролям --- ## Валидация входов ### Обязательные проверки - Тип данных - Длина/размер - Формат (email, URL, etc) - Допустимые значения (whitelist) ### Где валидировать - На границе системы (API endpoints) - Перед сохранением в БД - Перед отображением пользователю --- ## Аутентификация ### Требования к паролям - Минимум 12 символов - Смесь uppercase, lowercase, цифр, символов - Проверка против списков популярных паролей ### Cookie безопасность ``` httpOnly: true — нет доступа из JS secure: true — только HTTPS sameSite: strict — защита от CSRF ``` --- ## Управление секретами ### Категорически нельзя - Хардкодить секреты в коде - Коммитить .env файлы - Логировать секреты ### Правильно - Переменные окружения - Secrets managers (Vault, AWS Secrets Manager) - Регулярная ротация credentials ### .gitignore для секретов ``` .env .env.* !.env.example secrets/ *.pem *.key ``` --- ## Логирование для безопасности ### Что логировать - Успешные/неуспешные логины - Изменения прав доступа - Доступ к sensitive данным - Административные действия ### Что НЕ логировать - Пароли (даже хэшированные) - API ключи - Session токены - Номера карт - PII в открытом виде --- ## Чеклисты ### Перед каждым коммитом - [ ] Нет хардкодированных секретов - [ ] Валидация входов на всех endpoints - [ ] Проверки авторизации присутствуют - [ ] Сообщения об ошибках не раскрывают информацию - [ ] Нет SQL/command injection - [ ] Rate limiting на sensitive endpoints ### Перед каждым деплоем - [ ] Dependencies проверены на уязвимости - [ ] Security headers настроены - [ ] HTTPS enforced - [ ] Секреты в безопасном хранилище - [ ] Логирование настроено правильно --- ## Гарантии TAUSIK - `bash_firewall.py` блокирует `rm -rf /`, `Remove-Item -Recurse -Force C:\`, `git reset --hard origin`, force-push — **на обоих оболочечных каналах**, Bash и PowerShell. Полная матрица покрытия каналов: [`enforcement-coverage.md`](/ru/docs/enforcement-coverage) - `git_push_gate.py` требует `TAUSIK_ALLOW_PUSH=1` (выставляется `/ship` после подтверждения) - `memory_pretool_block.py` блокирует Write/Edit/MultiEdit **и Bash/PowerShell** в любой чужой memory-сток — `~/.claude/**/memory/`, `.cursor/rules/`, `.windsurf/rules/`, `.github/copilot-instructions.md`, `.clinerules`, `.roo/rules/`, `.continue/rules/`, `.aider*` (deny-list: `scripts/memory_sinks.py`) - Гейт `memory_route` и хук `pre-commit` применяют тот же deny-list IDE-агностично по working tree; литмус-блок в генерируемых rule-файлах закрывает хосты с облачной памятью, которая не пишет файла вообще - `publication_boundary.py` (на детекторах `brain_scrubbing`) вычищает пути, e-mail, приватные URL и имена проектов в `knowledge export --redacted` - Валидация slug'ов в scaffold ролей/стеков блокирует path traversal - Команду дефолтного гейта нельзя подменить чужим инструментом: `.tausik/config.json` едет вместе с репозиторием, поэтому переопределение `gates.<имя>.command` обязано вызывать тот же инструмент, что и дефолт. Аргументы, путь и обёртку-раннер менять можно (`vendor/bin/phpstan analyse --level=8` принимается; `eslint {files}` вместо дефолтного `npx eslint {files}` тоже — обёртки видны насквозь), сам инструмент — нет (`python -c pass` вместо `ruff` отвергается, гейт остаётся с дефолтной командой) - Установка скиллов гейтится на **обоих** путях в активированное дерево — `install` (`skill_manager.copy_skill`) и `activate` (`service_skills.skill_activate`): подпись издателя (ed25519, key-pinned на репо), guard'ы обхода пути и симлинков и общий **контент-скан невидимого Unicode** (`skill_content_scan.assert_skill_tree_clean`), который отказывает скиллу, прячущему инструкции для агента в zero-width / bidi / U+E0000 tag-block символах, по каждому prose/config/script-файлу. Полная модель: [`skill-supply-chain-threat-model.md`](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/skill-supply-chain-threat-model.md) ### Границы гарантий Надзор проверяется машиной там, где это возможно машине. Оставшееся записано здесь честно, а не подразумевается. - **Инертные аргументы того же инструмента НЕ обнаруживаются.** Проверка сравнивает вызываемый инструмент, а не смысл вызова, поэтому `ruff --version` или `pytest --collect-only` в переопределении пройдут: инструмент тот самый, гейт формально включён и всегда зелёный. Машинного критерия «команда делает настоящую работу» не существует, а правило «команда обязана начинаться с дефолтного префикса» сломало бы и вендоренный путь, и легитимный отказ от обёртки `npx`. Переопределения команд гейтов в чужом репозитории читай глазами — гейт с подозрительно быстрым зелёным заслуживает взгляда на свою команду. - **Встроенные гейты** (`filesize`, `class_surface`, `tdd_order`, `renar_drift_*`) команды не имеют вовсе; попытка её добавить отвергается. --- ## Русский · /ru/docs/known-limitations [Русский](/ru/docs/known-limitations) | [English](/docs/known-limitations) # Намеренные пробелы, открытые дефекты и что вне области Три разных вещи, которые до этой страницы выглядели одинаково: «мы решили этого не делать», «мы это не доделали» и «это сломано». Сторож, чьё слепое пятно не объявлено, читается как полный — поэтому пробелы объявлены здесь, а не выведены читателем из тишины. **Признак намеренного пробела — названная причина, почему закрыть его сейчас дороже, чем жить с ним.** Запись без такой причины не пробел, а дефект, и её место в задачах. Поэтому у каждой строки ниже есть и причина, и ссылка на то, что держит границу. ## Намеренные пробелы Каждая запись отвечает на один вопрос: **что именно НЕ гарантировано** — не «работает частично», а что проедет мимо и в каком случае. ### Гейт маршрутизации знаний не видит игнорируемый приёмник **Не гарантировано:** запись знания в файл, который игнорируется git, не будет остановлена на закрытии задачи или коммите. Так же не виден путь вне репозитория (`~/.claude/**/memory/`). **Почему живём:** входом гейта служит `git status --porcelain`, и `--ignored` затянул бы в него `.tausik/`, `node_modules/` и каждый артефакт сборки. Игнорируемый файл до коммита всё равно не доходит, а утечку в него ловит другой слой — хук `PreToolUse`. Вопрос гейта — «несёт ли изменение, которое проект сейчас записывает, знание за его пределы», вопрос хука — «утекает ли эта запись», и только второй видит игнорируемый путь. **Держит границу:** докстринг `scripts/gate_memory_route.py` (оба предела названы там же, где код), `tests/test_memory_sinks.py`. ### Гейт размера файла смотрит только на файлы задачи **Не гарантировано:** файл, уже превышающий лимит, но не тронутый текущей задачей, не будет назван. Гейт отвечает на «не вырос ли файл, который вы правите», а не на «нет ли в дереве файлов над лимитом». **Почему живём:** это ratchet на изменении, а не аудит дерева: аудит потребовал бы объявить исключение для каждого исторического файла сразу, иначе он блокировал бы любую задачу за чужой долг. Полное дерево проверяется отдельной лентой. **Держит границу:** `run_filesize_gate(gate, files)` принимает область задачи; `tests/test_gates.py`. ### Круговой гейт проекции — opt-in и fail-open **Не гарантировано:** расхождение дерева `tausik/` с БД будет замечено в проекте, где БД отсутствует (гейт пропускает — на свежем клоне это правильный ответ), и внутренний сбой самого гейта не уронит коммит. **Почему живём:** гейт, который блокировал бы проект без БД, заставлял бы завести БД, а не сходиться проекции. Сбой сторожа — не доказательство расхождения. **Держит границу:** докстринг `scripts/gate_state_roundtrip.py`, `tests/test_state_roundtrip_gate.py`. ### Маркер исторической цитаты проверяет ФОРМУ хеша, а не существование коммита **Не гарантировано:** `путь (удалён в )` перестаёт считаться протухшей ссылкой, даже если такого коммита нет. Семь и более hex-символов в скобках сразу за путём — уже достаточное основание. **Почему живём:** спросить у git — значит позволить сломанному git молча выключить весь детектор протухших ссылок; это дороже, чем поддельный маркер, который надо написать руками и который ничего не скрывает, кроме одной ссылки. **Держит границу:** `_REMOVED_CITATION` в `scripts/memory_cleanup.py`, `tests/test_repo_hygiene_ratchet.py`. ### Поиск модулей-сирот не видит имя, собранное выражением **Не гарантировано:** модуль, к которому обращаются только как `__import__(f"mod_{kind}")`, будет назван сиротой. Строковый литерал (`__import__("mod")`) виден, вычисленное имя — нет. **Почему живём:** вычисленное имя не называет ОДНОГО модуля, и догадка обменяла бы ложное срабатывание на ложный пропуск — детектор стал бы утверждать ссылку, которой не видел. **Держит границу:** `_dynamic_import_name` в `scripts/audit_orphan_files.py`, `tests/test_repo_hygiene_ratchet.py`. ### Проверка протухших ссылок требует, чтобы КАТАЛОГ существовал **Не гарантировано:** ссылка на файл в удалённом целиком каталоге не будет названа протухшей. **Почему живём:** проза полна слэшей, которые не пути (`lru_cache/functools.cache`, `release/1.8`). Требование существующего родителя убирает этот шум; без него список находок становится нечитаемым, и читатель перестаёт смотреть на настоящие. **Держит границу:** `_parent_dir_exists` в `scripts/memory_cleanup.py`. ### Архивация задач необратима и не уменьшает дерево **Не гарантировано:** снять `archived_at` командой нельзя — такой команды нет; и файл проекции задачи остаётся в `tausik/tasks/` после архивации, потому что экспортёр выбирает задачи без фильтра по этому полю (у памяти фильтр есть). **Почему живём:** архивация задумана как «скрыть из `task list`», и в этой роли работает. Обе половины заведены задачами, и обратимость объявлена предусловием сокращения проекции. **Держит границу:** `docs/ru/task-archive-spec.md`, оговорки в `docs/ru/cli.md`, `tests/test_hygiene_cli.py`. ### Сервер MCP держит старый код до перезапуска IDE **Не гарантировано:** правка в `scripts/` немедленно доедет до инструментов MCP. До перезапуска хоста сервер может исполнять прежние модули. **Почему живём:** перезагрузка модулей на живом сервере ломает состояние соединений дороже, чем стоит немедленность. Расхождение ОБЪЯВЛЯЕТСЯ: `self_check.drift_detected` поднимает флаг, и тогда ответам MCP в этой смене доверять нельзя — работает CLI, который перечитывает диск на каждый вызов. **Держит границу:** `tausik_self_check`, раздел «MCP Health» в скилле `/start`. ### `/rewind` не отменяет того, что агент сделал через shell **Не гарантировано:** что `/rewind` в Claude Code вернёт файлы, изменённые агентом. Его чекпойнты отслеживают только правки собственными файловыми инструментами; всё, что записано через Bash, скрипт или другой процесс, в снимок не попадает и не откатывается. **Почему живём:** это механизм хоста, а не наш, а агенты TAUSIK значительную часть работы делают через shell. Делать вид, что это не так, — значит оставить следующему агенту страховку, которой нет. **Держит границу:** git. Перед рискованным шагом — коммит или stash; `git diff` покажет, чего откат не увидит. ### Изменчивая часть файла правил стоит последней **Не гарантировано:** что всё переданное агенту остаётся в кэше промпта. Хост кэширует самый длинный неизменный префикс, и изменившееся место обнуляет всё, что после него. **Почему живём:** часть контекста обязана меняться — состояние сессии и хвост памяти. Она собрана в один блок в самом конце файла правил (``), после всех стабильных правил, поэтому её изменение стоит только её самой. Текст хуков на каждый промпт приходит после префикса разговора по той же причине. **Держит границу:** `tests/test_context_order.py` падает, если после изменчивого блока стоит что-то стабильное — в порождённом файле правил или в `CLAUDE.md` этого репозитория. ### Временные окна имеют СЕКУНДНУЮ гранулярность **Не гарантировано:** событие, записанное в ту же секунду, в которую читается окно с верхней границей `now`, попадёт в выборку. **Почему живём:** `utcnow_iso()` пишет секунды по всему проекту, и переход на миллисекунды затронул бы каждый timestamp в схеме и в проекции. Для открытого интервала верхняя граница просто не ставится — она бессмысленна. **Держит границу:** запись памяти об окне событий, `tests/test_med_findings_fix.py`. ### Проверки doctor — best-effort **Не гарантировано:** падение отдельной проверки будет отличено от её отрицательного результата: исключение внутри проверки превращается в WARN с текстом ошибки, а не в FAIL. **Почему живём:** `doctor` — обзор, а не гейт. Одна сломанная проверка не должна скрывать остальные девять. **Держит границу:** `scripts/project_cli_doctor.py` (каждая проверка в своём `try`), `tests/test_doctor_*.py`. ## Открытые дефекты **Руками здесь не перечисляются — и это решение, а не пропуск.** Список дефектов, поддерживаемый вручную, отстаёт молча; ровно так документ, объявлявший себя картой направления, дошёл до утверждения про две версии назад. Открытые дефекты живут в БД задач, и оттуда их видно командой: ```bash tausik task list --status planning # что заведено и ждёт tausik roadmap # состав релиза со счётчиками ``` Дефект, найденный внутри задачи, заводится отдельной задачей, а не правится походя — поэтому «ни одного открытого» здесь означало бы только то, что никто не смотрел. ## Вне области Чего TAUSIK не делает по замыслу, а не по недоделанности: * **Не правит падение гейта сам.** Гейт называет проверку, причину и команду исправления; исправляет человек или агент. Гейт, который починил бы сам себя, учит не смотреть на причину. * **Не даёт агенту прямого доступа к БД.** Только MCP и CLI. Прямой SQL обошёл бы проекцию, триггеры и квитанции — то есть всё, на чём держится воспроизводимость. * **Не заменяет ревью кода.** Гейты проверяют дисциплину и границы, а не замысел. Разделение обязанностей L3 требует другой модели, чем у автора. * **Не обслуживает несколько проектов из одного демона.** Сервер MCP привязан к проекту при запуске; общесистемный режим — предмет отдельной большой работы, а не пробел. --- ## Русский · /ru/docs/troubleshooting [English](/docs/troubleshooting) | **Русский** # Troubleshooting > Машино-читаемый гайд: ошибка → диагноз → фикс. ## Stale MCP-модули (тихие зависания) Симптом: `tausik_verify` или `tausik_task_done` не возвращаются. CLI той же операции работает мгновенно. MCP-сервер исполняет stale Python-модули — обычно потому, что код service-слоя редактировался ПОСЛЕ старта IDE, а MCP-дочерний процесс не перезапустился. Несколько MCP project-серверов для одного проекта — тоже жёсткий признак (каждое прошлое IDE-окно оставляет один процесс). | Симптом | Диагноз | Фикс | |---|---|---| | MCP-инструмент висит > 60 с, тот же flow через CLI отрабатывает мгновенно | `tausik_self_check` скорее всего показывает `drift_detected=true` или `sibling_mcp_count > 0` — старые модули в памяти | Перезапусти IDE, чтобы MCP project-сервер пересоздался. Пока что — `.tausik/tausik` CLI. | | `/start` предупреждает `⚠ MCP Health` со списком stale-модулей | mtime watched-модуля стал больше времени старта MCP | Перезапусти IDE; повтори `/start`. | | `sibling_mcp_count > 0` | Несколько MCP project-серверов на одном проекте (window leak) | Закрой stale IDE-окна, затем `Get-Process python` (Windows) / `pgrep -f mcp/project/server.py` (POSIX), убей старые PID'ы. | | `tausik_self_check` возвращает `error: self_check unavailable` | Запущенный MCP-сервер старше этой диагностики (предшествует v1.4 polish) | Перезапусти IDE — на новом старте инструмент уже зарегистрируется. | Сопутствующие gotchas в `.tausik/tausik.db`: #77 (verify висит после правки `service_verification.py`/`gate_runner.py`), #79 (`task_done` виснет на большом evidence), #80 (root cause = stale-модули + параллельные MCP-серверы). Envelope timeout (60 с, `verify_pipeline_timeout_seconds`) Verify-First Contract'а ловит новые серверы; старые загрузили свой код ДО появления envelope и игнорируют его. ## Failed verify-gate → tausik-gate-fixer (auto-helper) Когда `tausik verify` или `tausik task done` падает с blocking-failure (filesize / ruff / mypy / pytest), вместо ручного парсинга stderr вызови sub-agent **`tausik-gate-fixer`** — он читает stderr + `docs/en/troubleshooting.md` + `docs/en/architecture.md` и возвращает 1-3 шаговый JSON fix plan. ``` Agent( subagent_type="tausik-gate-fixer", prompt="gate_name=ruff; stderr=; relevant_files=[...]; task_slug=; goal=", ) ``` Ответ: ```json {"gate":"ruff","family":"style","plan":[{"step":1,"action":"edit","target":"scripts/foo.py:42","change":"...","why":"..."}],"meta":{...}} ``` **Action vocabulary** (фиксированный, агент не выдумывает): `edit`, `extract_module`, `add_test`, `move_file`, `delete_dead_code`, `re_run_gate`. Применяешь plan — потом `.tausik/tausik verify --task ` ещё раз. Sub-agent read-only: никогда не правит код сам. ## Bootstrap | Симптом | Диагноз | Фикс | |---|---|---| | `python: command not found` | Python не в PATH | Установи Python 3.11+, проверь `python --version` | | `Bootstrap halts on .claude/ write` | Read-only FS | Mount `.tausik/` writable; bootstrap пишет и в `.claude/` | | Bootstrap "Skills: 0 copied" | `harness/skills/` пуст | Pull актуальный source, проверь `ls harness/skills/` | | Bootstrap занимает >60s на Windows | Antivirus сканирует каждый файл | Исключи `.claude/` и `.tausik/` из real-time scan | ## CLI / MCP | Симптом | Диагноз | Фикс | |---|---|---| | `tausik_task_done` зависает / таймаут в VS Code Claude Extension | Тяжёлые гейты (pytest, tsc) шли inline, хост убил вызов на per-tool таймауте | v1.4 Verify-First Contract: сначала `tausik_verify` (стримит прогресс, можно прервать), затем `tausik_task_done` читает кеш и закрывает за миллисекунды. См. [Лимиты хоста](#лимиты-хоста-task_done-ux). | | `tausik_task_done` возвращает generic timeout без traceback | Старый MCP-сервер (до 1.4) проглатывал исключения в одну строку | Обнови MCP-сервер: `python bootstrap/bootstrap.py` (1.4 пишет traceback в stderr). | | Агент запускает тяжёлые гейты inline при закрытии задачи | MCP-сервер в проекте предшествует 1.4 (Verify-First Contract) | Обнови bootstrap: `python bootstrap/bootstrap.py`. В 1.4 verify отделён от close — `tausik_verify` стримит прогресс, `tausik_task_done` читает кеш. | | MCP tool returns stale data | MCP server cached старые scripts/* модули | Restart IDE session (re-bootstrap не помогает) | | `tausik doctor` reports drift | Source `scripts/` отличается от `.claude/scripts/` | Re-run `python bootstrap/bootstrap.py --ide claude` | | `Memory #N not found` | Не указано в какой DB | Сейчас всегда project DB; строки общего хранилища видны через `tausik memory search` (они помечены как shared) | ## Лимиты хоста & `task_done` UX MCP-серверы TAUSIK работают внутри IDE-хоста (VS Code Claude Extension, JetBrains, Cursor, Claude Code и т.д.). Каждый хост применяет per-tool таймаут (~60с в текущих сборках). Если `task_done` запускал тяжёлый стек проверок inline, большие monorepo выходили за таймаут, хост убивал вызов, и агент видел generic transport error — а не полезный отчёт об ошибке. **Workflow (предпочитаемый):** 1. `tausik_verify(task_slug=…)` — запускает тяжёлые гейты (pytest, tsc, cargo, phpstan, …), стримит прогресс, пользователь может корректно прервать. Результат кешируется на 10 минут. 2. `tausik_task_done(slug=…, ac_verified=True, relevant_files=[…])` — читает кеш, закрывает задачу за миллисекунды. Возвращает structured JSON (`stage`, `gate_results`, `blocking_failures`). **Opt-out (CI / batch):** добавь `{ "task_done": { "auto_verify": true } }` в `.tausik/config.json`. `task_done` будет запускать тяжёлые гейты inline как в 1.3 — это нормально вне интерактивного хоста, где нет per-tool таймаута. **`tausik_task_done` — единая QG-2 entrypoint.** В предыдущих доках упоминалась миграция `task_done_v2 vs task_done`; v1.4 публикует только один `tausik_task_done` (structured JSON: `stage`, `gate_results`, `blocking_failures`). Соблюдает Verify-First Contract — тяжёлые гейты должны быть уже прогнаны через `tausik_verify`, результат кешируется на 10 минут, `task_done` читает этот кеш. **Streaming-прогресс (v1.4):** когда `task done` запускает гейты inline (`auto_verify=true` или интерактивный `tausik task done`), `gate_runner` шлёт событие `run_start` ДО первого гейта с `total` (количество гейтов) и `max_seconds` (сумма таймаутов), чтобы MCP-хост показал ETA до того, как pytest заблокирует канал. CLI-обработчик мапит каждое событие в одну stderr-строку: ``` [gates] Running 2 gate(s) (trigger=task-done, max ~125s). [gates] 1/2 filesize ... [gates] 1/2 filesize PASS (8 ms) [gates] 2/2 pytest ... [gates] 2/2 pytest PASS (1062 ms) ``` `TAUSIK_QUIET=1` глушит вывод (CI / скриптовые прогоны). MCP-серверы получают тот же payload и могут отрендерить структурированное progress-сообщение. ## VS Code Claude Extension — полный справочник (v1.4) VS Code Claude Extension — самый строгий MCP-хост для TAUSIK потому что (1) у него жёсткий per-tool таймаут, который нельзя поменять изнутри инструмента, (2) TAUSIK не генерирует для него hooks payload, (3) рендерит результат MCP-tool как одну строку. v1.4 настроена под этот хост явно. Эта секция консолидирует полный список нюансов. ### Статус хуков Таблица перечисляет ТРИ хоста из пяти и ведётся руками. Настоящий ответ — измеряемый: `.tausik/tausik doctor` печатает по каждому хосту хуки и плагины, реально развёрнутые в его профиле, и файл правил каждого хоста открывается тем же утверждением, выведенным оттуда же. «TAUSIK их не разворачивает» — факт о TAUSIK, а не утверждение о хосте: примет ли хост payload с хуками — отдельный вопрос, и эта таблица на него не отвечает. | Категория хуков | Claude Code (CLI) | Cursor | VS Code Claude Ext. | Qwen Code | |---|---|---|---|---| | PreToolUse / PostToolUse / SessionStart / SessionEnd | ✅ Реальные, enforced | ❌ TAUSIK их не разворачивает | ❌ TAUSIK их не разворачивает | ✅ Реальные, enforced (полный паритет с v1.4) | | `task_gate.py` (Rule 9.1) | Hard block | Только инструкция | Только инструкция | Hard block | | `secret_scan.py` (Rule 10.12) | Warn / strict-block | Только инструкция | Только инструкция | Warn / strict-block | | `git_push_gate.py` | Hard block | Только инструкция | Только инструкция | Hard block | Практический вывод: VS Code extension не может заблокировать запись файла, когда нет активной задачи TAUSIK. Агент *должен* соблюдать правило (оно явно прописано в `CLAUDE.md` и multi-model onboarding-блоке `AGENTS.md`), но safety net отсутствует. В этом хосте правила — это конвенция, не gate. ### Per-tool таймаут MCP Текущая сборка extension убивает любой MCP-вызов длиннее ~60 секунд. Клиентского переключателя нет. Затронутые tools и v1.4 mitigation: | Tool | До 1.4 | Mitigation в 1.4 | |---|---|---| | `tausik_task_done` | Запускал pytest/tsc/cargo inline → 60с+ на большом repo → kill → generic timeout | **Verify-First Contract**: отказывается закрывать пока не отработал `tausik_verify`. Verify пишет stderr прогресс, юзер может прервать, результат кешируется на 10 мин. Затем `task done` читает кеш и закрывает за <100мс. Возвращает structured JSON (`stage`, `gate_results`, `blocking_failures`) вместо одной строки ошибки. | | `codebase-rag.reindex` (full) | Молча обходил все файлы → kill на 60с | Принимает `max_seconds` soft-limit; пишет `[rag] indexed X/Y files...` в stderr каждые 100 файлов. По умолчанию `incremental` — только файлы изменённые с `last_commit`. | | `tausik_verify` | (введён в 1.4) | Намеренная foreground-точка для тяжёлой работы. Стримит прогресс; юзер видит что происходит; хост не таймаутит пока мы пишем байты в свой idle-порог. | ### Рекомендованный workflow Для рутинного закрытия задачи в VS Code Claude Extension: 1. Скилл/агент сначала зовёт `tausik_verify(task_slug=...)`. Это единственное место, где pytest/tsc/etc. разрешено запускать inline. 2. Юзер видит streaming-прогресс и может вмешаться (Ctrl-C доходит до MCP-процесса). 3. На зелёном `tausik_verify` пишет строку в `verification_runs`. 4. Скилл зовёт `tausik_task_done(task_slug=..., relevant_files=[...], ac_verified=true)`. 5. `task_done` проверяет кеш (≤10 мин, тот же `files_hash`, та же gate signature), убеждается в зелёном статусе и закрывает задачу за ~100мс. 6. Если в кеше промах — `task_done` возвращает структурированную ошибку с указанием конкретного отсутствующего gate, **не** transport timeout. Для CI / batch / non-interactive прогонов (где нет per-tool таймаута), поставь `{ "task_done": { "auto_verify": true } }` в `.tausik/config.json` чтобы вернуть legacy 1.3-поведение когда `task_done` запускает гейты inline. ### Диагностика | Симптом | Что проверить | |---|---| | Скилл зовёт legacy-форму `task_done` | v1.4 публикует единственный `tausik_task_done`. Если SKILL.md в проекте всё ещё пишет "call v2", перезапусти `python bootstrap/bootstrap.py` — `.claude/skills/` (и аналоги) обновятся под текущий контракт. | | Verify никогда не возвращает | Запусти ту же verify-команду из обычного терминала (`.tausik/tausik verify --task `) — если работает там, но висит через extension, проблема на стороне хоста, не TAUSIK. | | Хуки не срабатывают на `Write` | Ожидаемо — TAUSIK не разворачивает хуки для VS Code extension. Агент обязан соблюдать правила без enforcement; для критичных по соблюдению задач переключайся на Claude Code CLI или Qwen Code, если важны hard-blocks. | ## RAG (codebase-rag) | Симптом | Диагноз | Фикс | |---|---|---| | `reindex` зависает / падает по таймауту на большом monorepo | `mode=full` обходит все tracked-файлы; на 50k+ файлах вызов превышает per-tool timeout MCP-хоста | Передай `max_seconds=N` (soft limit — вернётся частичный результат с `truncated=true`) или используй `mode=incremental` (по умолчанию; реиндексирует только файлы изменённые с `last_commit`). v1.4 пишет `[rag] indexed X/Y files, N chunks, ZZs elapsed` в stderr каждые 100 файлов — хост рендерит прогресс вместо «зависло». | ## Prompt caching не активен Симптом: stoимость токенов на сессию растёт быстрее, чем ожидается; LLM-биллинг показывает почти весь input как «обычный», а не как `cache_read`. Проверь через `python scripts/validate_prompt_caching.py --auto` (или передай путь к JSONL). Exit code 2 = API не возвращает cache-поля вовсе; exit code 1 = `cache_creation > 0`, но `cache_read = 0` — каждый ход кешируется заново. | Симптом | Диагноз | Фикс | |---|---|---| | `validate_prompt_caching.py` exit=2 (нет cache-полей) | Текущий клиент / endpoint не запрашивает caching | Используй официальный Claude Code (он включает caching по умолчанию). У сторонних оболочек проверь, что они шлют `cache_control` хотя бы на system prompt + tools. | | Exit=1 (creation > 0, reads = 0) | Префикс нестабилен — что-то меняется между ходами | См. ниже список инвалидаторов. | | `cache_read_input_tokens` падает к нулю в середине сессии | `tausik_update_claudemd` переписал dynamic-блок CLAUDE.md | Не запускай `update_claudemd` между tool-вызовами; держи его на границах (`/start`, `/checkpoint`, `/end`). | | Низкий hit-rate (<50%) даже без правок CLAUDE.md | Скилл-файл (`SKILL.md`) или MCP `tools.py` редактировался в worktree между ходами | После любой правки агентских артефактов перезапусти MCP / IDE — старый префикс уже инвалидирован, продолжать сессию бессмысленно с точки зрения кеша. | | Хочется видеть hit-rate в реальном времени | — | `python scripts/validate_prompt_caching.py --auto` после каждой длинной сессии. Записи `usage`-блоков уже лежат в `~/.claude/projects//*.jsonl`. | См. [architecture.md](/ru/docs/architecture) секцию «Prompt caching» — там список кешируемых поверхностей и какие правки переписывают префикс. ## Hooks | Симптом | Диагноз | Фикс | |---|---|---| | Hook не срабатывает | TAUSIK_SKIP_HOOKS установлено | Проверь env, удали переменную | | `git push` blocked | Защитный gate сработал — это нормально | Используй `/ship` или `/commit` skills (после твоего "y" они запускают `tausik push-ok && git push`). Ручная авторизация: `tausik push-ok && git push` — пишет одноразовый 60-секундный тикет, привязанный к SHA HEAD. Старый `TAUSIK_ALLOW_PUSH=1` удалён в v1.4 (был broken-by-design). Debug bypass: `TAUSIK_SKIP_PUSH_HOOK=1`. | | Memory write blocked | Попытка записи в `~/.claude/projects/*/memory/` | Если действительно нужен cross-project memory — добавь маркер `confirm: cross-project` | --- ## Русский · /ru/docs/upgrade [English](/docs/upgrade) | **Русский** # Безопасное обновление Что bootstrap трогает, что не трогает, и как обновляться без потери своей работы. ## Два дерева ``` / ├── stacks/ ← владение фреймворка. Bootstrap перезаписывает. ├── scripts/ ← владение фреймворка. ├── bootstrap/ ← владение фреймворка. ├── harness/ ← владение фреймворка. ├── .claude/ ← вывод bootstrap (можно игнорировать; регенерируется). └── .tausik/ ← ВАШИ данные. Bootstrap НИКОГДА их не трогает. ├── tausik.db ├── config.json ├── stacks/ ← ваши stack-override └── venv/ ``` ## Bootstrap-owned vs user-owned | Путь | Владелец | Переживает обновление? | |---|---|---| | `stacks//stack.json` | Фреймворк | Нет — перезаписывается на каждый bootstrap. | | `harness/stacks/*.md` (legacy) | Фреймворк | Заменяется на bootstrap; только как legacy fallback. | | `.claude/` | Вывод bootstrap | Регенерируется при каждом запуске. | | `.tausik/stacks//stack.json` | **Вы** | **Да — bootstrap не трогает.** | | `.tausik/config.json` | **Вы** | **Да.** | | `.tausik/tausik.db` | **Вы** | **Да.** | Тестовый набор `tests/test_bootstrap_non_destructive.py` форсит этот контракт. Каждый bootstrap-путь проверяется на то, что он не пишет внутрь `.tausik/`. ## Workflow обновления 1. `git pull` (или обновите фреймворк тем способом, как вы его установили). 2. `python bootstrap/bootstrap.py --no-detect` — обновляет `.claude/`, копирует новые `stacks/`, регенерирует блок `_STACKS_ENUM` в MCP. 3. `tausik stack lint` — валидирует, что ваши override всё ещё парсятся против новой схемы. 4. `tausik stack diff ` — для каждого override-стека, sanity-check, что встроенный не изменился способом, который ломает ваши предположения. ## Что может сломать stack-override Нетривиальное обновление фреймворка *может* инвалидировать override, даже если файл не трогался: - В схеме появилось обязательное поле. `tausik stack lint` сообщит. - Поле было переименовано (например `detect[].kind` → `detect[].type`). `lint` сообщит об unknown-field ошибках. - Цель `extends: "builtin:NAME"` была переименована или удалена. `lint` всплывёт "extends target not found". - Имя gate перемещено между стеками. Ваш override, ссылающийся на него через `null` (для disable), станет no-op молча — проверьте через `stack export`. Фреймворк предпочитает аддитивные изменения; ломающие изменения едут в major-версии с миграционной заметкой в changelog'е. ## Когда что-то пошло не так | Симптом | Что делать | |---|---| | Bootstrap падает или warn'ит про `.tausik/stacks/` | Запустите `tausik stack lint`, чтобы увидеть validation-ошибки. | | `tausik task add --stack X` отвергает стек, который вы определили | Подтвердите, что `.tausik/stacks/X/stack.json` существует и парсится; проверьте `tausik stack list`. | | Gate перестал срабатывать после обновления | `tausik stack info ` показывает активные gates; `stack export ` подтверждает порядок merge. | | Хочется начать заново | `tausik stack reset ` удаляет один override; `rm -rf .tausik/stacks/` сносит все. | ## Disaster recovery Если `.tausik/` повреждён или вы хотите снести локальное состояние, только база данных (`.tausik/tausik.db`) незаменима — всё остальное регенерируется повторным запуском bootstrap. Сделайте бэкап `.tausik/tausik.db` и `.tausik/stacks/` перед чем-либо разрушительным. ## Версионная политика - **Patch (x.y.Z)** — багфиксы, без изменений API/схемы. - **Minor (x.Y.0)** — новые возможности, аддитивные миграции БД, API расширяется без break. - **Major (X.0.0)** — breaking changes API/схемы, миграционная заметка в `CHANGELOG.md` обязательна. `tausik doctor` после обновления покажет, есть ли drift или проблемы с конфигом. ## См. также - [Кастомизация](https://github.com/Kibertum/tausik-core/blob/v1.11.0/docs/ru/customization.md) — как переопределять без правки фреймворковых файлов. - [Архитектура](/ru/docs/architecture) — модель слоёв и владений.