lvlup-sw/exarchos

invariants

Guided authoring of an architectural invariant catalog entry through a 6-step interview (elicit, locate, weight, enforce, number, commit).

ソースを見る
リポジトリの原文

見出し、例、コード、表、リンク、参照画像を含む原文を表示しています。

Authoring Invariants Skill

Overview

This skill turns an architectural rule in the author's head into a registered, enforced catalog entry. It is the on-ramp described in docs/designs/archive/2026-05-25-invariants-authoring-wizard.md — an LLM-driven authoring conversation, not a stdin question loop.

The division of labor is strict and load-bearing:

  • The agent (you) supplies judgment + natural-language elicitation. You

interview the author, draft prose, propose globs, and shape the entry.

  • The verbs own validation + writing. Every mutation goes through a

deterministic, schema-validated orchestrate action — invariants_scaffold, invariants_add, and invariants_amend. You never hand-write, append, or edit catalog YAML yourself. The verb renders it; you confirm it.

This shape is the only one consistent with the agent-first philosophy: inputs are constrained at the schema level (INV-5a), mutating verbs default to dry-run (INV-5c), and authoring is event-sourced (INV-1).

When to Use

  • The author wants a new architectural rule enforced on their own SDLC path

(surfaced at ideate, turned into acceptance criteria at planning, audited at review by the check_invariant_conformance gate).

  • An author says "add an invariant", "enforce that X", "make this a rule".
  • An author wants to correct an already-committed entry — a wrong summary, a

stale glob, a weak audit-prompt. That is invariants_amend, and it is the only sanctioned way to change a shipped entry (see step 7).

When NOT to Use

  • The author wants to hand-write YAML — point them at the verbs instead; the

skill exists so they don't have to.

  • The rule is a one-off lint, not an architectural invariant → a project linter

is the right home.

  • You are mid-workflow editing state, running a review, or planning — those are

other skills. This skill only authors catalog entries.

The verbs (what you drive — never bypass)

Action (MCP)CLI facadeWhat it does
invariants_scaffoldexarchos invariants scaffoldCreate a starter catalog file for a tier; idempotently register it in .exarchos.yml. Never overwrites an existing file.
invariants_addexarchos invariants addValidate ONE new entry against the v3 schema (including the .strict() enforcement DSL — INV-4), then append it. `dryRun` defaults to true: returns the rendered entry + file diff, writes nothing. Append-only: a colliding id is rejected.
invariants_amendexarchos invariants amendCorrect ONE existing entry in place. id names the target (identity is not patchable); patch names the top-level fields to replace, and anything it omits survives verbatim. The merged entry is re-validated in full. `dryRun` defaults to true: returns the amended entry + a before/after diff, writes nothing.
doctorexarchos doctorReuse the existing invariants-catalog check — the validator. No new validate verb.
invariants_effectiveexarchos view invariants_effectiveThe merged, projected catalog the gate will enforce. Post-write confirmation.

The agent never declares an entry valid on its own authority — the verb is the validator. invariants_add returns the INV-5b carrier shape: success carries next_actions (["doctor", "view invariants_effective"]); validation errors carry validTargets / expectedShape / suggestedFix sourced from the Zod error, so you can self-correct and re-run rather than re-guess.

The interview (6 steps)

Walk the author through these in order. Elicit in prose, never make them think in YAML field names.

1. Elicit — the rule → summary

Ask the author to state the rule in one sentence. Distill it into a precise summary. Probe for the failure it prevents ("what goes wrong if this is violated?") — that sharpens both the summary and the later enforcement.

2. Locate — dimension, applies-to, affinities

  • dimension: a free-text grouping (e.g. audit-completeness, error-handling).
  • applies-to: the glob(s) the rule governs — ask the author to name the paths

(src/handlers/**, **/*.ts). These are author-named globs, never framework-inferred (INV-6: the surface is workload-neutral).

  • phase-affinity: phases where it bites (`ideate | plan | delegate | review |

synthesize`). Absent ⇒ all phases.

  • workflow-affinity: workflow types (`feature | debug | refactor | discovery |

oneshot`). Absent ⇒ all.

3. Weight — severity, integrity-class, and the tier you author into

  • severity.default: blocking or advisory.
  • severity.by-workflow (optional): downgrade for cheap workflows

(e.g. oneshot: advisory).

  • integrity-class (entry field; enum substrate | sdlc | authoring | user):

the entry's override authority, not its namespace. For a consumer-authored rule this is user. (substrate/sdlc are exarchos's own classes — you do not author those.)

  • tier (the verb arg) picks the catalog namespace, and the choice is

exarchos-substrate vs project-authored, NOT "which developers".** This is the one that bites — get it wrong and you silently collide with exarchos's own ids:

  • tier: userU-N ids — your project's own invariants. The default for

everyone consuming exarchos.** If you are authoring a rule for your own repo, this is always the answer (even if your project happens to name its rules INV-N internally — they map to U-N here).

  • `tier: dev` → `INV-N` ids — exarchos's _own_ reserved substrate catalog.

Exarchos ships its own INV-1..6 inside the tool, and they merge into every invariants_effective projection. Authoring into dev from a consumer repo collides your `INV-N` with exarchos's own — a silent namespace clash the doctor check can't catch (it only flags INV-* in a user catalog). Use dev only when working inside the exarchos repo itself. The verbs enforce this: invariants_scaffold / invariants_add reject tier: dev outside the exarchos repo (heuristic: package.json name ≠ @lvlup-sw/exarchos) with a RESERVED_TIER error that redirects to user; a genuine exarchos fork opts in with allowReservedTier: true.

4. Enforce — DEFAULT mode: audit, mode: check is opt-in

Default to `mode: audit`. You draft the audit-prompt from the elicited rule — a question the review subagent answers against the diff. Audit mode is pure judgment and always portable:

yaml
enforcement:
  mode: audit
  audit-prompt: >-
    Does this diff let a request handler return before emitting an audit event?
    Cite the offending file + line.

Offer mode: check only as an advanced opt-in when the rule is mechanically checkable. If the author opts in, propose a declarative combinator tree over grep/structural/heuristic leaves and validate it live via `invariants_add` with `dryRun: true` before showing it. See @references/check-mode.md for the combinator vocabulary and the opt-in flow. The enforcement DSL is .strict() and declarative-only: there is no script / exec / code escape hatch (INV-4), so you cannot emit an executable check even if asked.

5. Number — auto-id in the target namespace

Do not pick an id. invariants_add auto-assigns the next free id in the target catalog's namespace (U-N for user, INV-N for dev). Mention the id the verb assigned when you show the dry-run.

6. Commit — dry-run → confirm → write → verify

This step is a gate, not a formality. Always:

  1. Call invariants_add with dryRun: true (the default). This renders the

entry + diff and writes nothing.

  1. Show the author the rendered entry and the file diff verbatim. Make the

confirmation explicit: ask "commit this entry?" — do not silently re-invoke.

  1. Only on explicit confirmation, re-invoke invariants_add with

dryRun: false. This appends the entry, wires .exarchos.yml if the catalog is unregistered, and emits invariant.authored (+ catalog.registered on first registration — INV-1).

  1. Run doctor (the invariants-catalog check) to validate the resolved catalog.
  2. Show the invariants_effective delta so the author sees exactly what the

gate will now enforce.

If invariants_add returns a CATALOG_NOT_FOUND error, the target catalog does not exist yet — run invariants_scaffold first (the error's suggestedFix names the call), then resume at step 6.

7. Amend — correcting an entry that is already committed

Steps 1–6 author a NEW entry. Correcting one that already exists is a different verb, and invariants_add is not it: it only ever appends, and re-running it with an existing id is rejected (DUPLICATE_INVARIANT_ID) precisely because committing that duplicate would author a catalog the loader then refuses to read.

Use invariants_amend. It is id-targeted and field-scoped:

  • id names the entry to correct. It is not patchable — an entry's identity

survives an amendment, because every references: pointer and audit record naming the old id would otherwise go stale. Renaming is not an amendment.

  • patch names only the top-level fields that change. **Every field the patch

omits is carried through verbatim* — you do not restate the whole entry, and an amendment cannot silently drop a `references` list or an affinity the author never mentioned. A named field is replaced wholesale* (patching enforcement swaps the entire enforcement block).

  • The merged entry is re-validated against the full v3 schema, so an amendment

can never produce an entry that would have been rejected at authoring time — including the .strict() enforcement DSL (INV-4).

The gate is the same as step 6: dryRun: true first (the default) → show the author the before/after diff verbatim → explicit confirmation → re-invoke with dryRun: false, which emits invariant.amended naming the changed fields → doctor → show the invariants_effective delta.

Two refusals worth recognizing:

  • ENTRY_NOT_FOUND — no entry carries that id. The error's validTargets lists

the ids actually resolved, so pick from those or author a new entry instead.

  • CATALOG_EMPTY / CATALOG_UNREADABLE — the catalog resolved zero entries, or

its invariants: list could not be read at all. Neither is reported as a clean "not found": a moved or renamed catalog must not look like a catalog that simply lacks your entry. Check you named the right catalog.

Tool invocations

Scaffold a user catalog (idempotent; never overwrites):

ts
exarchos:exarchos_orchestrate({
  action: "invariants_scaffold",
  tier: "user",
  path: ".exarchos/invariants.md"
})

Dry-run preview (DEFAULT — writes nothing):

ts
exarchos:exarchos_orchestrate({
  action: "invariants_add",
  tier: "user",
  catalog: ".exarchos/invariants.md",
  entry: { /* the fields from steps 1-4; NO id — auto-assigned */ }
})

Commit after explicit confirmation:

ts
exarchos:exarchos_orchestrate({
  action: "invariants_add",
  tier: "user",
  catalog: ".exarchos/invariants.md",
  entry: { /* same entry */ },
  dryRun: false
})

Amend an EXISTING entry — dry-run first (note: only the fields that change; everything else is carried through):

ts
exarchos:exarchos_orchestrate({
  action: "invariants_amend",
  tier: "user",
  catalog: ".exarchos/invariants.md",
  id: "U-3",
  patch: { summary: "The corrected one-sentence rule." }
})

Commit the amendment after explicit confirmation:

ts
exarchos:exarchos_orchestrate({
  action: "invariants_amend",
  tier: "user",
  catalog: ".exarchos/invariants.md",
  id: "U-3",
  patch: { summary: "The corrected one-sentence rule." },
  dryRun: false
})

Use exarchos:exarchos_orchestrate({ action: "describe" }) (or the CLI --help) to discover the exact schema at runtime — flags auto-emit from each action's Zod schema (the CLI is schema-driven; do not assume hand-added flags).

Worked example

For one U-* entry authored end-to-end through all 6 steps, see @references/worked-example.md.

Anti-Patterns

Don'tDo Instead
Hand-write or Edit catalog YAMLRoute mutations through a verb: invariants_add to append a NEW entry, invariants_amend to correct an EXISTING one
Re-run invariants_add with an existing id to "update" an entryinvariants_add only appends — a colliding id is rejected. Use invariants_amend
Re-author a whole entry to change one fieldinvariants_amend is field-scoped: patch names only what changes, everything else survives
Rename an entry's idid is the catalog's primary key and is not patchable — every reference to the old id would go stale
Declare an entry valid yourselfThe verb validates; you confirm
Default to mode: checkDefault to mode: audit; check is opt-in (@references/check-mode.md)
Skip the dry-rundryRun: true first, ALWAYS, then explicit confirm
Silently re-invoke with dryRun: falseMake the confirmation step explicit
Pick an id by handThe verb auto-assigns the next free id in the namespace
Author into dev/INV-N from a consumer repodev is exarchos's reserved substrate namespace — use user/U-N (the verb rejects consumer tier: dev)
Infer globs from the frameworkAsk the author to name the globs (INV-6)
Skip doctor + invariants_effective after commitVerify the resolved catalog and show the delta
同じリポジトリから

関連する Skills

すべての Skills