Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jorgeferrando/sdd-skills/llms.txt

Use this file to discover all available pages before exploring further.

A specialist is a directory in specialists/{name}/ with a manifest.yaml and one or more .md convention files. Custom specialists let you codify domain expertise specific to your stack, team, or organization — and have /sdd-apply, /sdd-audit, and /sdd-verify enforce those conventions automatically, exactly as they do for the built-in specialists.

Directory structure

Every specialist requires exactly two things in its directory:
specialists/
└── your-specialist/
    ├── manifest.yaml              # metadata — name, description, scope, files
    └── conventions-your-name.md  # the actual RFC 2119 rules
You may include more than one .md file if your domain naturally separates into sub-areas (for example, a graphql specialist might ship both conventions-graphql-schema.md and conventions-graphql-resolvers.md). Every file listed in manifest.yaml will be copied to openspec/steering/ on install.

manifest.yaml format

The manifest is a small YAML file that tells the installer what to copy and where. Here is the real manifest for the async-node specialist:
name: async-node
description: Node.js async/concurrency advisor — async/await correctness, unhandled rejections, child_process safety, stream handling, and timeout discipline for Node.js backends.
applies_to: node-projects
files:
  - conventions-async.md → openspec/steering/conventions-async.md
FieldRequiredDescription
nameMachine-readable identifier. Must match the directory name.
descriptionHuman-readable summary shown by ./install-specialist.sh. Shown in the installer listing — be specific about what domain expertise this adds.
applies_toScope hint. Use all for universal applicability, or a descriptor like node-projects, typescript-projects, or a list such as ["react", "vue", "svelte"]. Skills use this to decide whether to load the specialist for a given task.
filesOne entry per convention file, in the format source.md → openspec/steering/target.md. The source path is relative to the specialist directory.
The separator in the files list is cosmetic — the installer uses it to show you where each file lands, but the actual copy is driven by cp source target. Keep the naming convention conventions-{name}.md so it stays consistent with the built-in specialists.

Convention file format

A convention file is plain Markdown with RFC 2119 rules grouped into sections. Here is the actual conventions-async.md from the async-node specialist — use this as your template:
# Specialist: Async Node.js

> Rules for correct async/concurrency patterns in Node.js backends.
> Read by sdd-apply, sdd-audit, and sdd-verify.
> Violations marked **Critical** block PRs. Others are warnings.

## async/await

- **MUST** use `async/await` throughout. Never mix `.then()`/`.catch()` chains
  with `await` in the same function — pick one style per codebase and stick to it.
- **MUST** `await` every Promise that can reject. Floating Promises (calling an
  async function without `await` and without `.catch()`) cause silent failures.
- **MUST NOT** use `new Promise()` wrappers around code that is already
  Promise-based. Only use `new Promise()` to promisify genuinely callback-based APIs.
- **MUST NOT** use `async` on a function and then never `await` inside it. Either
  the function needs `await` or it should not be `async`.
- **SHOULD** mark intentionally fire-and-forget calls with a `void` prefix
  (`void sendMetric()`) so readers know the omission is deliberate, not a bug.

## Error handling

- **MUST** handle every `await` that can reject — either with try/catch at an
  infrastructure boundary (converting to `Result`) or by letting it propagate to
  a guaranteed boundary handler (e.g. Fastify's global error handler).
- **MUST NOT** swallow errors silently in catch blocks. At minimum, log the error
  with context before discarding it.
- **MUST** register a process-level `unhandledRejection` handler that logs and
  exits cleanly. Never rely on Node's default behavior to surface these in production.

## child_process (git, gh CLI)

- **MUST** use `execFile` or `spawn` with argument arrays, not `exec` with string
  interpolation. String interpolation enables shell injection when inputs come from
  user repos.
- **MUST** set a timeout on every `child_process` call. Git operations on large
  repos and `gh` API calls can hang indefinitely without one.
- **MUST** check the exit code of every shell command. A non-zero exit must be
  treated as an error and surfaced to the caller — never silently ignored.
- **MUST NOT** pass user-controlled strings (branch names, file paths from repos,
  commit messages) directly into shell command strings. Validate and sanitize first,
  or use argument arrays.

## Concurrency and parallelism

- **MUST NOT** run unbounded parallel operations with `Promise.all` over
  user-supplied arrays. Cap concurrency with a semaphore or process in batches.
  Unbounded parallel DB queries or API calls cause resource exhaustion.
- **SHOULD** use `Promise.all` only when all operations are independent and the
  array size is bounded and known.

## Timeouts and deadlines

- **MUST** set an explicit timeout on every outbound network call (Claude API,
  PostgreSQL queries, child_process). Never rely on OS-level or library defaults alone.
- **SHOULD** define timeout constants at the module level, not inline. Makes
  them easy to find, tune, and test.

## Streams

- **SHOULD** use streams for large file reads instead of `fs.readFile` into memory.
- **MUST** handle `error` events on all readable/writable streams. An unhandled
  stream error crashes the process in Node.js.
- **MUST NOT** buffer an entire stream into a string before checking its size.
  Check size as you read and abort early if it exceeds the limit.

## How to detect violations

When reviewing code during sdd-apply or sdd-audit, flag:
1. `async function` with no `await` inside
2. Promise returned from a function call with no `await`, `void`, or `.catch()`
3. `.then().catch()` chains mixed with `await` in the same file
4. `exec(` with template literals or string concatenation
5. `Promise.all(array.map(...))` where `array` comes from user input or external data
6. Missing timeout option on `pg.query`, `fetch`, `axios`, or `child_process` calls
7. Empty `catch` blocks or `catch (e) {}` with no logging
8. `new Promise()` wrapping an already-Promise-returning function
The “How to detect violations” section at the bottom is particularly important — it gives /sdd-audit a concrete, enumerated checklist rather than leaving detection to interpretation.

Guidelines for writing specialist rules

Follow the same conventions as the built-in specialists to keep everything consistent and enforceable.

Use RFC 2119 levels

Write **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, or **MAY** — bold, uppercase. MUST is a hard requirement; SHOULD is a strong recommendation with room for justified exceptions.

Include 'How to detect violations'

End every convention file with a numbered checklist of what to look for in code. This is what /sdd-audit works from. Concrete patterns (function names, keywords, structural anti-patterns) are more useful than general descriptions.

Classify violations correctly

Default to Important (technical debt, non-blocking). Reserve Critical for genuine hard gates — security vulnerabilities, TDD violations. Overusing Critical trains the model to ignore it.

Keep rules actionable

**MUST** use X for Y is better than consider using X. Each rule should specify the required action clearly enough that there is no ambiguity about compliance.

Include the why

Follow each rule with a brief reason after a dash. **MUST NOT** use readFileSync — blocks the event loop and degrades throughput under load. The reason helps the model make better decisions when the rule’s edge cases arise.

Don't duplicate conventions.md

Specialists add domain-specific knowledge. If a rule already exists in your project’s conventions.md, skip it here. Duplication creates confusion about which file is authoritative and inflates context on every skill invocation.

Installing your custom specialist

Once the files are in place, the workflow is identical to installing a built-in specialist:
# 1. Create the specialist directory and add your files
mkdir specialists/my-specialist
# ... write manifest.yaml and conventions-my-specialist.md ...

# 2. Install it into openspec/steering/
./install-specialist.sh my-specialist

# 3. Confirm it appears in the installed list
./install-specialist.sh --installed
The installer output shows exactly where each file landed:
Installing specialist: my-specialist

  ✓      conventions-my-specialist.md
  → Active for sdd-apply, sdd-audit, sdd-verify

Done. Specialists are now active in the SDD workflow.
If the file already exists in openspec/steering/ (for example, you installed a previous version), the installer will skip it and print skip (already exists). Remove the old file first with --remove or delete it manually, then re-install.
./install-specialist.sh --remove my-specialist
./install-specialist.sh my-specialist
Once installed, the specialist is picked up automatically on the next skill invocation — no need to reference it explicitly in your prompts or steering files. Skills read all .md files in openspec/steering/ whose names match the expected conventions-*.md pattern, so your custom specialist is treated identically to any built-in one.

Complete example: a custom GraphQL specialist

To illustrate the full workflow end to end, here is what a minimal graphql specialist looks like: specialists/graphql/manifest.yaml
name: graphql
description: GraphQL API advisor — schema design, resolver patterns, N+1 prevention with DataLoader, and query depth limits.
applies_to: ["graphql", "typescript", "node"]
files:
  - conventions-graphql.md → openspec/steering/conventions-graphql.md
specialists/graphql/conventions-graphql.md
# Specialist: GraphQL

> Rules for GraphQL schema design and resolver implementation.
> Read by sdd-apply, sdd-audit, and sdd-verify.

## Schema design

- **MUST** use input types for all mutation arguments. Never pass scalar
  arguments directly to mutations.
- **MUST NOT** expose internal database IDs as GraphQL IDs without encoding
  them — use opaque cursor-style IDs.
- **SHOULD** version breaking changes via a new field rather than changing
  an existing field's type.

## Resolver patterns

- **MUST** use DataLoader (or equivalent) for any field resolver that fetches
  a related entity. Direct DB calls inside resolvers cause N+1 queries.
- **MUST NOT** perform authorization logic inside resolvers. Authorization
  belongs in the service/use-case layer that the resolver delegates to.

## Query safety

- **MUST** set a maximum query depth limit. Deeply nested queries can cause
  exponential resolver execution.
- **SHOULD** set a query complexity limit in addition to depth. Depth alone
  does not catch wide queries with many sibling fields.

## How to detect violations

When reviewing code during sdd-apply or sdd-audit, flag:
1. Mutation fields with scalar arguments instead of input types
2. Resolver functions that call the database directly without DataLoader
3. Authorization checks (role comparisons, permission checks) inside resolver functions
4. Missing depth-limit or complexity-limit configuration in the GraphQL server setup
5. Resolvers that fetch the same entity type in a loop
Install it:
./install-specialist.sh graphql
From this point on, /sdd-apply will enforce DataLoader usage whenever it writes GraphQL resolvers, and /sdd-audit will flag any resolvers that bypass it.

Build docs developers (and LLMs) love