# github/spec-kit — codebase unpack

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

This is a deterministic Repo Unpack scan of github/spec-kit — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 175adbcd826befdccce8c60faa07ae3d8bbc8d87 (2026-09-22)
- Last upstream commit: refactor: reorganize bundle CLI commands (#4663)
- Generated by codevetter 1.12.0 on 2026-09-22
- 714 files scanned · 10.0 MB

## Languages

- Python: 457 files
- Markdown: 152 files
- YAML: 51 files
- JSON: 17 files
- Shell: 12 files
- CSS: 1 files
- TOML: 1 files

## Repository structure

- tests/ — 257 files
- src/ — 191 files
- extensions/ — 59 files
- docs/ — 45 files
- .github/ — 41 files
- presets/ — 29 files
- scripts/ — 18 files
- templates/ — 16 files

Probable source entrypoint: `pyproject.toml` (inferred from the tree, not upstream docs)

## Package

- pyproject.toml: specify-cli v1.0.10.dev0 — 4 declared dependencies

## Structural health signals

Average score 6.7/10 across 96 files; 31 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 175adbcd826b (2026-09-22) refactor: reorganize bundle CLI commands (#4663)
- b9e7389d1414 (2026-09-21) refactor: organize integration CLI commands (#4662)
- c0d041dfa08e (2026-09-21) refactor: organize preset CLI commands (#4657)
- 0aae991b805a (2026-09-22) fix(integrations): declare OpencodeIntegration multi_install_safe (#4620) (#4625)
- 06de4ff52900 (2026-09-21) [preset] Update Autonomous Run Governance preset to v0.4.4 (#4586)

## Overview

Spec Kit is GitHub's toolkit for Spec-Driven Development (SDD): the Python package `specify-cli` ships a `specify` CLI that scaffolds a `.specify/` project directory containing prompt templates, helper scripts (bash/PowerShell/Python), workflows, and per-agent 'integration' files so that ~40 supported AI coding agents (Copilot, Claude, Gemini, Codex, ...) expose `/speckit.*` commands such as specify → plan → tasks → implement. Beyond init, the CLI manages a full package ecosystem — integrations, extensions, presets, workflows, and bundles — resolved through local and remote catalogs, plus a YAML workflow engine and an agent-native event/hook dispatcher. Distinctively, the wheel bundles all assets into `specify_cli/core_pack/` so `specify init` works fully offline, and every installed file is tracked by SHA-256 manifest so uninstall never clobbers user edits.

## System map

The system is a single Python CLI (`specify`, entrypoint `specify_cli:main`) built on Typer/Rich. The root app in `src/specify_cli/__init__.py` wires together command groups that each own a subpackage: init/check/version/self, integration, extension, preset, workflow, bundle, artifact, and event. All distributable content (templates, scripts, extensions, workflows, presets, catalogs) is bundled into the wheel via hatchling force-include as `core_pack`, with a repo-root fallback for source checkouts. State is stored inside each target project under `.specify/` (init-options.json, integration.json, extensions.yml, manifests, workflow run state). External integrations are limited to HTTPS downloads (GitHub API/releases, catalog JSON, extension archives) and subprocess invocation of agent CLIs.
- The console script `specify` maps to `specify_cli:main`; `main()` forces UTF-8 stdout/stderr on Windows then invokes the root Typer `app` defined in `src/specify_cli/__init__.py`. — [pyproject.toml#L19-20](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L19-L20) [src/specify_cli/__init__.py#L602-617](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L602-L617)
- The root Typer app registers every command group centrally: `init` via `commands/init.py`, `check`/`version`/`self` locally, plus `extension`, `integration`, `event`, `preset`, `artifact`, `bundle`, and `workflow` groups imported from their `_commands.py`/`commands/*.py` modules and attached via `register(app)` calls. — [src/specify_cli/__init__.py#L392-600](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L392-L600) [src/specify_cli/commands/init.py#L259-343](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L259-L343)
- The wheel bundles all runtime assets into `specify_cli/core_pack/` via `[tool.hatch.build.targets.wheel.force-include]`: page templates, `templates/commands`, bash/powershell/python scripts, bundled extensions (git, agent-context, assess, bug), workflows (speckit, bugfix, assess), presets (lean, constitution-sync), and bundle catalog snapshots — making `specify init` work without network access. — [pyproject.toml#L26-57](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L26-L57)
- `_assets.py` is the stdlib-only base of the dependency graph: `_locate_core_pack()` finds the wheel-bundled `core_pack/` directory and `_repo_root()` falls back to the source checkout; `_locate_bundled_extension|workflow|preset` check core_pack first, then `extensions/`, `workflows/`, `presets/` under the repo root. — [src/specify_cli/_assets.py#L13-101](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_assets.py#L13-L101)
- `INTEGRATION_REGISTRY` in `integrations/__init__.py` is the single source of truth for AI-agent integrations; `_register_builtins()` instantiates and registers ~40 integrations (claude, copilot, gemini, codex, kilocode, cursor-agent, kiro-cli, generic, ...) in alphabetical order, with duplicate or empty keys rejected. — [src/specify_cli/integrations/__init__.py#L28-150](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/__init__.py#L28-L150) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Command-group layout is codified in `design/cli.md`: each real CLI command lives in `command_<name>.py`, private phases in `_command_<name>_<phase>.py`, group registration in `_commands.py`, and nested CLI namespaces (e.g. `extension catalog add`) map to matching subdirectories — `extensions/` is the reference implementation. — [design/cli.md#L30-160](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L30-L160) [src/specify_cli/extensions/_commands.py#L1-47](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/_commands.py#L1-L47) [src/specify_cli/extensions/catalog/command_add.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/catalog/command_add.py)
- The workflow subsystem pairs `workflows/engine.py` (WorkflowDefinition parsing, `validate_workflow`, WorkflowEngine orchestration, RunState persistence) with `workflows/__init__.py`'s `STEP_REGISTRY`, which registers 11 built-in step types: command, shell, prompt, gate, if, init, slot, switch, while, do-while, fan-out, fan-in. — [src/specify_cli/workflows/engine.py#L34-145](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/engine.py#L34-L145) [src/specify_cli/workflows/__init__.py#L45-82](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/__init__.py#L45-L82)
- Authentication is opt-in and registry-based: `AUTH_REGISTRY` holds GitHubAuth and AzureDevOpsAuth providers; credentials are only sent when the user creates `~/.specify/auth.json` mapping hosts to providers, and `_github_http.py` restricts `Authorization` headers (from GITHUB_TOKEN/GH_TOKEN) to a fixed GITHUB_HOSTS allowlist. — [src/specify_cli/authentication/__init__.py#L1-50](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/authentication/__init__.py#L1-L50) [src/specify_cli/_github_http.py#L1-40](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_github_http.py#L1-L40)
- Project-scoped commands resolve the project root through `_require_specify_project()`, which requires a `.specify/` directory in the cwd unless the `SPECIFY_INIT_DIR` env var overrides it — this single chokepoint applies uniformly to integration, extension, preset, workflow, bundle, and artifact subcommands. — [src/specify_cli/__init__.py#L541-562](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L541-L562) [src/specify_cli/_project.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_project.py)
- The package targets Python >=3.11 with deps typer, click, rich, platformdirs, readchar, pyyaml, packaging, pathspec, json5; tests run under pytest (`testpaths = ["tests"]`) and CI exercises Linux/macOS/Windows on Python 3.13/3.14. — [pyproject.toml#L6-17,65-74](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L6-17,65-L74) [.github/workflows/test.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/.github/workflows/test.yml)
- Distribution is a PyPI wheel (`specify-cli`) typically installed via `uv tool install specify-cli`; releases are cut by pushing `v*` tags which trigger `.github/workflows/release.yml` to build notes and create the GitHub release. — [README.md#L39-47](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/README.md#L39-L47) [.github/workflows/release.yml#L1-30](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/.github/workflows/release.yml#L1-L30)
- Self-version reporting uses `importlib.metadata.version("specify-cli")` first and falls back to reading `pyproject.toml` from the repo root, returning "unknown" when neither works. — [src/specify_cli/_assets.py#L104-121](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_assets.py#L104-L121) [src/specify_cli/_version.py#L55-75](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_version.py#L55-L75)

## Feature catalog

The CLI surface splits into project bootstrapping (`init`, `check`, `version`, `self check|upgrade`), four parallel package managers (`integration`, `extension`, `preset`, `bundle`) each with add/remove/list/info/search/enable/disable/update and nested `catalog` commands, a workflow engine front-end (`workflow run/resume/status/...` plus `step` and `overlay` groups), a read-only `artifact` introspection group, and an `event run` target used by generated hook dispatchers. On the content side it ships ten core SDD command templates, four bundled extensions, three bundled workflows, and four repo presets.
- `specify init [NAME]|.|--here` scaffolds a project with flags `--script sh|ps|py`, `--integration <key>`, `--integration-options`, `--preset`, repeatable `--extension` (bundled name, local path, or HTTPS URL), `--trust-extension-urls`, `--force`, `--non-interactive`, `--ignore-agent-tools`; several legacy flags (`--skip-tls`, `--github-token`, `--offline`, `--debug`) remain as hidden no-ops. — [src/specify_cli/commands/init.py#L259-381](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L259-L381)
- `specify check` iterates `AGENT_CONFIG` and runs `check_tool` (shutil.which-style probe) for each `requires_cli` agent, marks IDE-based agents as skipped, and also probes `code`/`code-insiders`. — [src/specify_cli/__init__.py#L398-436](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L398-L436) [src/specify_cli/_utils.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_utils.py)
- `specify version` prints a Rich info panel; `--features` emits stable capability flags (e.g. `workflow_catalog`, `integration_upgrade_command`) and `--json` requires `--features`. `specify self check|upgrade` lives in `_version.py` and compares the installed distribution version against the latest GitHub release tag. — [src/specify_cli/__init__.py#L439-512](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L439-L512) [src/specify_cli/_version.py#L93-145](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_version.py#L93-L145)
- `specify integration` is a command group (app `integration_app`) with command modules for install, uninstall, list, status, switch, use, upgrade, info, search, scaffold, plus a nested `catalog` group (add/list/remove). — [src/specify_cli/integrations/_commands.py#L21-47](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/_commands.py#L21-L47) [src/specify_cli/integrations/command_install.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/command_install.py) [src/specify_cli/integrations/command_scaffold.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/command_scaffold.py) [src/specify_cli/integrations/catalog/command_add.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/catalog/command_add.py)
- `specify extension add` supports `--dev` (local directory), `--from <url>` (external archive with an HTTPS-or-localhost check and a default-deny trust prompt), `--force`, and `--priority`; the group also has remove, list, info, search, enable, disable, set-priority, update, and `catalog` subcommands. — [src/specify_cli/extensions/command_add.py#L19-100](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/command_add.py#L19-L100) [src/specify_cli/extensions/_commands.py#L31-35](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/_commands.py#L31-L35)
- `specify preset` manages template-override packs with the same verb set (add/remove/list/info/search/enable/disable/set-priority/update) plus `resolve` (shows the composed resolution stack) and a nested `catalog` group. — [src/specify_cli/presets/_commands.py#L12-99](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/presets/_commands.py#L12-L99) [src/specify_cli/presets/command_resolve.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/presets/command_resolve.py)
- `specify workflow` exposes run, resume, status, list, add, remove, update, enable, disable, search, info, resolve plus nested `catalog`, `step` (with its own `catalog`), and `overlay` groups; `workflow run` accepts an installed workflow ID or a local YAML path. — [src/specify_cli/workflows/_commands.py#L36-68,1308-1320](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py#L36-68,1308-L1320)
- `specify bundle` packages/installs role-based bundles with verbs init, add, install, build, validate, list, info, search, remove, update and a nested `catalog` group; bundled catalog JSON ships `bugfix` and `assess` first-party bundles. — [src/specify_cli/bundles/_commands.py#L20-163](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/bundles/_commands.py#L20-L163) [src/specify_cli/bundles/command_build.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/bundles/command_build.py) [bundles/catalog.json](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/bundles/catalog.json)
- `specify artifact list|info` is a thin read-only introspection group over `ArtifactCatalog` (commands/templates/scripts/hooks), emitting strict JSON envelopes on stdout/stderr and documented in `docs/reference/artifacts.md`. — [src/specify_cli/artifacts/_commands.py#L1-40](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/artifacts/_commands.py#L1-L40) [src/specify_cli/artifacts/__init__.py#L1-37](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/artifacts/__init__.py#L1-L37) [docs/reference/artifacts.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/docs/reference/artifacts.md)
- `specify event run <command_name> <event_name> [timeout]` is the dispatcher target for agent-native hooks: it reads a JSON payload from stdin capped at 1 MiB (counting encoded bytes via `sys.stdin.buffer`), then calls `resolve_and_run_event_command` from `events.py`. — [src/specify_cli/commands/event.py#L13-60](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/event.py#L13-L60) [src/specify_cli/events.py#L59-67](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/events.py#L59-L67)
- Ten core SDD command templates ship under `templates/commands/`: specify, plan, tasks, implement, converge, analyze, clarify, constitution, checklist, taskstoissues — mirrored by `SKILL_DESCRIPTIONS` in the package root. — [templates/commands/specify.md#L1-20](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/templates/commands/specify.md#L1-L20) [src/specify_cli/__init__.py#L378-389](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L378-L389)
- Bundled first-party content: extensions `git` (branch workflow + hooks), `agent-context` (manages CLAUDE.md/AGENTS.md sections), `assess` (idea triage), `bug` (assess→fix→test), plus `template`/`selftest` scaffolds; workflows `speckit` (specify→plan→tasks→implement with gates), `bugfix`, `assess`; presets `lean`, `constitution-sync`, `scaffold`, `self-test`. — [extensions/git/extension.yml#L18-41](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/git/extension.yml#L18-L41) [extensions/agent-context/extension.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/agent-context/extension.yml) [workflows/speckit/workflow.yml#L39-74](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/workflows/speckit/workflow.yml#L39-L74) [presets/lean/preset.yml#L15-46](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/presets/lean/preset.yml#L15-L46)
- Per-agent command output supports four layouts selected by base class: Markdown files (MarkdownIntegration), TOML (TomlIntegration, e.g. gemini), YAML recipes (YamlIntegration), and skill directories `speckit-<name>/SKILL.md` (SkillsIntegration, e.g. claude/codex); `invoke_separator` switches `/speckit.plan` vs `/speckit-plan` spellings. — [src/specify_cli/integrations/base.py#L1-12,1079-1165](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L1-12,1079-L1165) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)

## Data flow

Inputs enter through Typer commands or content manifests (extension.yml, preset.yml, workflow.yml, bundle.yml, catalog JSON). `specify init` resolves an integration from `INTEGRATION_REGISTRY`, writes `.specify/init-options.json`, renders command templates with per-agent placeholders into the agent's config directory, and copies shared scripts/templates into `.specify/` — every written file hash-recorded in an `IntegrationManifest`. Extensions and presets layer on top via `CommandRegistrar` rendering and a priority-resolved template stack; hooks land in `.specify/extensions.yml`. Workflow runs persist resumable state under `.specify/workflows/runs/<run_id>/`. Outbound data is confined to HTTPS downloads with byte caps and optional SHA-256 pins, and subprocess calls to agent CLIs.
- `specify init` persists the user's choices to `.specify/init-options.json` via `save_init_options`; `load_init_options`/`is_ai_skills_enabled`/`resolve_active_agent_for_registration` read it back and distinguish missing-file (legacy fallback) from malformed-file (fail closed) via the `MISSING_INIT_OPTIONS_FILE` sentinel. — [src/specify_cli/_init_options.py#L9-83](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_init_options.py#L9-L83)
- Core command templates declare per-script-type helper invocations in `scripts:` frontmatter (`sh`, `ps`, `py` entries) and carry placeholders (`{SCRIPT}`, `$ARGUMENTS`, `__SPECKIT_COMMAND_<NAME>__`) that integrations resolve at install time through `process_template()` using the integration's `invoke_separator` (`.` for markdown agents, `-` for skills agents). — [templates/commands/plan.md#L11-16](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/templates/commands/plan.md#L11-L16) [src/specify_cli/integrations/base.py#L1079-1165](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L1079-L1165) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Every file an integration installs is recorded with a SHA-256 in `.specify/integrations/<key>.manifest.json` via `IntegrationManifest.record_file`/`record_existing`; `teardown()` delegates to `manifest.uninstall()` which deletes only files whose current hash still matches (skipping user-modified files) unless `--force`. — [src/specify_cli/integrations/manifest.py#L1-48,104-120](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/manifest.py#L1-48,104-L120) [src/specify_cli/integrations/base.py#L997-1012](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L997-L1012) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Shared infrastructure (`_install_shared_infra_impl` in `shared_infra.py`) copies `.specify/scripts/<variant>/` and `.specify/templates/` from core_pack or the repo root, resolves `__SPECKIT_COMMAND_*__` placeholders, tracks files in `speckit.manifest.json`, and writes a managed `.specify/.gitignore` covering `feature.json` and per-machine `extensions/*/local-config.yml`. — [src/specify_cli/shared_infra.py#L1-33](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/shared_infra.py#L1-L33) [src/specify_cli/__init__.py#L135-215](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L135-L215)
- Workflow run state is persisted under `.specify/workflows/runs/<run_id>/` as atomically-written `state.json` + `inputs.json` plus an appended `log.jsonl`; `RunState.load` validates `run_id` against a strict pattern before building the path, and `workflow resume` reloads the persisted workflow copy and merges new inputs over persisted ones. — [src/specify_cli/workflows/engine.py#L594-930,1094-1160](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/engine.py#L594-930,1094-L1160)
- Extension installs parse `extension.yml` into an `ExtensionManifest` (validating `provides.commands` names against `^speckit\.<ns>\.<name>$`, hooks, events, templates, scripts), copy files via `ExtensionManager.install_from_directory`/`install_from_zip`, and merge `hooks` entries into `.specify/extensions.yml` through `register_hooks`. — [src/specify_cli/extensions/__init__.py#L66-77,290-430,5063-5160](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py#L66-77,290-430,5063-L5160) [extensions/git/extension.yml#L42-130](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/git/extension.yml#L42-L130)
- Remote content (catalog JSON, extension/preset/workflow archives) is fetched with hard byte ceilings — `MAX_DOWNLOAD_BYTES` 50 MiB, `MAX_JSON_METADATA_BYTES` 1 MiB, `MAX_JSON_CATALOG_BYTES` 8 MiB, per-entry and total zip caps — via `read_response_limited`/`safe_extract_archive`; catalog-declared `sha256` pins are verified with `hmac.compare_digest` before install. — [src/specify_cli/_download_security.py#L26-49](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_download_security.py#L26-L49) [src/specify_cli/shared_infra.py#L42-99](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/shared_infra.py#L42-L99)
- The events flow generates a self-contained `.specify/events.py` dispatcher plus native hook config per integration; the dispatcher resolves the event's command template and runs its script confined under the project root (`EVENT_SCRIPT_PATH_CONFINEMENT = True`), with `specify event run` as the fallback entry point that accepts a ≤1 MiB stdin payload. — [src/specify_cli/events.py#L33-100](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/events.py#L33-L100) [src/specify_cli/commands/event.py#L24-58](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/event.py#L24-L58)
- `CommandRegistrar` (agents.py) lazily derives `AGENT_CONFIGS` from `INTEGRATION_REGISTRY` `registrar_config` dicts (propagating `invoke_separator` and `dev_no_symlink`), then renders commands into each agent's directory/format for extensions and presets — so the registry is the single metadata source for both the integration layer and the registrar. — [src/specify_cli/agents.py#L24-75](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/agents.py#L24-L75) [src/specify_cli/_agent_config.py#L9-18](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_agent_config.py#L9-L18)
- Skills-mode installs branch on the persisted `ai_skills` flag: `resolve_active_skills_dir` maps the project to `<agent_folder>/skills` (with symlink/containment checks via `_ensure_safe_shared_directory`), special-cases `generic` (no static folder → disabled) and `kimi` (native-skills fallback when flag unset). — [src/specify_cli/__init__.py#L286-344](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L286-L344)
- The workflow engine resolves the dispatch integration by layered fallback — step `integration:` → workflow default → `.specify/integration.json` project default — with `"auto"` meaning the project-recorded integration, then `CommandStep.execute` builds the agent CLI argv via `build_exec_args` and streams output to the terminal. — [src/specify_cli/workflows/engine.py#L1700-1730](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/engine.py#L1700-L1730) [src/specify_cli/workflows/steps/command/__init__.py#L31-113](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/steps/command/__init__.py#L31-L113) [src/specify_cli/integration_state.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integration_state.py)
- Catalog stacks (`CatalogStackBase` + per-domain subclasses) merge an ordered list of catalog sources, each entry carrying `url`, `priority`, and `install_allowed`; catalog entries from discovery-only catalogs are treated as untrusted — e.g. `_command_safe_id` refuses to interpolate non-`[a-z0-9-]` IDs into suggested shell commands. — [src/specify_cli/catalogs.py#L14-40](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/catalogs.py#L14-L40) [src/specify_cli/extensions/_commands.py#L59-84](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/_commands.py#L59-L84)

## Behavior traces

Three flows traced end-to-end: `specify init` (bootstrap), `specify workflow run` (primary automation), and extension install plus agent-side command execution (persistence and runtime paths). Self-upgrade and the event-dispatch path were also walked. Each flow funnels through `_require_specify_project` or asset-resolution helpers before touching the filesystem, and all writes go through hash-tracked manifests or safe-write helpers.
- `specify init` ordered trace: `show_banner` → `get_integration` validation → `--here`/directory-exists handling (`--force` merge vs confirmation prompt vs non-interactive error) → agent selection (`select_with_arrows` or non-interactive default via `SPECKIT_INTEGRATION_DEFAULT`/copilot) → `check_tool` when `requires_cli` → script-type selection → `StepTracker`+`Live` phases: `IntegrationManifest` → `resolved_integration.setup()` → `_install_shared_infra_or_exit` → `ensure_executable_scripts` → `ensure_constitution_from_template` → bundled workflow install → `_install_extension_during_init` per `--extension` → `save_init_options`. — [src/specify_cli/commands/init.py#L398-700](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L398-L700) [src/specify_cli/__init__.py#L191-267](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L191-L267)
- URL-based `--extension` installs during init are resolved for trust BEFORE the Rich `Live` display starts: `_confirm_extension_url_trust` shows an 'Untrusted Source' panel per URL, defaults to deny (and auto-denies when non-interactive), with `--trust-extension-urls` as the pre-authorization override. — [src/specify_cli/commands/init.py#L56-98,647-659](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L56-98,647-L659)
- `specify workflow run <src>` trace: decide file-source vs installed-ID → `_require_specify_project()` (or cwd for file sources) → `_reject_unsafe_workflow_storage` → `load_custom_steps(project_root)` → `WorkflowEngine.load_workflow` → `validate` → `_parse_input_values` → `engine.execute` → `_execute_steps` dispatches each step by `type` through `STEP_REGISTRY` → results recorded to `RunState` → exit code from `_run_outcome_exit_code`; paused runs print a `workflow resume <run_id>` hint. — [src/specify_cli/workflows/_commands.py#L1308-1454](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py#L1308-L1454) [src/specify_cli/workflows/engine.py#L986-1073,1208-1271](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/engine.py#L986-1073,1208-L1271)
- A `command` step resolves `integration` (step → workflow default → project `.specify/integration.json`, `"auto"` → recorded key) and `model`, evaluates `{{ }}` expressions in `input`/`options`, guards every non-string value with a per-step FAILED result (rather than crashing the run), then invokes the agent CLI via `build_exec_args`, streaming output live. — [src/specify_cli/workflows/steps/command/__init__.py#L31-113](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/steps/command/__init__.py#L31-L113) [src/specify_cli/integrations/base.py#L1089-1108](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L1089-L1108)
- `specify extension add git` trace: `_require_specify_project` → `ExtensionManager(project_root)` → `--dev` local dir / `--from` URL (HTTPS check + trust prompt) / bundled lookup / catalog download → `install_from_directory` or `install_from_zip` validates `extension.yml`, copies artifacts, records files, and `register_hooks` merges `hooks:` into `.specify/extensions.yml` → `_refresh_events_and_warn` refreshes native hook config. — [src/specify_cli/extensions/command_add.py#L19-100](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/command_add.py#L19-L100) [src/specify_cli/extensions/__init__.py#L2085-2130,2626-2630](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py#L2085-2130,2626-L2630) [src/specify_cli/extensions/_commands.py#L100-120](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/_commands.py#L100-L120)
- `specify self check` trace: `_get_installed_version` (importlib.metadata, 'unknown' sentinel) → `_fetch_latest_release_tag` does exactly one `open_url` call to `api.github.com/repos/github/spec-kit/releases/latest` with a 5 s timeout and a 1 MiB JSON cap → `_normalize_tag` → `_is_newer` PEP 440 compare; failures categorize as offline/rate-limited/HTTP <code> without a catch-all. — [src/specify_cli/_version.py#L55-145](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_version.py#L55-L145)
- Agent-side `/speckit.specify` execution trace (what the installed command file instructs the agent to do): read `.specify/extensions.yml` for `hooks.before_specify` (mandatory hooks emitted as EXECUTE_COMMAND and awaited; optional hooks as prompts) → generate a 2-4 word short name → run the `create-new-feature` helper script which returns a `FEATURE_DIR`/`BRANCH_NAME` JSON contract → render `spec.md` from `spec-template.md` under `specs/NNN-<short-name>/`. — [templates/commands/specify.md#L21-80](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/templates/commands/specify.md#L21-L80) [scripts/bash/create-new-feature.sh#L1-80](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/scripts/bash/create-new-feature.sh#L1-L80) [extensions/git/extension.yml#L42-51](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/git/extension.yml#L42-L51)
- Agent-native event trace: the agent's hook config (emitted by `install_integration_events` during `setup()`) invokes the generated `.specify/events.py` dispatcher → dispatcher resolves the event's command template and confines the script token under the project root → or falls back to `specify event run <cmd> <event> <timeout>` → `resolve_and_run_event_command` executes the script with the stdin payload. — [src/specify_cli/events.py#L33-100](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/events.py#L33-L100) [src/specify_cli/commands/event.py#L13-60](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/event.py#L13-L60) [src/specify_cli/integrations/base.py#L1014-1035](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L1014-L1035)
- Uninstall trace: `IntegrationBase.teardown` → `remove_events` (strips Specify-authored native hook entries) → `manifest.uninstall(project_root, force=...)` removes only files whose on-disk SHA-256 still equals the recorded hash, returning `(removed, skipped)` lists so user-modified files survive. — [src/specify_cli/integrations/base.py#L997-1031](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L997-L1031) [src/specify_cli/integrations/manifest.py#L104-120](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/manifest.py#L104-L120)
- `specify integration uninstall`/shared-infra refresh respects a three-tier overwrite policy: `force=True` overwrites everything (still skipping symlinks), `refresh_managed=True` overwrites only files whose hash matches the manifest (user edits preserved with a warning + caller-supplied remediation hint), default adds only missing files. — [src/specify_cli/__init__.py#L135-188](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/__init__.py#L135-L188) [src/specify_cli/shared_infra.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/shared_infra.py)

## Testing signals

Testing is pytest-based (~253 files under `tests/`) with coverage configured in pyproject. The suite mirrors the CLI command surface under `tests/specify_cli/` per `design/cli.md`, has one file per integration under `tests/integrations/`, parity tests proving bash↔python↔powershell script equivalence, contract tests for wheel bundling and catalog/manifest schemas, and workflow/overlay/engine suites. CI runs a 3-OS × 2-Python pytest matrix plus ruff, markdownlint, a dependency security audit, and CodeQL.
- pytest is configured in pyproject with `testpaths=["tests"]`, strict markers, and short tracebacks; the `test` extra adds `pytest` and `pytest-cov`, and `[tool.coverage]` scopes coverage to `src/`. — [pyproject.toml#L59-83](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L59-L83)
- Command tests mirror the source layout exactly — `src/specify_cli/extensions/command_add.py` ↔ `tests/specify_cli/extensions/test_command_add.py`, `catalog/command_add.py` ↔ `tests/specify_cli/extensions/catalog/test_command_add.py` — a convention mandated by `design/cli.md`. — [design/cli.md#L186-232](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L186-L232) [tests/specify_cli/extensions/test_command_add.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/specify_cli/extensions/test_command_add.py) [tests/specify_cli/extensions/catalog/test_command_add.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/specify_cli/extensions/catalog/test_command_add.py)
- Every registered integration has a dedicated test file `tests/integrations/test_integration_<key>.py` (hyphens→underscores), plus base-class suites (`test_integration_base_markdown/toml/yaml/skills`) and registry-level tests (`test_base`, `test_cli`, `test_events`). — [tests/integrations/test_integration_claude.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/integrations/test_integration_claude.py) [tests/integrations/test_integration_base_markdown.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/integrations/test_integration_base_markdown.py) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- tests/conftest.py provides the shared harness: `_has_working_bash()` probes for a real bash (rejecting the WSL launcher on Windows, override `SPECKIT_TEST_BASH=1`), autouse fixtures isolate `~/.specify/auth.json` and strip inherited `SPECIFY_*`/`SPECKIT_INTEGRATION_*` env vars, and argv0 fixtures fake uv-tool/pipx/uvx install locations for self-upgrade tests. — [tests/conftest.py#L18-238](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/conftest.py#L18-L238)
- Script parity is enforced by dedicated tests — `test_setup_plan_python_parity.py`, `test_create_new_feature_python_parity.py`, `test_resolve_template_python_parity.py`, `test_check_prerequisites_python_parity.py`, `test_setup_tasks_python_parity.py` — matching the AGENTS.md rule that all `scripts:` frontmatter entries must be behaviorally equivalent across sh/ps/py. — [tests/test_setup_plan_python_parity.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/test_setup_plan_python_parity.py) [tests/parity_helpers.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/parity_helpers.py) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Contract tests under `tests/contract/` pin the wheel's bundled payload and schemas: `test_wheel_core_pack_scripts.py`, `test_wheel_bundled_presets.py`, `test_wheel_bundled_workflows.py`, `test_catalog_schema.py`, `test_manifest_schema.py`, `test_firstparty_bundle_catalog_consistency.py`. — [tests/contract/test_wheel_core_pack_scripts.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/contract/test_wheel_core_pack_scripts.py) [tests/contract/test_catalog_schema.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/contract/test_catalog_schema.py)
- Workflow coverage lives in `tests/workflows/` (bundled speckit/bugfix/assess workflow validation, overlay composer/merge/schema/security, slot step, resolver integration) plus top-level `test_workflows.py` and `test_workflow_run_without_project.py`. — [tests/workflows/test_bundled_speckit_workflow.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/workflows/test_bundled_speckit_workflow.py) [tests/workflows/test_overlay_security.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/workflows/test_overlay_security.py) [tests/test_workflows.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/test_workflows.py)
- Security-focused suites exist for the risky paths: `test_download_security.py` (bounded reads/extraction), `test_registrar_path_traversal.py`, `test_command_add_path_traversal.py`, `test_security_workflow.py`, and `test_authentication.py`. — [tests/test_download_security.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/test_download_security.py) [tests/specify_cli/extensions/test_command_add_path_traversal.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/specify_cli/extensions/test_command_add_path_traversal.py) [tests/test_authentication.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/test_authentication.py)
- CI: `.github/workflows/test.yml` runs ruff (`uvx ruff@0.15.0 check src tests`) then pytest on ubuntu/windows/macos × Python 3.13/3.14; `lint.yml` adds `git diff --check` and markdownlint-cli2; `security.yml` runs a dependency audit script; `codeql.yml` runs CodeQL. — [.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) [.github/workflows/security.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/.github/workflows/security.yml) [.github/workflows/codeql.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/.github/workflows/codeql.yml)
- The suite is heavily fixture/monkeypatch-driven (tmp_path projects, faked argv0, stubbed catalogs) rather than spawning real agent CLIs; `_has_working_bash` auto-skips shell-dependent tests when bash is absent, so script-level coverage can silently thin out on under-provisioned runners. (inferred) — [tests/conftest.py#L18-67,179-238](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/conftest.py#L18-67,179-L238)
- Extension behavior has both legacy consolidated suites (`tests/test_extensions.py`, `tests/test_presets.py`) and newer per-command mirrored suites; `design/cli.md` explicitly allows the consolidated domain suites to remain until separately reorganized, so coverage is duplicated across both styles rather than missing. — [design/cli.md#L234-248](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L234-L248) [tests/test_extensions.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/test_extensions.py) [tests/specify_cli/extensions/test_command_update.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/tests/specify_cli/extensions/test_command_update.py)

## Risk map

The highest-risk surface is remote-code paths the CLI intentionally supports: project-local custom workflow steps are imported and executed, URL-based extension/bundle installs download and extract archives, and generated event dispatchers + workflow `shell`/`command` steps run subprocesses with user privileges. These are hardened with HTTPS-only rules, byte caps, zip-slip defenses, SHA-256 pins, trust prompts, and path confinement — but the security posture is defense-in-depth around inherently dangerous features. Secondary risks: three giant modules concentrate blast radius, compatibility shims create fragile monkeypatch coupling, and a few silent-fallback paths can hide breakage.
- `load_custom_steps` dynamically imports and executes `__init__.py` from every package under `.specify/workflows/steps/` — arbitrary code execution by design for community step types. Mitigations: symlink rejection on every path component, sanitized synthetic module names, sys.modules cleanup on failure — but any file dropped into that directory runs with user privileges. — [src/specify_cli/workflows/__init__.py#L85-213](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/__init__.py#L85-L213)
- Workflow `shell` and `command` steps run subprocesses with the user's full privileges; the engine explicitly documents that `requires:` (and any hypothetical `permissions:` key — which is rejected by validation) is advisory and 'NOT a security boundary'. — [src/specify_cli/workflows/engine.py#L74-85,126-133](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/engine.py#L74-85,126-L133) [src/specify_cli/workflows/steps/shell/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/steps/shell/__init__.py)
- URL installs (`--extension <url>`, `extension add --from`, catalog downloads) fetch remote archives: hardened by `is_https_or_localhost_http`, `is_safe_download_redirect`, `read_response_limited` byte caps, `safe_extract_archive` (entry count/size/path limits, Windows reserved names), and optional `sha256` catalog pins — but a missing pin means integrity rests on HTTPS alone. — [src/specify_cli/_download_security.py#L26-49,95-113](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_download_security.py#L26-49,95-L113) [src/specify_cli/shared_infra.py#L42-99](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/shared_infra.py#L42-L99) [src/specify_cli/extensions/command_add.py#L48-91](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/command_add.py#L48-L91)
- The generated `.specify/events.py` dispatcher runs event-mapped scripts inside agent hooks; `EVENT_SCRIPT_PATH_CONFINEMENT = True` exists because an older stale dispatcher would otherwise run unconfined script tokens — i.e. this code path was previously exploitable and is now gated by a version sentinel. — [src/specify_cli/events.py#L33-46](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/events.py#L33-L46)
- Three files concentrate enormous blast radius: `presets/__init__.py` (6259 LOC), `extensions/__init__.py` (5481 LOC), `workflows/_commands.py` (3901 LOC) — each mixes domain logic, validation, and CLI plumbing, so a bad edit can break an entire command group. — [src/specify_cli/presets/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/presets/__init__.py) [src/specify_cli/extensions/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py) [src/specify_cli/workflows/_commands.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py)
- Fragile test-coupling pattern: `_commands.py` modules keep thin forwarders that re-fetch helpers (`_require_specify_project`, `load_init_options`, …) from the package root at call time purely so existing monkeypatch paths keep working; renaming or moving the root helpers silently breaks tests and commands. — [src/specify_cli/extensions/_commands.py#L37-56](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/_commands.py#L37-L56) [src/specify_cli/workflows/_commands.py#L1-7](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py#L1-L7) [design/cli.md#L98-112](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L98-L112)
- Subprocess policy is enforced by lint rather than review: ruff `extend-select` enables S602/S604/S605 so any `shell=True`/`os.system`/`popen2` reintroduction needs an explicit noqa, but other subprocess invocations (agent CLIs, scripts) are unrestricted list-form exec. — [pyproject.toml#L85-93](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L85-L93)
- `specify init --here --force` (and `--force` on existing dirs) merges/overwrites into non-empty directories; the confirmation/EOF handling is intricate (Abort vs EOFError distinction) and a scripting mistake there writes into a user's existing project. — [src/specify_cli/commands/init.py#L429-521](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L429-L521)
- Deprecated hidden flags (`--skip-tls`, `--github-token`, `--offline`, `--debug`) are accepted as silent no-ops — scripts relying on the old behavior get no warning that TLS verification or offline mode semantics changed. — [src/specify_cli/commands/init.py#L294-317](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L294-L317)
- Custom step loading swallows every exception (`except Exception  # noqa: BLE001` → `continue`), so a broken or maliciously-aborting step package fails invisibly; same silent-skip posture applies to malformed `step.yml`. — [src/specify_cli/workflows/__init__.py#L209-213](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/__init__.py#L209-L213)
- Credential handling: `~/.specify/auth.json` maps hosts to providers and `_github_http.py` attaches GITHUB_TOKEN/GH_TOKEN only to a fixed host allowlist (github.com, api.github.com, raw.githubusercontent.com, codeload.github.com) — correct, but the file is user-managed with no permission checks enforced in the registry layer. — [src/specify_cli/_github_http.py#L18-27](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_github_http.py#L18-L27) [src/specify_cli/authentication/__init__.py#L1-8](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/authentication/__init__.py#L1-L8) [src/specify_cli/authentication/http.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/authentication/http.py)
- `_load_core_command_names` falls back to a hardcoded `_FALLBACK_CORE_COMMAND_NAMES` frozenset when neither core_pack nor the repo templates are readable; if the bundled template set drifts from the fallback, extension command-name validation silently uses stale data (a previous `.parent`-counting bug already broke this once, per the docstring). — [src/specify_cli/extensions/__init__.py#L52-123](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py#L52-L123)

## Extension points

The repo is essentially a plugin framework. Five registration seams exist: (1) `INTEGRATION_REGISTRY` for AI agents via small class-attr subclasses of four base classes; (2) `STEP_REGISTRY`/`StepBase` for workflow step types, including project-local dynamically-loaded custom steps; (3) extension packages (`extension.yml`) adding commands/hooks/events/config; (4) presets (`preset.yml`) overriding templates via priority-resolved `replaces:` entries; (5) bundles (`bundle.yml`) packaging all of the above. All five are discoverable/installable through per-domain catalog stacks that users can extend with custom catalog URLs.
- New AI-agent integrations plug in via `INTEGRATION_REGISTRY`: create `src/specify_cli/integrations/<pkg>/__init__.py` with a class setting `key`, `config`, `registrar_config`, then add one import + one `_register()` call (both alphabetical) in `_register_builtins()`; package dirs use underscores while `key` keeps the hyphenated CLI name. — [src/specify_cli/integrations/__init__.py#L54-150](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/__init__.py#L54-L150) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Four integration base classes cover the output formats — `MarkdownIntegration`, `TomlIntegration`, `YamlIntegration`, `SkillsIntegration`, or `IntegrationBase` for fully custom setup (e.g. Copilot's dual skills/commands layouts) — with documented override points: `setup()`, `teardown()`, `command_filename()`, `options()`, `post_process_command_content()`, `effective_invoke_separator()`. — [src/specify_cli/integrations/base.py#L1079-1165,1173-1180](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L1079-1165,1173-L1180) [src/specify_cli/integrations/copilot/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/copilot/__init__.py) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Per-integration CLI flags are declared via `IntegrationOption` entries returned from `options()` and consumed through `--integration-options` (e.g. codex `--skills`, copilot `--commands`, generic `--commands-dir`); `options()` auto-adds `--events` when the integration declares `CANONICAL_TO_NATIVE` + `events_config_file`. — [src/specify_cli/integrations/base.py#L79-95,167-180](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L79-95,167-L180) [src/specify_cli/integrations/codex/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/codex/__init__.py) [src/specify_cli/integrations/generic/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/generic/__init__.py)
- `specify integration scaffold` generates a new integration subpackage from templates (`command_scaffold.py` + `_command_scaffold_generation.py`), giving a code-level entry point for the documented 'add an integration in 5 steps' flow. — [src/specify_cli/integrations/command_scaffold.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/command_scaffold.py) [src/specify_cli/integrations/_command_scaffold_generation.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/_command_scaffold_generation.py) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)
- Workflow step types plug into `STEP_REGISTRY` (`type_key` → `StepBase` subclass); beyond the 11 builtins, `load_custom_steps` imports project-local step packages from `.specify/workflows/steps/<pkg>/` defined by `step.yml` (`step.type_key`) + `__init__.py` (`StepBase` subclass). — [src/specify_cli/workflows/__init__.py#L22-35,85-140](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/__init__.py#L22-35,85-L140) [src/specify_cli/workflows/base.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/base.py)
- Extensions are self-contained packages declared by `extension.yml` (`schema_version`, `extension` metadata, `requires`, `provides.commands|config|templates|scripts`, `hooks.<before|after>_<command>`, `events`, `config.defaults`) — validated by `ExtensionManifest` and installed by `ExtensionManager`; `extensions/template/` is the starter scaffold. — [extensions/git/extension.yml#L1-45](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/git/extension.yml#L1-L45) [extensions/template/extension.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/template/extension.yml) [src/specify_cli/extensions/__init__.py#L290-430](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py#L290-L430)
- Presets override core behavior declaratively: `preset.yml` `provides.templates` entries with `replaces: "speckit.<cmd>"` substitute command/template/script artifacts, and `PresetResolver` composes the stack by priority (lower number wins) — e.g. `lean` replaces the five core commands with minimal variants. — [presets/lean/preset.yml#L15-46](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/presets/lean/preset.yml#L15-L46) [src/specify_cli/presets/__init__.py#L1-56](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/presets/__init__.py#L1-L56) [presets/constitution-sync/preset.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/presets/constitution-sync/preset.yml)
- Bundles (`bundle.yml`) compose extensions, presets, workflows, and step types into one installable unit (`specify bundle install`); first-party `bugfix` and `assess` bundles plus a community catalog JSON ship in the wheel for offline discovery. — [bundles/bugfix/bundle.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/bundles/bugfix/bundle.yml) [bundles/catalog.json](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/bundles/catalog.json) [src/specify_cli/bundles/installer.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/bundles/installer.py) [pyproject.toml#L55-57](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L55-L57)
- Every domain (integrations, extensions, presets, workflows, bundles) supports user-added remote catalogs through `catalog add/list/remove` commands backed by `CatalogStackBase` entries with `url`, `priority`, and `install_allowed` flags — so organizations can host private catalogs. — [src/specify_cli/catalogs.py#L14-40](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/catalogs.py#L14-L40) [src/specify_cli/extensions/catalog/command_add.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/catalog/command_add.py) [src/specify_cli/integrations/catalog/command_add.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/catalog/command_add.py) [integrations/catalog.json](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/integrations/catalog.json)
- Workflow overlays (`workflows/overlays/` composer + `workflow overlay` command group) let projects layer modifications onto installed workflows without forking them — merge, layer_sources, and schema modules define the overlay pipeline. — [src/specify_cli/workflows/overlays/composer.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/overlays/composer.py) [src/specify_cli/workflows/overlays/merge.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/overlays/merge.py) [src/specify_cli/workflows/_commands.py#L63-68](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py#L63-L68)
- Agent-native runtime events plug in per-integration: a class declaring `CANONICAL_TO_NATIVE` (canonical snake_case events → native hook names) and `events_config_file` automatically gets `--events` support and native hook emission via `install_integration_events`; canonical events are session_start, pre_tool_use, post_tool_use, session_end, user_prompt_submit, stop. — [src/specify_cli/events.py#L59-67](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/events.py#L59-L67) [src/specify_cli/integrations/base.py#L167-180,1014-1035](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/integrations/base.py#L167-180,1014-L1035)
- The `agent-context` extension owns all context-file (CLAUDE.md/AGENTS.md) management as a fully opt-in, generic component driven by `agent-context-config.yml` markers — deliberately keeping context-file knowledge out of the CLI/registry so new integrations add nothing for it. — [extensions/agent-context/extension.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/agent-context/extension.yml) [extensions/agent-context/agent-context-config.yml](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/extensions/agent-context/agent-context-config.yml) [AGENTS.md](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/AGENTS.md)

## Contributor handoff

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)

Source repository: https://github.com/github/spec-kit
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/spec-kit.json

## Public product links

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