Skip to content

Reference

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.


Getting Started

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:

specs_dir = "specs"
source_dirs = ["src"]
schema_dir = "db/migrations"
ai_provider = "anthropic"
ai_model = "claude-sonnet-4-6"
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

{
  "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-6",
  "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

OptionTypeDefaultDescription
specsDirstring"specs"Directory containing *.spec.md files (searched recursively)
sourceDirsstring[]["src"]Source directories for coverage analysis
schemaDirstring?noneSQL schema directory for db_tables validation
schemaPatternstring?CREATE TABLE regexCustom regex for extracting table names (first capture group = table name)
requiredSectionsstring[]7 defaultsMarkdown ## sections every spec must include
excludeDirsstring[]["__tests__"]Directory names skipped during coverage scanning
excludePatternsstring[]Common test globsFile patterns excluded from coverage (additive with language-specific test exclusions)
sourceExtensionsstring[]All supportedRestrict to specific extensions (e.g., ["ts", "rs"])
aiProviderstring?noneAI provider: anthropic, openai, openrouter, gemini, deepseek, groq, mistral, xai, together, or ollama. Deprecated: claude (routes to anthropic), copilot, cursor. Overridable via --provider / SPECSYNC_AI_PROVIDER
aiModelstring?Provider defaultModel name override (e.g., "claude-sonnet-4-6", "gpt-4o", "llama3.3"). Overridable via --model / SPECSYNC_AI_MODEL. openai and together require an explicit model
aiCommandstring?noneDeprecated trusted shell escape hatch, a command that reads a prompt on stdin and writes markdown to stdout. Never auto-selected; prefer the HTTP providers above. Overridable via SPECSYNC_AI_COMMAND
aiApiKeystring?noneAPI key for the selected provider (prefer the per-provider env var <PROVIDER>_API_KEY, e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY, OLLAMA_API_KEY)
aiBaseUrlstring?noneCustom base URL for API providers (e.g., proxies, self-hosted endpoints, or a non-default Ollama host via OLLAMA_HOST)
aiTimeoutnumber?120Seconds before AI command times out per module
exportLevelstring?"member"Export validation depth: "type" (classes/structs only) or "member" (all public symbols)
modulesobject?{}Custom module definitions mapping module names to { files, depends_on }
rulesobject?{}Custom validation rules (see Validation Rules below)
taskArchiveDaysnumber?noneDays after which completed tasks in companion tasks.md files are auto-archived
githubobject?noneGitHub integration settings (see GitHub Config below)

AI Provider Resolution

AI calls go through the shared corvid-ai crate over plain HTTP, with no CLI tool required. The provider is resolved flag > env > config:

  1. --provider CLI flag
  2. SPECSYNC_AI_PROVIDER env var
  3. aiProvider/ai_provider in config: shared config.toml first, then .specsync/config.local.toml (gitignored, per-developer overrides)

With nothing set, generate auto-detects:

  • No <PROVIDER>_API_KEY anywhere → keyless local Ollama (http://localhost:11434), the zero-config default
  • Exactly one <PROVIDER>_API_KEY set → that provider
  • Several keys set → an interactive picker on a TTY, otherwise a deterministic order: Ollama, Anthropic, OpenAI, OpenRouter, Gemini, DeepSeek, Groq, Mistral, xAI, Together

The model follows the same flag > env > config precedence: --model > SPECSYNC_AI_MODEL > aiModel/ai_model config > provider default (anthropicclaude-sonnet-4-6, Ollama → llama3.3; openai and together require an explicit model).

aiTimeout (default 120s) controls the per-module AI timeout. aiBaseUrl (or OLLAMA_HOST for Ollama) points a provider at a custom endpoint.

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

Every provider calls its HTTP API directly, with no CLI tool needed. Just set the provider and its key:

{
  "aiProvider": "anthropic"
}

Then set ANTHROPIC_API_KEY (or the relevant <PROVIDER>_API_KEY) in your environment, or use aiApiKey in config for local use (not recommended for shared repos). Local Ollama needs no key at all.

Shell escape hatch (aiCommand)

aiCommand/SPECSYNC_AI_COMMAND remain as an explicit, trusted shell escape hatch: a command that reads a prompt on stdin and writes markdown to stdout. It is deprecated and never auto-selected; prefer the HTTP providers above.


Validation Rules

Fine-tune validation behavior with the rules object:

{
  "rules": {
    "maxChangelogEntries": 20,
    "requireBehavioralExamples": true,
    "minInvariants": 2,
    "maxSpecSizeKb": 50,
    "requireDependsOn": false
  }
}
RuleTypeDescription
maxChangelogEntriesnumber?Warn if a spec's Change Log exceeds this many entries
requireBehavioralExamplesbool?Require at least one Behavioral Example scenario
minInvariantsnumber?Minimum number of invariants required per spec
maxSpecSizeKbnumber?Warn if spec file exceeds this size in KB
requireDependsOnbool?Require non-empty depends_on in frontmatter

GitHub Config

Configure GitHub integration for drift detection and issue verification:

{
  "github": {
    "repo": "owner/repo",
    "driftLabels": ["spec-drift"],
    "verifyIssues": true
  }
}
OptionTypeDefaultDescription
repostring?Auto-detectedRepository in owner/repo format (auto-detected from git remote)
driftLabelsstring[]["spec-drift"]Labels applied when creating drift issues
verifyIssuesbooltrueWhether 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:

{
  "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

{
  "specsDir": "specs",
  "sourceDirs": ["src"],
  "excludePatterns": ["**/__tests__/**", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}

Rust project

{
  "specsDir": "specs",
  "sourceDirs": ["src"],
  "sourceExtensions": ["rs"]
}

Monorepo

{
  "specsDir": "docs/specs",
  "sourceDirs": ["packages/core/src", "packages/api/src"],
  "schemaDir": "packages/db/migrations"
}

Minimal

{
  "requiredSections": ["Purpose", "Public API"]
}