# Contributor handoff — github/spec-kit

> Canonical page: https://codevetter.com/unpack/spec-kit/agent-handoff

# Contributor handoff — github/spec-kit

Scanned commit 175adbcd826befdccce8c60faa07ae3d8bbc8d87 (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

This repo is engineered for agent contributors: AGENTS.md gives a 5-step integration quickstart and design/cli.md defines a strict file-naming contract (`command_<name>.py`, `_command_<name>_<phase>.py`, `_commands.py`, mirrored tests). The safe-edit boundary is the registry/declarative layer — prefer adding a subpackage, extension.yml, preset.yml, or workflow.yml over touching the three giant domain modules. Non-negotiables: all installed files go through manifest recording, no `shell=True`, preserve root-level re-exports and `_commands` forwarders (tests monkeypatch them), honor the dual core_pack/repo-root asset lookup, and keep sh/ps/py script behavior identical.
- Read `AGENTS.md` first — it is the authoritative guide for adding integrations (base-class table, required class attrs, registry insertion, context-file ownership by the agent-context extension, test file naming), and `design/cli.md` before adding or moving any CLI command. — [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md) [design/cli.md#L1-30](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L1-L30)
- Module naming contract: real CLI commands live in `command_<name>.py` (the only files that may register handlers), private phases in `_command_<name>_<phase>.py`, group apps/registration in `_commands.py`, nested CLI namespaces get matching directories — tests mirror under `tests/specify_cli/` as `test_command_<name>.py`. — [design/cli.md#L32-160,186-232](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L32-160,186-L232)
- Build/test commands: `uv sync --extra test` then `uv run pytest`; lint with `uvx ruff check src tests`; markdown via markdownlint-cli2; CI matrix is ubuntu/windows/macos × Python 3.13/3.14 — keep tests passing on Windows (no POSIX-only assumptions). — [pyproject.toml#L59-74](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L59-L74) [.github/workflows/test.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/.github/workflows/test.yml) [.github/workflows/lint.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/.github/workflows/lint.yml)
- Never introduce `shell=True`, `os.system`, or `popen2` — ruff rules S602/S604/S605 are extended-selected specifically to make any deviation require a visible `# noqa`. — [pyproject.toml#L85-93](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L85-L93)
- Route every file an integration writes through `manifest.record_file()`/`record_existing()` in `setup()` — files not recorded are invisible to uninstall, and files written directly bypass the hash-check that protects user modifications. — [src/specify_cli/integrations/manifest.py#L104-120](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/manifest.py#L104-L120) [src/specify_cli/integrations/base.py#L986-995](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L986-L995) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Preserve compatibility surfaces: root-package re-exports (`workflow_add`, `check_tool`, `load_init_options`, `_require_specify_project`, …) and `_commands.py` call-time forwarders exist because tests monkeypatch `specify_cli.<name>`; removing them breaks the suite without breaking imports. — [src/specify_cli/__init__.py#L533-600](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L533-L600) [src/specify_cli/extensions/_commands.py#L37-56](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/_commands.py#L37-L56) [design/cli.md#L98-112](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L98-L112)
- Asset lookups must handle both layouts: wheel installs have `specify_cli/core_pack/` (force-included at build time) while source checkouts/edits do not — always go through `_locate_core_pack()`/`_repo_root()`/`_locate_bundled_*` rather than `Path(__file__).parent` arithmetic (a past regression broke command discovery this way). — [src/specify_cli/_assets.py#L13-32](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_assets.py#L13-L32) [src/specify_cli/extensions/__init__.py#L82-99](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py#L82-L99) [pyproject.toml#L29-57](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L29-L57)
- `init-options.json` has tri-state semantics — missing file (legacy fallback to detect-all-agents), malformed/empty `ai` (fail closed, register nothing), valid string key — use `resolve_active_agent_for_registration`/`load_init_options`/`is_ai_skills_enabled` rather than reading the JSON ad hoc; a dangling symlink must fail closed, not fall back. — [src/specify_cli/_init_options.py#L49-83](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_init_options.py#L49-L83)
- Path safety helpers already exist — reuse them instead of rolling new checks: `_validate_rel_path`/`_ensure_safe_manifest_*` (manifest), `_ensure_safe_shared_directory`/`_ensure_safe_shared_destination`/`_write_shared_*` (shared_infra), `relative_extension_path_violation` (_utils), symlink-component checks in workflows/_commands. — [src/specify_cli/integrations/manifest.py#L29-102](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/manifest.py#L29-L102) [src/specify_cli/shared_infra.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/shared_infra.py) [src/specify_cli/workflows/_commands.py#L118-121](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py#L118-L121)
- Every new integration needs `tests/integrations/test_integration_<key>.py` (hyphens→underscores in the filename); multi-install-safe integrations must use static unique dirs, and registry tests enforce those invariants — run the focused file plus `tests/test_agent_config_consistency.py`. — [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md) [tests/integrations/test_integration_claude.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/integrations/test_integration_claude.py) [tests/test_agent_config_consistency.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/test_agent_config_consistency.py)
- Env-var surface an agent must know: `SPECIFY_INIT_DIR` retargets every project-scoped command; `SPECKIT_INTEGRATION_DEFAULT` changes the non-interactive init default; `SPECKIT_INTEGRATION_<KEY>_EXECUTABLE`/`_EXTRA_ARGS` and `SPECKIT_WORKFLOW_RUN_ID` exist — tests strip all `SPECIFY_*`/`SPECKIT_INTEGRATION_*` vars via conftest, so set them explicitly in tests. — [src/specify_cli/__init__.py#L541-561](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L541-L561) [src/specify_cli/_agent_config.py#L20-50](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_agent_config.py#L20-L50) [tests/conftest.py#L153-170](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/conftest.py#L153-L170) [src/specify_cli/workflows/engine.py#L1021-1028](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/engine.py#L1021-L1028)
- Command templates must stay tri-script: any `templates/commands/*.md` change that references a helper script needs behaviorally equivalent `sh`/`ps`/`py` entries in `scripts:` frontmatter and the parity tests (`test_*_python_parity.py`) pin the stdout contract (`FEATURE_DIR:`, `AVAILABLE_DOCS:`, `--json` shapes). — [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md) [templates/commands/plan.md#L11-16](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/templates/commands/plan.md#L11-L16) [tests/test_setup_plan_python_parity.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/test_setup_plan_python_parity.py)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
