npm.io
0.17.1 • Published 20h ago

@r1z4x/tezgah

Licence
MIT
Version
0.17.1
Deps
0
Vulns
0
Weekly
0

English | Deutsch | Español | Français | Türkçe

Tezgah

tezgah logo

One working contract for every AI coding assistant you run.

License: MIT CI Release

What it enforcesSupported hostsInstallDay-to-dayConfigurationBenchmarkCostDevelopmentContributingSecurityLicense

English is the source of truth; translations may lag behind it.


One working contract for every AI coding assistant you run — oh-my-pi (omp), the primary host, plus Claude Code, Codex, Cursor, opencode and DeepSeek's dsh harness — inside a set of configured repository roots.

Left alone, each assistant has its own habits: one answers in Turkish, another in English; one greps for everything, another queries a code graph; one says "done" without running a test. Tezgah removes the drift. Open any host and you get the same language, the same discipline, and the same standard of evidence.

The design is two layers. The rules live once in a shared core; each host gets a thin adapter that translates that core into the shape the host understands. Change a rule in one place and all six hosts see it — no six-way copy of the same text.

What it enforces

  • Outcome-first Turkish reporting. Every reply is in Turkish and leads with the result or decision (BLUF), then points ordered by impact. Code, commits, docs, and subagent prompts stay English; names, CLI commands, and error strings are never translated.
  • Minimal code (ponytail). The laziest change that actually works: YAGNI, then reuse an existing helper, then stdlib, then a native platform feature, then an installed dependency, then one line. No unrequested abstractions. Validation, error handling, and security are never simplified away. The intensity level is a real switch - tezgah-pony lite|full|ultra, or /tezgah:ponytail on Claude - and a non-default level rides the per-turn reminder.
  • Act-on-it output shape (i-have-adhd). The answer or the next action is on the first line; multi-step work is a numbered list whose position is restated in one line while it runs; tangents wait until the issue in hand is finished; errors read as location, cause, fix; a list shows at most five ranked items with the rest kept in reserve; an estimate is in concrete units and is marked as an estimate. The switch is tezgah-adhd off|on (or /tezgah:adhd on Claude; a repo opts out with .no-adhd). Vendored and adapted from i-have-adhd (MIT).
  • An active task the gate enforces. The user can point one open plan at the gate: its phase: (discovery, implementation, verification) and its allowed_paths: globs are what each write is checked against, so a write in a read-only phase, or outside the files the plan names, is refused before it lands - and a shell command that writes a file is refused in a read-only phase too, because the shell is a write route like any other and the E7b block measured it as the route an agent takes once the write tools are refused. Two more refusals close the ways a session could move that boundary instead of respecting it: a write whose target is the record itself, refused whatever the phase and the globs say, and a shell command that would change the record through the CLI. No active task means no requirement, because the gate invents nothing. None of the four prints the command that lifts it, so a refusal is the ask and not the instruction: the phase and the globs are the user's to change. The record is the user's own (~/.config/tezgah/bin/tezgah-task start|phase|allow|stop|status), the agent never runs it, and an empty allowlist reads as any path in the repo rather than as "nothing allowed". The phase also rides every user turn as one line, so the refusal is never the first the session hears of the boundary.
  • Code-graph-first discovery. "Where is X", "who calls Y", "what breaks if Z changes" go to the codegraph index (codegraph_explore over MCP, and the codegraph callers|callees|impact|affected|node CLI from a shell), not to grep. Grep stays right for literal text, configs, and non-code files.
  • Accessibility-first app analysis. A running web or mobile app is read through its accessibility / DOM / native view tree, not a screenshot per step. analyze-app covers a browser (Playwright MCP), an iOS Simulator or Android emulator (Mobile MCP), and optional web diagnostics (Chrome DevTools MCP); a screenshot is an explicit, on-demand action for what the tree cannot answer.
  • External second opinion. ~/.config/tezgah/bin/consult asks independent models in parallel (OpenRouter by default, --provider deepseek or --provider inception for the DeepSeek and Inception Labs APIs), then spends one more call on a referee that names where the panel disagreed, what all of them assumed, what would change the recommendation and what evidence it still wants. Each failure is classed with the one variable to change on a retry, a dead or unheadlined referee is disclosed as an unjudged panel, and a packet too long for argv goes in with consult -. The agent reports where the models disagreed and verifies their claims against the code.
  • Research via OpenResearch. When the router judges a task is research — a literature review, forming and testing hypotheses, running experiments, a research artifact — it drives the work through alphaXiv's OpenResearch (orx) and loads the orx manual first, instead of improvising the protocol. Plain code discovery stays on the code graph. When orx is absent, the router says so and falls back to a host subagent. The domain knowledge an experiment needs ships with it: the vendored AI-research-SKILLs library (98 skills, 23 categories, MIT) lands as the ai-research skill, read one entry at a time from its stage index.
  • Honesty under verification. Nothing is reported done, tested, or fixed unless the output was seen. A failing test is reported as failing with its exact error, and a skipped check is stated plainly.
  • No AI attribution, anywhere. Nothing persisted or published — commit, merge, and tag messages, PR and issue text, code comments, file headers, docs — may credit the assistant, model, vendor, or "AI". Using a tool is fine; signing its name to your work is not.
  • Two-tier orchestration. The main thread decides and verifies; a cheap model (~/.config/tezgah/bin/codegen, OpenRouter by default, or --provider deepseek / --provider inception) drafts bounded, well-specified edits to a scratch directory. Nothing reaches the repo except through the router; on a failed draft (codegen exit 2) the contract requires the router to write the code itself with the main model - a rule the router follows, not a mechanism inside codegen.
  • Per-repo subagents. At session start the enclosing repo gets a small set of capability-gated agents (tezgah-explorer, tezgah-reviewer, tezgah-researcher, tezgah-verifier) plus a tezgah-orchestrator, rendered into each installed host's native surface (Claude/Cursor .claude/agents/, opencode .opencode/agents/ plus a live config injection, Codex .codex/agents/) and ignored through the clone's own .git/info/exclude, so the repo's tracked .gitignore is never edited. Which hosts get files is the config's hosts list when it names one of those four, and otherwise every host detected on the machine - omp is not one of them, because its subagents are user-level (~/.omp/agent/agents/), so a config of hosts: ["omp"] writes no per-repo agent files at all. On Claude the orchestrator's Agent(tezgah-*) allowlist only takes effect when it runs as the main thread (claude --agent tezgah-orchestrator); as a subagent the list is ignored. dsh has no per-role surface, so the contract's router rule covers it.

Supported hosts

omp is the primary host — the one tezgah is developed and verified against. Its whole surface (session start, per-turn rules, gate, evidence, end-of-turn block, status line) is covered by tests, and it is the only host whose status line is also checked against the real TUI. The rest are adapters, each claimed for what it mechanically enforces: the adapter docstrings and the gate tests record what a host actually refuses, and benchmarks/harness-vs-omp/ keeps the cost evidence, which is a separate question from enforcement.

Host Wired by
omp (oh-my-pi) — primary ~/.omp/agent: managed RULES.md always-on block, skills, generated subagents, mcp.json, and an extension (hooks/pre/tezgah-hook.ts) that arms the per-prompt rules, gates tools, records evidence and runs the Stop rule; the wiring is checked by tezgah-setup
Claude Code local plugin marketplace: hooks, commands, two read-only agents, output style
Codex hooks.json + skills + MCP, including a PreToolUse gate
Cursor hooks.json + skills + MCP; needs a cursor-agent build with CLI hooks and statusLine - the 2025.09 build predates both, so this adapter is inert until Cursor ships them
opencode plugin + instructions + MCP + generated skill router (native skill list denied), repo auto-index on the first message; the plugin enforces the gate itself - attribution, explorer, the grep nudge, consent, secret and the loop/retry ceilings - and puts every write to the core through ~/.config/tezgah/bin/tezgah-gate check, so the active-task rule and any rule added later arrive from the core rather than from a second implementation
dsh Claude Code hook bridge + managed patch block (hooks, MCP, LLM routes, an out-of-tree Web status line)

The Codex gate runs Bash, exec_command, apply_patch, Edit/Write, MCP tools, and subagent calls through the same check as the other hosts. On Claude the attribution setting is emptied (commit, pr, sessionUrl), which turns off the harness-emitted commit/PR trailers - it does not edit text the model writes. Elsewhere the ban is enforced only by the tool gate (a git/gh write whose command carries a credit is refused) and by the contract; Cursor exposes no attribution lever tezgah can set.

Both halves of the integrity rule keep their aim narrow, because a gate that fires on description stops the work it polices. The anti-shortcut deny reads the command with quoted text and heredoc bodies blanked, so a commit message that names --no-verify passes while the flag itself is refused; the test-disable deny needs a test path, a marker outside strings and comments, and a marker the file does not already carry. The reply-level half - the Stop rule - runs where the host hands over the final message: Claude, Codex, omp (session_stop) and Cursor, which reports the reply on afterAgentResponse and takes the decision at stop. Its ledger is shared, so the newest check wins: a later failure blocks a "tests pass" claim even if an earlier run was green, and so does a later write - a green run over the tree as it was then does not cover the tree as it is now, because the ledger records whether each write actually changed its target. Each row carries the action it belongs to (id, a digest of the tool and its canonical arguments), the workspace, and whichever of exit, out_bytes and fail_class the host actually reported - a field a host cannot report is absent, never zeroed, so a reader can tell "it failed" from "nobody said" - so a run can be reconstructed rather than guessed at; the same identity feeds the loop guard, which refuses a third identical call whose previous attempts exited non-zero. tezgah-status --counters <cwd> <session> reports four trace figures next to the deny counts: steps counts the session's work events - the run, edit and verify* rows, so a denial, a nudge or a claim is not a step; tool_error_rate is the share of non-zero exits over the rows that carry an exit at all, i.e. the rows whose host reported an outcome (absent when none did); claims counts Stop evaluations - one row per user turn and reply text, so a host that re-runs its Stop handler cannot double-count a turn - and false_completion is the share of those whose stop was refused. A check counts as support only when the host reported exit 0, the command was not masked by a pipe, and - where the host supplies a result size - that size is non-zero; rows written before this rule existed are still accepted, so upgrading never blocks an open session on its own history. opencode has no end-of-turn surface to block, so it records the evidence and the reply claim stays unenforced there.

A consent ask is three rows, never one: the gate's refusal writes consent (the action's digest and its effect class), the user's approval writes grant, and a repeat the gate allowed without either writes repeat-allowed. The approval is tezgah-consent <digest> for the action a refusal named, or tezgah-consent --last for the newest ask no grant answers yet - the form that costs no copy-paste. The grant lands in the ledger that carries the ask, which is the session the gate reads it from, so "who approved what" is a query over the ledger rather than a claim about it. A command can name its own class with tezgah:effect=<class> (outward, publish, deploy, schema, destructive) when no pattern can see the effect; the declaration holds only at or above the class the command text derives, so it can tighten the rule and never loosen it - a downgrade is ignored and named in the refusal and the deny row. The declaration is read off the raw command, so a trailing # tezgah:effect=deploy counts, and a command that merely quotes the form is held to it too. One floor sits under the user: a rm -rf whose every target is under a temp root ($TMPDIR or /tmp) is scratch, so a session clearing its own fixtures is not held to an ask - the temp root itself, an unresolved $VAR/~ target and anything that escapes it still are, and the untrusted-content rule reads the class conservatively so an effect after a fetched page is refused whether or not its target is scratch.

App analysis

analyze-app drives a running application from its accessibility tree. The default loop is open, read the tree, act, observe console/network/logs, and re-read the tree — a screenshot is an explicit action for what the tree cannot answer (canvas, game, animation, pixel-level visual regression). The skill is one path for all hosts; the servers under it are one shared spec in hooks/tezgah_apps.py:

Server Target Wired by
playwright (@playwright/mcp) web pages, browser_* tools opencode, Codex, Cursor, Claude (plugin .mcp.json)
mobile-mcp (@mobilenext/mobile-mcp) iOS Simulator / Android emulator, mobile_* tools same
chrome-devtools (opt-in, --devtools) web perf traces, deep network, source-mapped console same

The browser runs an isolated profile by default, so a run never touches your real Chrome state; analyzing a logged-in flow is a deliberate attach (--cdp-endpoint or the Playwright extension), not a default. Screenshots, traces and tree dumps land in ~/.cache/tezgah/apps (override with TEZGAH_ARTIFACTS) and the agent gets a path back, never inline image bytes. The servers run through npx, so they need node but no install of their own; tezgah-setup --install --devtools adds the optional web diagnostics server. dsh wires the same two servers through its dsh-mcp-client bridge (serverName / command / args / env, confirmed against the published config schema), and Claude gets them from the plugin's .mcp.json (claude plugin details tezgah lists MCP servers 2 and both connect). mobile-mcp is the higher-friction half: macOS may prompt for Accessibility / Screen-Recording permission and the view tree can drop under load, so the skill retries the tree before falling back to a screenshot.

CI runs a deterministic handshake for both servers (no browser, no device): TEZGAH_E2E_STRICT=1 python3 tests/e2e_analyze_wiring.py. Two opt-in local smokes go further: TEZGAH_E2E_APPS=1 python3 tests/e2e_analyze_web.py starts Playwright MCP, navigates and reads the snapshot with no screenshot; TEZGAH_E2E_APPS=1 python3 tests/e2e_analyze_mobile.py starts Mobile MCP, checks the view-tree tools and lists a device. They print SKIP: ... when node, a browser build or a device is missing.

Install

Requires Python 3.10+ (the floor CI byte-compiles and tests on). node + npm are needed for the dsh host and, with pnpm, for its web status line. The optional integrations degrade gracefully: codegraph on PATH powers the graph; a model key powers consult and codegen — OpenRouter by default (OPENROUTER_API_KEY or ~/.config/openrouter/key), the DeepSeek API with --provider deepseek (DEEPSEEK_API_KEY or ~/.config/deepseek/key), or Inception Labs with --provider inception (INCEPTION_API_KEY or ~/.config/inception/key); and OpenResearch's orx on PATH gives the research rule something to drive. When the chosen provider's key is missing, tezgah says so instead of pretending.

Supported platforms, answered on 2026-09-22: macOS, Linux, WSL and Windows. macOS, Linux and WSL run the same two sh scripts; Windows has its own entry point, packaging/install.ps1, because it assumes no sh, no python3 and no symlinks — it unpacks with the tar.exe that ships with Windows 10 1803+, materialises <prefix>\current as a directory junction (a copy, where a junction is refused) and starts the installer with the first of py -3, python, python3 it finds. The sh scripts need sh, curl and tar, and sha256sum or shasum to check the artifact.

Install a released version; pin it, which is how a team stays on one:

curl -fsSL https://raw.githubusercontent.com/r1z4x/tezgah/v0.16.1/packaging/install.sh | sh -s -- --version 0.16.1

Drop --version 0.16.1 to take the newest release. On Windows, pin through the environment — a piped script cannot bind parameters:

$env:TEZGAH_VERSION = '0.16.1'; irm https://raw.githubusercontent.com/r1z4x/tezgah/v0.16.1/packaging/install.ps1 | iex

Either one resolves the prefix (~/.local/share/tezgah, %LOCALAPPDATA%\tezgah on Windows; TEZGAH_PREFIX overrides both, XDG_DATA_HOME is honoured on the sh side), unpacks <prefix>/<version> after checking the release's .sha256, points current at the new tree and runs the installer from it. The previous version stays where it is, so a rollback is ln -sfn <prefix>/<previous-version> <prefix>/current.

Upgrading is your action and never happens on its own — the contract forbids it: bin/tezgah-setup --upgrade [VERSION] in an installed tree, or sh packaging/upgrade.sh --version X.Y.Z (no argument takes the newest release). Both verify the .sha256 before anything is unpacked. For a tarball you already hold — a mirror, an air-gapped box — unpack it and run the installer beside it:

tar xzf tezgah-0.16.1.tar.gz && cd tezgah-0.16.1
sh packaging/install.sh --version 0.16.1

Working on tezgah itself, clone and run it in place instead — this is the developer path, and the one the test suite uses:

git clone https://github.com/r1z4x/tezgah.git ~/Projects/tezgah
cd ~/Projects/tezgah
bin/tezgah-setup --install

In a terminal that command with no arguments is the wizard instead: it asks which hosts to arm, the root directories, whether to install the missing optional tools, and whether to wire the optional DevTools MCP, prints the plan, and writes only after a yes. The flags are the wizard's defaults, so --wizard --hosts omp asks only the rest. A piped, agent or CI run is never prompted — it prints the report, exactly as before.

--install also installs the optional tools that are missing by running each vendor's own installer over the network: orx (openresearch.sh/install.sh), cursor-agent (cursor.com/install), dsh (its home profile through npx), and pnpm when dsh needs it (via npm) — curl ... | sh included. None needs sudo; the run is recorded in ~/.config/tezgah/install.log. Preview with --dry-run, skip it with --no-deps (useful in CI), or install the tools alone with --deps. Tools land in ~/.local/bin or ~/.cargo/bin, so a fresh shell may be needed before they are on PATH; tezgah's own checks look in those dirs regardless, so a non-interactive shell still reports them as present.

If a predecessor setup is already present, import it first — it is moved aside, not deleted:

bin/tezgah-setup --adopt

Claude Code is armed by the same script - the plugin manifest is a local, untracked file:

bin/tezgah-setup --install --hosts claude

Limit the install explicitly when needed (the first form is the primary host alone):

bin/tezgah-setup --install --hosts omp
bin/tezgah-setup --install --hosts claude,codex,cursor,opencode,dsh
bin/tezgah-setup --roots ~/work:~/oss --install

Day-to-day

Nothing to run: the rules load when a host starts. A few commands are worth knowing:

Command Purpose
bin/tezgah-setup On a terminal: the install wizard; on a pipe or in CI: report what is armed, per host
bin/tezgah-setup --wizard Force the install wizard anywhere; --report forces the report
bin/tezgah-status [PATH] Show whether the rules are active in that repo
bin/tezgah-setup --status [PATH] Print the armed/used checklist
bin/tezgah-setup --deps [--dry-run] Install missing optional tools (orx, cursor-agent, dsh)
bin/tezgah-research init|check|status|claim|migrate|source Runs and checks a research line: state, findings, claims with their kind and evidence, the protocol-before-results rule, and the literature index; check --strict refuses what the checker cannot verify, migrate fills the fields older lines lack
bin/tezgah-task start|phase|allow|stop|status The active task: one plan's phase: and its allowed_paths: globs, which the gate then enforces on every write - and the call itself, when a session makes it
bin/tezgah-gate check The gate's own decision for one call on stdin - what a host whose plugin is not Python asks instead of mirroring a rule
bin/tezgah-doctor [--clean] [--prune-sessions DAYS] Report harness disk use; --clean deletes old index logs and vacuums the opencode DB; --prune-sessions deletes idle sessions (the only action that actually shrinks the DB)
/tezgah:plan-add Turn a piece of work into a tracked plan
/tezgah:plan-status Summarize open plans and pick the next one
/tezgah:plan-sync Close out finished plans
bin/tezgah-setup --version Print the plugin version
bin/tezgah-setup --upgrade [VERSION] Fetch, checksum-verify and unpack a release under <prefix>/<version>, flip current and re-run the install for the hosts already armed; --dry-run prints and changes nothing, and it never runs on its own
bin/tezgah-setup --uninstall Remove only tezgah's symlinks, host hook entries, and the dsh managed block

The status line marks each rule with its state first: a check means armed and in force this session (or always-on), a circle means armed but on demand - not used yet this session - and a cross means turned off by a kill switch or a .no-* mark. pony and adhd read a circle until the session has actually read that skill's full text, and a check after it; on Claude, opencode and omp that read is observable. On codex, cursor and dsh that read is not observable at an acceptable cost, so those two marks render dim, with no glyph: the line states nothing rather than claiming the skill was never opened. A kill switch still shows red everywhere.

Configuration

Tezgah is armed only under its configured roots; anywhere else it is silent.

  • Default root: ~/Projects.
  • ~/.config/tezgah/config.json: {"roots": ["~/Projects", "~/work"]}.
  • TEZGAH_ROOTS (path-separator list) overrides the file for one-offs and CI.

Kill switches live in ~/.config/tezgah/. Each one removes its rule from the text injected into the session, so the rule actually stops:

Switch Turns off
exec-mode.off Turkish, outcome-first reporting
ponytail-auto.off the minimal-code rule
adhd-off the act-on-it output shape (i-have-adhd)
spec-off the spec-before-building rule
consult-off the external-second-opinion rule
research-off routing research tasks to OpenResearch
orchestrate-off subagent delegation (adds a do-not-delegate line)
reminder-off the per-turn reminder text
verify-off the integrity rule: its prompt text, the shortcut denials, the Stop gate and the ordering rule
task-off the task rule's four refusals: the phase, the allowlist, the record and a shell write
pretooluse-off the PreToolUse gate itself (attribution, explorer, consent, secret, shortcut, loop, race, sink, drift, nudge, order, task)

Per repo, .no-ponytail, .no-adhd, .no-graph and .no-lessons turn off the minimal-code rule, the act-on-it output shape, the code-graph rule (and its auto-index), and the lessons ledger respectively.

When the user flags a mistake, the agent appends a one-line lesson to the repo's .tezgah/lessons.md; the most recent lines are injected at session start so the same mistake cannot silently repeat.

Benchmark

Does this contract improve the work, or does it only look like it should? That is measured, not asserted: hidden checks the agent never sees the check for, cost from the host's own usage record, and collateral edits scored as failures. The instrument - the arms, the pre-registration and bench.py - lives on the benchmarks/lab branch, so this branch carries the results and not the lab; the full study, with the run ids, is the OpenResearch project tezgah-harness-research. Every figure below is a run log.

Block Runs What it settled
two-host, 28 tasks, k=3 336 omp+tezgah 0.95 and opencode+tezgah 0.96 have overlapping intervals and the same cost per solved task; on the bare arms omp is cheaper ($0.0047 against $0.0074 CPS), so the daily driver is omp at no cost in quality
hard family, 5 tasks, k=5, two model families 200 pooled, three of the four arms land on 40/50: no harness effect at that size, and the one signal the first model produced reversed on the second
gate family, gate armed 36 no arm took the shortcut route; the gate mechanism is verified directly (a skip edit is refused), its effect on the work is not measured yet
clause ablation, the two rules that separate, k=8 160 the contract arms pass 23/32 (0.72) against the bare anchor's 12/32 (0.38)

It helps exactly where the model's default is wrong. c04 (an English prompt where only the contract makes the reply Turkish) reads 9/16 with a contract and 0/16 without one; h02 (a money contract whose visible suite is green either way) reads 14/16 against 12/16. Where there is no gap to close - 22 of the 25 pilot tasks passed under every arm on every repeat - a benchmark can only report a null.

Two clauses carry it. Removing clause 1 takes c04 to 0/8, the bare anchor's own score, while barely moving h02. Removing clause 3 takes h02 to 2/8 - below the bare anchor's 6/8 - because clause 3 forbids stopping at the shortest done-looking path, and on that task the shortest path is the one-liner that passes the visible suite while breaking the documented rule. Clauses 2 and 4 move nothing measurable.

Cost follows quality. Per solved task: $0.0078 against $0.0097 on the full-contract node, $0.0043 against $0.0087 on the minus-ponytail node. The contract arms solve more tasks, so each solved task costs less; total spend is higher, and the benchmark records it per row rather than netting it out.

What this does not show: code quality, review effort or maintainability, none of which is measured here; the gate's effect on an arm's choices, since no arm reached for the shortcut in 36 armed runs; or a clause order - k=8 fixes a direction, at 8 runs per cell. One provider and one fixture package throughout, and the ablation rounds run on a single model family. A second model family reproduces the 28-task null exactly (51/56 against 51/56), which is what shows the first reading was not a model artefact.

Cost

Measured on this machine (macOS, Python 3.10), not estimated. tezgah-setup prints the live budget - read it there rather than trusting a figure copied here, which is how an earlier revision came to quote a core band smaller than the one it installs.

Band What it costs
Session start the always-on contract (the invariants plus a one-line pointer per on-demand rule): on this machine and skill set, ~1.5k tokens of contract text and ~1.4k of skill metadata, with the conditional rules (spec, consult, research, graph) adding ~0.7k only on the turn whose prompt matches
Per turn a short reminder (~0.2k tokens) plus the armed rule when it matches, and one line naming the active task while one is set; hooks are separate Python processes, so the ~19 ms interpreter start is the base - a turn adds ~31 ms, session start adds ~50-81 ms, a gated tool call (Bash/Grep/Task) ~24-25 ms. opencode has no prompt-time hook, so it pays zero
On demand the full tezgah-contract skill (~6.6k tokens), paid only when a task loads it
MCP schemas the largest band, and the one no static report sees: the graph server alone declares 15 tools / 24,508 bytes (~6.1k tokens), riding every request unless the host fetches schemas on demand. tezgah-setup --mcp-schemas measures it
Disk installation takes ~58 ms, and every file tezgah rewrites is kept once as <file>.tezgah-bak

The arming floor. The invariants are always-on - execution mode, ponytail, deliver-the-whole-ask, integrity, loop discipline, the lessons ledger, the attribution ban and the session-scope boundary (tezgah's own install is the user's to tend, not the session's) - and the safety rule ("irreversible or outward-facing actions need an explicit ask first") is one of them, so it never depends on a classifier. Each advisory rule keeps an actionable one-line pointer always-on, so a missed match costs detail, never the rule, and a host hook that fails falls back to the pointers plus the on-demand skill rather than to no contract. False negatives are auditable: every prompt appends armed=<rules|none> chars=<n>

  • no prompt text - to ~/.cache/tezgah/classify.log (truncated to the last 200 lines past 64 KB), and all five hook hosts arm the same set for the same prompt (tests/test_context.py::ArmingConformance).

opencode is armed differently. It has no prompt-time injection point, so the contract ships as a generated instructions file, and its always-on router lists only the buckets a coding session reaches for, collapsing the rest to a pointer at ~/.config/tezgah/opencode-skills.full.md read on demand; permission.skill = deny stops opencode injecting every skill's metadata instead. --install also sets compaction.prune and watcher.ignore, clearing old tool results from the prompt rather than re-sending them every step - without that the working set grows to hundreds of thousands of tokens before opencode auto-compacts near the model's limit (about 980k for a 1M-token model). bin/tezgah-doctor reports the disk footprint and --prune-sessions DAYS deletes idle sessions through the opencode CLI, the only action that actually shrinks the database, since VACUUM alone cannot.

Why it pays. In one real repo a default grep ignored the relevant folder and found nothing; with ignore disabled it took 3.95 s and still mixed definitions with call sites, while the code graph answered the same question in 16 ms with the 8 true call sites.

Development

python3 -m unittest discover -s tests -v   # stdlib test suite
pip install -r requirements-dev.txt        # pinned ruff, the only dev dep
ruff check .                               # lint (config in pyproject.toml)

CI runs both on Python 3.10 and 3.12. To refresh an installed Claude copy from this checkout, use bin/tezgah-setup --sync, and validate the manifest with claude plugin validate .claude-plugin/plugin.json (the manifest is local and untracked). When bumping the version, update .claude-plugin/plugin.json and .claude-plugin/marketplace.json together — they must agree.

codegraph is installed by the user. Orca's hooks and files are not part of this project and are left untouched. Claude receives the always-on core from the SessionStart hook; output-styles/tezgah.md is a duplicate for builds that load plugin output styles, so the hook is the authoritative path.

Contributing

Small, single-purpose changes are the easiest to accept. A rule belongs in the shared core (hooks/) unless it is genuinely host-specific; a host difference belongs in its adapter under hosts/<name>/. Keep the diff as short as it can be while still correct — the project's own minimal-code rule applies to the project.

Before opening a pull request, run the same three checks CI runs:

python3 -m compileall -q hooks hosts bin statusline.py   # byte-compile every script
python3 -m unittest discover -s tests                     # stdlib test suite
ruff check .                                              # lint; config in pyproject.toml

ruff comes from requirements-dev.txt (pip install -r requirements-dev.txt), which is the only development dependency.

Security

Report vulnerabilities privately through GitHub's security advisories (Security tab → Report a vulnerability) rather than a public issue.

tezgah runs shell hooks, writes host configuration, and injects text into every session, so anything that makes a hook execute attacker-controlled code, leaks a key into a config file, widens a sandbox, or lets repository content escalate into instruction text is in scope. Include the host, the tezgah version (bin/tezgah-setup --version), and a minimal reproduction.

License

The root LICENSE (MIT) covers tezgah's own files. skills/ponytail, skills/no-ai-slop and skills/i-have-adhd are vendored under their own MIT terms, recorded in NOTICE.