---
title: Configuration
layout: default
nav_order: 4
---
# Configuration
{: .no_toc }
SpecSync is configured via `.specsync/config.toml` (v4) or legacy `specsync.json` / `.specsync.toml` in your project root. All fields are optional — sensible defaults apply.
{: .fs-6 .fw-300 }
<details open markdown="block">
<summary>Table of contents</summary>
{: .text-delta }
1. TOC
{:toc}
</details>
---
## Getting Started
```bash
specsync init
```
Creates `.specsync/config.toml` (v4) with defaults. SpecSync also works without a config file.
### TOML Config
Config resolution order: `.specsync/config.toml` → `.specsync/config.json` → `.specsync.toml` (legacy) → `specsync.json` (legacy) → defaults. If `.specsync/config.local.toml` exists (gitignored), it's merged on top for per-developer overrides.
Example:
```toml
specs_dir = "specs"
source_dirs = ["src"]
schema_dir = "db/migrations"
ai_provider = "anthropic"
ai_model = "claude-sonnet-4-20250514"
ai_timeout = 120
export_level = "member"
required_sections = ["Purpose", "Public API", "Invariants", "Behavioral Examples", "Error Cases", "Dependencies", "Change Log"]
exclude_dirs = ["__tests__"]
exclude_patterns = ["**/__tests__/**", "**/*.test.ts"]
task_archive_days = 30
[rules]
max_changelog_entries = 20
require_behavioral_examples = true
min_invariants = 1
[github]
drift_labels = ["spec-drift"]
verify_issues = true
```
Config resolution order: `.specsync/config.toml` → `.specsync/config.json` → `.specsync.toml` (legacy) → `specsync.json` (legacy) → defaults. Per-developer overrides via `.specsync/config.local.toml` are merged on top.
---
## Full Config
```json
{
"specsDir": "specs",
"sourceDirs": ["src"],
"schemaDir": "db/migrations",
"schemaPattern": "CREATE (?:VIRTUAL )?TABLE(?:\\s+IF NOT EXISTS)?\\s+(\\w+)",
"requiredSections": ["Purpose", "Public API", "Invariants", "Behavioral Examples", "Error Cases", "Dependencies", "Change Log"],
"excludeDirs": ["__tests__"],
"excludePatterns": ["**/__tests__/**", "**/*.test.ts", "**/*.spec.ts"],
"sourceExtensions": [],
"exportLevel": "member",
"aiProvider": "anthropic",
"aiModel": "claude-sonnet-4-20250514",
"aiCommand": null,
"aiApiKey": null,
"aiBaseUrl": null,
"aiTimeout": 120,
"taskArchiveDays": 30,
"modules": {},
"rules": {
"maxChangelogEntries": 20,
"requireBehavioralExamples": true,
"minInvariants": 1,
"maxSpecSizeKb": 50,
"requireDependsOn": false
},
"github": {
"repo": "owner/repo",
"driftLabels": ["spec-drift"],
"verifyIssues": true
}
}
```
---
## Options
| `specsDir` | `string` | `"specs"` | Directory containing `*.spec.md` files (searched recursively) |
| `sourceDirs` | `string[]` | `["src"]` | Source directories for coverage analysis |
| `schemaDir` | `string?` | — | SQL schema directory for `db_tables` validation |
| `schemaPattern` | `string?` | `CREATE TABLE` regex | Custom regex for extracting table names (first capture group = table name) |
| `requiredSections` | `string[]` | 7 defaults | Markdown `##` sections every spec must include |
| `excludeDirs` | `string[]` | `["__tests__"]` | Directory names skipped during coverage scanning |
| `excludePatterns` | `string[]` | Common test globs | File patterns excluded from coverage (additive with language-specific test exclusions) |
| `sourceExtensions` | `string[]` | All supported | Restrict to specific extensions (e.g., `["ts", "rs"]`) |
| `aiProvider` | `string?` | — | AI provider name: `claude`, `anthropic`, `openai`, `ollama`, `copilot`, or `custom` |
| `aiModel` | `string?` | Provider default | Model name override (e.g., `"claude-sonnet-4-20250514"`, `"gpt-4o"`, `"mistral"`) |
| `aiCommand` | `string?` | — | Custom CLI command for AI generation (reads stdin prompt, writes stdout markdown) |
| `aiApiKey` | `string?` | — | API key for `anthropic` or `openai` providers (prefer env vars `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` instead) |
| `aiBaseUrl` | `string?` | — | Custom base URL for API providers (e.g., for proxies or self-hosted endpoints) |
| `aiTimeout` | `number?` | `120` | Seconds before AI command times out per module |
| `exportLevel` | `string?` | `"member"` | Export validation depth: `"type"` (classes/structs only) or `"member"` (all public symbols) |
| `modules` | `object?` | `{}` | Custom module definitions mapping module names to `{ files, depends_on }` |
| `rules` | `object?` | `{}` | Custom validation rules (see [Validation Rules](#validation-rules) below) |
| `taskArchiveDays` | `number?` | — | Days after which completed tasks in companion `tasks.md` files are auto-archived |
| `github` | `object?` | — | GitHub integration settings (see [GitHub Config](#github-config) below) |
---
## AI Provider Resolution
When you run `specsync generate --provider auto`, the provider is resolved in this order:
1. `--provider` CLI flag (explicit)
2. `aiCommand` in config — checked in shared config first, then `.specsync/config.local.toml` (gitignored, per-developer overrides)
3. `aiProvider` in config (same merge order as above)
4. `SPECSYNC_AI_COMMAND` env var
5. Auto-detect: installed CLIs (`claude`, `copilot`, `ollama` — alphabetical), then API keys
> **Multi-agent teams**: Don't put `ai_provider` or `ai_command` in the shared `config.toml`. Instead, each contributor creates `.specsync/config.local.toml` with their preferred AI settings. This file is automatically gitignored.
### API Providers
The `anthropic` and `openai` providers call their respective APIs directly — no CLI tool needed. Just set the API key:
```json
{
"aiProvider": "anthropic"
}
```
Then set `ANTHROPIC_API_KEY` in your environment (or use `aiApiKey` in config for local use — **not recommended for shared repos**).
---
## Validation Rules
Fine-tune validation behavior with the `rules` object:
```json
{
"rules": {
"maxChangelogEntries": 20,
"requireBehavioralExamples": true,
"minInvariants": 2,
"maxSpecSizeKb": 50,
"requireDependsOn": false
}
}
```
| `maxChangelogEntries` | `number?` | Warn if a spec's Change Log exceeds this many entries |
| `requireBehavioralExamples` | `bool?` | Require at least one Behavioral Example scenario |
| `minInvariants` | `number?` | Minimum number of invariants required per spec |
| `maxSpecSizeKb` | `number?` | Warn if spec file exceeds this size in KB |
| `requireDependsOn` | `bool?` | Require non-empty `depends_on` in frontmatter |
---
## GitHub Config
Configure GitHub integration for drift detection and issue verification:
```json
{
"github": {
"repo": "owner/repo",
"driftLabels": ["spec-drift"],
"verifyIssues": true
}
}
```
| `repo` | `string?` | Auto-detected | Repository in `owner/repo` format (auto-detected from git remote) |
| `driftLabels` | `string[]` | `["spec-drift"]` | Labels applied when creating drift issues |
| `verifyIssues` | `bool` | `true` | Whether to verify linked issues exist during `specsync check` |
---
## Custom Module Definitions
Map custom module names to specific files when auto-detection doesn't fit your layout:
```json
{
"modules": {
"auth": {
"files": ["src/auth/service.ts", "src/auth/middleware.ts"],
"dependsOn": ["database"]
},
"api": {
"files": ["src/routes/"],
"dependsOn": ["auth", "database"]
}
}
}
```
Module definitions override the default subdirectory/flat-file discovery for `specsync generate` and `specsync coverage`.
---
## Example Configs
### TypeScript project
```json
{
"specsDir": "specs",
"sourceDirs": ["src"],
"excludePatterns": ["**/__tests__/**", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}
```
### Rust project
```json
{
"specsDir": "specs",
"sourceDirs": ["src"],
"sourceExtensions": ["rs"]
}
```
### Monorepo
```json
{
"specsDir": "docs/specs",
"sourceDirs": ["packages/core/src", "packages/api/src"],
"schemaDir": "packages/db/migrations"
}
```
### Minimal
```json
{
"requiredSections": ["Purpose", "Public API"]
}
```