specgate/docs v0.20.0

Domain packs#

Reusable, versioned domain knowledge. A pack is to a spec tree what a dependency is to code: you install it, you pin it, and you upgrade it deliberately.

The three repositories The tool is an npm package. A domain pack repository holds stack-agnostic templates written by domain experts and is version-tagged like a library. An implementation project renders those templates for one stack and records which pack version it came from, so a later pack release can be pulled in as a diff. Specgate the tool · one npm package npx @rsaglobaltech/specgate Domain pack by domain experts · stack-agnostic pack.yaml templates/spec.md.tpl templates/features/**.feature.tpl pinned by tag — v0.2.0, like a library one pack, many stacks Your project by humans and agents · one stack spec.md · AI_RULES.md features/**.feature docs/specs/traceability.md .specops.lock — which pack version, exactly Spring · Quarkus · Node · FastAPI · … reads and lints it renders it here specops add specops sync
The pack is a dependency, not a copy-paste. A new pack version arrives as a diff you review — which is what keeps eleven services from drifting apart.

Two kinds of pack in this repository#

Proven packs come from products built with Specgate from a client brief: every requirement was implemented and tested by that product, and the values in the scenarios are the ones its tests asserted. What a pilot could not answer ships as Needs Clarification — the question, not an invented answer.

PackFrom the pilotProven onReqsScenariosLeft open
ledger/backendCore banking (FinCore)Spring Boot 3.3, Java 21, PostgreSQL1014—
transfers/backendCore banking (FinCore)Spring Boot 3.3, Java 21, PostgreSQL1114—
event-registration/backendEvent operations (EventTech)Quarkus 3, event sourcing, PostgreSQL811—
clinical-record/backendClinical cases (HealthTech)Quarkus 3, PostgreSQL, fictitious data911drug interaction alerts
appointment-booking/backendBookingsTypeScript, Next.js, PostgreSQL1014—
shop-orders/backendCredit shopTypeScript, Next.js, PostgreSQL910shipping costs
time-attendance/backendRebar CrewTypeScript, Expo + Next.js811—

The pilots' code is private; the packs carry their requirements and scenarios, in English and with no client data, in separate id ranges (REQ-101…REQ-708) so one project can install all seven. Install them with specops add into a project made by specgate init: init --from-pack currently fails on a requirement with two scenarios (a known issue).

Starter packs — auth, billing, audit-log, notifications, feature-flags, multi-tenant, file-storage, search, reporting, webhooks, sample-contracts — are examples of the format: a few generic Draft requirements each, and no product was built from them. metadata.tier says which a pack is (proven or starter; absent means starter), and pack lint refuses proven without a metadata.provenance saying what proved it.


Author a domain pack from scratch#

Goal: package a reusable bundle of requirements, use cases, aggregates, events, and Gherkin templates.

bash
# 1. Scaffold a pack
npx @rsaglobaltech/specgate@latest pack init \
  --out ./domain-packs \
  --name "Billing Backend" \
  --type backend

# 2. Edit ./domain-packs/billing/backend/pack.yaml
#    Required fields (see schemas/pack.schema.json for the full schema):
#      schema_version, metadata.{name,version,language,project_type}
#      variables.required
#    Optional: requirements, use_cases, commands, aggregates, events,
#              scenarios, outputs.files, rules

# 3. Lint
npx @rsaglobaltech/specgate@latest pack lint \
  --pack-root ./domain-packs \
  --pack billing/backend

Minimum viable pack.yaml:

yaml
# yaml-language-server: $schema=../../../schemas/pack.schema.json
schema_version: "1.2.0"
metadata:
  name: "Billing Backend"
  version: "0.1.0"
  language: "en"
  project_type: "backend"
variables:
  required: [PROJECT_NAME, PROJECT_SLUG, DOMAIN]
requirements:
  - id: REQ-001
    title: "Charge customer at checkout"
    priority: Must
    status: Draft
outputs:
  files:
    - target: "features/billing/charge.feature"
      template: |
        Feature: Charge customer at checkout
          Scenario: Successful payment for {{PROJECT_NAME}}
            Given a valid card on file
            When the user confirms the order
            Then a CHARGE event is emitted

pack lint blocks publication on schema violations, missing requirements, unresolved placeholders, and duplicate IDs.



Apply a domain pack to an existing project#

Goal: layer a pack onto a project generated in §1, supplying its template variables.

The recommended ergonomic path is specops add (npm-install-style):

bash
# From inside your project (auto-detected project dir)
specgate specops add \
  --pack-repo https://github.com/acme/billing-specops.git \
  --pack-version v0.1.0 \
  --pack backend \
  --var PROJECT_NAME="Acme Energy Hub" \
  --var PROJECT_SLUG=acme-energy-hub \
  --var DOMAIN="community energy"

add writes/updates .specops.lock so subsequent specops sync / specops diff calls remember the source, version, and vars.

Lower-level alternative (expand) — same behaviour, more flags:

bash
specgate expand \
  --pack-root ./domain-packs \
  --pack billing/backend \
  --project-dir /tmp/acme-energy-hub \
  --var PROJECT_NAME="Acme Energy Hub" \
  --var PROJECT_SLUG=acme-energy-hub \
  --var DOMAIN="community energy"

To take a pack OFF the project:

bash
specgate specops remove backend    # the pack id .specops.lock records

remove drops the entry from .specops.lock but does not delete generated files — you might have hand-edited tests pointing at them. Use git status afterwards and clean up by hand.

Side effects:

  • New .feature files under features/.
  • New rows in docs/specs/traceability.md.
  • Rich-DDD docs (use-cases.md, aggregates.md, …) populated/extended.
  • A .specops.lock file at the project root recording pack repo, version, and the vars you passed. Commit this file.

Flags:

FlagUse
--dry-runPrint actions without writing.
--no-examplesSkip files marked seed: true (good for production projects).
--cache-dir <path>Override the cache directory for remote packs.


Build a contracts pack for API-first work#

Goal: ship a pack that defines REST/AsyncAPI contracts and consumer-driven Pact tests.

Set project_type: contracts in pack.yaml. The schema then accepts two extra top-level fields:

  • api_contracts — list of {id, title, type, provider, consumers[], schema_ref, requirement} entries.
  • consumer_driven_tests — list of {id, consumer, provider, pact_file, requirement} entries.

Example excerpt:

yaml
schema_version: "1.2.0"
metadata:
  name: "API Contracts Pack"
  version: "0.1.0"
  project_type: "contracts"
variables:
  required: [PROJECT_NAME, PROVIDER_SERVICE, CONSUMER_SERVICE]

api_contracts:
  - id: AC-001
    title: "Provider REST API — v1"
    type: REST
    provider: "{{PROVIDER_SERVICE}}"
    consumers: ["{{CONSUMER_SERVICE}}"]
    schema_ref: "contracts/openapi/provider-v1.yaml"
    requirement: REQ-001

consumer_driven_tests:
  - id: CDT-001
    consumer: "{{CONSUMER_SERVICE}}"
    provider: "{{PROVIDER_SERVICE}}"
    pact_file: "contracts/pacts/{{CONSUMER_SERVICE}}-{{PROVIDER_SERVICE}}.json"
    requirement: REQ-002

When applied with expand, a contracts pack generates docs/specs/test-strategy.md describing the TDD gates (failing pact = failing build, breaking-change rules, contract versioning policy). Pair it with validate --strict-tdd so every AC-NNN must trace to a CDT-NNN and a .feature.

Browse packs/sample-contracts/contracts/pack.yaml for a complete working example.



Compose multiple packs with specops.config.yaml#

Goal: declare every pack a project depends on in one place, so a fresh clone can rebuild specs with specops sync.

Create specops.config.yaml at the project root:

yaml
specops_version: 1
packs:
  - repo: https://github.com/acme/parking-specops.git
    version: v0.1.0
    pack_id: backend
    vars:
      PROJECT_NAME: Smart Parking
      PROJECT_SLUG: smart-parking
      DOMAIN: parking operations
  - repo: https://github.com/acme/billing-specops.git
    version: v0.2.0
    pack_id: contracts
    vars:
      PROJECT_NAME: Smart Parking
      PROVIDER_SERVICE: billing-svc
      CONSUMER_SERVICE: parking-svc

Then on a fresh clone:

bash
npx @rsaglobaltech/specgate@latest specops sync --project-dir .

When .specops.lock is absent, sync reads specops.config.yaml, expands every listed pack, and writes the lockfile. When the lockfile exists, the lockfile wins — specops.config.yaml is the intent, the lockfile is the resolved state (think package.json vs package-lock.json).



Packs in a private repository#

A team's packs often live in a private repository. Locally your git credentials are used. In CI, the job specgate ci init writes for GitHub looks for a SPECOPS_TOKEN secret: create a fine-grained token with read-only access to the pack repository, add it to the project repository as that secret, and the drift check can clone the pack. Without it the check fails with pack_unavailable and says it needs credentials.


Bump a pack version safely (specops diff + sync)#

Goal: upgrade parking-management/backend from v0.1.0 to v0.2.0 without surprises.

bash
# 1. Preview the change (no writes)
npx @rsaglobaltech/specgate@latest specops diff \
  --project-dir ./smart-parking \
  --pack parking-management/backend \
  --pack-version v0.2.0

# Output:
# ── parking-management/backend @ v0.2.0 (current: v0.1.0) ──
#   + features/pricing/dynamic_pricing.feature
#   ~ docs/specs/use-cases.md
#   ~ docs/specs/traceability.md
#   1 added · 2 modified · 9 unchanged

# 2. Apply once you're satisfied
npx @rsaglobaltech/specgate@latest specops sync \
  --project-dir ./smart-parking \
  --pack parking-management/backend \
  --pack-version v0.2.0

# 3. Re-validate
npx @rsaglobaltech/specgate@latest validate ./smart-parking --strict

# 4. Commit the updated .specops.lock and the regenerated spec files
git add .specops.lock docs/specs features
git commit -m "chore(specs): bump parking-management/backend to v0.2.0"

Plain sync (no --pack / --pack-version) re-expands every pack in the lockfile using the vars persisted there — no need to retype --var flags.

What sync does with the files a pack shares with the project:

  • Requirement trace lines (<!-- specgate:trace … -->) merge field by field. A field the pack changed — a new depends, a renamed use case — takes the pack's value; the fields the project wrote (status from done, links) stay.
  • A generated matrix (specgate:derived) is regenerated once every pack is synced, never merged.
  • The domain catalogs (aggregates.md, events.md, commands.md, use-cases.md, domain-model.md) hold every installed pack's rows: a pack replaces the rows with its ids and leaves the others. Change a catalog row in the pack, not in the project — the pack's next sync rewrites it.


Next#