From febf6dbae150bb5fc27df7d67bc7c68539674c63 Mon Sep 17 00:00:00 2001 From: prode Date: Tue, 15 Sep 2026 18:10:11 -0300 Subject: [PATCH] refactor(assets): drop the command files on Claude Code, move the prefix onto the skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code registers a skill at `skills//SKILL.md` as `/` by itself, and its own documentation now says commands and skills are one mechanism with nothing a command can do that a skill cannot. So `commands/scc-wiki.md` beside `skills/scc-wiki/` was two entry points in the picker for one thing and — the half that is not cosmetic — a second `description` preloaded into every request whether anybody ran it or not. Eight of them, ~980 characters of saying twice what the skill's own description says once. `paths.Harness.SkillsAreCommands` is the capability. opencode still gets command files because nothing there turns a skill into one; Codex still gets none because its prompts are user-global and deprecated in favour of skills. The rename is what makes the removal safe rather than a second collision. With no command file the skill's own name is what the user types, and `/init` is a command Claude Code already ships — the same failure the review agents had under their unprefixed names one version ago, arriving by the same door. So the skills are `scc-init`, `scc-wiki` and the rest, and every reference to `/scc-init` already written keeps resolving, in the Dockerfile and in `scc launch`. What the command bodies carried and the skills did not is the default job: what to do when nobody typed an argument, which for five of them is "run `scc validate` and clear this artifact's findings". That is in the skill bodies now, where the run that invokes it pays for it instead of every session, and `argument-hint` moved with it since SKILL.md takes the same key. The command templates opencode still gets are a dispatch stub, so there is one source. Preloaded, Claude Code, fresh workspace: 40178 -> 39074 bytes. --- .claude/skills/gauntlet-loop/SKILL.md | 133 ++++++++++++++++++ CLAUDE.md | 4 +- internal/assets/assets.go | 92 ++++++++---- internal/assets/assets_test.go | 31 ++-- internal/assets/templates/commands/scc-adr.md | 13 +- .../assets/templates/commands/scc-codewiki.md | 11 +- .../assets/templates/commands/scc-glossary.md | 10 +- .../assets/templates/commands/scc-init.md | 20 +-- .../assets/templates/commands/scc-plan-run.md | 30 +--- internal/assets/templates/commands/scc-prd.md | 14 +- .../assets/templates/commands/scc-stack.md | 14 +- .../assets/templates/commands/scc-wiki.md | 9 +- internal/assets/templates/entry.md | 4 +- .../skills/{adr => scc-adr}/SKILL.md | 8 +- .../{codewiki => scc-codewiki}/SKILL.md | 10 +- .../{glossary => scc-glossary}/SKILL.md | 10 +- .../skills/{init => scc-init}/SKILL.md | 6 +- .../{plan-run => scc-plan-run}/SKILL.md | 6 +- .../skills/{prd => scc-prd}/SKILL.md | 9 +- .../skills/{stack => scc-stack}/SKILL.md | 10 +- .../skills/{wiki => scc-wiki}/SKILL.md | 9 +- internal/cli/update_test.go | 2 +- internal/paths/paths.go | 21 ++- internal/scaffold/update_test.go | 10 +- 24 files changed, 335 insertions(+), 151 deletions(-) create mode 100644 .claude/skills/gauntlet-loop/SKILL.md rename internal/assets/templates/skills/{adr => scc-adr}/SKILL.md (95%) rename internal/assets/templates/skills/{codewiki => scc-codewiki}/SKILL.md (90%) rename internal/assets/templates/skills/{glossary => scc-glossary}/SKILL.md (90%) rename internal/assets/templates/skills/{init => scc-init}/SKILL.md (98%) rename internal/assets/templates/skills/{plan-run => scc-plan-run}/SKILL.md (98%) rename internal/assets/templates/skills/{prd => scc-prd}/SKILL.md (93%) rename internal/assets/templates/skills/{stack => scc-stack}/SKILL.md (90%) rename internal/assets/templates/skills/{wiki => scc-wiki}/SKILL.md (94%) diff --git a/.claude/skills/gauntlet-loop/SKILL.md b/.claude/skills/gauntlet-loop/SKILL.md new file mode 100644 index 0000000..5c33caa --- /dev/null +++ b/.claude/skills/gauntlet-loop/SKILL.md @@ -0,0 +1,133 @@ +--- +name: gauntlet-loop +description: Turns any goal into one short, paste-ready "gauntlet loop" prompt - a prompt that makes an agent set a concrete quality bar, split the work into small judgeable pieces, run a builder and a separate harsh critic on each, compare blind against the bar, and loop until it wins. Works for builds, writing, code, research, or design. Triggers on "/gauntlet-loop", "gauntlet loop", "gauntlet this", "make a gauntlet prompt", "loop until it beats X". +--- + +# Gauntlet Loop + +The user gives a goal. You give back ONE short prompt they can paste into a fresh agent session. + +You are not doing the work. You are writing the prompt that makes another agent grind on the work until it beats a real reference. + +## Flow + +1. **Read the goal.** One line restatement in your head, not on screen. +2. **Set the bar.** If the user supplied a reference, use it. If not, offer **2 or 3 candidate bars**, one line each, and stop. Wait for their pick. Do not write the prompt yet. +3. **Write the prompt.** One block, paste-ready, no preamble, no headings inside it, no narration after it. +4. **Offer to run it.** One flat line under the prompt: "I can run this here." Not a question. + +If they say run it, you become the lead agent and follow the prompt you just wrote. + +## The bar is the whole trick + +Everything else in a gauntlet loop is scaffolding. The loop only produces quality if the thing it compares against is real. + +A bar has to pass three tests: + +- **Named.** A specific thing, not a category. "Stripe's pricing page" works. "Award-winning SaaS sites" does not. +- **Fetchable.** The critic can actually get it - screenshot the live page, read the published piece, run the binary, open the repo, watch the footage. If the agent cannot obtain it, it will hallucinate the comparison. +- **Comparable.** Both can sit side by side and a judge can pick one. If you cannot imagine the A/B, it is not a bar. + +Bars by goal type: + +| Goal | Bar that works | +|---|---| +| Website, app, UI | The live site of a specific best-in-class product, screenshotted at the same viewport | +| Game, 3D, visual | Real footage or screenshots from a named shipped title | +| Writing | A specific published piece by a named author or publication, same length and format | +| Code, tooling | A named repo's implementation, plus its benchmark or test suite as the measurable half | +| Research, analysis | A named analyst report or a paper's methods section, judged on rigour and coverage | +| Deck, doc, deliverable | A real artifact from a firm known for it, same page count | + +When you propose bars, prefer the hardest one the agent can genuinely reach. A bar that is too easy makes the loop exit on round one. + +If the goal has a measurable half (load time, token cost, benchmark score, word count, pass rate), name it alongside the reference. Taste plus a number beats taste alone. + +## Prompt template + +Adapt the wording every time. Fill the brackets, keep it short, keep the last line. + +``` +Build [GOAL]. + +The bar is [BAR]. Get the real thing first and compare against it directly, not against a description of it. + +Break this into the smallest pieces that can be improved and judged on their own. For each piece, fan out a builder and a separate critic with fresh context. The critic inspects the actual output, puts it next to the bar blind with the labels stripped, says which one is better, and names the single biggest remaining gap. Then it goes back to the builder. + +The critic should be a harsh critic. Praise is not useful. If ours does not win, it keeps going. + +/loop on each piece until the critic picks ours blind. Do not stop before that. + +Keep a live progress page updating as the work evolves so I can watch it. + +Fan out subagents and ultracode. +``` + +Rules for what you fill in: + +- Bake the bar in as a concrete, fetchable thing. URL, product name, repo, title. +- Add a budget or cost ceiling line **only if the user named one**. No default cap. +- Add tool names only if the goal needs them (image or video generation, a browser, a deploy target). +- Everything else stays out. No architecture, no file layout, no decomposition, no round count, no stack choice unless the user demanded it. The agent decides those, and it decides better than a spec written before the work started. + +## Length and voice + +Short. Around 120 to 180 words. If the prompt needs a heading to stay readable, it is too long. + +Plain sentences. No bullet lists inside the prompt. It should read like someone telling an agent what perfect looks like and refusing to accept less. + +## Portability + +`/loop` and `ultracode` are Claude Code features. `/loop` reruns the prompt on an interval or lets the model pace itself. `ultracode` opts the turn into multi-agent orchestration. + +For any other agent, swap the last two lines for: "Keep looping until the critic picks ours. Run the builders and critics as parallel subagents." The structure carries over unchanged. + +## Two filled examples + +**Visual goal.** User: "landing page for my running brand, athletic, green and dark, has to feel alive." + +Bars offered: A) Nike's current running campaign page B) On Running's homepage C) Gymshark's product landing page. User picks A. + +``` +Build a landing page for a running brand. Athletic, peak performance, green and dark, energetic, aimed at a young healthy audience. It needs to be interactive and visually unmistakable. + +The bar is Nike's current running campaign page. Screenshot it at desktop and mobile and compare against those directly, not against a description of them. + +Break this into the smallest pieces that can be improved and judged on their own - hero, motion, type, colour, imagery, interaction, mobile. For each piece, fan out a builder and a separate critic with fresh context. The critic opens the real page in a browser, puts our screenshot next to Nike's blind with the labels stripped, says which is better, and names the single biggest remaining gap. Then it goes back to the builder. + +The critic should be a harsh critic. Praise is not useful. If ours does not win, it keeps going. + +/loop on each piece until the critic picks ours blind. Do not stop before that. + +Keep a live progress page updating as the work evolves so I can watch it. + +Fan out subagents and ultracode. +``` + +**Non-visual goal.** User: "a 2000-word explainer on vector databases for non-engineers." + +Bars offered: A) a specific Stripe engineering blog explainer B) a named Julia Evans post C) the Wikipedia article plus a comprehension test. User picks B. + +``` +Write a 2000-word explainer on vector databases for readers who are smart but not engineers. + +The bar is Julia Evans' writing on hard technical topics. Pull three of her actual posts and compare against them directly, not against a description of her style. + +Break this into the smallest pieces that can be judged on their own - the opening, each explanation, the diagrams, the analogies, the ending. For each piece, fan out a writer and a separate critic with fresh context. The critic reads ours and hers blind with the bylines stripped, says which one a non-engineer would understand faster, and names the single biggest remaining gap. Then it goes back to the writer. + +The critic should be a harsh critic. Praise is not useful. If ours does not win, it keeps going. + +/loop on each piece until the critic picks ours blind. Do not stop before that. + +Keep a live progress page updating as the work evolves so I can watch it. + +Fan out subagents and ultracode. +``` + +## What breaks a gauntlet loop + +- **A vague bar.** The critic invents a comparison and approves everything. Most common failure by far. +- **The builder judging its own work.** The critic must be a separate agent with fresh context. It should not know how hard the builder tried. +- **A soft critic.** Say "harsh" in the prompt and give it a binary job: which one is better, A or B. Scores out of 10 drift upward every round. +- **Named exit after N rounds.** The exit is winning the comparison, or the user stopping the run. Never a round count. +- **Over-specifying.** Every extra instruction is one fewer decision the agent makes with its own judgment. Minimal wins. diff --git a/CLAUDE.md b/CLAUDE.md index 42698c0..bce5dad 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 harness directory, `specs/`, `plans/`, or `docs/` are committed) — those trees only exist in workspaces the binary scaffolds, and in test temp dirs. -**Status: v0.4.0-shaped.** Everything through `design/plan.md` phase 10 is built and green: scaffolding (`init`), artifact creation (`spec`, `plan`), and all ten validators behind `scc validate`. `init` also scaffolds the eight skills named in `design/orchestration.md` §6 — the six knowledge-base authors (one per `docs/` artifact a validator checks, plus `prd`) and the two workflow skills, `plan-run` and `init` — each with a `scc-`-prefixed slash command derived from the same list (`assets.Skills()`, which is `KnowledgeSkills` + `WorkflowSkills`), wherever the harness has a command surface. +**Status: v0.4.0-shaped.** Everything through `design/plan.md` phase 10 is built and green: scaffolding (`init`), artifact creation (`spec`, `plan`), and all ten validators behind `scc validate`. `init` also scaffolds the eight skills named in `design/orchestration.md` §6 — the six knowledge-base authors (one per `docs/` artifact a validator checks, plus `prd`) and the two workflow skills, `scc-plan-run` and `scc-init`. All eight carry the `scc-` prefix on the skill itself (`assets.Skills()`, which is `KnowledgeSkills` + `WorkflowSkills`), so the name is what the user types wherever the harness turns a skill into a slash command, and a command file is written only where it does not. These landed after phase 10, and all are documented in `design/orchestration.md` §6 and §12: @@ -326,6 +326,8 @@ Five packages sit off to the side of that tree — `rtk`, `headroom`, `codegraph **The rules are a standing budget, not a place to explain things.** Every unscoped file under `/rules/` is preloaded into every request on a harness that reads them, so a line added there is paid continuously rather than once. `TestRulesStayShortEnoughToBePreloaded` caps a rule at 55 lines (three predate the budget and are capped where they stand; they may shrink, never grow), and `TestScaffoldedEntryFileStaysShort` caps the entry file at 60. A scoped rule is capped on its prose, not on the `paths:` header that took it out of the preloaded set. +**On Claude Code there are no command files, and the `scc-` prefix is on the skill.** Claude Code registers a skill as `/` by itself and its own documentation now says commands and skills are one mechanism, so `commands/scc-wiki.md` beside `skills/scc-wiki/` was a second entry point in the picker for one thing and a second preloaded `description` — ~980 characters of saying twice what the skill says once. `paths.Harness.SkillsAreCommands` is the capability; opencode still gets command files because nothing there turns a skill into one, and Codex still gets none because its prompts are user-global. **The rename is what makes the removal safe rather than a second collision**: with no command file the skill's own name is what the user types, and `/init` is a command Claude Code already ships — the review agents' collision one version earlier, arriving by the same door. Every `/scc-init` already written keeps resolving. What the command bodies carried and the skills did not is the *default job* — for five of them, "run `scc validate` and clear this artifact's findings" — and that moved into the skill bodies, where the run that invokes it pays for it. + **A skill's description is on that same budget and its body is not.** The harness loads every `description` at session start to decide when to invoke the skill, and leaves the body until it does — so the body is paid once by the run that needed it, and the description by every request whether the skill runs or not. Eight of them had grown to 4087 characters, mostly enumerating findings `scc validate` already names by rule; `TestSkillDescriptionsStayShortEnoughToPreload` caps one at 340 and the set at 2400. The body is where the detail belongs, on exactly the reasoning that sends a rule's detail into a `--help` string. When a feature does not fit, the answer is to move the detail into a `--help` string — read only when consulted — and keep the question→command table in the rule. The plan-format change was measured in and out on this basis: it landed at +2 lines and +709 bytes across `entry.md` + `artifacts.md` + `tasks.md`, against ~14k tokens saved per plan reread. **A command that edits an artifact verifies it afterwards.** `scc patch` snapshots the file, writes, re-runs the validator that owns it, and restores the snapshot if the edit introduced a finding the file did not already have. Two details are load-bearing: the comparison is on `rule + message` and deliberately **not on line number**, because an insertion moves every finding below it and comparing on line would blame this edit for the whole tail of a pre-existing problem; and an artifact scc has no validator for is written and *reported as unverified* rather than silently claimed clean. diff --git a/internal/assets/assets.go b/internal/assets/assets.go index 364ef7e..aa05294 100644 --- a/internal/assets/assets.go +++ b/internal/assets/assets.go @@ -249,7 +249,28 @@ import ( // Claude Code ships its own under the unprefixed names, so `delivery.md` naming them // in prose resolved to whichever the harness picked — a collision nobody would ever // see, because both produce a review and the one that ran is the one that answers. -const Version = "30" +// +// 31: the slash commands come off Claude Code, and the `scc-` prefix moves onto the +// skill. Claude Code registers a skill as `/` by itself and its own +// documentation now says commands and skills are one mechanism — so a command file +// beside each skill was a second entry point in the picker for one thing, and a +// second `description` preloaded into every request whether anybody ran it or not. +// That is the whole cost: ~980 characters of saying twice what the skill says once. +// +// The rename is what makes the removal safe rather than a second collision. With no +// command file the skill's own name is what the user types, and `/init` is a command +// Claude Code already ships — the same failure the review agents had under their +// unprefixed names, one version ago, by the same door. So the skills are `scc-init`, +// `scc-wiki` and the rest, and every reference to `/scc-init` already written keeps +// resolving. opencode still gets command files, because nothing there turns a skill +// into one. +// +// What the command bodies carried and the skills did not is the *default job* — what +// to do when nobody typed an argument, which for five of them is "run `scc validate` +// and clear this artifact's findings". That moved into the skill bodies, where it is +// paid by the run that invokes it instead of by every session; `argument-hint` moved +// with it, since SKILL.md takes the same key. +const Version = "31" // The embedded tree. "all:" so nothing is silently dropped for having a name the // default embed pattern skips. @@ -417,19 +438,31 @@ func Workspace(h paths.Harness) []File { Rel: under(h.SkillsSeg, skill, "SKILL.md"), Kind: Plain, }) - // 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 the harness loads them from and `/adr` - // would collide on contact. + // A command per skill, for a harness where a skill is not already one. + // + // **Where it is, the command file is pure cost.** Claude Code registers + // every skill as `/` by itself, so shipping `commands/scc-wiki.md` + // beside `skills/scc-wiki/` put two entry points in the picker for one + // thing and, worse, a second `description` in front of the model — and a + // description is the half that is preloaded into every request whether + // anybody runs it or not. Eight of them came to ~980 characters of saying + // twice what the skill's own description says once. // - // Codex is the exception and gets none: its custom prompts live in the + // The prefix moved onto the skill to make that possible, and it is load- + // bearing rather than tidy: with the command gone the skill's own name is + // what the user types, and `init` is a slash command Claude Code already + // ships. `/init` resolving to two different things is the failure the + // review agents had under their unprefixed names, arriving by the same + // door — so the names are `scc-init`, `scc-wiki`, and every reference to + // `/scc-init` that was already written keeps working. + // + // Codex gets none for a different reason: its custom prompts live in the // user's home directory and are deprecated in favor of skills, so there is - // nothing project-scoped to write. The skills above are the whole surface - // there, which is what Codex itself now recommends. - if h.CommandsSeg == "" { + // nothing project-scoped to write. + if h.CommandsSeg == "" || h.SkillsAreCommands { continue } - cmd := commandPrefix + skill + ".md" + cmd := skill + ".md" set = append(set, File{ Name: "commands/" + cmd, Rel: under(h.CommandsSeg, cmd), @@ -553,7 +586,7 @@ var ReviewAgents = []string{"scc-code-review", "scc-security-review"} // delivery are rules under the harness's rules directory, 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"} +var KnowledgeSkills = []string{"scc-adr", "scc-codewiki", "scc-glossary", "scc-prd", "scc-stack", "scc-wiki"} // WorkflowSkills names the skills that drive the methodology instead of authoring a // document. There is one, and the bar for a second is the rule above: if a skill @@ -573,7 +606,7 @@ var KnowledgeSkills = []string{"adr", "codewiki", "glossary", "prd", "stack", "w // moment its own concern arrives. Bootstrapping an existing repository is the one job // that needs the order *across* the six, a survey before any of them, and a bar on // what may be written when the answer is being reconstructed rather than remembered. -var WorkflowSkills = []string{"init", "plan-run"} +var WorkflowSkills = []string{"scc-init", "scc-plan-run"} // Skills is every skill scc ships, knowledge first. Both the skill directory and its // slash command are derived from this one list, so the two cannot drift apart, and a @@ -588,8 +621,13 @@ func Skills() []string { return append(append([]string{}, KnowledgeSkills...), WorkflowSkills...) } -// commandPrefix namespaces the scaffolded slash commands. -const commandPrefix = "scc-" +// SkillPrefix namespaces everything scc scaffolds that the user invokes by name. +// +// It sits on the skill rather than on a command wrapping it, because on a harness +// that turns a skill into a slash command by itself the skill's name *is* what +// gets typed — and `init` is a command Claude Code already ships. The review +// agents carry it for the same reason under the same names. +const SkillPrefix = "scc-" // Dirs returns every directory `scc init` creates, including the ones it has no // file to put in. An agent that can see specs/, plans/, and docs/adr/ knows where @@ -644,7 +682,10 @@ type layout struct { Agents string Commands string HasCommands bool - Manifest string + // SkillsAreCommands says the harness turns a skill into a slash command by + // itself, so the layout block names no commands directory — there is not one. + SkillsAreCommands bool + Manifest string // RulesPreloaded says the harness already put Rules in the agent's context, // so a template can stop telling it to go and read them. See // paths.Harness.PreloadsRules. @@ -691,16 +732,17 @@ func column(p string) string { func layoutOf(h paths.Harness) layout { l := layout{ - Harness: h.ID, - Label: h.Label, - Dir: h.Dir, - Entry: h.EntryFile, - Rules: path.Join(h.Dir, h.RulesSeg), - Skills: path.Join(h.Dir, h.SkillsSeg), - Agents: path.Join(h.Dir, h.AgentsSeg), - Manifest: path.Join(h.Dir, paths.ManifestSeg), - RulesPreloaded: h.PreloadsRules, - RulesScoped: h.ScopedRules, + Harness: h.ID, + Label: h.Label, + Dir: h.Dir, + Entry: h.EntryFile, + Rules: path.Join(h.Dir, h.RulesSeg), + Skills: path.Join(h.Dir, h.SkillsSeg), + Agents: path.Join(h.Dir, h.AgentsSeg), + Manifest: path.Join(h.Dir, paths.ManifestSeg), + RulesPreloaded: h.PreloadsRules, + RulesScoped: h.ScopedRules, + SkillsAreCommands: h.SkillsAreCommands, } l.RulesCol = column(l.Rules) l.SkillsCol = column(l.Skills) diff --git a/internal/assets/assets_test.go b/internal/assets/assets_test.go index 78a5b01..d9aea91 100644 --- a/internal/assets/assets_test.go +++ b/internal/assets/assets_test.go @@ -491,11 +491,17 @@ func TestAgentProseIsSafeToEmbedInTOML(t *testing.T) { } } -// Every skill in Skills() — knowledge and workflow alike — ships a SKILL.md in every -// harness, and its slash command wherever the harness has a command surface. The two -// are derived from one list so they cannot drift, and this is the assertion that -// keeps the derivation honest — including for Codex, where the answer is -// deliberately no commands at all. +// Every skill in Skills() ships a SKILL.md in every harness, and is reachable as a +// slash command wherever the harness has one at all — by a command file scc writes, +// or by the harness turning the skill into one by itself. +// +// Two harnesses answer that differently and both are deliberate. Claude Code +// registers a skill as `/` unaided, so a command file beside it is a second +// entry point for one thing and a second description preloaded into every request. +// Codex has no project-level command surface to write into. What the assertion +// holds is the property underneath both: a skill the user cannot invoke by name is +// a skill only the model can reach, and the `scc-` prefix on the name is what keeps +// `/scc-init` from being Claude Code's own `/init`. func TestEverySkillShipsWithItsCommand(t *testing.T) { for _, h := range paths.Harnesses() { skills, commands := map[string]bool{}, map[string]bool{} @@ -514,7 +520,7 @@ func TestEverySkillShipsWithItsCommand(t *testing.T) { t.Fatalf("%s: %d skills, want %d", h.ID, len(skills), len(all)) } wantCommands := len(all) - if h.CommandsSeg == "" { + if h.CommandsSeg == "" || h.SkillsAreCommands { wantCommands = 0 } if len(commands) != wantCommands { @@ -524,8 +530,15 @@ func TestEverySkillShipsWithItsCommand(t *testing.T) { if !skills[name] { t.Errorf("%s: %s is in Skills() and ships no SKILL.md", h.ID, name) } - if wantCommands > 0 && !commands[commandPrefix+name] { - t.Errorf("%s: %s ships no %s%s command", h.ID, name, commandPrefix, name) + if wantCommands > 0 && !commands[name] { + t.Errorf("%s: %s ships no command", h.ID, name) + } + // Whatever the route, the name the user types is the same one — and it + // is prefixed, because an unprefixed `init` is a slash command the + // harness may already define. + if !strings.HasPrefix(name, SkillPrefix) { + t.Errorf("%s: skill %q is not namespaced; /%s can collide with the harness's own", + h.ID, name, name) } } } @@ -1062,7 +1075,7 @@ func (e errNoSuchRule) Error() string { // uses wherever a change is cheap to make and expensive to make by accident. func TestTheTemplateVersionMovesWithTheTemplates(t *testing.T) { // Bump Version, then replace this with the digest the failure prints. - const fingerprint = "32ec486ca64c66ba775896f352adf72c33cf50256770a4eb9e4b8480140d93f8" + const fingerprint = "fcd82bf91a559baa6c72268203627cc74c0e4a8839f1febe636641ff6a0d5aa2" sum := sha256.New() // Version goes into the hash, and without it this test does not do the job its diff --git a/internal/assets/templates/commands/scc-adr.md b/internal/assets/templates/commands/scc-adr.md index 35c68da..d56b0fc 100644 --- a/internal/assets/templates/commands/scc-adr.md +++ b/internal/assets/templates/commands/scc-adr.md @@ -1,15 +1,8 @@ --- -description: Record a hard-to-reverse decision as a numbered ADR under docs/adr/, or supersede one that stopped being true +description: Record a decision that is hard to reverse as a numbered ADR under docs/adr/, and supersede rather than edit an old one. Use it when a choice would be expensive to undo — storage, a wire protocol, a module boundary, a vendor — and on any adr.* finding. argument-hint: [the decision, or the ADR being superseded] --- -Use the `adr` skill. +Use the `scc-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. +Request: $ARGUMENTS diff --git a/internal/assets/templates/commands/scc-codewiki.md b/internal/assets/templates/commands/scc-codewiki.md index 34c58e0..f724a9f 100644 --- a/internal/assets/templates/commands/scc-codewiki.md +++ b/internal/assets/templates/commands/scc-codewiki.md @@ -1,13 +1,8 @@ --- -description: Narrate an area of the codebase into docs/codewiki/, every section citing the exact lines it explains +description: Narrate an area of this codebase into docs/codewiki/, every section citing the lines it explains. Use it when a subsystem is hard to enter cold, when someone asks for an architecture walkthrough, and on any codewiki.* finding. argument-hint: [area or path to narrate | repair] --- -Use the `codewiki` skill. +Use the `scc-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. +Request: $ARGUMENTS diff --git a/internal/assets/templates/commands/scc-glossary.md b/internal/assets/templates/commands/scc-glossary.md index 85c7d6f..3a52219 100644 --- a/internal/assets/templates/commands/scc-glossary.md +++ b/internal/assets/templates/commands/scc-glossary.md @@ -1,12 +1,8 @@ --- -description: Add, settle, or rename a canonical term in docs/glossary.md, and list the synonyms to avoid +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 or renamed, before naming anything that will outlive the session, and on any glossary.* finding. argument-hint: [term, or the ambiguity to settle] --- -Use the `glossary` skill. +Use the `scc-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. +Request: $ARGUMENTS diff --git a/internal/assets/templates/commands/scc-init.md b/internal/assets/templates/commands/scc-init.md index d96e8b0..c2da68f 100644 --- a/internal/assets/templates/commands/scc-init.md +++ b/internal/assets/templates/commands/scc-init.md @@ -1,22 +1,8 @@ --- -description: Bootstrap the knowledge base from the code that is already here — survey the repository, then write stack, glossary, the project rule's real commands, the wiki, and the ADRs for decisions already taken +description: Bootstrap this project's knowledge base from the code that already exists: survey the repository, then write the stack, glossary, wiki, codewiki and ADRs, and the project's real build, test, lint and format commands. Use it on a workspace whose docs/ is still the seeded anchors, or to document an existing codebase. argument-hint: [anchors | full, and the subtree to cover if not the whole repository] --- -Use the `init` skill. +Use the `scc-init` skill. -Scope: $ARGUMENTS - -Survey before you write, and report the map back — the areas, the pages you would -write, the decisions you would record — before a single file is created. Ask the -graph and the history rather than reading the tree file by file. - -**Write only what you can point at.** Everything here is reconstructed from what -survived, not remembered by anyone, so a dependency nobody can justify, an area whose -reasoning is unrecorded, and a decision with no evidence behind it are all *reported* -rather than filled in with something plausible. A gap is visible; an invention is -believed. - -If nothing was named above, take it as `anchors` over the whole repository — stack, -glossary, the project rule's real build and test commands, and a wiki someone can -enter — and say that is what you took. +Request: $ARGUMENTS diff --git a/internal/assets/templates/commands/scc-plan-run.md b/internal/assets/templates/commands/scc-plan-run.md index e3531d6..98d625e 100644 --- a/internal/assets/templates/commands/scc-plan-run.md +++ b/internal/assets/templates/commands/scc-plan-run.md @@ -1,32 +1,8 @@ --- -description: Run a plan under plans/ to completion — implement group by group, then deliver as one PR per group or one at the end, and settle CI before calling it delivered +description: Drive a whole plan under plans/ to completion — brief it once, then implement group by group, asking scc for the next task rather than opening the file, and deliver per group or at the end. Resumes from the repository, not from memory. Use it to implement or finish an entire plan. argument-hint: [the plan, plus how to run it and any standing instruction] --- -Use the `plan-run` skill. +Use the `scc-plan-run` skill. -Plan, and how to run it: $ARGUMENTS - -Brief the plan — `scc map brief ` — then `scc map ` for the counts, and -name the groups back, numbered and in order, before writing any code. The order is the -one thing the user can correct cheaply now and expensively after three merges. - -**Never open the plan file.** `brief` is its header and `tasks` is its checklist; -there is nothing else in it, and opening one as the first act of a run puts all of it -in context for every turn of a loop that lasts hours. Inside a group, ask `scc map -tasks --next` for the one task to do, and ask again once it is ticked. - -Then take every answer the line above already gave and ask only for what is left. -"Implement the whole plan, one PR at the end, delivered when CI is green" has settled -most of it; re-asking what someone just typed is the friction that stops people using -this at all. Restate what you took so a wrong reading is cheap to correct, then put -the remaining questions in one exchange — automatic or gated, one PR at the end or one -per group, and what happens once a PR is open. **These are the developer's calls.** Anything the plan's frontmatter already -records is a proposed answer to confirm, not a decision already made. - -The plan is delivered when CI is green on its pull request — never on the strength of -a passing local suite. - -Anything said above about *how* to implement is a standing instruction: it applies -to every group, and you carry it into each one explicitly rather than trusting it to -survive from the first group to the last. +Request: $ARGUMENTS diff --git a/internal/assets/templates/commands/scc-prd.md b/internal/assets/templates/commands/scc-prd.md index 3325d18..36066f3 100644 --- a/internal/assets/templates/commands/scc-prd.md +++ b/internal/assets/templates/commands/scc-prd.md @@ -1,16 +1,8 @@ --- -description: Turn an initiative too large for one spec into a plan under plans/ — decomposed into specs and tasks +description: Turn an initiative spanning more than one feature into a plan under plans/: a short header, a checklist, and references to the specs built separately. Use it for a PRD, a roadmap item, an epic, or an idea too large for one spec. argument-hint: [the initiative, epic, or PRD] --- -Use the `prd` skill. +Use the `scc-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. +Request: $ARGUMENTS diff --git a/internal/assets/templates/commands/scc-stack.md b/internal/assets/templates/commands/scc-stack.md index 25b6297..146e54e 100644 --- a/internal/assets/templates/commands/scc-stack.md +++ b/internal/assets/templates/commands/scc-stack.md @@ -1,16 +1,8 @@ --- -description: Record an adopted technology in docs/stack.md, or account for a dependency that nobody decided on +description: Own docs/stack.md — every adopted technology with one line on why it earned its place. Use it before adding a dependency to any manifest, when a technology is swapped or dropped, and on any stack.* finding. argument-hint: [technology being adopted or dropped] --- -Use the `stack` skill. +Use the `scc-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. +Request: $ARGUMENTS diff --git a/internal/assets/templates/commands/scc-wiki.md b/internal/assets/templates/commands/scc-wiki.md index 38ba7a2..3a1d08f 100644 --- a/internal/assets/templates/commands/scc-wiki.md +++ b/internal/assets/templates/commands/scc-wiki.md @@ -1,13 +1,8 @@ --- -description: Build or maintain docs/wiki/ — ingest a source from docs/raw/, answer from what is known, or clear wiki.* findings +description: Build and maintain docs/wiki/ — one page per concept, linked with wikilinks and reachable from index.md. Use it to distil a source from docs/raw/, to answer from accumulated knowledge, and on any wiki.* finding. argument-hint: [ingest | query | maintain] --- -Use the `wiki` skill. +Use the `scc-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/entry.md b/internal/assets/templates/entry.md index a127481..3c2cf23 100644 --- a/internal/assets/templates/entry.md +++ b/internal/assets/templates/entry.md @@ -54,7 +54,9 @@ plans/.md structure, plus a checklist and/or spec references docs/ knowledge base — wiki, adr, codewiki, glossary, stack, notes {{.RulesCol}}the methodology above {{.SkillsCol}}authoring each part of docs/, and running a plan group by group -{{- if .HasCommands}} +{{- if .SkillsAreCommands}} + on demand too: /scc-plan-run, /scc-wiki, /scc-adr, … +{{- else if .HasCommands}} {{.CommandsCol}}the same skills on demand: /scc-plan-run, /scc-wiki, /scc-adr, … {{- end}} ``` diff --git a/internal/assets/templates/skills/adr/SKILL.md b/internal/assets/templates/skills/scc-adr/SKILL.md similarity index 95% rename from internal/assets/templates/skills/adr/SKILL.md rename to internal/assets/templates/skills/scc-adr/SKILL.md index 12bfe0d..eb9f865 100644 --- a/internal/assets/templates/skills/adr/SKILL.md +++ b/internal/assets/templates/skills/scc-adr/SKILL.md @@ -1,6 +1,7 @@ --- -name: adr +name: scc-adr description: Record a decision that is hard to reverse as a numbered ADR under docs/adr/, and supersede rather than edit an old one. Use it when a choice would be expensive to undo — storage, a wire protocol, a module boundary, a vendor — and on any adr.* finding. +argument-hint: [the decision, or the ADR being superseded] --- You write the record of a decision, so that the reasoning survives the people who @@ -8,6 +9,11 @@ were in the room. The filename shape, the frontmatter, and the citation form are `{{.Rules}}/knowledge-base.md`. This skill is when to write one and how to write one worth reading. +## Nothing asked for + +`$ARGUMENTS` is the decision, or the record being superseded. Empty, run +`scc validate` and clear the `adr.*` findings. + ## Is this an ADR? One question: **how expensive would it be to undo?** diff --git a/internal/assets/templates/skills/codewiki/SKILL.md b/internal/assets/templates/skills/scc-codewiki/SKILL.md similarity index 90% rename from internal/assets/templates/skills/codewiki/SKILL.md rename to internal/assets/templates/skills/scc-codewiki/SKILL.md index f0f0369..3d99372 100644 --- a/internal/assets/templates/skills/codewiki/SKILL.md +++ b/internal/assets/templates/skills/scc-codewiki/SKILL.md @@ -1,6 +1,7 @@ --- -name: codewiki +name: scc-codewiki description: Narrate an area of this codebase into docs/codewiki/, every section citing the lines it explains. Use it when a subsystem is hard to enter cold, when someone asks for an architecture walkthrough, and on any codewiki.* finding. +argument-hint: [area or path to narrate | repair] --- You write prose that explains code, and every section of it points at the lines it @@ -11,6 +12,13 @@ becoming a lie. The format is in `{{.Rules}}/knowledge-base.md`. This skill is the procedure and the judgment. +## Nothing asked for + +`$ARGUMENTS` is the area to narrate. Empty, run `scc validate` and repair the +`codewiki.*` findings — a broken citation means the code moved and the prose +describing it is now suspect, so re-read before re-numbering. With no findings and no +area named, ask which area is hard to enter cold rather than picking one at random. + ## The citation form A link whose text is `path:start-end` and whose target is empty: diff --git a/internal/assets/templates/skills/glossary/SKILL.md b/internal/assets/templates/skills/scc-glossary/SKILL.md similarity index 90% rename from internal/assets/templates/skills/glossary/SKILL.md rename to internal/assets/templates/skills/scc-glossary/SKILL.md index 865a354..a46a818 100644 --- a/internal/assets/templates/skills/glossary/SKILL.md +++ b/internal/assets/templates/skills/scc-glossary/SKILL.md @@ -1,6 +1,7 @@ --- -name: glossary +name: scc-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 or renamed, before naming anything that will outlive the session, and on any glossary.* finding. +argument-hint: [term, or the ambiguity to settle] --- You own the project's vocabulary. Domain language drifts by default: two people @@ -11,6 +12,13 @@ two documents are describing the same concept or two different ones. The entry format is in `{{.Rules}}/knowledge-base.md`. This skill is when to reach for it and how to decide. +## Nothing asked for + +`$ARGUMENTS` is the term, or the ambiguity to settle. Read `docs/glossary.md` before +adding to it: an entry duplicating an existing concept under a different name makes +the canonical source itself ambiguous. With nothing named, run `scc validate` and +resolve the `glossary.*` findings. + ## The entry One entry per line, in `docs/glossary.md`: diff --git a/internal/assets/templates/skills/init/SKILL.md b/internal/assets/templates/skills/scc-init/SKILL.md similarity index 98% rename from internal/assets/templates/skills/init/SKILL.md rename to internal/assets/templates/skills/scc-init/SKILL.md index 8ab4eba..bfe0ae6 100644 --- a/internal/assets/templates/skills/init/SKILL.md +++ b/internal/assets/templates/skills/scc-init/SKILL.md @@ -1,9 +1,11 @@ --- -name: init +name: scc-init description: Bootstrap this project's knowledge base from the code that already exists: survey the repository, then write the stack, glossary, wiki, codewiki and ADRs, and the project's real build, test, lint and format commands. Use it on a workspace whose docs/ is still the seeded anchors, or to document an existing codebase. +argument-hint: [anchors | full, and the subtree to cover if not the whole repository] --- -You fill an empty knowledge base from a repository that already exists. +You fill an empty knowledge base from a repository that already exists. `$ARGUMENTS` +is the scope, if one was given; the table below is what to do when it was not. The formats are not yours. Each artifact has an owner — the `stack`, `glossary`, `wiki`, `codewiki`, and `adr` skills — and the rule they all enforce is diff --git a/internal/assets/templates/skills/plan-run/SKILL.md b/internal/assets/templates/skills/scc-plan-run/SKILL.md similarity index 98% rename from internal/assets/templates/skills/plan-run/SKILL.md rename to internal/assets/templates/skills/scc-plan-run/SKILL.md index 4436222..a4ef21d 100644 --- a/internal/assets/templates/skills/plan-run/SKILL.md +++ b/internal/assets/templates/skills/scc-plan-run/SKILL.md @@ -1,9 +1,11 @@ --- -name: plan-run +name: scc-plan-run description: Drive a whole plan under plans/ to completion — brief it once, then implement group by group, asking scc for the next task rather than opening the file, and deliver per group or at the end. Resumes from the repository, not from memory. Use it to implement or finish an entire plan. +argument-hint: [the plan, plus how to run it and any standing instruction] --- -You run a plan to the end. +You run a plan to the end. `$ARGUMENTS` names the plan and, often, how to run it — +take every answer it already gave and ask only for what is left. The mechanics of delivering *one* unit of work are not here — they are in `{{.Rules}}/delivery.md`, and repeating them would give this project two diff --git a/internal/assets/templates/skills/prd/SKILL.md b/internal/assets/templates/skills/scc-prd/SKILL.md similarity index 93% rename from internal/assets/templates/skills/prd/SKILL.md rename to internal/assets/templates/skills/scc-prd/SKILL.md index 109dfab..30c095f 100644 --- a/internal/assets/templates/skills/prd/SKILL.md +++ b/internal/assets/templates/skills/scc-prd/SKILL.md @@ -1,6 +1,7 @@ --- -name: prd +name: scc-prd description: Turn an initiative spanning more than one feature into a plan under plans/: a short header, a checklist, and references to the specs built separately. Use it for a PRD, a roadmap item, an epic, or an idea too large for one spec. +argument-hint: [the initiative, epic, or PRD] --- You take an initiative that is too big for one spec and turn it into a plan: one @@ -12,6 +13,12 @@ artifact is a **plan**, and the vocabulary in `{{.Rules}}/routing.md` is what the rest of the workspace uses. Do not create a `docs/prd/` directory. +## Nothing asked for + +`$ARGUMENTS` is the initiative. 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 ` rather than wrapping one feature in a plan for ceremony. + ## First, check this is a plan at all `{{.Rules}}/routing.md` asks one question, and it is worth asking diff --git a/internal/assets/templates/skills/stack/SKILL.md b/internal/assets/templates/skills/scc-stack/SKILL.md similarity index 90% rename from internal/assets/templates/skills/stack/SKILL.md rename to internal/assets/templates/skills/scc-stack/SKILL.md index 19a5daa..ed1584e 100644 --- a/internal/assets/templates/skills/stack/SKILL.md +++ b/internal/assets/templates/skills/scc-stack/SKILL.md @@ -1,6 +1,7 @@ --- -name: stack +name: scc-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 any manifest, when a technology is swapped or dropped, and on any stack.* finding. +argument-hint: [technology being adopted or dropped] --- You own `docs/stack.md`. The rule it enforces is in @@ -13,6 +14,13 @@ reads the direct dependencies out of every manifest it knows — `go.mod`, `composer.json`, `pom.xml` — and reports any that `stack.md` does not mention. Indirect dependencies are skipped: nobody decided those. +## Nothing asked for + +`$ARGUMENTS` is the technology being adopted or dropped. Empty, run `scc validate` +and work through the `stack.undocumented-dependency` findings: for each, 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. + ## Adding a dependency is two steps Adding it to the manifest is the first. The second is here, and it happens in the diff --git a/internal/assets/templates/skills/wiki/SKILL.md b/internal/assets/templates/skills/scc-wiki/SKILL.md similarity index 94% rename from internal/assets/templates/skills/wiki/SKILL.md rename to internal/assets/templates/skills/scc-wiki/SKILL.md index 6ccc3d2..46ab4ab 100644 --- a/internal/assets/templates/skills/wiki/SKILL.md +++ b/internal/assets/templates/skills/scc-wiki/SKILL.md @@ -1,6 +1,7 @@ --- -name: wiki +name: scc-wiki description: Build and maintain docs/wiki/ — one page per concept, linked with wikilinks and reachable from index.md. Use it to distil a source from docs/raw/, to answer from accumulated knowledge, and on any wiki.* finding. +argument-hint: [ingest | query | maintain] --- You own `docs/wiki/`: the durable half of what this project knows. A spec says what @@ -10,6 +11,12 @@ The format is not yours to invent — it is in `{{.Rules}}/knowledge-base.md`, and `scc validate` enforces it. This skill is the procedure. +## Nothing asked for + +`$ARGUMENTS` is the request. Empty, the default job is `docs/raw/`: anything sitting +there is unprocessed work. If `raw/` is empty, run `scc validate` and clear whatever +`wiki.*` findings it reports. If there are none, say so rather than inventing pages. + ## Ingest — something landed in `docs/raw/` `raw/` is a drop box, not storage. A file still there is unfinished work, and diff --git a/internal/cli/update_test.go b/internal/cli/update_test.go index c432fb6..df9a7b2 100644 --- a/internal/cli/update_test.go +++ b/internal/cli/update_test.go @@ -39,7 +39,7 @@ func TestUpdateNeedsAWorkspace(t *testing.T) { // deleted, before anything is written. func TestUpdateReportsThePlanAndWritesNothingOnADryRun(t *testing.T) { root := initWorkspace(t) - missing := ".claude/skills/adr/SKILL.md" + missing := ".claude/skills/scc-adr/SKILL.md" if err := os.Remove(filepath.Join(root, filepath.FromSlash(missing))); err != nil { t.Fatalf("Remove: %v", err) } diff --git a/internal/paths/paths.go b/internal/paths/paths.go index e117826..7ade915 100644 --- a/internal/paths/paths.go +++ b/internal/paths/paths.go @@ -88,6 +88,20 @@ type Harness struct { // the skills alone there rather than writing into the user's home directory. CommandsSeg string + // SkillsAreCommands is whether this harness already exposes a skill as a slash + // command, so scc need not write one. + // + // Claude Code does: a skill at `skills//SKILL.md` is invocable as + // `/` with nothing else on disk. A command file beside it is then two + // entry points for one thing in the picker, and — the half that is not + // cosmetic — a second `description` preloaded into every request whether + // anybody runs it or not. + // + // It is what makes the `scc-` prefix belong on the skill rather than on the + // command: with no command file, the skill's own name is what the user types, + // and `/init` is already Claude Code's. + SkillsAreCommands bool + // RulesSeg is where the methodology goes under Dir. The path is scc's choice // in all three, kept parallel so one layout is learned once — but what the // harness then does with it is not, which is what PreloadsRules records. @@ -214,9 +228,10 @@ var ( ID: "claude", Label: "Claude Code", Bin: "claude", Dir: ClaudeDir, EntryFile: "CLAUDE.md", AgentsSeg: "agents", AgentFormat: FormatMarkdown, SkillsSeg: "skills", CommandsSeg: "commands", RulesSeg: "rules", - SettingsSeg: "settings.json", - PreloadsRules: true, - ScopedRules: true, + SettingsSeg: "settings.json", + SkillsAreCommands: true, + PreloadsRules: true, + ScopedRules: true, // Yes, and documented as such: a non-fork subagent inherits the whole // CLAUDE.md hierarchy, project rules included. SubagentsInheritRules: true, diff --git a/internal/scaffold/update_test.go b/internal/scaffold/update_test.go index 381f82a..cb612a6 100644 --- a/internal/scaffold/update_test.go +++ b/internal/scaffold/update_test.go @@ -128,7 +128,7 @@ func TestApplyUpdateBringsTheTreeCurrent(t *testing.T) { applyTo(t, root, paths.OpenCode, false) rel := ".opencode/rules/routing.md" writeManaged(t, root, paths.OpenCode, rel, "# an older template\n", true) - missing := ".opencode/skills/wiki/SKILL.md" + missing := ".opencode/skills/scc-wiki/SKILL.md" if err := os.Remove(filepath.Join(root, filepath.FromSlash(missing))); err != nil { t.Fatalf("Remove: %v", err) } @@ -264,8 +264,12 @@ func TestApplyUpdateAddsASkillTheWorkspacePredates(t *testing.T) { var rels []string for _, skill := range assets.Skills() { rels = append(rels, path.Join(h.Dir, h.SkillsSeg, skill, "SKILL.md")) - if h.CommandsSeg != "" { - rels = append(rels, path.Join(h.Dir, h.CommandsSeg, "scc-"+skill+".md")) + // Only where scc writes one. A harness that turns a skill into a slash + // command by itself gets no command file, and asserting on one here + // would be this test insisting on a file the product deliberately + // stopped shipping. + if h.CommandsSeg != "" && !h.SkillsAreCommands { + rels = append(rels, path.Join(h.Dir, h.CommandsSeg, skill+".md")) } } m, _, err := manifest.Load(root, h)