Back to the catalog

gitu/specquill · repo-product/docs/specs

Bundle OKF 0.1 · 45 conceitos · gitu/specquill

Open source Repository Open in the app JSON README (API)

About

# Index

## decisions

- [Postgres as the metadata store](decisions/ADR-001.md) *(superseded)*
- [Content roots map server-side](decisions/ADR-002.md)
- [The server is a dumb CRDT relay](decisions/ADR-003.md)
- [Non-git sources become mirror repositories](decisions/ADR-004.md)
- [Embedded SQLite as the metadata store](decisions/ADR-005.md)

## glossary

- [Glossary](glossary/glossary.md)

## requirements

- [Protected default branch](requirements/REQ-001.md)
- [Byte-fidelity editing](requirements/REQ-002.md)
- [Projects in repository subfolders](requirements/REQ-003.md)
- [Multi-stage source authorization](requirements/REQ-004.md)
- [Conformant OKF bundles](requirements/REQ-005.md)
- [Real-time collaborative editing](requirements/REQ-006.md)
- [Grounded AI speccy](requirements/REQ-007.md)
- [Conflict-checked merges to the default branch](requirements/REQ-008.md)
- [External source importers](requirements/REQ-009.md)
- [Portable diagrams and sketches](requirements/REQ-010.md)
- [Project

Details

Kind
OKF bundles
Topic
Databases
Publisher
gitu
Origin
okf_github
Category
dados
Version
0.1
Open pull requests
3
Last push
2026-09-08T08:06:15Z
Repository state
ativo
Language
Go
Added
2026-09-09 12:03:57
Updated
2026-09-09 12:03:57
Origin id
gitu/specquill:repo-product/docs/specs/index.md

README

# SpecQuill

**Requirements as readable, structured Markdown — what you end up with is an
[OKF bundle](repo-product/docs/specs/specs/okf.md).** A git-native requirements-engineering tool:
requirements, specs, regulations and data mappings live as plain markdown in
git; SpecQuill is the editing and review surface on top — traceability graph,
timed dependencies, a git-derived change history, rich editors, and an in-app
branch-based merge flow, every commit authored by the logged-in user.

The artifact SpecQuill produces is deliberately **not proprietary**: a
workspace is a conformant **Open Knowledge Format (v0.1) bundle** — typed
frontmatter, generated `index.md`/`log.md`, plain relative links — fully
readable by humans, agents and any OKF consumer straight from git, with or
without SpecQuill running. Hand the whole bundle to an LLM as one zip via an
unauthenticated [share link](repo-product/docs/specs/specs/share-links.md).

Originally implemented from the Claude Design project
[`SpecQuill.dc.html`](design/SpecQuill.dc.html) (the static prototype it grew from lives in
[`design/prototype/`](design/prototype/)).

## Screenshots

A tour of every surface lives in
[`docs/screenshots/`](docs/screenshots/README.md) — editor, speccy, source
alignment, impact graph, timed dependencies, change history, and more.
Regenerate the gallery with `make shots` (isolated server + demo fixtures +
mock LLM, so no keys are needed).

[![Editor](docs/screenshots/editor.png)](docs/screenshots/README.md)

## Architecture

```
server/           Go single binary (specquill)
  internal/gitx     the only git surface: bare clone + per-branch worktrees,
                    status/commit (user = author & committer, service identity
                    as Co-authored-by), structured diffs, merge-tree merges,
                    env-token push/fetch
  internal/auth     forge-PAT login (GitLab/GitHub personal access tokens,
                    RAM-only token vault) + local argon2id fallback,
                    opaque session cookies in the store
  internal/store    embedded SQLite (modernc, cgo-free) at <data_dir>/specquill.db:
                    users, sessions, per-repo grants, workspace claims —
                    content never leaves git
  internal/api      REST under /api + embedded SPA (embed.FS)
web/              React + Vite + TypeScript SPA
  src/lib/model.ts  frontmatter/link parsing → workspace model (all client-side)
  src/editors/      Milkdown WYSIWYG (mermaid click-to-edit node view,
                    excalidraw embeds), CodeMirror 6 source mode,
                    schema-driven PropertiesForm (yaml Document API),
                    @excalidraw/excalidraw modal
repo/             demo "trading-specs" workspace (fixture source)
docs/             project docs: the feature screenshot gallery
                  (docs/screenshots/, regenerated with `make shots`)
```

Key properties:

- **The server never parses frontmatter** — it serves files + git operations; the model
  (graph, dashboards) is computed in the browser from a `/snapshot` of the branch.
- **The type model is configuration, not code.** Document families sit on a
  WHY → WHAT → HOW → WHEN axis: drivers (regulation, product, technical) explain WHY
  work exists, requirements say WHAT the product must do, specs say HOW it is
  realized, work items say WHEN it lands. The whole type system — entities, drivers,
  statuses, link types, ID schemes and the property schema — lives in ONE optional
  file, `.specquill/config.yml`; every section it omits runs on the built-in
  defaults, entities merge (override single fields, add families, or drop one with
  `hidden: true`), and the Model view shows a **sample config** spelling out the full
  default setup — importable in one click when no config exists. A stand-alone
  `.specquill/schema.json` keeps working as the legacy property-schema form.
- **Timed dependencies** ([REQ-026](repo-product/docs/specs/requirements/REQ-026.md)).
  A document whose frontmatter carries a validity window — `starts`/`ends`, or
  regulatory wording like `effective_from`, all configurable under `timed:` —
  lands on a timeline as pending / active / expiring / expired, together with the
  readiness of everything that links to it. A window that opens inside the horizon
  while its dependents are still unfinished is flagged **at risk** on the Overview
  and as a badge on the rail. No document *about* change is required: the dates
  live on the documents themselves.
- **Change history from git** ([REQ-027](repo-product/docs/specs/requirements/REQ-027.md)).
  `/history` reads the workspace's commits (content-root scoped) and classifies every
  touched path through the current config, so the feed reads "3 requirements · 1 spec"
  rather than a file list. A selected commit is explained as a **semantic delta** —
  frontmatter properties that moved, normative statements added, dropped or reworded,
  sections that came and went — with the text diff one click away and, when an AI tier
  is configured, a cached one-sentence summary generated from that delta. `/changes` is
  the branch-scoped counterpart: uncommitted drafts, commits ahead of main, open MR.
- **Protected main, personal workspaces.** The default branch is never edited directly:
  the first edit transparently creates/switches to the user's `ws/<user>` branch
  (server-claimed, fast-forwarded onto main when safe). Direct API writes to protected
  branches 403. Drafts autosave to the branch worktree (debounced), survive branch
  switches and navigation (localStorage recovery + unload keepalive), and an explicit
  Commit turns them into history. Tree badges are real `git status`; merging
  prompts to commit pending changes first.
- **State lives in git; the database is bookkeeping.** Drafts are uncommitted
  changes on a per-branch worktree, history is git commits — SQLite holds
  identity, sessions, grants and workspace claims, never documents. Concurrent
  saves of the same file are guarded by a `baseSha` precondition: the later
  writer gets a 409 and a "file changed — reload" prompt instead of silently
  clobbering.
- **Two ways to land on main.** Local-auth deployments merge directly in-app: a
  workspace branch lands on the protected default branch through a previewed merge
  (diff + conflict check + dirty-worktree refusal); `git merge-tree` does the work
  as a merge commit or squash. Forge-PAT deployments instead **propose**: the branch
  is pushed with the user's own token and a merge request / pull request is opened
  via the forge API (idempotent — re-proposing pushes onto the open MR); review and
  the merge happen on the forge, and main comes back via fetch.
- **Forge-PAT auth (`auth.forge`).** Users sign in with a personal access token from
  the deployment's GitLab/GitHub; identity comes from the forge `/user` API and the
  deployment role from the user's actual permission on the main project. The token
  lives in the browser's localStorage and, per session, in a RAM-only server vault —
  never in the database. Every user gets **fully independent server-side clones**
  fetched with their own token, so nothing one token can reach ever leaks to another
  user. Reference sources are defined in-repo (`.specquill/config.yml` `sources:`) —
  listing one there grants nothing; the user's own forge permission is the gate.
- **Honest git identity.** The logged-in user is both **author and committer** on every
  commit and merge; the SpecQuill service identity is recorded as a `Co-authored-by:`
  trailer instead.
- **Byte-fidelity editing.** Untouched documents save byte-identical; frontmatter edits
  go through the `yaml` Document API (comments/formatting preserved); WYSIWYG edits
  normalize markdown to house style (covered by a golden round-trip suite).
- **Rich WYSIWYG.** Slash-command menu (`/` inserts headings, lists, task lists,
  quotes, tables, dividers, code/mermaid blocks, images, sketches), floating selection
  toolbar (bold/italic/strike/code/link), link dialog (Ctrl+K, hover to preview/edit),
  table editing controls (add/remove/align/drag rows & columns), a collapsible outline
  panel with click-to-jump, markdown-aware clipboard, and inline formatting via
  fixed toolbar, ⌘B/⌘I, or markdown syntax. **Images**: paste, drag-drop, or upload —
  files land in `<docdir>/assets/` on the branch worktree (`POST /assets`, served raw
  via `GET /raw/{path}`), embedded as doc-relative markdown. In edit mode internal
  links follow on Ctrl/Cmd+click (plain click places the cursor).
- **Sketches are PNGs.** New excalidraw sketches save as `*.excalidraw.png` — a real
  PNG with the scene JSON embedded (excalidraw's export-embed-scene), so they render
  natively anywhere git renders images (GitHub included) and stay fully editable in
  the built-in sketch editor. Legacy `*.excalidraw` JSON files keep working.
- **Sessions idle out after 10 minutes** without a request (sliding expiry server-side;
  `session.ttl` in config). The cookie is a browser-session cookie — activity keeps you
  signed in indefinitely.
- **Responsive reading.** Under 900px the rail/tree/speccy collapse (tree becomes a
  hamburger drawer, speccy an overlay) and documents read full-width.
- **Read-only input repos** (e.g. a regulations repo) are fetched on an interval,
  browsable in the tree (🔒), and refuse writes server-side.
- **OKF bundles.** Workspaces conform to the
  [Open Knowledge Format](repo-product/docs/specs/specs/okf.md) (v0.1): every document carries a
  `type`, and opted-in bundles get `index.md` listings regenerated on every
  commit — readable by any OKF consumer or agent straight from git. The
  `log.md` change history is NOT materialized in the repo (git is the
  history): it is generated on the fly and injected only when the OKF bundle
  is exported through a share link. Untyped OKF body links show up as dashed
  reference edges in the traceability graph.
- **Workspace onboarding.** `specquill init <dir> [-types requirements,specs,changes,…]`
  scaffolds a new workspace repo: folder skeleton per chosen document family
  (requirements, specs, regulations, data-mappings, changes, work-items, decisions, glossary),
  the combined `.specquill/config.yml` (model + property schema), starter documents, a server-config
  stub — and the speccy's workspace-side brain: **authoring skills** under
  `.specquill/skills/`, an **instructions** starter (`.specquill/instructions.md`,
  with `speccy.instructions` in `config.yml` as the short inline form) and the
  **project memory** convention (`.specquill/memory/`, one decision per file).
  All of it is pinned into the system prompt, versioned in git, and reviewed
  like any other change.
- **Two model tiers.** `ai.model` is the main (thinking-class) tier for chat and
  draft edits; `ai.quick_model` is a fast one-shot tier for small tasks. Commit
  messages are auto-drafted from the uncommitted diff on the quick tier
  (`POST /commit-message`) and prefill the commit dialog — editable, regenerable,
  never overwriting what you typed. `<think>…</think>` reasoning tags are stripped.
- **Speccy** (`ai:` config) talks to any **OpenAI-compatible** chat endpoint —
  OpenAI, Gemini (`…/v1beta/openai`), Azure, Ollama — with the branch snapshot as
  grounding (no index; the workspace is prompt-sized). Chat streams over SSE;
  "Draft edits & open as diff" asks the model for surgical search/replace edits,
  validates them (impacted files only, unique match), and applies them as
  **uncommitted saves on a `speccy/<doc>` branch** — the human reviews via the
  normal status → commit → merge flow. `scripts/mock-llm.py` is a keyless dev provider.
- **Chat tools.** On a writable workspace branch the chat can act directly:
  `read_file`/`list_files`/`search` (full files, listings and text search over
  the workspace AND **every selected reference source** — `grounding: true`
  only decides which sources are additionally excerpted into the prompt, so
  large implementation repos stay explorable without prompt-stuffing),
  `edit_file`/`create_file` (unique search/replace or new documents —
  always **uncommitted drafts** on the current branch, never on protected ones;
  frontmatter must still parse and `created:`/`updated:` are maintained
  server-side), and `ask_user` (a clarifying question with option chips that
  pauses the conversation). Tool descriptions carry the workspace's own
  vocabulary — statuses, schema enums, family folders, ID patterns. Extra
  authoring rules live in `.specquill/instructions.md` and/or
  `speccy.instructions` in `.specquill/config.yml`, pinned into every prompt
  next to the skills. Speccy interviews rather than assumes: undefined
  behavior becomes pointed `ask_user` questions grounded in what the
  referenced repositories already do, and durable answers are persisted as
  **project memory** — one decision per file under `.specquill/memory/`
  (merge-friendly by construction), pinned above the specs in every
  conversation and reviewed/committed like any other workspace change.

## Run (dev)

```sh
make dev-fixture        # local bare origins under data/origin/ from repo/
                        # (also drops the store so it can't outlive the fixtures)
make web server         # build SPA into the embed dir + build specquill
python3 scripts/mock-llm.py &          # keyless speccy provider for dev
./server/specquill -config specquill.dev.yml -dev
# → http://localhost:8643  (dev flag auto-authenticates as auth.dev_user)
```

Frontend dev loop with HMR: `cd web && npm run dev` (Vite on 127.0.0.1:5643, proxying /api). A server started with `-dev` reverse-proxies the SPA routes on :8643 to vite while it runs — :8643 never serves a stale build in dev — and falls back to the embedded build when vite is down.

## Run (production-ish)

```sh
make build && ./server/specquill setup     # interactive wizard writes specquill.yml
# (or: cp specquill.example.yml specquill.yml and edit — running the server
#  without any config offers the wizard too)
./server/specquill -config specquill.yml
# forge-PAT mode needs no server-side credentials at all — users bring their own
# tokens. Local-auth mode instead: export the token_env vars and add users with
./server/specquill -config specquill.yml user add flo 'Flo' flo@example.com
```

Requirements: `git` ≥ 2.38 on the server (checked at startup). Exactly one `writable`
repo plus any number of `readonly` ones. The forge identity's `name`/`email` become
the git author on every commit.

## Configuration — what lives where

Two auth modes, two splits. The rule of thumb: **credentials and identity follow the
mode; content-shaped settings live in the repo.**

**Forge-PAT mode (`auth.forge`, the v1 deployment)** — the server config is minimal
and credential-free; access rides each user's own token:

| lives in server YAML | lives in `.specquill/config.yml` (in the repo) | lives with the user |
|---|---|---|
| forge kind + base URL (`auth.forge`) | reference **source definitions** (`sources:` — name, https remote on an allowlisted host, branch) | the PAT (browser localStorage + RAM-only session vault) |
| the workspace repo (`projects:` — remote, default branch, content root) | reference **selection** (`references:` — paths filter, `grounding:` = prompt excerpting; every selected source is chat-tool-explorable) | identity + git author (forge `/user`) |
| optional: scopes / token-creation link overrides, `admin_emails`, `default_role` floor | taxonomy, entities, views, schema — and the speccy's brain: skills, `speccy.instructions`, `instructions.md`, `memory/` | deployment role (forge permission on the main project, refreshed each login) |
| **no tokens, no source catalog** (a top-level `sources:` block is rejected) | | per-user clones under `data/…/repos/u<id>/` |

**Local-auth mode (`auth.local`, the v2 developer setup)** — the server owns shared
credentials, so source definitions must stay server-side: the YAML carries the source
**catalog** (git + url/openapi/confluence importers) with `token_env` env-var
credentials, and the in-repo config only **selects** cataloged sources by name
(selection ∩ catalog — in-repo config can never mint access). In-app merges,
boot clones and background sync loops exist only in this mode.

The authoritative version of this table is
[`specs/forge-auth.md`](repo-product/docs/specs/specs/forge-auth.md); the
authorization reasoning is [`REQ-004`](repo-product/docs/specs/requirements/REQ-004.md)
and [`REQ-024`](repo-product/docs/specs/requirements/REQ-024.md).

### Example: forge-PAT deployment with reference sources

A specs workspace grounded on regulatory texts, with the implementation repo
that is *built from* these specs selected as a read-only source — so the speccy
can check the code against the requirements (drift detection).

Server YAML — minimal and credential-free; the top-level `sources:` block must
stay empty in this mode:

```yaml
listen: ":8080"
data_dir: /var/lib/specquill
base_url: https://specquill.acme.com

projects:
  - id: trading-specs
    remote: https://gitlab.acme.com/trading/trading-specs.git
    default_branch: main

auth:
  forge:
    kind: gitlab
    base_url: https://gitlab.acme.com
    # in-repo source remotes may only name the forge/project hosts;
    # extra hosts (e.g. a public mirror) must be listed here
    allowed_source_hosts: [gitlab.esma-mirror.org]
  admin_emails: [ops@acme.com]

ai:
  enabled: true
  base_url: https://api.openai.com/v1
  model: gpt-4o
  quick_model: gpt-4o-mini
  api_key_env: SPECQUILL_AI_KEY
```

`.specquill/config.yml` in the workspace repo — sources are **defined** here
(git repos, https only, no credentials; each user fetches them with their own
PAT, so a definition never mints access) and **selected** under `references:`:

```yaml
version: 2
project: trading-specs
default_branch: main

sources:
  - name: regulations            # regulatory texts the requirements derive from
    remote: https://gitlab.acme.com/compliance/regulations.git
  - name: esma-rts               # public mirror — needs the allowed_source_hosts entry
    remote: https://gitlab.esma-mirror.org/esma/rts-texts.git
    default_branch: master
  - name: trading-platform       # the implementation built from these specs
    remote: https://gitlab.acme.com/trading/trading-platform.git

references:
  # small, load-bearing texts: pin into the speccy system prompt
  - source: regulations
    grounding: true
  - source: esma-rts
    grounding: true
    paths: [rts22/]              # grounding-only prefix filter
  # the implementation is too big to prompt-stuff: no grounding — the speccy
  # still reads it on demand via its list_files/search/read_file tools
  - source: trading-platform
```

`grounding: true` only decides what gets excerpted into the prompt; every
selected source is fully explorable through the chat tools regardless.

## Verify

```sh
make test               # Go: gitx/auth/API suites · web: model, frontmatter, Milkdown round-trip
make e2e                # Playwright against a running dev server: edit → commit → merge
python3 scripts/verify-write-path.py   # API-level write/commit/push/409 checks
python3 scripts/mock-forge.py &        # mock GitLab for exercising forge-PAT auth
```

## Deploy

`Dockerfile` builds the whole thing into one alpine+git image (pushed to
ghcr.io on every push to `main` and every tag); [`DEPLOY.md`](DEPLOY.md)
documents self-hosting it — one binary or container, one YAML file, a
persistent directory, a reverse proxy.

## Notes & future work

- Speccy grounding is whole-snapshot prompting — fine at workspace scale; a retrieval
  index would be needed for large corpora or multi-repo grounding.
- Read-only repos are browse-only inputs; federating them into the traceability model
  (cross-repo `drives` links) is future work.
- Conflicting PRs are blocked with the conflicted paths listed; materializing the
  conflict into the source worktree for in-app resolution is future work.

More