ways

Authoring a way

way.yaml anatomy — skills, knowledge, requirements, slots, flow — and the rules that make it installable.

A way is a directory with a way.yaml at its root, plus the files it declares. The full JSON Schemas ship with the package (schemas/ways/v1alpha1/); this page is the working tour.

Anatomy

apiVersion: ways/v1alpha1
kind: Way                        # or RulePack for standalone rules
metadata:
  name: incu/dev                 # MUST be namespaced ns/name — conformance-enforced
  version: 1.0.0
  discipline: development
  publisher: incu

spec:
  skills:                        # → placed by skills.sh
    - path: skills/incu-way-development     # bundled (relative, no "..")
      uses: [vcs-host]                       # slot ids this skill uses → derives the way's slots
    - source: github:acme/skills/review     # or external — resolved by skills.sh

  knowledge:                     # → placed by steering, converted per agent
    - id: ts-rules
      file: rules/typescript.md
      applicability: { mode: fileMatch, globs: ["**/*.ts"] }
    - pack: { name: incu/nestjs-rules }     # external RulePack

  hooks:                         # → placed by hooks.sh — auto-executing event handlers
    - path: hooks/gate-guard                # bundled pack (dir with hooks.json)
    - source: github:acme/hooks/pr-lint      # or external — acquired + pinned

  requires:
    cli:
      - { name: git, required: true, check: "git --version" }
      - { name: gh,  required: false, check: "gh --version", install: "brew install gh" }
    env:
      - { name: GH_TOKEN, required: false, secret: true, docsUrl: "https://github.com/settings/tokens" }
    mcp:
      - { name: io.sentry/sentry, required: false }   # server.json vocabulary

  contracts:                     # where each contract NAMESPACE resolves from (a catalog)
    ways.dev: { source: github:incu-tech/ways-contracts, version: ^1.0.0 }
    # ways.dev has a built-in default — omit this block if you only use ways.dev/* contracts

  needs:                         # slots — enrich the derived ones or declare way-level ones
    - id: vcs-host               # (capabilitySlots is the deprecated alias of needs)
      displayName: Version-control host
      description: Hosts the repo and PRs.
      contract: ways.dev/vcs-host@1
      required: true
      options: [github, gitlab]  # wizard menu suggestions
  defaults:
    bindings: { vcs-host: github }

  flow:                          # see Gates & conformance
    phases: [...]
    gates: [...]
    generates: [...]
    state: .ways/state.json      # one state file per worktree — see State files

Rules that bite (learned from real ways)

  • Namespace the name. metadata.name must be ns/name (incu/dev) — it's the collision key for the lock, the cache, and the future registry. Non-namespaced ways are not conformant and won't install.
  • Ship Binding profiles inside the bundle (e.g. bindings/github.yaml). Profiles that live outside the way directory don't travel with the install, and bound slots will report unknown instead of pass.
  • A Binding's identity is {contract, impl}, not its filename — one impl may satisfy several contracts, so author one profile per contract (snyk-sast.yaml, snyk-sca.yaml).
  • Bundled paths must exist and stay inside the bundle (no ..) — add pre-flights this and refuses non-self-contained ways. Use source/pack for third-party content.
  • Declare check commands for anything that matters. git --version, gh auth status — they run only after the consumer approves your way, and they're what upgrade a pass from "binary on PATH" to "actually works".
  • Never put credentials in bindingConfig. Secrets belong in requires.env (secret: true) — the bind wizard refuses to prompt secret-shaped config keys, and doctor never echoes values.
  • At least one hard human gate. approval: user + enforcement: blocking, or the way isn't conformant — by design.
  • Let slots derive from skills[].uses. Declare what each skill uses and the slot appears automatically; add a needs[] entry only to enrich it (a displayName/description so bind/doctor read human, suggested options, a softened required). Use needs, not the deprecated capabilitySlots alias — conformance warns, and declaring both is a schema error. Slot ids must be unique within the declaration.
  • Point contract namespaces at a catalog. A slot's contract (acme.internal/scanner@1) must resolve — declare its namespace under spec.contracts (ways.dev has a built-in default), or bundle the SlotContract + Binding yourself. Conformance fails an unknown reference and names the near-miss. See Resolving contracts.

Hooks

spec.hooks[] declares hook packs — auto-executing event handlers — the same way skills[] declares skills: { path } for a bundled pack (a directory with a hooks.json + scripts) or { source } for an external one. ways gives them first-class treatment:

  • Full-clarity approval. The install plan expands each pack to its hook <event>[matcher] → <command> lines under the executable surface — you see exactly what runs on which event before approving. A hook file the manifest doesn't declare is disclosed and marked undeclared — not installed.
  • Placement → hooks.sh. add delegates each pack to hooks.sh (the third installer next to skills.sh and steering; override with WAYS_HOOKS_CMD), with the captured agent selection fanned out.
  • Pinned + inventoried. External hook sources are {sha, digest}-pinned in the lock (sibling of skills); ways install reproduces them and refuses on drift, and ways list --components shows a ⚡N hooks marker.
spec:
  hooks:
    - path: hooks/gate-guard   # PreToolUse on Bash(git commit*) — blocks commits while a gate is unapproved

Authoring a family

Shipping more than one flow under one identity? Don't fake a single flow — package them as a WayFamily: spec.members[] references each member way (each keeps its own flow), and shared skills/knowledge/needs/requires are declared once at the family level. A family declares no flow of its own.

Validate while you author

npx ways.sh validate ./my-way/way.yaml       # schema (any kind — Way, WayFamily, RulePack, …)
npx ways.sh conformance ./my-way/way.yaml    # the rules above
npx ways.sh add ./my-way && npx ways.sh doctor   # the full consumer experience, locally

On this page