Agent Instructions
September 16, 2026 · View on GitHub
Discovery path: read this file → skim
CLAUDE.mdfor the locked design decisions → checkdocs/AI_LESSONS.mdfor a failure that already happened in the area you are about to touch → load only thedocs/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
| Area | Codemap |
|---|---|
| High-level topology | docs/CODEMAPS/architecture.md |
| MCP tools (count in codemap) | docs/CODEMAPS/mcp-tools.md |
| CLI flags / commands | docs/CODEMAPS/cli.md |
| Language plugins (count in codemap) | docs/CODEMAPS/languages.md |
| Output formatters | docs/CODEMAPS/formatters.md |
| Security boundary | docs/CODEMAPS/security.md |
Test Runtime Contract
- The default local quick-gate command is
uv run pytest -q. - Transition note:
maininherits the curated quick-gatepytest.iniat the next release; until then bareuv run pytest -qonmainstill runs the full suite. When validating onmainbefore that sync, use the comprehensive command explicitly — one command must mean one thing, so the canonical full-suite path is always the explicittests/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=900and--timeout=30. (Bumped from 300 in v1.13.1 — seedocs/POSTMORTEM_v1.13.md§ 9.) - After edits, run
uv run python -m tree_sitter_analyzer --change-impact --format jsonand follow itsverification_command. - If
test_requiredisfalse, do not run tests just to look busy; run the reported non-test verification such asgit diff --check. - For targeted code feedback, prefer
verification_command/test_command;pytest_requiredandpytest_commandare retained for pytest-specific compatibility. - For PRs that change Python source, run focused tests with
--cov=tree_sitter_analyzer --cov-report=json, then runuv run python scripts/check_patch_coverage.py --base origin/develop --coverage-json coverage.jsonbefore 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 proveuv run pytest -qstill 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.ymlrunsfull_languagetests only on the single Linux coverage axis; every no-coverage matrix job must excludenot full_language.- PR feedback uses
matrix-profile: prto run a small representative matrix (Linux coverage/full-language, Linux latest Python, Windows, macOS). Release/hotfix/push validation must keepmatrix-profile: full. .github/workflows/test-coverage.ymlis 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.ymlowns ordinary PR routing throughscripts/ci_route.pyandconfig/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:
- Before edits: run
uv run python -m tree_sitter_analyzer --change-impact --format jsonto get the affected surface and verification command. - 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.
- 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.
- Memory capture: store a concise JSON record in project memory with
branch,task,tools_used,signals,decision,verification, andfollowups. Use the availablememory_storeMCP 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_snapshotaction, snapshot/generation/lease controls, and aread_existingsequence 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:
| Surface | Codemap |
|---|---|
tree_sitter_analyzer/mcp/_tool_registry.py — the registered tool-name set | docs/CODEMAPS/mcp-tools.md |
tree_sitter_analyzer/cli/**/*.py — the whole add_argument flag set plus installed console-script entry points | docs/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_parityintests/contracts/test_mcp_surface_metadata_contract.py— the CI net formcp-tools.mdtest_cli_codemap_flag_count_matches_the_real_parserintests/contracts/test_agent_docs_contract.py— the CI net forcli.mdtest_codemap_sync_gate_sees_every_registered_mcp_tool/..._sees_every_cli_flag/..._watches_the_whole_cli_flag_surfacein 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 — Titleentry 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 guardrailsection 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.
| Operation | Branch name | Cut from | Target (via PR) |
|---|---|---|---|
| Feature | feature/<name> | develop | develop |
| Release prep | release/v<X.Y.Z> | develop | main and back into develop |
| Production hotfix | hotfix/<name> | main | main and back into develop |
| Chore / docs / test (non-release) | chore/<name> · docs/<name> · test/<name> · fix/<name> | develop | develop |
Agents MUST NEVER:
- Push directly to
mainordevelop— open a PR from a properly-named branch instead - Open a PR targeting
mainfrom anything other thanrelease/v*orhotfix/* - Cut a
feature/*frommain— it must come fromdevelop - Force-push or delete
main,develop, or any released tag (v*) - Skip the
developmerge-back after a release or hotfix is published - Use
hotfix/*for non-release fixes. Pushing tohotfix/*auto-triggershotfix-automation.yml→ PyPI publish with a version bump. Reservehotfix/*for "production is broken, needs a same-day patch release". For a generic bug fix, CI YAML repair, workflow tweak, etc., usefix/*·ci/*·chore/*againstdevelop.
Release flow (every detail in GITFLOW.md):
release/v<X.Y.Z>cut fromdevelop- Push triggers
.github/workflows/release-automation.yml→ test → build → publish to PyPI - After PyPI is live: PR
release/v*→main, tagv<X.Y.Z>on main, GitHub Release - Merge
release/v*→developto bring back release-prep commits (version bumps, CHANGELOG) - 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_presentintests/governance/test_gitflow_contract.py— guards againstGITFLOW.mdbeing deleted orAGENTS.mdlosing 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.
-
No skip-and-paper-over. Every new
pytest.skip*MUST include a tracking reference in itsreason=text — issue number (#123/GH-123),POSTMORTEM, ortracked: .... Thetest_skips_have_tracking_referencescontract enforces a soft budget; new untracked skips push you over it. (Postmortem § 1.) -
YAML/Actions changes go through
actionlint. Edits to.github/workflows/*.ymlor.github/actions/**/action.ymlare validated by theactionlintpre-commit hook. Don't bypass it with--no-verify; fix the actual lint. (Postmortem § 3, § 4.) -
shell: powershellblocks are ASCII-only. Thescripts/check_ps_ascii.pypre-commit hook fails commits that put emoji/Unicode inside an inline Windows PowerShellrun:block. If you really need Unicode, switch the step toshell: pwsh(PowerShell Core, UTF-8 by default). (Postmortem § 5.) -
tree-sitter grammar snapshots regen on Linux only. The
tree-sitter-c-sharp 0.23.1wheel 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.) -
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. Thetest_python_version_floor_is_consistentcontract guards the ruff/mypy/pyproject alignment. (Postmortem § 7.) -
developmust not rot behindmain. Before mergingrelease/v*back into develop, rungit log --oneline develop..mainand 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.) -
Release-prep PRs >30 commits: prefer rebase-merge over squash. Squashing a large consolidation PR makes
git bisectuseless on main for every bug it introduced. Use squash only for short feature PRs. (Postmortem § 10.) -
Never lower
--session-timeoutin pytest config. v1.13 release CI repeatedly capped failures at 10 while the actual count was ~85, forcing multi-hour debug cycles.--session-timeout=900is the locked floor;test_default_pytest_runtime_contract_is_lockedenforces 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_existstest_agents_md_documents_v1_13_anti_patternstest_check_ps_ascii_script_is_present_and_pre_commit_wiredtest_actionlint_is_wired_into_pre_committest_no_powershell_blocks_contain_non_asciitest_skips_have_tracking_referencestest_python_version_floor_is_consistenttest_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.