From a4a5a917760b75106a72faae3cd15e2359d36739 Mon Sep 17 00:00:00 2001 From: Mateus Andrade Date: Thu, 30 Jul 2026 19:50:51 -0300 Subject: [PATCH] feat(assets): scaffold the six knowledge-base skills and their commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `scc init` shipped eight validators and no skill teaching the formats they enforce, so a fresh workspace demanded conformance to documents nobody was told how to write. design/plan.md recorded this as an open deviation: the design listed skills among what the binary scaffolds and never said which, which left 3.3 and 6.3 satisfiable by a workspace containing no skills. Which skills ship is now picked mechanically — one per docs/ artifact a validator checks (wiki, codewiki, glossary, stack, adr), plus prd as the entry point for work too large for one spec. Each ships with a namespaced slash command derived from the same list, so the two cannot drift. The methodology stays in .claude/rules/. csdd shipped tdd-cycle, unit-cycle and verify-change as skills; here that would be a second copy of a rule, and the copy is the one that goes stale. Skills carry the procedure and the judgment, the format stays in knowledge-base.md, stated once. Commands are prefixed scc- because slash commands share a flat namespace across every source Claude Code loads them from, and a bare /adr collides on contact. Skills are not prefixed: a skill is reached through its description. TestScaffoldedSkillsPassTheSkillValidator asserts the scaffolded count before it asserts the clean run, which is the part that was missing while 6.3 was hollow. Template version bumped to 2. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01K2uD2SD6syaoXnhtqHbzhM --- CLAUDE.md | 4 +- design/orchestration.md | 34 +++++++ design/plan.md | 21 +++-- internal/assets/assets.go | 41 ++++++++- internal/assets/assets_test.go | 73 +++++++++++++++ internal/assets/templates/CLAUDE.md | 2 + .../templates/claude/commands/scc-adr.md | 15 ++++ .../templates/claude/commands/scc-codewiki.md | 13 +++ .../templates/claude/commands/scc-glossary.md | 12 +++ .../templates/claude/commands/scc-prd.md | 16 ++++ .../templates/claude/commands/scc-stack.md | 16 ++++ .../templates/claude/commands/scc-wiki.md | 13 +++ .../templates/claude/skills/adr/SKILL.md | 88 +++++++++++++++++++ .../templates/claude/skills/codewiki/SKILL.md | 78 ++++++++++++++++ .../templates/claude/skills/glossary/SKILL.md | 67 ++++++++++++++ .../templates/claude/skills/prd/SKILL.md | 85 ++++++++++++++++++ .../templates/claude/skills/stack/SKILL.md | 74 ++++++++++++++++ .../templates/claude/skills/wiki/SKILL.md | 72 +++++++++++++++ internal/cli/cli.go | 2 +- internal/cli/init.go | 5 +- internal/cli/validate_test.go | 29 ++++++ 21 files changed, 745 insertions(+), 15 deletions(-) create mode 100644 internal/assets/templates/claude/commands/scc-adr.md create mode 100644 internal/assets/templates/claude/commands/scc-codewiki.md create mode 100644 internal/assets/templates/claude/commands/scc-glossary.md create mode 100644 internal/assets/templates/claude/commands/scc-prd.md create mode 100644 internal/assets/templates/claude/commands/scc-stack.md create mode 100644 internal/assets/templates/claude/commands/scc-wiki.md create mode 100644 internal/assets/templates/claude/skills/adr/SKILL.md create mode 100644 internal/assets/templates/claude/skills/codewiki/SKILL.md create mode 100644 internal/assets/templates/claude/skills/glossary/SKILL.md create mode 100644 internal/assets/templates/claude/skills/prd/SKILL.md create mode 100644 internal/assets/templates/claude/skills/stack/SKILL.md create mode 100644 internal/assets/templates/claude/skills/wiki/SKILL.md diff --git a/CLAUDE.md b/CLAUDE.md index 607f2ae..24b1652 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ This file covers working *on* scc. The product's own rules and methodology are n Note: this repo is not itself an scc workspace (no `.claude/`, `specs/`, or `docs/` are committed) — those trees only exist in workspaces the binary scaffolds, and in test temp dirs. -**Status: v0.3.0-shaped.** Everything through `design/plan.md` phase 10 is built and green: scaffolding (`init`), artifact creation (`spec`, `plan`), and all eight validators behind `scc validate`. `scc update` (phase 11) is not started, deliberately — a three-way merge is untestable until a second template version exists, so until then the upgrade story is "re-run `init`; it never overwrites what you edited". +**Status: v0.3.0-shaped.** Everything through `design/plan.md` phase 10 is built and green: scaffolding (`init`), artifact creation (`spec`, `plan`), and all eight validators behind `scc validate`. `init` also scaffolds the six knowledge-base skills named in `design/orchestration.md` §6 — one per `docs/` artifact a validator checks, plus `prd` — each with a `scc-`-prefixed slash command derived from the same list (`assets.KnowledgeSkills`). `scc update` (phase 11) is not started, deliberately — a three-way merge is untestable until a second template version exists, so until then the upgrade story is "re-run `init`; it never overwrites what you edited". `scc` is a redesign of `csdd` (`github.com/protonspy/csdd`), narrowed to spec-driven development and deliberately leaner. When reaching for something from there, port the *decision*, not the file. Already decided against: a TUI, an embedded web dashboard, an MCP server, a devcontainer. @@ -68,7 +68,7 @@ cmd/scc/main.go os.Exit(cli.Run(os.Args[1:])) | `internal/textutil` | Line-ending and BOM normalization, in exactly one place. | | `internal/finding` | One finding type and one frozen JSON shape (`{findings, count}`) for every validator, plus the grouped human report. | | `internal/manifest` | `.claude/scc-manifest.json`: `{path, hash, version}` per managed file, deterministic serialization, `Status → pristine\|edited\|missing`. Unknown fields are preserved. | -| `internal/assets` | The embedded template set. **Workspace templates are data-free** (that is what makes a future three-way merge possible); **artifact templates take data** (`spec new` renders them and the user owns the result). | +| `internal/assets` | The embedded template set — rules, review agents, skills, slash commands, artifact templates. **Workspace templates are data-free** (that is what makes a future three-way merge possible); **artifact templates take data** (`spec new` renders them and the user owns the result). `Version` is the template-set version and must be bumped whenever a workspace template changes. | | `internal/scaffold` | Applies the template set to a root. Idempotent, never overwrites, manifest written last. | | `internal/mdscan` | The only Markdown parser: fence- and HTML-comment-aware headings, checkboxes, links, wikilinks, slugs, plus a small frontmatter reader. `Body` is the comment/fence-stripped text every validator applies its grammar to. | | `internal/ears` | EARS requirement parsing, all five patterns plus complex. | diff --git a/design/orchestration.md b/design/orchestration.md index 742febb..0b42640 100644 --- a/design/orchestration.md +++ b/design/orchestration.md @@ -381,6 +381,40 @@ Don't cite it for a length rule.) import from `CLAUDE.md` too — a second file and an indirection to buy portability to tools this product isn't for. Anyone who wants cross-tool context can write it themselves. +### Which skills ship — the authors of `docs/` + +Six, and the rule that picks them is mechanical: **a skill ships for each `docs/` +artifact a validator checks, and for nothing else.** + +| Skill | Authors | Checked by | +|---|---|---| +| `wiki` | `docs/wiki/` — ingest from `docs/raw/`, query, repair | `wiki` | +| `codewiki` | `docs/codewiki/` — narrated code, cited by line range | `codewiki` | +| `glossary` | `docs/glossary.md` — canonical terms and banned synonyms | `glossary` | +| `stack` | `docs/stack.md` — adopted technology, one line of why | `stack` | +| `adr` | `docs/adr/` — hard-to-reverse decisions, superseded not edited | `adr` | +| `prd` | `plans/.md` — an initiative decomposed into specs | `plan` | + +Five of the six pair with a validator of the same name. `prd` is the exception and +earns its place for the opposite reason: it is the **entry point** for work too large +for one spec, the one place §1's routing question gets asked out loud, and without it +the only on-ramp scc offers is `scc plan new` handing the user an empty template. + +**The methodology is deliberately not a skill.** The cycles, verification, and +delivery are rules under `.claude/rules/` (§3, §7, §9) — read when the concern is +live. csdd shipped `tdd-cycle`, `unit-cycle`, and `verify-change` as skills; here +that would be a second copy of a rule, and the copy is the one that goes stale. + +**One slash command per skill, namespaced `scc-`.** A skill is model-invoked through +its description; a command is the human saying *now*. Both are cheap, they are derived +from one list so they cannot drift, and the prefix is not decoration — slash commands +share a flat namespace across every source Claude Code loads them from, and a bare +`/adr` collides on contact. + +The skills carry the *procedure and the judgment*; the format they produce stays in +`.claude/rules/knowledge-base.md`, stated once. A skill that restated the format would +be the same duplication the methodology rules are being kept out of skills to avoid. + ## 7 · Agents — review only Two subagents ship with the workspace, and both of them read rather than write: diff --git a/design/plan.md b/design/plan.md index 40b1d52..8e4d340 100644 --- a/design/plan.md +++ b/design/plan.md @@ -459,7 +459,7 @@ Phase 2 — init Phase 3 — the template set - [x] 3.1 (Unit) .claude/rules/: the nine rule files - [x] 3.2 (Unit) CLAUDE.md and the two review agents -- [ ] 3.3 (Unit) shipped skills — none ship; the design never says which would (§9) +- [x] 3.3 (Unit) shipped skills — the six §6 names, each with its scc- slash command - [x] 3.4 (Unit) spec templates (optional sections marked optional) and the plan template Phase 4 — creating work @@ -475,7 +475,7 @@ Phase 5 — markdown Phase 6 — skill - [x] 6.1 (TDD) validate: Agent Skills conformance against the published spec - [x] 6.2 (Unit) cli: scc skill validate, exit 2 on findings -- [x] 6.3 (Unit) the shipped skills pass their own validator (vacuously true; see 3.3) +- [x] 6.3 (Unit) the shipped skills pass their own validator — count asserted, not vacuous Phase 7 — spec - [x] 7.1 (TDD) internal/ears: all five patterns plus complex, clause order enforced @@ -544,11 +544,18 @@ data to them is meaningless, while artifact templates *do* take data because the authored from birth and never tracked in the manifest. Two functions make the difference impossible to get wrong at a call site. -**Deviation: no skills ship, so 3.3 and 6.3 are hollow.** The design lists skills among -what the binary scaffolds but never says which. Nothing was invented to fill the gap. -`skill validate` is complete and tested against fixtures, and the test asserting that -shipped skills pass it will start meaning something the moment one ships. **One line in -the design saying which skills ship, if any, closes this.** +**Resolved: six skills ship, so 3.3 and 6.3 now assert something.** The design listed +skills among what the binary scaffolds and never said which, so for v0.3.0 none did and +both checkboxes were vacuous. §6 now names them, picked mechanically — one skill per +`docs/` artifact a validator checks (`wiki`, `codewiki`, `glossary`, `stack`, `adr`), +plus `prd` as the entry point for work too large for one spec. Each ships with a +namespaced slash command derived from the same list. The methodology stays in +`.claude/rules/`: a skill restating a rule is a second copy of one fact. + +3.3 and 6.3 are no longer satisfiable by an empty workspace — +`TestScaffoldedSkillsPassTheSkillValidator` asserts the scaffolded count before it +asserts the clean run, which is the part that was missing while the test was hollow. +Template version bumped to `2`. **Also settled along the way**, all of it now in the rules the binary scaffolds, because a validator needs a written convention to check against: ADR frontmatter (`status`, diff --git a/internal/assets/assets.go b/internal/assets/assets.go index 4108038..5da8603 100644 --- a/internal/assets/assets.go +++ b/internal/assets/assets.go @@ -1,6 +1,6 @@ // Package assets is the template set compiled into the binary: the rules, the -// review agents, and the artifact templates, all of it plain Markdown embedded -// with //go:embed. +// review agents, the knowledge-base skills and their commands, and the artifact +// templates, all of it plain Markdown embedded with //go:embed. // // There are two kinds of template here and the difference matters: // @@ -40,7 +40,9 @@ import ( // It is not the binary's version. Two scc releases that ship identical templates // share a template version, which keeps `scc update` a no-op across them instead // of touching every managed file to record a number nobody read. -const Version = "1" +// +// 2: the knowledge-base skills and their commands. +const Version = "2" // The embedded tree. "all:" so nothing is silently dropped for having a name the // default embed pattern skips. @@ -104,10 +106,41 @@ func Workspace() []File { Rel: claude(paths.AgentsSeg, agent), }) } + // The knowledge base's authors. Every one of these produces an artifact + // `scc validate` already checks — a workspace that ships the eight validators + // and no skill teaching the formats would demand conformance to documents + // nobody was told how to write. + for _, skill := range KnowledgeSkills { + set = append(set, File{ + Name: "claude/skills/" + skill + "/SKILL.md", + Rel: claude(paths.SkillsSeg, skill, "SKILL.md"), + }) + // One command per skill, so the human has an explicit entry point where the + // model has a description. Namespaced, because slash commands share a flat + // namespace with every other source Claude Code loads them from and `/adr` + // would collide on contact. + cmd := commandPrefix + skill + ".md" + set = append(set, File{ + Name: "claude/commands/" + cmd, + Rel: claude(paths.CommandsSeg, cmd), + }) + } sort.Slice(set, func(i, j int) bool { return set[i].Rel < set[j].Rel }) return set } +// KnowledgeSkills names the skills scc ships, each one the author of a `docs/` +// artifact a validator checks. Both the skill directory and its slash command are +// derived from this list, so the two cannot drift apart. +// +// The methodology is deliberately absent from it: the cycles, verification, and +// delivery are rules under `.claude/rules/`, read when the concern is live. A skill +// restating a rule is a second copy of one fact, and the copy goes stale. +var KnowledgeSkills = []string{"adr", "codewiki", "glossary", "prd", "stack", "wiki"} + +// commandPrefix namespaces the scaffolded slash commands. +const commandPrefix = "scc-" + // Dirs returns the directories `scc init` creates even when it has no file to put // in them. An agent that can see specs/, plans/, and docs/wiki/ knows where its // output goes; one that has to infer the layout from a rule file guesses. @@ -115,6 +148,8 @@ func Dirs() []string { return []string{ path.Join(paths.ClaudeDir, paths.RulesSeg), path.Join(paths.ClaudeDir, paths.AgentsSeg), + path.Join(paths.ClaudeDir, paths.SkillsSeg), + path.Join(paths.ClaudeDir, paths.CommandsSeg), paths.SpecsSeg, paths.PlansSeg, path.Join(paths.DocsSeg, paths.WikiSeg), diff --git a/internal/assets/assets_test.go b/internal/assets/assets_test.go index 04e0c18..ebfb1d0 100644 --- a/internal/assets/assets_test.go +++ b/internal/assets/assets_test.go @@ -259,6 +259,79 @@ func TestAgentsCarryTheirFrontmatter(t *testing.T) { } } +// Every skill in KnowledgeSkills ships both a SKILL.md and a slash command, and +// nothing else lands in either directory. The two are derived from one list so they +// cannot drift, and this is the assertion that keeps the derivation honest. +func TestEverySkillShipsWithItsCommand(t *testing.T) { + skills, commands := map[string]bool{}, map[string]bool{} + for _, f := range Workspace() { + switch { + case strings.HasPrefix(f.Rel, ".claude/skills/"): + name := strings.TrimSuffix(strings.TrimPrefix(f.Rel, ".claude/skills/"), "/SKILL.md") + skills[name] = true + case strings.HasPrefix(f.Rel, ".claude/commands/"): + name := strings.TrimSuffix(strings.TrimPrefix(f.Rel, ".claude/commands/"), ".md") + commands[name] = true + } + } + if len(skills) != len(KnowledgeSkills) || len(commands) != len(KnowledgeSkills) { + t.Fatalf("%d skills and %d commands, want %d of each", len(skills), len(commands), len(KnowledgeSkills)) + } + for _, name := range KnowledgeSkills { + if !skills[name] { + t.Errorf("%s is in KnowledgeSkills and ships no SKILL.md", name) + } + if !commands[commandPrefix+name] { + t.Errorf("%s is in KnowledgeSkills and ships no %s%s command", name, commandPrefix, name) + } + } +} + +// The Agent Skills contract is what `scc skill validate` enforces on everyone else, +// and the two fields it can break loading on are checked here at the source. The +// full conformance run happens against a scaffolded workspace in internal/cli — a +// tool that ships non-conforming skills has no standing to check anyone else's. +func TestSkillsCarryTheirFrontmatter(t *testing.T) { + for _, f := range Workspace() { + if !strings.HasPrefix(f.Rel, ".claude/skills/") { + continue + } + raw, err := Content(f.Name) + if err != nil { + t.Fatalf("%s: %v", f.Name, err) + } + if !strings.HasPrefix(raw, "---\n") { + t.Errorf("%s does not open with frontmatter", f.Rel) + continue + } + // The name must equal the parent directory or the skill does not load at + // all, in any tool that reads this format. + dir := strings.TrimSuffix(strings.TrimPrefix(f.Rel, ".claude/skills/"), "/SKILL.md") + if !strings.Contains(raw, "\nname: "+dir+"\n") { + t.Errorf("%s: frontmatter name does not match the directory %q", f.Rel, dir) + } + if !strings.Contains(raw, "\ndescription: ") { + t.Errorf("%s: no description in frontmatter", f.Rel) + } + } +} + +// A slash command with no description is one that cannot be found in the picker. +func TestCommandsCarryTheirDescription(t *testing.T) { + for _, f := range Workspace() { + if !strings.HasPrefix(f.Rel, ".claude/commands/") { + continue + } + raw, err := Content(f.Name) + if err != nil { + t.Fatalf("%s: %v", f.Name, err) + } + if !strings.HasPrefix(raw, "---\n") || !strings.Contains(raw, "\ndescription: ") { + t.Errorf("%s: a command needs frontmatter carrying a description", f.Rel) + } + } +} + // Directories init creates on its own must be inside the workspace and // slash-separated, for the same reason destinations must be. func TestDirsAreRelativeAndSlashed(t *testing.T) { diff --git a/internal/assets/templates/CLAUDE.md b/internal/assets/templates/CLAUDE.md index 340efeb..686126a 100644 --- a/internal/assets/templates/CLAUDE.md +++ b/internal/assets/templates/CLAUDE.md @@ -29,6 +29,8 @@ specs// requirements.md · design.md · tasks.md plans/.md one file: structure, plus a checklist and/or spec references docs/ the knowledge base — wiki, adr, codewiki, glossary, stack .claude/rules/ the methodology above +.claude/skills/ how to author each part of docs/ — invoked when it applies +.claude/commands/ the same skills on demand, as /scc-wiki, /scc-adr, /scc-prd, … ``` ## Checking your work diff --git a/internal/assets/templates/claude/commands/scc-adr.md b/internal/assets/templates/claude/commands/scc-adr.md new file mode 100644 index 0000000..35c68da --- /dev/null +++ b/internal/assets/templates/claude/commands/scc-adr.md @@ -0,0 +1,15 @@ +--- +description: Record a hard-to-reverse decision as a numbered ADR under docs/adr/, or supersede one that stopped being true +argument-hint: [the decision, or the ADR being superseded] +--- + +Use the `adr` skill. + +Decision: $ARGUMENTS + +First ask whether this is an ADR at all: how expensive would it be to undo? A +decision that is cheap to change belongs in the spec's `design.md`, and an `adr/` +full of reversible choices buries the records that actually explain the system. + +If an existing record is being replaced, write the new one and mark the old one +superseded — never edit its prose. diff --git a/internal/assets/templates/claude/commands/scc-codewiki.md b/internal/assets/templates/claude/commands/scc-codewiki.md new file mode 100644 index 0000000..34c58e0 --- /dev/null +++ b/internal/assets/templates/claude/commands/scc-codewiki.md @@ -0,0 +1,13 @@ +--- +description: Narrate an area of the codebase into docs/codewiki/, every section citing the exact lines it explains +argument-hint: [area or path to narrate | repair] +--- + +Use the `codewiki` skill. + +Area: $ARGUMENTS + +If no area was named, run `scc validate` and repair the `codewiki.*` findings it +reports — a broken citation means the code moved and the prose describing it is now +suspect, so re-read before re-numbering. If there are no findings and no area was +named, ask which area is hard to enter cold rather than picking one at random. diff --git a/internal/assets/templates/claude/commands/scc-glossary.md b/internal/assets/templates/claude/commands/scc-glossary.md new file mode 100644 index 0000000..85c7d6f --- /dev/null +++ b/internal/assets/templates/claude/commands/scc-glossary.md @@ -0,0 +1,12 @@ +--- +description: Add, settle, or rename a canonical term in docs/glossary.md, and list the synonyms to avoid +argument-hint: [term, or the ambiguity to settle] +--- + +Use the `glossary` skill. + +Term: $ARGUMENTS + +Read `docs/glossary.md` before adding to it — an entry that duplicates an existing +concept under a different name makes the canonical source itself ambiguous. If +nothing was named, run `scc validate` and resolve the `glossary.*` findings. diff --git a/internal/assets/templates/claude/commands/scc-prd.md b/internal/assets/templates/claude/commands/scc-prd.md new file mode 100644 index 0000000..3325d18 --- /dev/null +++ b/internal/assets/templates/claude/commands/scc-prd.md @@ -0,0 +1,16 @@ +--- +description: Turn an initiative too large for one spec into a plan under plans/ — decomposed into specs and tasks +argument-hint: [the initiative, epic, or PRD] +--- + +Use the `prd` skill. + +Initiative: $ARGUMENTS + +Check the routing question before you start: work that is one feature with unsettled +requirements is a spec, not a plan — run `scc spec new ` instead of wrapping +one feature in a plan for ceremony. + +If the initiative is too vague to decompose, ask a small batch of concrete +multiple-choice questions once, then decompose. Do not guess at the scope, and do not +interview at length — stop asking the moment you can name the leaves. diff --git a/internal/assets/templates/claude/commands/scc-stack.md b/internal/assets/templates/claude/commands/scc-stack.md new file mode 100644 index 0000000..25b6297 --- /dev/null +++ b/internal/assets/templates/claude/commands/scc-stack.md @@ -0,0 +1,16 @@ +--- +description: Record an adopted technology in docs/stack.md, or account for a dependency that nobody decided on +argument-hint: [technology being adopted or dropped] +--- + +Use the `stack` skill. + +Technology: $ARGUMENTS + +If nothing was named, run `scc validate` and work through the +`stack.undocumented-dependency` findings: for each one, either record the decision or +establish that nobody can justify the dependency and remove it. Both are correct +outcomes; listing a name with no reason is not. + +Adopting technology is a decision with a long tail. If it is not obviously the right +call, stop and ask rather than committing on someone else's behalf. diff --git a/internal/assets/templates/claude/commands/scc-wiki.md b/internal/assets/templates/claude/commands/scc-wiki.md new file mode 100644 index 0000000..38ba7a2 --- /dev/null +++ b/internal/assets/templates/claude/commands/scc-wiki.md @@ -0,0 +1,13 @@ +--- +description: Build or maintain docs/wiki/ — ingest a source from docs/raw/, answer from what is known, or clear wiki.* findings +argument-hint: [ingest | query | maintain] +--- + +Use the `wiki` skill. + +Request: $ARGUMENTS + +If nothing was asked for specifically, look at `docs/raw/` first — anything sitting +there is unprocessed work and is the default job. If `raw/` is empty, run +`scc validate` and clear whatever `wiki.*` findings it reports. If there are none, +say so rather than inventing pages. diff --git a/internal/assets/templates/claude/skills/adr/SKILL.md b/internal/assets/templates/claude/skills/adr/SKILL.md new file mode 100644 index 0000000..ae737f9 --- /dev/null +++ b/internal/assets/templates/claude/skills/adr/SKILL.md @@ -0,0 +1,88 @@ +--- +name: adr +description: Record a decision that is hard to reverse as a numbered ADR under docs/adr/, and supersede an old record rather than editing it. Use it when a choice is made that would be expensive to undo — storage, wire protocol, a module boundary, a vendor holding data, a framework the code will shape itself around — when someone asks why the project is built this way, and when `scc validate` reports adr.* findings such as a numbering gap, a superseded record with no successor, or a citation resolving to no record. +--- + +You write the record of a decision, so that the reasoning survives the people who +were in the room. The filename shape, the frontmatter, and the citation form are in +[knowledge-base.md](../../rules/knowledge-base.md). This skill is when to write one +and how to write one worth reading. + +## Is this an ADR? + +One question: **how expensive would it be to undo?** + +Write one when the answer is "expensive" — a datastore, a wire format, an +authentication model, a boundary between services, a vendor that will end up holding +data, a framework the rest of the code will be shaped around. Also write one when a +previous decision is being **reversed**, which is the case people skip and the case a +reader most needs. + +Do not write one for a decision that is cheap to change. That belongs in the spec's +`design.md`, where it is read by the people building the feature and forgotten +harmlessly once it stops being true. An `adr/` directory full of reversible choices +buries the four records that actually explain the system. + +Rough test: if changing your mind next month means editing one package, it is not an +ADR. If it means a migration, it is. + +## Writing one + +```markdown +--- +status: accepted # proposed | accepted | rejected | superseded +--- + +# 0007 · Use SQLite for the cache + +## Context +## Decision +## Consequences +``` + +Numbered contiguously from `0001`; the filename is `NNNN-kebab-slug.md`, and the +number is never reused — gaps are findings because a gap means a record went +missing. Take the next number after the highest one present. + +**Context** is the section that does the work. Write what was true when the decision +was made — the constraint, the load, the deadline, the thing that was already built. +A reader a year from now is trying to learn whether the reasons still hold, and they +can only do that if the reasons are written down. "We chose SQLite because it is +simple" tells them nothing; "single writer, under 10GB, and operating a second +service was the cost we were avoiding" tells them exactly when to revisit. + +**Decision** is one paragraph in the active voice. What was chosen, and what was +chosen against — name the alternatives that were real, and say what ruled each out. + +**Consequences** includes the ones you do not like. A record listing only benefits +is marketing, and the next reader will discover the downside on their own and trust +nothing else in the file. + +## Superseding + +**A superseded record is marked, never edited.** The point of an ADR is that it +records what was believed at the time; rewriting it destroys the thing it exists to +preserve — and the new record loses the context that explains why the change was +needed. + +Write the new record, then in the old one set only the frontmatter: + +```yaml +status: superseded +superseded-by: 0012-move-the-cache-to-redis +``` + +Leave the prose exactly as it was. The new record's Context says what changed. + +## Findings + +| Finding | What it means | The fix | +|---|---|---| +| `adr.malformed-filename` | Not `NNNN-kebab-slug.md`. | Rename it. The number is how it is cited. | +| `adr.duplicate-number` | Two records share a number. | Renumber the later one; a citation cannot resolve to two records. | +| `adr.numbering-gap` | A number is missing from the sequence. | A record was deleted or never committed. Restore it, or renumber — do not leave the hole, because a gap reads as a lost decision. | +| `adr.missing-status` / `adr.status-invalid` | The frontmatter `status` is absent or not one of the four. | Set it. A record with no status does not say whether it is in force. | +| `adr.superseded-without-successor` | Marked superseded with no `superseded-by`. | Name the record that replaced it, or the reader is told this is obsolete and given nowhere to go. | +| `adr.unknown-successor` / `adr.unknown-citation` | A reference resolves to no record. | Fix the slug. | + +Cite a record from anywhere in `docs/` as `adr:0007-use-sqlite-for-the-cache`. diff --git a/internal/assets/templates/claude/skills/codewiki/SKILL.md b/internal/assets/templates/claude/skills/codewiki/SKILL.md new file mode 100644 index 0000000..08bb802 --- /dev/null +++ b/internal/assets/templates/claude/skills/codewiki/SKILL.md @@ -0,0 +1,78 @@ +--- +name: codewiki +description: Narrate an area of this codebase into docs/codewiki/, where every section cites the exact lines it explains. Use it when a subsystem is hard to enter cold and the code does not say why it is shaped that way, when someone asks for onboarding notes or an architecture walkthrough, and when `scc validate` reports codewiki.* findings — a citation that no longer resolves, one that runs past the end of its file, or a section that cites nothing at all. +--- + +You write prose that explains code, and every section of it points at the lines it +is about. The citation is what makes this different from a wiki page: it goes stale +loudly. When the code moves, `scc validate` fails instead of the page quietly +becoming a lie. + +The format is in [knowledge-base.md](../../rules/knowledge-base.md). This skill is +the procedure and the judgment. + +## The citation form + +A link whose text is `path:start-end` and whose target is empty: + +```markdown +## How the dispatcher routes + +[internal/cli/cli.go:48-64]() + +One switch, no registration. Adding a subcommand means... +``` + +- The path is relative to the workspace root, always — never to the page. +- A single line is `path:42`. A range is `path:42-58`. +- The target stays empty. `scc validate` resolves the path itself, confirms the file + exists, and confirms the range is inside it. + +## Writing a page + +1. **Read the area first — all of it.** You are claiming to explain this code. Every + sentence you write is checkable against the lines you cited, and a reader who + catches one wrong claim stops believing the page. +2. **Pick the sections from the reader's questions**, not from the file layout. "How + the dispatcher routes" is a section. "cli.go" is a table of contents. +3. **Cite before you narrate.** Put the citation directly under the heading, then + explain. It anchors the section and it is how you notice you are about to write a + paragraph that is not about any particular code. +4. **Every `##` section cites something.** This is checked. A section with no + citation is prose that has drifted free of what it describes — either find the + lines it is about, or the section belongs in `docs/wiki/` instead. +5. **Headings must be unique on the page.** Two identical headings share one anchor, + and a link to it lands on whichever came first. +6. `scc validate` before you call it done. + +## What to narrate, and what to leave alone + +Write about the parts where **reading the code does not tell you why it is like +that**: + +- A constraint the shape exists to satisfy, which is invisible from any one file. +- An invariant several files cooperate to hold. +- A deliberate choice that looks wrong until you know what it prevents. +- The entry point — where execution actually begins, which is often the single + hardest thing to find in an unfamiliar codebase. + +Do not narrate what a reader can see. "This function takes a name and returns a +path" is the signature restated at greater length, and a page full of that trains +people to skip the page. + +Prefer the range that makes the point. Citing a whole 400-line file says "the answer +is in here somewhere", which the reader already knew. + +## Repairing drift + +| Finding | What it means | The fix | +|---|---|---| +| `codewiki.citation-unresolved` | The file is gone or was renamed. | Find where the code went. If it was deleted, delete the section — a section about code that no longer exists is not repairable. | +| `codewiki.citation-out-of-range` | The file is shorter than the citation claims. | The code moved. **Re-read it before re-numbering**: the lines may now be something else entirely, and shifting the range to make the finding go away without reading is how a page becomes confidently wrong. | +| `codewiki.citation-invalid` | Malformed, or the path escapes the workspace. | A citation names a path inside the checkout. Fix the path. | +| `codewiki.section-cites-nothing` | A section explains no specific code. | Cite the lines, or move the section to `docs/wiki/`. | +| `codewiki.duplicate-heading` | Two sections share one anchor. | Rename one. | + +**A finding here is usually a real problem, not a bookkeeping chore.** The citation +broke because the code changed, which means the prose describing it is now suspect +in a way no validator can measure. Re-read, then rewrite what is no longer true. diff --git a/internal/assets/templates/claude/skills/glossary/SKILL.md b/internal/assets/templates/claude/skills/glossary/SKILL.md new file mode 100644 index 0000000..830df93 --- /dev/null +++ b/internal/assets/templates/claude/skills/glossary/SKILL.md @@ -0,0 +1,67 @@ +--- +name: glossary +description: Own docs/glossary.md — one canonical term per concept, and the synonyms nobody should use for it. Use it when a domain term is coined, contested, fuzzy, or renamed; before naming a spec, a plan, a wiki page, or an identifier that will outlive the session; and when `scc validate` reports glossary.* findings — a duplicate entry, a term listed as both canonical and avoided, or an avoided synonym used somewhere in docs/. +--- + +You own the project's vocabulary. Domain language drifts by default: two people +working in parallel produce three names for one thing inside a week, and every one +of them ends up in code, requirements, and the wiki, where nobody can tell whether +two documents are describing the same concept or two different ones. + +The entry format is in [knowledge-base.md](../../rules/knowledge-base.md). This +skill is when to reach for it and how to decide. + +## The entry + +One entry per line, in `docs/glossary.md`: + +```markdown +- **order total** — the amount charged, in minor units. Avoid: grand total, sum +- **workspace** — a directory holding .claude/scc-manifest.json. Avoid: project root +``` + +- The term is bold. The definition follows an em dash. +- `Avoid:` lists the synonyms, comma-separated. It is optional — a term with no + contested spellings does not need one. +- Every listed synonym becomes a finding wherever it appears as a whole word in + `docs/`. That is the enforcement, so list a synonym only when you mean it. + +## Adding a term + +1. **Check it is not already there** under another name. Read the file before adding + to it — a glossary with two entries for one concept is worse than none, because + now the canonical source is itself ambiguous. +2. **Define what it is, not what it is called.** "The amount charged, in minor + units" settles arguments. "The total of the order" settles nothing. +3. **List the synonyms you are banning**, and only those. Do not ban a word the + project uses correctly in an unrelated sense — the check is whole-word across all + of `docs/`, so banning a common word turns the glossary into noise the first time + it fires on an innocent sentence. +4. **Then use it.** Rename the occurrences in `docs/` that the validator will now + flag, and prefer the canonical term in new requirements, wiki pages, and + identifiers. +5. `scc validate` before you call it done. + +## When to reach for this + +- **A term is coined.** Something in the domain just got a name for the first time. +- **A term is contested.** Two documents or two people use different words and you + cannot tell whether they mean the same thing. They probably do; that is the point. +- **A term is renamed.** Move the old name into the new entry's `Avoid:` list — this + is the case the enforcement is most useful for, because it finds every place the + rename was not applied. +- **Before naming anything durable** — a spec directory, a plan, a wiki slug, a + type. The name will be read far more often than it was chosen. + +## Findings + +| Finding | What it means | The fix | +|---|---|---| +| `glossary.duplicate-term` | Two entries define the same term. | Merge them. One canonical term means one entry. | +| `glossary.synonym-is-canonical` | A term is defined as canonical and also listed as a synonym to avoid. | The glossary is contradicting itself. Decide which one the project uses. | +| `glossary.avoided-synonym` | A banned synonym is used in `docs/`. | Rewrite it to the canonical term — that is the entire point of listing it. If the sentence is genuinely about something else, the ban was too broad: narrow the `Avoid:` list. | + +**Do not define what is obvious to anyone in the domain.** A glossary that opens +with "user — a person who uses the system" is one nobody reads to the entry that +mattered. Define what is specific to this project, ambiguous in general use, or +already being said three ways. diff --git a/internal/assets/templates/claude/skills/prd/SKILL.md b/internal/assets/templates/claude/skills/prd/SKILL.md new file mode 100644 index 0000000..c4e5d84 --- /dev/null +++ b/internal/assets/templates/claude/skills/prd/SKILL.md @@ -0,0 +1,85 @@ +--- +name: prd +description: Turn an initiative that spans more than one feature into a plan under plans/ — a decomposition where each leaf is either a task you will do here or a reference to a spec that will be built separately. Use it when someone arrives with a PRD, a roadmap item, an epic, or a rough idea too large for one spec, and the first job is to find out what it actually decomposes into. For a single feature whose shape is already clear, skip this and run `scc spec new`. +--- + +You take an initiative that is too big for one spec and turn it into a plan: one +file under `plans/`, holding the decomposition and nothing else. + +**A note on the word.** "PRD" is what you arrive with — a product document, a +roadmap line, a paragraph in a chat. It is not an artifact this project keeps. The +artifact is a **plan**, and the vocabulary in +[routing.md](../../rules/routing.md) is what the rest of the workspace uses. Do not +create a `docs/prd/` directory. + +## First, check this is a plan at all + +[routing.md](../../rules/routing.md) asks one question, and it is worth asking +before you start: does this need requirements and a design settled before any code? + +- **Too large for one spec** — decomposes into several. That is a plan. Continue. +- **One feature, requirements not yet settled** — that is a spec. Run + `scc spec new ` and write `requirements.md`. Do not wrap one feature in a + plan for ceremony. +- **Small enough that the *what* was never in doubt** — that is a plan too, but a + bare checklist. Skip the interrogation below and write the list. + +## Then find out what it really is + +An initiative arrives underspecified. Your job before writing anything is to make it +specific enough to decompose, and the fastest way there is **a small batch of +concrete multiple-choice questions**, asked once — not a long interview, and not +guessing: + +1. **Who is this for, and what can they not do today?** An initiative that cannot + name the user is not ready to be decomposed. +2. **What is in and what is explicitly out?** The out-list is the more valuable + half; it is the one that prevents the third spec from growing a fourth. +3. **What does "done" mean for the whole of it?** Not per feature — the condition + under which the initiative is finished and the plan can be closed. +4. **What is already decided and not up for discussion?** A deadline, a vendor, an + existing system that has to keep working. + +Ask them together, offer concrete options rather than open prompts, and stop asking +once you can name the leaves. **If a question's answer would not change the +decomposition, do not ask it.** + +## Decompose + +``` +scc plan new +``` + +Then fill it in. The rules the file must hold to: + +- **A leaf is either a spec reference or a task with a checkbox — never both.** Where + an item is a task, the box is its state. Where it references a spec, the state + lives in that spec and is read from there. Two records of one fact disagree, and + the copy is the one that goes stale. `scc validate` reports this as + `plan.item-has-two-records`. +- **A leaf that is a spec is an ordinary spec.** `specs//` is not nested + under the plan and is built by exactly the same rules as one somebody asked for + directly. +- **Each spec-sized leaf is one coherent feature** — something a person could + describe in a sentence and verify on its own. If a leaf needs three sentences and + an "and", it is two leaves. +- **Say what order they go in, and what depends on what**, under `## Notes`. This is + the part only you know right now, and the part the next session most needs. +- **A plan is work, not knowledge.** It lives in `plans/`, never in `docs/`. + +Referenced specs must exist, or `scc validate` reports `plan.unknown-spec`. Create +them with `scc spec new` as you name them — an empty spec directory is a real +placeholder; a reference to nothing is a broken plan. + +## Before you hand it over + +- `scc validate` — exit 0, or fix what it names. +- **Read the leaf list back as a whole and ask what is missing.** Migration, + backfill, the switch-over, the thing that has to keep working while this ships, + and how it gets turned off if it goes wrong. Decompositions fail at the seams, not + in the middle of a feature. +- **Check the vocabulary.** Every term the plan coins is a term three specs will + inherit. If any of them is contested or new, use the `glossary` skill now, while + it costs one edit. +- **A choice made here that is hard to reverse is an ADR**, not a line in the plan's + notes. Use the `adr` skill. diff --git a/internal/assets/templates/claude/skills/stack/SKILL.md b/internal/assets/templates/claude/skills/stack/SKILL.md new file mode 100644 index 0000000..720902a --- /dev/null +++ b/internal/assets/templates/claude/skills/stack/SKILL.md @@ -0,0 +1,74 @@ +--- +name: stack +description: Own docs/stack.md — every adopted technology with one line on why it earned its place. Use it before adding a dependency to go.mod, package.json, or any other manifest; when a technology is being swapped, dropped, or evaluated; and when `scc validate` reports stack.missing or stack.undocumented-dependency, which means something is installed that nobody recorded a decision about. +--- + +You own `docs/stack.md`. The rule it enforces is in +[knowledge-base.md](../../rules/knowledge-base.md), and it is short: **technology +not listed here is an open decision, never something adopted silently.** + +This is checkable because dependency manifests are structured data. `scc validate` +reads the direct dependencies out of `go.mod` and `package.json` and reports any +that `stack.md` does not mention. Indirect dependencies are skipped — nobody decided +those. + +## Adding a dependency is two steps + +Adding it to the manifest is the first. The second is here, and it happens in the +same change, not later: + +1. **Say what problem it solves** — the one you actually have, not the category. +2. **Say what it was chosen over**, if anything real was considered. This is the + line that pays off in a year, when someone asks why not the obvious alternative. +3. **Say what it costs.** Every dependency is a supply-chain surface, a version to + keep current, and an API someone will have to learn. If you cannot name the cost + you have not finished evaluating it. + +```markdown +## Go + +- **stdlib only** — the binary ships to six platforms and every dependency is a + supply-chain surface. A dependency has to be worth that. +``` + +The validator matches on the module path or its last segment, so writing `chi` for +`github.com/go-chi/chi` is enough. It is looking for a decision, not a spelling. + +## Before you add anything + +Ask the three questions, in this order: + +1. **Can the standard library do it?** For most of what a dependency is reached for + first, it can, at the cost of code you then own. Sometimes that trade is right. +2. **Is this dependency load-bearing or convenient?** A convenience with a + maintainer who stops answering is a migration you did not plan. +3. **Does the project already have something that does this?** Two libraries solving + one problem is a cost paid by everyone who reads the code afterwards, forever. + +**If the answer is not obviously yes, stop and ask.** Adopting technology is a +decision with a long tail, and it is one of the few things worth interrupting an +automatic run for. Bringing a dependency in unasked is not autonomy, it is a +commitment made on someone else's behalf. + +## Removing one + +Delete it from the manifest, delete the entry, and say in the commit what replaced +it. A stack entry for something no longer installed is worse than a missing one: it +describes a project that does not exist. + +## When it is also an ADR + +`stack.md` is the inventory — what is adopted, and why. An **ADR** is the record of +a decision that is hard to reverse. Reach for the `adr` skill as well when the +choice locks something in: a database, a wire protocol, a framework the code will +shape itself around, a vendor with data in it. + +The two do not duplicate each other. The stack entry stays one line and names the +ADR; the ADR carries the context, the alternatives, and the consequences. + +## Findings + +| Finding | What it means | The fix | +|---|---|---| +| `stack.missing` | The project declares dependencies and has no `stack.md`. | Write it, one entry per direct dependency already installed. Expect to discover at least one nobody can justify. | +| `stack.undocumented-dependency` | Something is installed that `stack.md` does not mention. | Record the decision — or, if no one can state what it is for, remove the dependency. Both are correct outcomes. Silencing the finding by listing the name with no reason is not. | diff --git a/internal/assets/templates/claude/skills/wiki/SKILL.md b/internal/assets/templates/claude/skills/wiki/SKILL.md new file mode 100644 index 0000000..e9be4b4 --- /dev/null +++ b/internal/assets/templates/claude/skills/wiki/SKILL.md @@ -0,0 +1,72 @@ +--- +name: wiki +description: Build and maintain docs/wiki/ — one page per concept, linked with wikilinks and reachable from index.md. Use it when a source lands in docs/raw/ and has to be distilled into a page, when a question is better answered from accumulated knowledge than from re-reading the code, and when `scc validate` reports wiki.* findings — a broken wikilink, an orphan page, a changelog naming a page that no longer exists, or a source still sitting unprocessed in docs/raw/. +--- + +You own `docs/wiki/`: the durable half of what this project knows. A spec says what +one feature does now; the wiki says what is true across features and outlasts them. + +The format is not yours to invent — it is in +[knowledge-base.md](../../rules/knowledge-base.md), and `scc validate` enforces it. +This skill is the procedure. + +## Ingest — something landed in `docs/raw/` + +`raw/` is a drop box, not storage. A file still there is unfinished work, and +`scc validate` says so by name. Empty it: + +1. **Read the source completely** before writing anything. A page distilled from a + skim is worse than no page — it looks authoritative and is not. +2. **Decide the concept, not the document.** One page per concept. A source that + covers three concepts becomes three pages, or one page and two edits to pages + that already exist. Do not mirror the source's own structure. +3. **Check `docs/glossary.md` first.** If the source names something the project + already has a canonical term for, use the canonical term. If it coins a term + worth keeping, that is the `glossary` skill's job — do it, then come back. +4. **Write the page** at `docs/wiki/.md`. The filename is the slug, so + `order-total.md` is what `[[order-total]]` resolves to. +5. **Link it in.** Add a `[[wikilink]]` from `index.md`, or from a page already + reachable from it. A page nobody links is a page nobody finds again. +6. **Record it in `changelog.md`** — what changed, naming the pages with wikilinks. +7. **Delete the source from `docs/raw/`.** This is the step that gets skipped. The + finding exists because of it. +8. `scc validate` before you call it done. + +A source that turns out not to be worth a page still gets deleted. "Read and +rejected" is a complete outcome; leaving the file behind pretends the work is +pending. + +## Query — answer from what is already known + +Read `index.md` first, follow wikilinks from there, and answer from the pages. Cite +the page you answered from so the reader can check you. + +If the wiki does not cover it, say so plainly and go read the code. Then consider +whether what you just had to reconstruct should have been a page — the second time +someone reconstructs the same thing is the signal, not the first. + +## Maintain — clear what `scc validate` reports + +| Finding | What it means | The fix | +|---|---|---| +| `wiki.missing-index` | Pages exist with no entry point, so every page is an orphan. | Write `index.md` and link the pages. | +| `wiki.broken-link` | A `[[wikilink]]` resolves to no page. | Fix the slug, or write the page it was reaching for. | +| `wiki.orphan-page` | The page exists and nothing reaches it. | Link it from `index.md` or from a reachable page. Or delete it — an unreachable page nobody has missed is a candidate. | +| `wiki.missing-changelog` | The wiki has pages and no log of how it got that way. | Write `changelog.md`. | +| `wiki.changelog-desync` | The log names a page that no longer exists. | The page was renamed or removed; the log is a record, so correct the entry rather than rewriting history around it. | +| `wiki.unprocessed-source` | A file is still in `docs/raw/`. | Ingest it, above. | + +**`index.md` is a map, not a dump.** When it grows into a flat list of every page, +group it — the entry point earns its keep by making the shape of the knowledge +visible, and a hundred unsorted links do the opposite. + +## What does not belong here + +- **A decision that is hard to reverse.** That is an ADR — use the `adr` skill. The + wiki can link to it; it does not restate it. +- **An explanation of specific code.** That is `docs/codewiki/`, where every claim + cites the lines it is about — use the `codewiki` skill. +- **What one feature does now.** That is `specs//requirements.md`. +- **Anything you have not verified.** The wiki's whole value is that it can be + trusted without checking. One confidently wrong page costs more than the ten + correct ones beside it earn. diff --git a/internal/cli/cli.go b/internal/cli/cli.go index ea61759..33059aa 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -100,7 +100,7 @@ Usage: %s [flags] Commands: - init Scaffold a workspace: rules, review agents, layout, manifest + init Scaffold a workspace: rules, agents, skills, commands, layout, manifest spec Create and inspect specs — new | list | show | delete | validate plan Create and inspect plans — new | list | delete | validate skill Agent Skills conformance — validate diff --git a/internal/cli/init.go b/internal/cli/init.go index cfd1f37..51ec260 100644 --- a/internal/cli/init.go +++ b/internal/cli/init.go @@ -10,8 +10,9 @@ import ( "github.com/protonspy/spec-claude-code/internal/workspace" ) -// runInit scaffolds a workspace: the rules, the review agents, the directory -// layout, and the manifest that makes the directory findable as a workspace. +// runInit scaffolds a workspace: the rules, the review agents, the knowledge-base +// skills and their commands, the directory layout, and the manifest that makes the +// directory findable as a workspace. // // It is safe to re-run. Without --force it never overwrites anything, so "run init // again" is the honest upgrade story until `scc update` exists — additive by diff --git a/internal/cli/validate_test.go b/internal/cli/validate_test.go index 87aefc1..61683b9 100644 --- a/internal/cli/validate_test.go +++ b/internal/cli/validate_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/protonspy/spec-claude-code/internal/assets" "github.com/protonspy/spec-claude-code/internal/paths" "github.com/protonspy/spec-claude-code/internal/validate" ) @@ -49,6 +50,34 @@ func TestFreshArtifactsPassTheirOwnValidators(t *testing.T) { } } +// The shipped skills pass scc's own Agent Skills validator — and there are shipped +// skills for it to pass. The count check is the point: without it this test stays +// green on a workspace containing no skills at all, which is what it asserted for as +// long as none shipped. +func TestScaffoldedSkillsPassTheSkillValidator(t *testing.T) { + root := initWorkspace(t) + + entries, err := os.ReadDir(paths.Skills(root)) + if err != nil { + t.Fatalf("init scaffolded no skills directory: %v", err) + } + var got []string + for _, e := range entries { + if e.IsDir() { + got = append(got, e.Name()) + } + } + if len(got) != len(assets.KnowledgeSkills) { + t.Errorf("scaffolded skills = %v, want %v", got, assets.KnowledgeSkills) + } + + stdout, stderr, code := run(t, "skill", "validate", "--root", root, "--json") + if code != ExitOK { + t.Errorf("skill validate on the skills scc itself ships exited %d\nstdout: %s\nstderr: %s", + code, stdout, stderr) + } +} + // Exit 2 is the contract: a finding is a legitimate answer to a lint question, not a // failure of the tool, and CI branches on the difference. func TestValidateExitsTwoOnFindings(t *testing.T) {