@sous-io/sous 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ # Providers
2
+
3
+ A **provider** is everything sous knows about one kind of repository host. It is the only place
4
+ a host-specific fact is allowed to live, so every command above it (add, subscribe, build,
5
+ release, submit) talks about repositories in the same terms whatever host they live on.
6
+
7
+ Sous ships three: `github`, `gitlab` and `local`. This page covers how one is chosen for a URL,
8
+ what a URL becomes once a provider has taken it apart, how private repositories authenticate,
9
+ what each provider can do when you propose a change, and what writing a fourth one involves.
10
+
11
+ ## What a provider answers
12
+
13
+ Every provider answers the **read path**, which is deliberately small:
14
+
15
+ - recognize a repository URL as its own;
16
+ - take that URL apart into a host, an owner and a name;
17
+ - hand back the repository's `sous.index.json`;
18
+ - fetch **one** recipe's subtree at one tag.
19
+
20
+ Nothing there clones a whole repository. A provider that can also carry a contribution answers
21
+ the **write path**: report whether its command line tool is installed and signed in, say whether
22
+ you may push to the repository itself, fork it onto your account, and open the proposal.
23
+
24
+ Each provider declares which of the two features, `fetch` and `submit`, it genuinely answers, and
25
+ sous consults that declaration rather than a provider's name. Asking for something outside a
26
+ provider's features is refused with a sentence naming it and what it cannot do, never a crash.
27
+
28
+ ## How a URL is matched
29
+
30
+ When you name a repository, sous tries each provider in turn (`github`, then `gitlab`, then
31
+ `local`) and the first one that recognizes the URL handles it.
32
+
33
+ | Provider | Recognizes |
34
+ |---|---|
35
+ | `github` | any URL whose host is `github.com` |
36
+ | `gitlab` | any URL whose host is `gitlab.com`, or whose host begins with `gitlab.` |
37
+ | `local` | an absolute filesystem path, or the same path in `file:///...` form |
38
+
39
+ Three spellings of a hosted URL are all understood, because all three are what people paste:
40
+
41
+ ```text
42
+ https://github.com/sous-io/sous-recipes
43
+ git@github.com:sous-io/sous-recipes.git
44
+ ssh://git@github.com/sous-io/sous-recipes
45
+ ```
46
+
47
+ A trailing slash and a trailing `.git` are dropped before anything else happens, and the scheme
48
+ and the host are lowercased. Everything between the host and the last segment is the owner, so
49
+ a GitLab group path of any depth (`group/subgroup/project`) is carried whole.
50
+
51
+ `local` is tried last and matches only a path, so it can never intercept a hosted URL. A path
52
+ you type relative (`../my-recipes`, `~/recipes`, `.`) is expanded and resolved against your
53
+ working directory before any provider sees it, and the absolute form is what gets stored; a
54
+ repository on this machine is machine-specific either way. In a config file that same relative path
55
+ is refused outright, because an entry is read from a file that several working directories may run
56
+ against; see
57
+ [A local repository named by a relative path](repositories-troubleshooting.md#a-local-repository-named-by-a-relative-path).
58
+
59
+ ### Naming a provider explicitly
60
+
61
+ `sous repo add --provider <id>` (and the `provider:` key on a repository entry) names the
62
+ provider outright. That is what a self-hosted instance behind an unfamiliar host name needs:
63
+
64
+ ```bash
65
+ sous repo add https://git.example.com/team/recipes --provider gitlab
66
+ ```
67
+
68
+ A named provider is honored for any host nobody recognizes. It is refused only when a
69
+ **different** provider plainly owns the URL, because that is a contradiction rather than a hint:
70
+
71
+ ```text
72
+ Error: The gitlab provider does not handle https://github.com/sous-io/sous-recipes; that is a
73
+ github URL, which the github provider handles.
74
+ Drop '--provider' and let sous work it out, or name 'github'.
75
+ ```
76
+
77
+ With no provider named and nothing recognizing the host, sous says so and points at the flag:
78
+
79
+ ```text
80
+ Error: Sous does not recognize the host in the repository URL
81
+ https://git.example.com/team/recipes.
82
+ Sous ships these providers: github, gitlab, local. For a self-hosted instance, name the
83
+ provider that host runs on the repository entry, as in 'sous repo add
84
+ https://git.example.com/team/recipes --provider <provider>'.
85
+ ```
86
+
87
+ `sous repo list` shows which provider each trusted repository resolved to:
88
+
89
+ ```text
90
+ Repository Provider Origin Linked Recipes URL
91
+ ------------ -------- -------- ------ ----------- ---------------------------------------
92
+ qa-recipes local user no 0 /home/me/Projects/qa-recipes
93
+ sous-recipes github built in no not fetched https://github.com/sous-io/sous-recipes
94
+ ```
95
+
96
+ ## Repository identity
97
+
98
+ Canonicalizing a URL produces the repository's **identity**, `<host>/<owner path>/<name>`,
99
+ lowercased and with any `.git` suffix gone:
100
+
101
+ ```text
102
+ github.com/sous-io/sous-recipes
103
+ gitlab.example.com/group/subgroup/project
104
+ localhost/home/me/projects/my-recipes
105
+ ```
106
+
107
+ A local repository's identity uses the host `localhost` and the directory's parent path as its
108
+ owner, so a path on disk keys exactly the way a hosted repository does. Identity is then what
109
+ every machine-wide thing keys by:
110
+
111
+ | Keyed by identity | Looks like |
112
+ |---|---|
113
+ | the store entry | `~/.sous/cache/github.com/sous-io/sous-recipes/<namespace>/<recipe>/<version>/` |
114
+ | the cached index | `~/.sous/cache/_indexes/github.com/sous-io/sous-recipes.json` |
115
+ | the index sidecar | `~/.sous/cache/_indexes/github.com/sous-io/sous-recipes.meta.json` |
116
+ | the lockfile's `identity` field | `github.com/sous-io/sous-recipes` |
117
+
118
+ A repository's **short name** (`sous-recipes`) is something your project chose, and it keys
119
+ nothing shared: two projects may call one repository different things, and two may use one name
120
+ for different repositories. Keying by identity is what lets them share one cached copy without
121
+ colliding. See [The store on disk](repositories-file-formats.md#the-store-on-disk).
122
+
123
+ Canonicalizing also produces the two clone URLs sous hands to git, `https://<host>/<owner>/<name>.git`
124
+ and `git@<host>:<owner>/<name>.git`. For the `local` provider the HTTPS slot carries the
125
+ absolute directory, because that is what git is given when a recipe is fetched.
126
+
127
+ ?> A published manifest names a dependency in another repository by a locator URL whose
128
+ scheme is the provider's identifier: `github://sous-io/sous-recipes/workflow/sat@^1.1`. A first
129
+ segment containing a dot is read as the host, so `gitlab://gitlab.example.com/group/project/ns/recipe`
130
+ addresses a self-hosted instance; without one, the provider's public host is assumed. `local://`
131
+ is refused, because a path on your disk is not a published location. The grammar is in
132
+ [Dependencies named by location](repositories-file-formats.md#dependencies-named-by-location).
133
+
134
+ ## Private repositories
135
+
136
+ Sous reads a repository in two very different ways, and they authenticate differently.
137
+
138
+ **The index** is one plain HTTPS GET of a raw file, carrying a bearer token when sous has one:
139
+
140
+ ```text
141
+ https://raw.githubusercontent.com/<owner>/<name>/HEAD/sous.index.json
142
+ https://<gitlab host>/<owner>/<name>/-/raw/HEAD/sous.index.json
143
+ ```
144
+
145
+ A token is looked for in two places, in this order:
146
+
147
+ 1. the environment: `GITHUB_TOKEN` for GitHub, `GITLAB_TOKEN` for GitLab;
148
+ 2. the host's own command line tool, `gh auth token` or `glab auth token`, when it is installed
149
+ and signed in.
150
+
151
+ Neither is required. A public repository needs no token at all, and a missing `gh` or `glab` is
152
+ never an error on the read path; sous simply fetches without one. When the host refuses, the
153
+ error says which of the two situations you are in:
154
+
155
+ ```text
156
+ Sous could not fetch the repo index from https://raw.githubusercontent.com/acme/recipes/HEAD/sous.index.json.
157
+ The server answered 404 Not Found.
158
+ Either the repository publishes no sous index yet, or the URL names a repository that does
159
+ not exist.
160
+ ```
161
+
162
+ A 401 or 403 says the repository is private or was not authorized, and points at the two sources:
163
+
164
+ ```text
165
+ Sous could not fetch the repo index from https://raw.githubusercontent.com/acme/recipes/HEAD/sous.index.json.
166
+ The server answered 403 Forbidden.
167
+ The repository is private or the request was not authorized. Sous uses a token from the
168
+ environment, or from the provider's command line tool when one is installed and signed in.
169
+ ```
170
+
171
+ **A recipe's files** come from git itself: a shallow, blobless, sparse clone at the version's
172
+ tag, fetching only the blobs inside that one recipe folder. That means git's own credentials
173
+ apply, exactly as they would for a manual clone: credential helpers, the SSH agent, proxies and
174
+ `insteadOf` rewrites are all inherited from your git configuration, and sous adds nothing of its
175
+ own. Cloning a working copy with `sous repo link` works the same way.
176
+
177
+ ?> For a private repository, make sure both halves work. `gh auth login` (or `GITHUB_TOKEN`)
178
+ covers the index, and a git credential helper or an SSH key covers the recipe files. A build that
179
+ finds the index but cannot clone has only the second half missing.
180
+
181
+ ## Proposing a change
182
+
183
+ [`sous repo submit`](repositories-authoring.md#contribute-to-someone-elses-repository) validates
184
+ your repository, then hands the host-specific mechanics to the provider that owns its `origin`
185
+ remote. Providers differ, and sous says so rather than pretending otherwise:
186
+
187
+ | Provider | Tool | Push permission | Forking | Proposal |
188
+ |---|---|---|---|---|
189
+ | `github` | `gh` | read from GitHub, so a contributor without it is forked automatically | `gh repo fork`, with a `fork` remote added for you | pull request |
190
+ | `gitlab` | `glab` | sous cannot tell, so it pushes to `origin` and says so | not done for you | merge request |
191
+ | `local` | none | not applicable | not applicable | not applicable |
192
+
193
+ **GitLab reports "cannot tell" rather than guessing.** Sous has no cheap, reliable way to ask
194
+ whether you may push, and a wrong guess would send you down a fork path this provider cannot
195
+ finish. So the submission announces that it could not tell and pushes to `origin` as it stands. If
196
+ that push is refused, the command stops at the push step and reports git's own error alongside
197
+ everything it had already done; forking the project and pushing there is then a manual route.
198
+
199
+ **A local repository never submits.** It declares `fetch` only, so `sous repo submit` stops before
200
+ anything is written and prints the repository's own contribution route instead:
201
+
202
+ ```text
203
+ Error: The 'local' provider cannot propose a change on your behalf.
204
+ This repository asks that changes be sent this way:
205
+ https://github.com/sous-io/sous-recipes/blob/main/CONTRIBUTING.md
206
+ ```
207
+
208
+ That second line is the `contribute` field from the repository's `sous.repo.yaml`; sous prints it
209
+ whenever a provider cannot carry the proposal, so a contributor is never left without a route. A
210
+ manifest that sets none gets "This repository's manifest does not say where to send a change, so
211
+ send it the way its maintainers prefer." Set the field in your own repository for the same reason;
212
+ see [the repository manifest](repositories-file-formats.md#sousrepoyaml-the-repository-manifest).
213
+
214
+ ## Self-hosted GitLab
215
+
216
+ A self-hosted instance needs no special configuration beyond being recognized. A host that
217
+ begins with `gitlab.` is matched automatically; any other host name is matched by naming the
218
+ provider once, on the repository entry:
219
+
220
+ ```jsonc
221
+ {
222
+ "repos": {
223
+ "team-recipes": {
224
+ "url": "https://git.example.com/platform/recipes",
225
+ "provider": "gitlab"
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ That block goes at the top level of your primary config or of a `conf.d/` layer of your own; sous
232
+ writes its own entries to the `conf.d/500-repos.jsonc` layer it manages. Every key is listed under
233
+ [configuration keys](repositories-file-formats.md#configuration-keys).
234
+
235
+ Everything downstream then works normally: the index is read from that host's own raw endpoint,
236
+ `GITLAB_TOKEN` or `glab` supplies the token, and the identity is
237
+ `git.example.com/platform/recipes`. Group paths of any depth are preserved, so
238
+ `group/subgroup/project` stays one repository rather than being mistaken for a namespace.
239
+
240
+ `sous repo submit` is the exception: it runs inside the repository checkout and never reads a
241
+ project's config, so it picks the provider from the `origin` remote URL alone. A host that does not
242
+ begin with `gitlab.` goes unrecognized there, and sous prints the repository's `contribute` pointer.
243
+
244
+ ## The local provider
245
+
246
+ A repository on this machine is an ordinary directory holding a `sous.repo.yaml`, read through
247
+ the `local` provider:
248
+
249
+ ```term
250
+ $ sous repo add ../my-recipes --name my-recipes --trust
251
+ // sous resolves the path, reads the index and records the absolute form
252
+ ▶ Adding a repository:
253
+ Repository: my-recipes
254
+ Location : /home/me/Projects/my-recipes
255
+ Provider : local
256
+ // the namespace and recipe rows are left out here
257
+ ```
258
+
259
+ It exists for local development and for tests: authoring a repository, trying a recipe before
260
+ publishing it, or running a whole workflow with no network at all. Two details make it behave
261
+ like a host rather than like a shortcut.
262
+
263
+ - The index is read from the **working tree** when `sous.index.json` is there, so an index you are
264
+ still writing is picked up without a commit, and from `git show HEAD:sous.index.json` otherwise.
265
+ - A recipe's files come from a clone of the local repository at the version's **tag**, exactly as
266
+ a hosted repository would be fetched, so a version really is the version its tag points at. A
267
+ directory that is not a git repository, or one missing that tag, has no versions to honor, so
268
+ its working tree is copied instead.
269
+
270
+ !> A local path is added, and therefore trusted, through the same ceremony as a hosted
271
+ repository, because its recipes still run on this machine. See [Trust](repositories.md#trust).
272
+
273
+ A path that is not a repository is explained as a path mistake rather than as a provider
274
+ failure, naming what you typed, what sous resolved it to, and what it expected to find:
275
+
276
+ ```text
277
+ Error: There is no directory at './nope'.
278
+ Sous read that as the path /home/me/Projects/my-project/nope, and nothing is there.
279
+ A repository on this machine is a directory holding a 'sous.repo.yaml' file at its root.
280
+ Check the path, or create one with 'sous repo init'.
281
+ ```
282
+
283
+ For editing a repository you already subscribe to, reach for
284
+ [`sous repo link`](repositories-authoring.md#edit-a-repository-in-place) instead; it redirects
285
+ one repository's resolution at a working copy without changing what your project subscribes to.
286
+
287
+ ## Adding a provider
288
+
289
+ Adding a provider is one file plus one line. The interface is internal for now, not a published
290
+ plugin API, so it can still change shape; what follows is what a new provider writes today. A
291
+ provider class extends `ProviderBase`, which carries the plumbing no provider should repeat:
292
+ running a subprocess through the injectable runner, checking whether a tool exited cleanly,
293
+ capturing its output, finding a host token, and refusing every write-path call by name until a
294
+ subclass overrides it. Each member is documented where it lives, in
295
+ `src/lib/repos/providers/base.ts`.
296
+
297
+ The subclass supplies the rest:
298
+
299
+ - `id`, the identifier a repository entry and a locator scheme use;
300
+ - `features`, the ones it genuinely answers (`fetch`, and `submit` only if all four write calls
301
+ are real);
302
+ - `matches(url)` and `canonicalize(url)`, the URL half;
303
+ - `fetchIndex(repo, options)` and `fetchRecipeTree(repo, recipePath, tag, destDir, options)`, the
304
+ read half;
305
+ - `cli` and `proposalNoun` when it submits, so messages can name the tool and call a proposal
306
+ what the host calls it;
307
+ - overrides of the four write-path calls when it submits.
308
+
309
+ Every call takes an options object carrying the testing seams (`cwd`, `env`, `fetchImpl`, `run`),
310
+ which is why no provider reaches for `spawn` or the global `fetch` directly and no test in this
311
+ layer touches the network. The last change is adding the class to the built-in provider list in
312
+ `providers/index.ts`; nothing above the provider layer learns its name.
313
+
314
+ ## Where to go next
315
+
316
+ - [Repositories](repositories.md): the model, trust, and the lockfile
317
+ - [Consuming recipes](repositories-consuming.md): adding, subscribing and building
318
+ - [Authoring a repository](repositories-authoring.md): releasing and contributing
319
+ - [Repository file formats](repositories-file-formats.md): every manifest, index and lockfile
320
+ schema
321
+ - [Troubleshooting](repositories-troubleshooting.md): what the errors mean and how to clear them
322
+ - [Command reference](commands.md): every command and flag
@@ -0,0 +1,340 @@
1
+ # Repositories Quickstart
2
+
3
+ Twelve steps take you from an empty directory to a project whose agent skills come from the
4
+ official repository and from a repository you wrote yourself, with a lockfile a colleague can
5
+ restore from. Budget about ten minutes.
6
+
7
+ This page is the guided tour; [Repositories](repositories.md) explains the model behind it, and
8
+ [Consuming recipes](repositories-consuming.md) and
9
+ [Authoring a repository](repositories-authoring.md) are the reference guides for each half. You
10
+ need sous on your path (`npm install -g @sous-io/sous`), git, and a network connection for the two
11
+ steps that reach GitHub. Output below is trimmed: sous prints absolute paths and a banner that are
12
+ left out here, and a line reading `...` marks rows removed for length.
13
+
14
+ ## 1. Create a project
15
+
16
+ A sous project is a directory with a `.sous/` directory in it holding one config file. One line is
17
+ a valid config:
18
+
19
+ ```bash
20
+ export SOUS_HOME=~/tmp/sous-quickstart
21
+ mkdir -p ~/projects/my-project/.sous
22
+ cd ~/projects/my-project
23
+ echo 'name: my-project' > .sous/sous.config.yaml
24
+ ```
25
+
26
+ Everything else has a default: skills compile into `<project root>/.claude/skills`, and the
27
+ lockfile, the state file and the config layers sous writes land under `.sous/`. `SOUS_HOME` puts
28
+ the machine-wide store somewhere throwaway, so this walkthrough leaves your real one alone. See
29
+ [The config file](configuration.md) for the keys you will want later.
30
+
31
+ ## 2. Build once
32
+
33
+ ```term
34
+ $ sous build
35
+ ▶ Locking subscribed recipes:
36
+ pinned: core/sous-skills at version 0.2.0.
37
+ The lockfile has been updated. Commit it, so everyone building this project
38
+ gets exactly these versions.
39
+ ▶ Building:
40
+ ✓ .claude/skills/about-sous/SKILL.md (~782 tokens)
41
+ ✓ .claude/skills/about-sous-configuration/SKILL.md (~1,204 tokens)
42
+ ✓ .claude/skills/about-agent-skills/SKILL.md (~1,937 tokens)
43
+ ✓ .claude/skills/about-liquid-templates/SKILL.md (~1,486 tokens)
44
+ ✓ .claude/skills/create-skill/SKILL.md (~655 tokens)
45
+ ✓ Done.
46
+ ```
47
+
48
+ You subscribed to nothing and five skills appeared. That is the `core` namespace, which every
49
+ project is auto-subscribed to at the version matching the CLI you are running. It carries the
50
+ skills that teach an agent what sous manages and why it must not hand-edit a generated file.
51
+
52
+ ?> `core` ships inside the sous package and seeds the machine-wide store on first run, so this
53
+ step works with no network at all. It is an ordinary subscription and
54
+ [one line switches it off](repositories-consuming.md#opt-out-of-core).
55
+
56
+ ## 3. See what is on offer
57
+
58
+ ```bash
59
+ sous recipe list
60
+ ```
61
+
62
+ ```text
63
+ Recipe Repository Latest Pinned Subscribed What it is
64
+ -------------------------- ------------ ------ ------ ---------- ------------------------------
65
+ communication/control-flow sous-recipes 1.0.0 no Generic interaction skills
66
+ core/sous-skills sous-recipes 0.2.0 0.2.0 yes What sous is, and how it works
67
+ workflow/task-files sous-recipes 1.0.2 no Per-branch task files
68
+ ...
69
+ ```
70
+
71
+ This reads the cached index of every repository the project trusts, so it works offline and
72
+ downloads nothing. The official repository, `sous-recipes`, is trusted out of the box.
73
+
74
+ Before subscribing to something, read it:
75
+
76
+ ```bash
77
+ sous recipe show workflow/task-files
78
+ ```
79
+
80
+ ```text
81
+ Repository : sous-recipes
82
+ Latest version : 1.0.2
83
+ Subscribed : no
84
+ ...
85
+ The recipe's own files are not on this machine, so the questions it asks and
86
+ the files it publishes are not known here. Subscribing to it fetches them.
87
+ ```
88
+
89
+ On a machine that has never fetched the recipe, sous shows what the index knows: every published
90
+ version, and every dependency with each declared range beside the exact version the index resolved
91
+ it to. The questions it asks are not known until it is fetched; run the same command after
92
+ subscribing and it adds a table of the variables the recipe publishes.
93
+
94
+ ## 4. Subscribe to a recipe
95
+
96
+ ```bash
97
+ sous subscription add workflow/task-files
98
+ ```
99
+
100
+ Sous states what subscribing does, asks once, resolves the whole dependency closure, downloads it,
101
+ then asks the questions the recipe publishes. Answering the confirmation and the two questions:
102
+
103
+ ```text
104
+ ➔ What was installed:
105
+ Recipe Version Repository Why
106
+ ----------------------------- ------- ------------ --------------------------
107
+ workflow/sub-agent-delegation 1.0.0 sous-recipes needed by workflow/task-files
108
+ workflow/task-files 1.0.2 sous-recipes you subscribed to it
109
+
110
+ ➔ Variables:
111
+ Answers stored:
112
+ taskFileRoot : .sous/tasks TASK_FILE_ROOT in .env
113
+ ticketIdExample : PROJ-1234 TICKET_ID_EXAMPLE in .env
114
+
115
+ Left unanswered:
116
+ ticketPrefix : no answer yet, and this variable is optional
117
+ ```
118
+
119
+ A script, a pipeline or a coding agent has no terminal to answer on, so it passes the answers in:
120
+
121
+ ```bash
122
+ sous subscription add workflow/task-files --yes \
123
+ --answer taskFileRoot=.sous/tasks \
124
+ --answer ticketIdExample=PROJ-1234
125
+ ```
126
+
127
+ The answers land in `.sous/.env`, which is committed; a definition marked secret goes to the
128
+ gitignored `.sous/.env.local`. [Recipe variables](repositories-variables.md) covers the rest.
129
+
130
+ ## 5. Look at what landed
131
+
132
+ `sous subscription add` finishes by building, so the files are already on disk. `ls .claude/skills`
133
+ now lists the five `core` skills plus the seven this recipe ships, among them `about-task-files`,
134
+ `start-task` and `resume-task`. The build dependency contributes nothing: a recipe held through
135
+ `depends` is fetched and pinned, and its files stay out of your output.
136
+
137
+ Recipe files compile the way your own targets do, under the
138
+ [`.tpl.` convention](configuration.md#templates-and-the-tpl-convention): a file with `.tpl.` in
139
+ its name is rendered through LiquidJS and loses that segment (`SKILL.tpl.md` becomes `SKILL.md`),
140
+ and any other file is copied verbatim. They are tracked like every file sous writes, so
141
+ `sous prune` removes them when you unsubscribe. Where each kind lands is your project's decision,
142
+ under
143
+ [`recipeOutputs`](repositories-file-formats.md#recipeoutputs-where-the-files-land).
144
+
145
+ ## 6. Start a repository of your own
146
+
147
+ A recipe repository is not a sous project; it has no `.sous/` directory, and the three commands
148
+ that work inside one do not look for a config. Run this outside your project:
149
+
150
+ ```bash
151
+ sous repo init ~/projects/my-recipes --name my-recipes --namespace workflow
152
+ ```
153
+
154
+ ```text
155
+ ▶ Creating a recipe repository:
156
+ wrote sous.repo.yaml
157
+ wrote sous.index.json
158
+ wrote recipes/workflow/example/sous.recipe.yaml
159
+ wrote recipes/workflow/example/skills/example-skill/SKILL.md
160
+ wrote README.md
161
+ wrote .github/workflows/sous-release.yml
162
+ wrote .gitignore
163
+ ```
164
+
165
+ The scaffold validates as written, and every file it leaves is heavily commented. `sous.repo.yaml`
166
+ declares the namespaces and lists every recipe folder; each folder's `sous.recipe.yaml` is one
167
+ recipe, with its own version.
168
+
169
+ ## 7. Put something in the recipe
170
+
171
+ Edit `recipes/workflow/example/skills/example-skill/SKILL.md` into a real skill, then name the
172
+ recipe in its manifest:
173
+
174
+ ```yaml
175
+ formatVersion: 1
176
+ namespace: workflow
177
+ name: example
178
+ version: 0.1.0
179
+ description: One paragraph saying what a project gets by subscribing to it.
180
+
181
+ contents:
182
+ - kind: skills
183
+ include:
184
+ - skills/**/*.md
185
+ ```
186
+
187
+ `contents` is what a subscriber receives, one group per kind (`skills`, `memories`, `prompts` or
188
+ `config`), each with globs relative to the recipe folder. Copying the folder is how you start a
189
+ second recipe; its path goes in the `recipes` list at the root.
190
+
191
+ ## 8. Release it
192
+
193
+ Releasing reads versions from the recipe manifests, regenerates the index, commits and tags, so
194
+ the repository has to be a git repository with your work committed:
195
+
196
+ ```bash
197
+ cd ~/projects/my-recipes
198
+ git init -b main . && git add -A && git commit -m "First recipe"
199
+ sous repo release
200
+ ```
201
+
202
+ Releasing commits and tags on your behalf, so `git config user.name` and `git config user.email`
203
+ have to be set in this repository first. Sous then prints the versions it would publish and what
204
+ the run would do, and asks once.
205
+
206
+ ```text
207
+ ...
208
+ ▶ Publishing:
209
+ workflow/example: publishing version 0.1.0.
210
+ Wrote sous.index.json.
211
+ Committed: Release workflow/example@0.1.0
212
+ Created the tag workflow/example@0.1.0.
213
+ ```
214
+
215
+ Add `--push` to push the commit and the tags in the same run, and `--yes` to accept the plan
216
+ without being asked, which is what the scaffolded workflow does. Never edit `sous.index.json` by
217
+ hand; the recipe manifests are the source of truth for versions, and this command regenerates the
218
+ catalog from them. `sous repo release --check` validates without publishing, and runs on a pull
219
+ request.
220
+
221
+ ## 9. Add your repository to the project
222
+
223
+ Adding a repository is how you trust it, so sous asks before recording it. A path works as well as
224
+ a URL; sous reads a path through the built-in `local` provider:
225
+
226
+ ```term
227
+ $ cd ~/projects/my-project
228
+ $ sous repo add ../my-recipes --name my-recipes
229
+ One repository has to be trusted before this can continue.
230
+ my-recipes
231
+ Location: /home/you/projects/my-recipes
232
+ Required: my-recipes (required by this project)
233
+ // then what trusting means, and the question
234
+ ? Do you trust this repository? (y/N) y
235
+ ▶ Adding a repository:
236
+ Repository: my-recipes
237
+ Location : /home/you/projects/my-recipes
238
+ Provider : local
239
+ Namespaces: workflow
240
+ Recipes : 1
241
+ This project now trusts 'my-recipes'. Nothing from it has been installed.
242
+ ```
243
+
244
+ One file is fetched, the repository's `sous.index.json`, and nothing is installed. The entry lands
245
+ in `.sous/conf.d/500-repos.jsonc`, which is committed, so a colleague inherits the repository and
246
+ the trust decision together.
247
+
248
+ !> A repository on your own disk goes through the same question as a hosted one; its recipes still
249
+ run on this machine, and [Trust](repositories.md#trust) says what the decision covers. A local path is machine-specific, so a colleague cloning your project needs
250
+ that path to exist, or needs the repository pushed somewhere they can reach. To edit a repository
251
+ your project already subscribes to, use
252
+ [`sous repo link`](repositories-authoring.md#edit-a-repository-in-place) instead.
253
+
254
+ ## 10. Subscribe to your own recipe
255
+
256
+ ```bash
257
+ sous subscription add workflow/example
258
+ ```
259
+
260
+ ```text
261
+ ➔ What was installed:
262
+ Recipe Version Repository Why
263
+ ---------------- ------- ---------- --------------------
264
+ workflow/example 0.1.0 my-recipes you subscribed to it
265
+ ```
266
+
267
+ Your skill is now in `.claude/skills/` beside the ones from the official repository. Nothing about
268
+ a recipe's files is special once they are on disk, and nothing distinguishes yours from anyone
269
+ else's.
270
+
271
+ ## 11. Commit the lockfile
272
+
273
+ ```bash
274
+ sous subscription list
275
+ ```
276
+
277
+ ```text
278
+ Subscription Range Pinned version Origin Enabled
279
+ ------------------- ----------- ------------------------- -------- -------
280
+ core 0.2.0 core/sous-skills 0.2.0 built in yes
281
+ workflow/example any version workflow/example 0.1.0 user yes
282
+ workflow/task-files any version workflow/task-files 1.0.2 user yes
283
+ ```
284
+
285
+ Everything sous needs to rebuild this project is under `.sous/`, and it is all committed:
286
+
287
+ | File | Holds |
288
+ |------|-------|
289
+ | `.sous/sous.config.yaml` | your project's own config |
290
+ | `.sous/sous.lock.json` | the exact version and content hash of every recipe in use |
291
+ | `.sous/conf.d/500-repos.jsonc` | the repositories the project trusts |
292
+ | `.sous/conf.d/510-subscriptions.jsonc` | what the project subscribes to |
293
+ | `.sous/.env` | the answers to the recipes' questions, minus the secret ones |
294
+ | the rest of `.sous/conf.d/` | the README sous writes there, and the agent pointers beside it |
295
+
296
+ Gitignore compiled output (`.claude/`), the state file `.sous/sous.state.json`, and the secrets
297
+ file `.sous/.env.local`. The store on your machine is not in the project at all.
298
+
299
+ ```bash
300
+ git add .sous && git commit -m "Subscribe to task-files and my own example recipe"
301
+ ```
302
+
303
+ ## 12. Restore on a second machine
304
+
305
+ A clone has the lockfile and the subscriptions and no store at all. `sous build` restores exactly
306
+ what the lockfile pins, asking nothing:
307
+
308
+ ```term
309
+ $ git clone git@github.com:my-team/my-project.git
310
+ >> 100%
311
+ $ cd my-project
312
+ $ sous build
313
+ ▶ Restoring recipes:
314
+ This project's lockfile pins recipes that are not in the store on this machine,
315
+ so they are being fetched at exactly the versions it records.
316
+ restored: workflow/example
317
+ restored: workflow/sub-agent-delegation
318
+ restored: workflow/task-files
319
+ ▶ Building:
320
+ ✓ .claude/skills/about-task-files/SKILL.md (~942 tokens)
321
+ ✓ Done.
322
+ ```
323
+
324
+ Nothing was resolved and nothing was asked, because the lockfile already said what to fetch; see
325
+ [The lockfile](repositories.md#the-lockfile) for why restore decides nothing.
326
+
327
+ ?> The store those recipes were restored into, `$SOUS_HOME/cache`, is machine-wide and disposable;
328
+ everything in it is re-fetchable from a lockfile pin. `SOUS_HOME` moves it, and is what step 1 set
329
+ so this walkthrough keeps its own; unset, the store is `~/.sous/cache`. To undo everything above,
330
+ `rm -rf ~/projects/my-project ~/projects/my-recipes ~/tmp/sous-quickstart`; on a store you mean to
331
+ keep, `sous repo gc` reclaims what no lockfile pins instead.
332
+
333
+ ## Where to go next
334
+
335
+ - [Consuming recipes](repositories-consuming.md): dry runs, one-word refs, removal, `repo gc`
336
+ - [Authoring a repository](repositories-authoring.md): variables, editing in place, contributing
337
+ - [Recipe variables](repositories-variables.md): the resolution ladder and the `sous vars` commands
338
+ - [Repositories](repositories.md): trust, providers, freshness, and what lives where
339
+ - [Command reference](commands.md): every command and flag
340
+ - [Troubleshooting](repositories-troubleshooting.md): what the errors mean and how to clear them