Skip to content

Concepts

Lex edited this page Aug 21, 2026 · 3 revisions

Concepts

The core ideas behind SpecGit and how they fit together. For practical usage see Getting Started; for commands see the CLI Reference.

Philosophy

evidence not assertion   — acceptance is derived from git, PR, and CI facts
fail-closed not hopeful  — anything unverifiable is "unknown", never "accepted"
git is the contract      — the branch, the PR, and the checks are the deliverable

SpecGit does not manage plans, specs, or task lists. By the time SpecGit looks at your work, the work already exists as a branch, issues, a pull request, and CI checks. SpecGit's only job is to verify — from real evidence — that the delivery is complete. A checklist that says "done" is not evidence; a green required check on the PR head commit is.

The delivery binding aggregate

Every delivery is one aggregate, declared in a single record file (.specgit.yaml) plus the facts it points at:

Part What it is Where it lives
Delivery A kebab-case id for the work (add-login-flow) record
Execution context Where the work happens: a branch, or a linked worktree plus its branch live git
Issues 1..N forge issue numbers the delivery closes record → forge
Pull request Exactly one PR (or MR) that merges the delivery record → forge
Required checks CI check names that must pass at the PR head policy → forge

The record declares; git and the forge substantiate. Acceptance means every part was verified against the real thing.

One PR may close N issues. issues is a list: a single PR can close any number of issues (Closes #123, Fixes #124, …). Every bound issue must be closed by the PR's closing references — acceptance fails with the missing numbers listed if even one is absent.

Issue granularity: one issue = one WHY

One issue = one independently verifiable WHY. If a deliverable cannot be verified on its own evidence, split it before binding. One delivery binds N issues to one PR that closes them all — but each issue must stand alone as a verifiable reason for the work.

Execution context: branch or worktree

The execution context is resolved from live git at evaluation time — never passed as a flag, never taken from the record alone.

  • kind: branch — the record names a branch. Any checkout currently on that branch satisfies it.
  • kind: worktree — the record names a portable label plus a branch. The current checkout must be a linked worktree whose label resolves (via git worktree list) to that branch.

If HEAD is detached, the live branch disagrees with the record, or the worktree label does not resolve, context gates fail with precise codes. There is no "trust me" mode.

The three file tiers

repo root/
├── spec_git/
│   ├── policy.yaml      # required_checks — shared by every delivery
│   └── providers.yaml   # optional platform declaration (declared GitLab host)
└── .specgit.yaml        # this delivery's binding — committed on the delivery branch
Tier Files Regeneration / lifecycle
Authoritative spec_git/policy.yaml, .specgit.yaml, spec_git/providers.yaml Hand-owned; versioned like code
Derived harness .github/workflows/specgit-accept.yml, managed AGENTS.md/CLAUDE.md block Regenerate with specgit init --force; never hand-edit
Local integration Guard hooks, setup entry points Agent conveniences, merged non-destructively, never acceptance inputs
  • Policy (spec_git/policy.yaml): the project-level contract — the list of CI check names every delivery must pass. Created by specgit init.
  • Record (.specgit.yaml): the delivery-level binding. Written by specgit issue (script alias: bind), removed by specgit unbind. Unknown keys are preserved on rewrite.

No other state exists. No artifact directories, no caches, no global stores. Root discovery is git rev-parse --show-toplevel — SpecGit runs only inside a git repository, at its root.

Acceptance is derived, never stored

Delivery states — unbound, draft, bound, accepted, rejected, unknown — are computed on every invocation. Nothing is persisted, so a state can never drift from reality.

Evaluation runs eleven gates in order, from cheapest local facts to live forge evidence:

  1. Record present and valid
  2. Policy present and valid
  3. Completeness (≥1 issue, exactly 1 PR)
  4. Context matches live git
  5. Origin resolves to a forge repository
  6. Provider reachable and authenticated
  7. Issues exist (and are issues, not PRs)
  8. Sequence — when ordered_issues is on, no open issue with a smaller number precedes this delivery
  9. PR exists, open or merged, head matches the context branch, same repository
  10. PR body closes every bound issue
  11. Every required check is green at the PR head commit

Gates short-circuit across gates; within a gate, every failure is collected and reported.

Fail-closed

Every piece of evidence is either gathered successfully or it is a failure — there is no silent skip.

  • All evidence gathered, at least one gate failed → rejected (exit 1) with complete evidence.
  • Evidence could not be gathered (missing/invalid record or policy, no provider CLI, no auth, transport error, not a git repo) → unknown (exit 3).
  • Every gate passed with evidence → accepted (exit 0). Nothing else.

Consequences worth knowing:

  • specgit finish (alias accept) offline can return unknown, never accepted. Acceptance requires the provider by design.
  • A dirty working tree is reported as evidence but never fails acceptance — acceptance is about the PR head, not your local edits.
  • If your local HEAD differs from the PR head, you get an informational local_head_stale warning; checks are evaluated at the PR head commit, because that is what will be merged.

What SpecGit deliberately is not

  • Not a spec framework: there are no proposal, spec, design, or task artifacts.
  • Not a git wrapper: it reads git facts; it never rewrites history or touches your branches.
  • Not an issue tracker or CI system: it verifies against the forge and your existing CI.
  • Not a plugin platform: init creates only the policy and harness.

Chinese version: Concepts-zh

Clone this wiki locally