Agent Instructions

September 16, 2026 · View on GitHub

Discovery path: read this file → skim CLAUDE.md for the locked design decisions → check docs/AI_LESSONS.md for a failure that already happened in the area you are about to touch → load only the docs/CODEMAPS/ map matching the area you're touching. Do not load the full source tree blindly — the codemaps exist to keep agent context lean.

Codemap Index

AreaCodemap
High-level topologydocs/CODEMAPS/architecture.md
MCP tools (count in codemap)docs/CODEMAPS/mcp-tools.md
CLI flags / commandsdocs/CODEMAPS/cli.md
Language plugins (count in codemap)docs/CODEMAPS/languages.md
Output formattersdocs/CODEMAPS/formatters.md
Security boundarydocs/CODEMAPS/security.md

Test Runtime Contract

  • The default local quick-gate command is uv run pytest -q.
  • Transition note: main inherits the curated quick-gate pytest.ini at the next release; until then bare uv run pytest -q on main still runs the full suite. When validating on main before that sync, use the comprehensive command explicitly — one command must mean one thing, so the canonical full-suite path is always the explicit tests/ form above.
  • The comprehensive local command is uv run pytest tests/ -q --timeout=120 -m "not e2e and not network and not benchmark"; the explicit marker override restores slow and full-language tests that the quick gate excludes.
  • Do not run either tier serially. Project pytest config enables four xdist workers with work stealing.
  • The quick gate must finish in under 5 minutes. The config enforces --session-timeout=900 and --timeout=30. (Bumped from 300 in v1.13.1 — see docs/POSTMORTEM_v1.13.md § 9.)
  • After edits, run uv run python -m tree_sitter_analyzer --change-impact --format json and follow its verification_command.
  • If test_required is false, do not run tests just to look busy; run the reported non-test verification such as git diff --check.
  • For targeted code feedback, prefer verification_command/test_command; pytest_required and pytest_command are retained for pytest-specific compatibility.
  • For PRs that change Python source, run focused tests with --cov=tree_sitter_analyzer --cov-report=json, then run uv run python scripts/check_patch_coverage.py --base origin/develop --coverage-json coverage.json before pushing. The local patch gate must report no added executable misses; add effective tests instead of waiting for CI Codecov to block the PR.
  • Benchmark-only runs are the exception: use uv run pytest tests/benchmarks/ --benchmark-enable --benchmark-only -n 0 --session-timeout=0.
  • Do not remove or weaken these pytest defaults. They prevent repeated agent mistakes: serial full-suite runs, accidental benchmark execution, hidden hangs, and >5 minute feedback loops.
  • If a test-runtime setting must change, update tests/contracts/test_pytest_runtime_contract.py, explain why the new setting is faster or safer, and prove uv run pytest -q still finishes under 5 minutes. Preserve the comprehensive command above as the broad local path.

CI Test Tier Contract

  • CI must not run exhaustive all-language golden/regression tests on every OS/Python axis. Mark that class with @pytest.mark.full_language.
  • .github/workflows/reusable-test.yml runs full_language tests only on the single Linux coverage axis; every no-coverage matrix job must exclude not full_language.
  • PR feedback uses matrix-profile: pr to run a small representative matrix (Linux coverage/full-language, Linux latest Python, Windows, macOS). Release/hotfix/push validation must keep matrix-profile: full.
  • .github/workflows/test-coverage.yml is manual-only because reusable-test already uploads coverage on the Linux coverage axis. Do not re-enable PR/push triggers unless reusable-test coverage is removed in the same change.
  • .github/workflows/ci.yml owns ordinary PR routing through scripts/ci_route.py and config/ci-routing.yml. Expensive optional checks such as regression, SQL platform compatibility, benchmarks, and broad E2E must be path-routed or manual/scheduled instead of running unconditionally on every PR.
  • For language-plugin changes, rely on --change-impact --change-impact-scope ... during development, run the focused command it reports, then let CI's single full-language axis provide the final cross-language golden gate.

Agent Dogfood Feedback Loop

For non-trivial work, expert agents must use this project as their primary feedback instrument while they work, then preserve the learning in memory:

  1. Before edits: run uv run python -m tree_sitter_analyzer --change-impact --format json to get the affected surface and verification command.
  2. During exploration: prefer TSA queries over blind file scans. Use focused codegraph/query/health commands for the area being changed, and keep the raw command outputs small enough to compare before/after.
  3. After edits: rerun change-impact and the reported verification command. For Python source changes, also run the local patch coverage gate from the Test Runtime Contract.
  4. Memory capture: store a concise JSON record in project memory with branch, task, tools_used, signals, decision, verification, and followups. Use the available memory_store MCP when present; otherwise use the Claude Flow memory CLI (npx @claude-flow/cli@latest memory store --namespace tsa/agent-feedback ...). If neither memory backend is available, include the JSON in the final response so the lead can store it.

Memory records should capture reusable lessons, not logs: benchmark surprises, Codecov/CI failure patterns, query misses, performance bottlenecks, and successful verification recipes.

MCP/CLI Parity Contract

  • Every registered MCP tool must have a CLI access path.
  • Main CLI flags and standalone scripts are guarded by tests/contracts/test_mcp_cli_parity_contract.py.
  • MCP-equivalent CLI handler arguments, required file-path checks, and TOON output are guarded by tests/unit/cli/test_mcp_commands.py.
  • RFC-0022 Phase 0 has one narrow process-local exception: the edit.release_snapshot action, snapshot/generation/lease controls, and a read_existing sequence that requires those controls are exercised through the non-public same-process CLI-handler bridge rather than exposed as unusable cross-invocation CLI operations. Every other action-level CLI path and parameter remains mandatory; parity contracts must encode this exact exception, which does not authorize another tool, a one-shot composition command, or any other waiver.
  • When adding or changing an MCP tool, update the CLI path in the same change and run a real CLI smoke test, for example uv run python -m tree_sitter_analyzer <file> --smart-context --format json.
  • This keeps MCP-only features from becoming invisible to users, CI, and future agents.

注释语言规范(Comment Language Rule)

  • 今后所有新增或修改的代码注释、docstring 一律使用中文(领导 2026-09-05 裁决)。
  • 存量英文注释不做强制翻译;只在本次改动触碰到的行内顺手转换。
  • 提交信息(commit message)仍沿用仓库既有英文约定,不受本条约束。

Codemap-sync mandate

Any change touching one of these registries MUST update the corresponding docs/CODEMAPS/*.md in the same commit:

SurfaceCodemap
tree_sitter_analyzer/mcp/_tool_registry.py — the registered tool-name setdocs/CODEMAPS/mcp-tools.md
tree_sitter_analyzer/cli/**/*.py — the whole add_argument flag set plus installed console-script entry pointsdocs/CODEMAPS/cli.md
tree_sitter_analyzer/languages/<lang>_plugin/*docs/CODEMAPS/languages.md
tree_sitter_analyzer/formatters/*docs/CODEMAPS/formatters.md

The mandate is defined on the surface set, not on a file list: the gate compares the set of registered tool names and the set of CLI flags at HEAD against the staged index, and fires only when a set actually changed. Reordering, renames, comments and docstrings therefore do not trigger it, and a removal does.

Enforced by:

  • scripts/codemap-sync-check.sh (pre-commit hook + Claude PreToolUse soft-nag)
  • test_registered_mcp_tools_have_codemap_parity in tests/contracts/test_mcp_surface_metadata_contract.py — the CI net for mcp-tools.md
  • test_cli_codemap_flag_count_matches_the_real_parser in tests/contracts/test_agent_docs_contract.py — the CI net for cli.md
  • test_codemap_sync_gate_sees_every_registered_mcp_tool / ..._sees_every_cli_flag / ..._watches_the_whole_cli_flag_surface in the same file — the CI net for the gate itself, asserting exact set equality against the authoritative runtime enumerations plus zero unwatched flags

"Self-enforcing" is a claim about the detectors, so it has to be measured, not asserted. Run bash scripts/codemap-sync-check.sh --self-check: it fails unless the gate's static extractor reproduces the authoritative enumerations exactly and no add_argument flag under cli/** falls outside the watch filter. A count > 0 check is not sufficient — a tree whose only match is a stale docstring mention passes it while the detector is dead, and a loose lower bound on a deterministic count is what CLAUDE.md's exact-assertion rule forbids in the first place.

Escape hatch: SKIP_CODEMAP_SYNC=force git commit ..., which bypasses and appends an audit line to $GIT_DIR/codemap-sync-bypass.log. The older SKIP_CODEMAP_SYNC=1 now fails when it would have silenced a real violation, because pre-commit only surfaces output from failing hooks — so a warn-and-pass bypass was invisible and export SKIP_CODEMAP_SYNC=1 disabled the gate for a whole session with zero signal. Bypass is local-only: the CI parity tests above still run.

Why: previously the codemap drifted from 23 → 27 → 30 → 55 tools across 4 months with manual catch-up commits in between. Then the gate itself died — the MCP detector was still matching the pre-facade ("name", SomeTool( shape and the CLI detector was watching argument_parser_builder.py, which holds zero add_argument calls — and cli.md drifted to 295 against a real 324 while every test stayed green, because the hook's own test fixture was a synthetic copy of the old shape. Both the gate and its fixture were rebuilt against the real surface; the contract is self-enforcing for as long as --self-check and the CI nets above stay green, and no longer than that.

Measurement And Claim Rules

Publishing a number is a claim, and these are the ways one goes wrong here:

  • Name the state the number was taken in. Cold and warm differ by an order of magnitude for indexed work (health action=project: ~49 s cold, ~4.5 s warm on a mid-size repository), and they describe different user situations — a fresh checkout, a CI run, and a new agent session all start cold. Report the pair, not one number.
  • Name the machine and the corpus. A latency or size figure without them is not reproducible and cannot be compared to a later one.
  • Measure the term that dominates the cost. When a claim compares two tools, state the cost model first; a byte comparison and a turn comparison produce different answers, and the wrong model has already withdrawn one published ratio in this repository.
  • Correct the record where it was published. Fix the issue title, not only a reply, because a stale title keeps misinforming readers who never open the comments.

AI Lessons Mandate

A failure a later agent could repeat MUST become an entry in docs/AI_LESSONS.md in the same pull request that fixes it. "Same pull request" is the whole point: a lesson written later is a lesson that competes with the next task for attention, and it loses.

Write an entry when any of these is true:

  • A gate failed and the cause was not a typo — a wrong assumption, a missing verification step, or a check that could not see the thing it watched.
  • A claim was published (issue, PR, README, comment) and then corrected.
  • A measurement was retaken because the first one did not describe what it claimed to describe.
  • The same defect was found twice, or found by hand in one place and by a gate in another.

Each entry has three sections, in this order: ### Context (what happened, with the measured numbers), ### Lessons learned (numbered, reusable), and ### Required guardrail (what prevents the repeat, naming the file or command that does it).

Enforced by tests/contracts/test_agent_docs_contract.py:

  • test_agents_md_routes_agents_to_the_lessons_file — the discovery path above must link the file, so an agent reads it without being told.
  • test_every_lesson_entry_is_structured — every ## YYYY-MM — Title entry carries the three sections in order, so an entry cannot be a bare anecdote.
  • test_every_guardrail_names_a_live_artifact — every repository path a ### Required guardrail section names must exist. This is the part that rots: a guardrail citing a test file that was later renamed or deleted reads as protection while protecting nothing.

Why: the file already existed and was referenced from nowhere, so its one entry had no reader. The recurring failures were instead captured in an external memory service that the next agent has no reason to query, which is why the same class of mistake reappeared across sessions. A lesson that is not on the discovery path and not checked by a gate is a note to self, not a guardrail.

GitFlow Branching Mandate

Hard requirement. Every branch operation MUST follow GITFLOW.md (中文 / 日本語). The matrix below is the full surface — anything else is a violation.

OperationBranch nameCut fromTarget (via PR)
Featurefeature/<name>developdevelop
Release preprelease/v<X.Y.Z>developmain and back into develop
Production hotfixhotfix/<name>mainmain and back into develop
Chore / docs / test (non-release)chore/<name> · docs/<name> · test/<name> · fix/<name>developdevelop

Agents MUST NEVER:

  • Push directly to main or develop — open a PR from a properly-named branch instead
  • Open a PR targeting main from anything other than release/v* or hotfix/*
  • Cut a feature/* from main — it must come from develop
  • Force-push or delete main, develop, or any released tag (v*)
  • Skip the develop merge-back after a release or hotfix is published
  • Use hotfix/* for non-release fixes. Pushing to hotfix/* auto-triggers hotfix-automation.yml → PyPI publish with a version bump. Reserve hotfix/* for "production is broken, needs a same-day patch release". For a generic bug fix, CI YAML repair, workflow tweak, etc., use fix/* · ci/* · chore/* against develop.

Release flow (every detail in GITFLOW.md):

  1. release/v<X.Y.Z> cut from develop
  2. Push triggers .github/workflows/release-automation.yml → test → build → publish to PyPI
  3. After PyPI is live: PR release/v*main, tag v<X.Y.Z> on main, GitHub Release
  4. Merge release/v*develop to bring back release-prep commits (version bumps, CHANGELOG)
  5. Delete release/v<X.Y.Z> from origin

Enforcement layers:

  • .github/workflows/gitflow-guard.yml — CI fails on a PR whose head→base pair violates the matrix
  • GitHub branch protection on main (PR + status checks required, no force-push)
  • test_gitflow_documentation_is_present in tests/governance/test_gitflow_contract.py — guards against GITFLOW.md being deleted or AGENTS.md losing the link

Escape hatch: none. If GITFLOW.md itself needs to change, that's a PR like any other — argue the case in the description and update the matrix + tests in the same commit.

Anti-Patterns (from v1.13 postmortem)

These are failure modes the project has already paid for during the v1.13.0 / v1.13.1 release lifecycle. The full incident catalogue is in docs/POSTMORTEM_v1.13.md. The rules below are the standing defense — break them and you reintroduce a documented bug.

Each rule cites a postmortem section so you can read the original incident.

  1. No skip-and-paper-over. Every new pytest.skip* MUST include a tracking reference in its reason= text — issue number (#123 / GH-123), POSTMORTEM, or tracked: .... The test_skips_have_tracking_references contract enforces a soft budget; new untracked skips push you over it. (Postmortem § 1.)

  2. YAML/Actions changes go through actionlint. Edits to .github/workflows/*.yml or .github/actions/**/action.yml are validated by the actionlint pre-commit hook. Don't bypass it with --no-verify; fix the actual lint. (Postmortem § 3, § 4.)

  3. shell: powershell blocks are ASCII-only. The scripts/check_ps_ascii.py pre-commit hook fails commits that put emoji/Unicode inside an inline Windows PowerShell run: block. If you really need Unicode, switch the step to shell: pwsh (PowerShell Core, UTF-8 by default). (Postmortem § 5.)

  4. tree-sitter grammar snapshots regen on Linux only. The tree-sitter-c-sharp 0.23.1 wheel ships different compiled grammars per OS (macOS: 229 csharp nodes; Linux: 234, including C# 12 collection-expression nodes). Always commit snapshots produced on Linux CI / a Linux container — never from a local mac. (Postmortem § 6.)

  5. Before using a 3.11+ stdlib symbol, check the floor. requires-python = ">=3.10" is the contract. tomllib, from datetime import UTC, typing.Self, structural-pattern-match exhaustiveness checks, etc. are 3.11+ and break the Py3.10 matrix silently. The test_python_version_floor_is_consistent contract guards the ruff/mypy/pyproject alignment. (Postmortem § 7.)

  6. develop must not rot behind main. Before merging release/v* back into develop, run git log --oneline develop..main and verify nothing on main is being orphaned. If develop has fallen far behind, fast-forward / rebuild it from main before the merge-back. (Postmortem § 8.)

  7. Release-prep PRs >30 commits: prefer rebase-merge over squash. Squashing a large consolidation PR makes git bisect useless on main for every bug it introduced. Use squash only for short feature PRs. (Postmortem § 10.)

  8. Never lower --session-timeout in pytest config. v1.13 release CI repeatedly capped failures at 10 while the actual count was ~85, forcing multi-hour debug cycles. --session-timeout=900 is the locked floor; test_default_pytest_runtime_contract_is_locked enforces it. (Postmortem § 9.)

These rules are guarded by tests in tests/governance/test_postmortem_guards.py and tests/contracts/test_pytest_runtime_contract.py:

  • test_postmortem_v1_13_doc_exists
  • test_agents_md_documents_v1_13_anti_patterns
  • test_check_ps_ascii_script_is_present_and_pre_commit_wired
  • test_actionlint_is_wired_into_pre_commit
  • test_no_powershell_blocks_contain_non_ascii
  • test_skips_have_tracking_references
  • test_python_version_floor_is_consistent
  • test_default_pytest_runtime_contract_is_locked (also guards rule 8)
  • test_readme_mcp_tool_count_matches_registry

Why these are at the bottom of AGENTS.md: they're the rules an agent is most likely to forget on a fast cycle, and the cost of forgetting each was measured in CI hours during v1.13. Keep them visible.