@sous-io/sous 0.2.21 → 0.2.22

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.
Files changed (23) hide show
  1. package/docs/markdown/_sidebar.md +1 -0
  2. package/docs/markdown/repositories-authoring.md +9 -2
  3. package/docs/markdown/repositories-contributing.md +1 -1
  4. package/docs/markdown/repositories-quickstart.md +9 -4
  5. package/package.json +1 -1
  6. package/recipes/core/sous-skills/skills/about-agent-skills/SKILL.tpl.md +87 -19
  7. package/recipes/core/sous-skills/skills/about-agent-skills/examples/{do-something.md → do-something.tpl.md} +1 -3
  8. package/recipes/core/sous-skills/skills/about-agent-skills/references/{commands.md → commands.tpl.md} +4 -0
  9. package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +2 -2
  10. package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md +82 -0
  11. package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +5 -2
  12. package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +2 -2
  13. package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +179 -0
  14. package/recipes/core/sous-skills/skills/about-sous-repos/SKILL.tpl.md +177 -0
  15. package/recipes/core/sous-skills/skills/create-skill/SKILL.tpl.md +6 -4
  16. package/recipes/core/sous-skills/sous.recipe.yaml +2 -2
  17. package/src/lib/repos/scaffold/index.ts +11 -4
  18. package/src/lib/repos/scaffold/templates.ts +107 -5
  19. package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.md +0 -82
  20. /package/recipes/core/sous-skills/skills/about-agent-skills/examples/{about-something.md → about-something.tpl.md} +0 -0
  21. /package/recipes/core/sous-skills/skills/about-agent-skills/references/{advanced-patterns.md → advanced-patterns.tpl.md} +0 -0
  22. /package/recipes/core/sous-skills/skills/about-agent-skills/references/{frontmatter.md → frontmatter.tpl.md} +0 -0
  23. /package/recipes/core/sous-skills/skills/about-agent-skills/references/{substitutions.md → substitutions.tpl.md} +0 -0
@@ -24,3 +24,4 @@
24
24
  - [0003: Project setup with sous init](adrs/0003-project-init.md)
25
25
  - [0004: A global sous defers to the project's install](adrs/0004-project-install-handoff.md)
26
26
  - [0005: Follow-ups from the first project on the hand-off](adrs/0005-first-project-follow-ups.md)
27
+ - [0006: `sous repo init` follows `sous-recipes`](adrs/0006-repo-init-model-home.md)
@@ -17,8 +17,11 @@ $ sous repo init ./my-recipes --name my-recipes --namespace workflow
17
17
  wrote sous.repo.yaml
18
18
  wrote sous.index.json
19
19
  wrote recipes/workflow/example/sous.recipe.yaml
20
- wrote recipes/workflow/example/skills/example-skill/SKILL.md
20
+ wrote recipes/workflow/example/skills/example-skill/SKILL.tpl.md
21
21
  wrote README.md
22
+ wrote CONTRIBUTING.md
23
+ wrote CLAUDE.md
24
+ wrote AGENTS.md
22
25
  wrote .github/workflows/sous-release.yml
23
26
  wrote .gitignore
24
27
  ```
@@ -34,7 +37,11 @@ before releasing, since `sous repo release` reads and writes tags and fails in a
34
37
  commands spell `--yes`, `-y`, `--force` or `--trust`.
35
38
 
36
39
  `sous.repo.yaml` says what the repository publishes, `sous.index.json` is the catalog that `sous repo release`
37
- writes, and each recipe folder carries one `sous.recipe.yaml` beside the files it contributes. Nothing else is
40
+ writes, and each recipe folder carries one `sous.recipe.yaml` beside the files it contributes. `CONTRIBUTING.md`,
41
+ `CLAUDE.md` and `AGENTS.md` are for agents: they say that contribution instructions are written for agents, and
42
+ point an agent at the sous core skills, which explain how a recipe repository works. The scaffold follows
43
+ [`sous-io/sous-recipes`](https://github.com/sous-io/sous-recipes), a complete repository minus its recipes, and
44
+ its placeholder skill is named `SKILL.tpl.md` because it ends with a footer that only a render fills in. Nothing else is
38
45
  fixed: recipe folders may live anywhere, and what makes a folder a recipe is that `sous.repo.yaml` lists its
39
46
  path under `recipes`. Both hand-written manifests are YAML or JSON and never JavaScript, because manifests are
40
47
  data: sous reads, validates and browses them without executing anything, before a repository is trusted, in a
@@ -125,7 +125,7 @@ renaming a variable, or tightening a variable's validation). The changelog `sous
125
125
  recipe your change touches without a version raise, with a warning that a `--ci` release refuses it, so a missed
126
126
  one is visible in the proposal. The repository's
127
127
  own guide is named by the `contribute` field of its `sous.repo.yaml`; for `sous-recipes` it is its
128
- `CONTRIBUTING.md`.
128
+ `CONTRIBUTING.md`, and a repository `sous repo init` created starts with a `CONTRIBUTING.md` of the same kind.
129
129
 
130
130
  **Core is edited in sous itself.** The `core` recipes in `sous-io/sous-recipes` are machine-written copies. Their
131
131
  source is `recipes/core/sous-skills/` in `sous-io/sous`, and each sous release overwrites the copy, so an edit
@@ -44,11 +44,13 @@ $ sous build
44
44
  ✓ .claude/skills/about-sous-configuration/SKILL.md (~1,204 tokens)
45
45
  ✓ .claude/skills/about-agent-skills/SKILL.md (~1,937 tokens)
46
46
  ✓ .claude/skills/about-liquid-templates/SKILL.md (~1,486 tokens)
47
+ ✓ .claude/skills/about-sous-repos/SKILL.md (~1,579 tokens)
48
+ ✓ .claude/skills/about-sous-recipes/SKILL.md (~1,639 tokens)
47
49
  ✓ .claude/skills/create-skill/SKILL.md (~655 tokens)
48
50
  ✓ Done.
49
51
  ```
50
52
 
51
- You subscribed to nothing and five skills appeared. That is the `core` namespace, which every
53
+ You subscribed to nothing and seven skills appeared. That is the `core` namespace, which every
52
54
  project is auto-subscribed to at the version matching the CLI you are running. It carries the
53
55
  skills that teach an agent what sous manages and why it must not hand-edit a generated file.
54
56
 
@@ -133,7 +135,7 @@ gitignored `.sous/.env.local`. [Recipe variables](repositories-variables.md) cov
133
135
  ## 5. Look at what landed
134
136
 
135
137
  `sous subscription add` finishes by building, so the files are already on disk. `ls .claude/skills`
136
- now lists the five `core` skills plus the seven this recipe ships, among them `about-task-files`,
138
+ now lists the seven `core` skills plus the seven this recipe ships, among them `about-task-files`,
137
139
  `start-task` and `resume-task`. The build dependency contributes nothing: a recipe held through
138
140
  `depends` is fetched and pinned, and its files stay out of your output.
139
141
 
@@ -159,8 +161,11 @@ sous repo init ~/projects/my-recipes --name my-recipes --namespace workflow
159
161
  wrote sous.repo.yaml
160
162
  wrote sous.index.json
161
163
  wrote recipes/workflow/example/sous.recipe.yaml
162
- wrote recipes/workflow/example/skills/example-skill/SKILL.md
164
+ wrote recipes/workflow/example/skills/example-skill/SKILL.tpl.md
163
165
  wrote README.md
166
+ wrote CONTRIBUTING.md
167
+ wrote CLAUDE.md
168
+ wrote AGENTS.md
164
169
  wrote .github/workflows/sous-release.yml
165
170
  wrote .gitignore
166
171
  ```
@@ -171,7 +176,7 @@ recipe, with its own version.
171
176
 
172
177
  ## 7. Put something in the recipe
173
178
 
174
- Edit `recipes/workflow/example/skills/example-skill/SKILL.md` into a real skill, then name the
179
+ Edit `recipes/workflow/example/skills/example-skill/SKILL.tpl.md` into a real skill, then name the
175
180
  recipe in its manifest:
176
181
 
177
182
  ```yaml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sous-io/sous",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "Compiles AI coding agent configuration (CLAUDE.md, skills, memories) from LiquidJS templates",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -48,8 +48,10 @@ user-invocable: false
48
48
 
49
49
  **`name`**: becomes the `/slash-command`. Defaults to the directory name if omitted.
50
50
 
51
- **`description`**: tells the agent when to invoke this skill. Use strong trigger
52
- language: open with "YOU MUST load this skill when...". Under 500 chars.
51
+ **`description`**: tells the agent when to invoke this skill. For a skill the agent
52
+ may load on its own, use strong trigger language: open with "YOU MUST load this skill
53
+ when...". A command the user alone can run gets a plain description instead (see
54
+ "Writing a Skill's Text"). Under 500 characters.
53
55
 
54
56
  **`disable-model-invocation`**: set `true` to prevent the agent from invoking the
55
57
  skill automatically. Use this for command skills that represent intentional,
@@ -116,8 +118,9 @@ that inventory things which will evolve (e.g. available skills, current files in
116
118
  directory). Teach the agent where to look rather than providing a snapshot that will go
117
119
  stale. Only document things stable by nature.
118
120
 
119
- **Use strong trigger language.** Descriptions must open with `YOU MUST load this
120
- skill when...`. Cross-references to other skills must use `YOU MUST load`; weak
121
+ **Use strong trigger language where the agent decides.** A skill the agent may load on
122
+ its own MUST open its description with `YOU MUST load this skill when...`; a command the
123
+ user alone can run gets a plain description. Cross-references to other skills must use `YOU MUST load`; weak
121
124
  language like "consult" or "see" is not sufficient. In a skill body, write the
122
125
  requirement so it binds whichever agent executes ("The agent performing this work MUST
123
126
  load `x`"), not just the main session: delegated sub-agents start with fresh context and
@@ -133,29 +136,94 @@ user must return it to the orchestrator, which relays it.
133
136
  **If no action skill exists for a task, ask the user.** Do not improvise an action
134
137
  that warrants its own skill.
135
138
 
139
+ ## Writing a Skill's Text
140
+
141
+ These rules apply to every new skill, memory, partial and reference file. A skill is read by an
142
+ agent that has read nothing else, so it has to be unambiguous on its own.
143
+
144
+ MUST, MUST NOT, SHOULD, SHOULD NOT and MAY carry the meaning RFC 2119 gives them when, and only
145
+ when, they appear in capitals (RFC 8174). Write them in capitals wherever a sentence states a rule;
146
+ a lowercase "must" is ordinary English. NEVER means exactly MUST NOT, and ALWAYS exactly MUST, as
147
+ more emphatic forms.
148
+
149
+ - https://www.rfc-editor.org/rfc/rfc2119
150
+ - https://www.rfc-editor.org/rfc/rfc8174
151
+
152
+ The rules:
153
+
154
+ - **The user and the agent.** Instruction text says "the user" and "the agent", never "me", "I" or
155
+ "you" for either. An imperative ("Load `x` first.") needs no subject. Example output, meaning what
156
+ the agent will say to the user, speaks naturally, "I" and "you" included.
157
+ - **Brief.** A skill SHOULD be brief; move depth into `references/`.
158
+ - **Explain before listing.** Say what a list is before it starts, and define every term it depends
159
+ on. Assume the reader has read nothing else.
160
+ - **Show it applied.** Follow a list of rules with an example of them applied; several when each
161
+ adds real value. A skill that produces something for the user (a report, a list, a question) MUST
162
+ include an example of that output, in a `text` fence.
163
+ - **Description tone follows invocation.** A skill the agent may load on its own opens its
164
+ description with "YOU MUST load this skill when...". A command only the user can run
165
+ (`disable-model-invocation: true`) gets a plain description of what it does; the agent never sees
166
+ it, and the user reads it in the `/` menu.
167
+ - **Arguments are optional.** A command that takes arguments places `$ARGUMENTS` under a label
168
+ saying what they are, and treats them as refining the goal. With none, it takes the broadest
169
+ reasonable reading of roughly the last 10 turns, NEVER asks which of several targets was meant,
170
+ and names what it took in the first line of its reply.
171
+ - **Tiers, never models.** Name a sub-agent's model by tier ("the next tier down from the main
172
+ session"), never by product or provider.
173
+ - **Public by default.** A skill published in a recipe repository is public: its examples use
174
+ fictional people and projects, and it names no real colleague, private project or private URL.
175
+ - **Plain text.** No em-dashes (use semicolons, commas or parentheses), no emojis, and complete
176
+ sentences with no compressed notation.
177
+
178
+ Example of a command's opening under these rules:
179
+
180
+ ```markdown
181
+ ---
182
+ name: walk
183
+ description: Go through what still needs the user's input, or a named topic, one item at a time.
184
+ argument-hint: "[what to walk through]"
185
+ disable-model-invocation: true
186
+ ---
187
+
188
+ # Walk Through It, One Item at a Time
189
+
190
+ What to walk through: $ARGUMENTS
191
+
192
+ If nothing is named above, walk through everything in roughly the last 10 turns that still needs
193
+ the user's input, and name what was gathered in the first line of the reply. The agent MUST NOT
194
+ present the next item until the user says "Next".
195
+ ```
196
+
136
197
  ## Template-Compiled Skills
137
198
 
138
- Every skill distributed from a shared library, whether this library (`sous`) or any other
139
- shared skill library, must use `SKILL.tpl.md`, not `SKILL.md`. This is required because
140
- every distributed skill must end with a `## Source for this Skill` section (see below), and
141
- that section uses a template variable for the source path, which requires LiquidJS rendering.
142
- No exceptions.
199
+ Every skill MUST be named `SKILL.tpl.md`, not `SKILL.md`, because every skill ends with a
200
+ `## Source for this Skill` footer, and the footer's source path is a template variable that only a
201
+ LiquidJS render fills in. No exceptions, even for a skill with no other variables.
202
+
203
+ A skill published in a recipe names its recipe:
204
+
205
+ ```
206
+ ## Source for this Skill
207
+
208
+ This skill comes from the `<namespace>/<recipe>` recipe, installed by sous from a recipe
209
+ repository. It was compiled from a template, so edit the source, never this output file.
210
+
211
+ - Source Path: <resolved source path>
212
+ ```
143
213
 
144
- Every such skill's `SKILL.md` must end with a `## Source for this Skill` section:
214
+ A project's own skill says it belongs to the project:
145
215
 
146
216
  ```
147
217
  ## Source for this Skill
148
218
 
149
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
150
- the output file should not be edited directly.
219
+ This skill belongs to this project and was compiled by sous from a template, so edit the source,
220
+ never this output file.
151
221
 
152
222
  - Source Path: <resolved source path>
153
223
  ```
154
224
 
155
- This tells agents reading the compiled output where the skill originated and that the
156
- file must not be edited directly. Because this footer is required, all shared skills must
157
- use `SKILL.tpl.md` (not `SKILL.md`) so the source path variable can be rendered at
158
- compile time.
225
+ The footer tells an agent reading the compiled copy where the skill came from and where to make a
226
+ change.
159
227
 
160
228
  ## Examples
161
229
 
@@ -171,7 +239,7 @@ compile time.
171
239
 
172
240
  ## Source for this Skill
173
241
 
174
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
175
- the output file should not be edited directly.
242
+ This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
243
+ repository. It was compiled from a template, so edit the source, never this output file.
176
244
 
177
- - Source Path: {{ sousTemplatePath }}
245
+ - Source Path: {{ sousTemplatePath }}
@@ -3,9 +3,7 @@
3
3
  ```markdown
4
4
  ---
5
5
  name: deploy
6
- description: >
7
- YOU MUST use this skill when deploying the application. Do not use for rollbacks;
8
- those follow a different process.
6
+ description: Deploy the application to a target environment.
9
7
  disable-model-invocation: true
10
8
  ---
11
9
 
@@ -23,6 +23,10 @@ Commands often accept arguments (e.g. `/deploy staging`). When they do:
23
23
  be substituted. See `substitutions.md` for full syntax.
24
24
  2. Add an `argument-hint` field to frontmatter; it appears in autocomplete next to the
25
25
  command name.
26
+ 3. A command that takes arguments also works without them. Put `$ARGUMENTS` under a label
27
+ saying what the arguments are. With none, the agent takes the broadest reasonable
28
+ reading of roughly the last 10 turns, never asks which target was meant, and names what
29
+ it took in the first line of its reply.
26
30
 
27
31
  ```yaml
28
32
  ---
@@ -284,7 +284,7 @@ The following variables are in scope at compile time in this project:
284
284
 
285
285
  ## Source for this Skill
286
286
 
287
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
288
- the output file should not be edited directly.
287
+ This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
288
+ repository. It was compiled from a template, so edit the source, never this output file.
289
289
 
290
290
  - Source Path: {{ sousTemplatePath }}
@@ -0,0 +1,82 @@
1
+ # LiquidJS Built-in Filters
2
+
3
+ Standard filters available in all `.tpl.` files. Chained with `|`.
4
+
5
+ ## String
6
+
7
+ | Filter | Description | Example |
8
+ |:-------|:------------|:--------|
9
+ | `upcase` | Uppercase | `{% raw %}{{ "hello" | upcase }}{% endraw %}` → `HELLO` |
10
+ | `downcase` | Lowercase | `{% raw %}{{ "HELLO" | downcase }}{% endraw %}` → `hello` |
11
+ | `capitalize` | First char uppercase | `{% raw %}{{ "hello world" | capitalize }}{% endraw %}` → `Hello world` |
12
+ | `strip` | Remove leading/trailing whitespace | `{% raw %}{{ " hi " | strip }}{% endraw %}` → `hi` |
13
+ | `lstrip` | Remove leading whitespace | |
14
+ | `rstrip` | Remove trailing whitespace | |
15
+ | `strip_newlines` | Remove all newlines | |
16
+ | `strip_html` | Remove HTML tags | |
17
+ | `escape` | HTML-escape `<`, `>`, `&`, `"` | |
18
+ | `url_encode` | Percent-encode a URL string | |
19
+ | `url_decode` | Decode a percent-encoded string | |
20
+ | `append` | Append a string | `{% raw %}{{ "foo" | append: "bar" }}{% endraw %}` → `foobar` |
21
+ | `prepend` | Prepend a string | `{% raw %}{{ "bar" | prepend: "foo" }}{% endraw %}` → `foobar` |
22
+ | `replace` | Replace all occurrences | `{% raw %}{{ "aabbcc" | replace: "b", "x" }}{% endraw %}` → `aaxxcc` |
23
+ | `replace_first` | Replace first occurrence | |
24
+ | `remove` | Remove all occurrences | `{% raw %}{{ "aabbcc" | remove: "b" }}{% endraw %}` → `aacc` |
25
+ | `remove_first` | Remove first occurrence | |
26
+ | `truncate` | Truncate to N chars (adds `...`) | `{% raw %}{{ "hello world" | truncate: 7 }}{% endraw %}` → `hell...` |
27
+ | `truncatewords` | Truncate to N words | `{% raw %}{{ "one two three" | truncatewords: 2 }}{% endraw %}` → `one two...` |
28
+ | `split` | Split string into array | `{% raw %}{{ "a,b,c" | split: "," }}{% endraw %}` → `["a","b","c"]` |
29
+ | `newline_to_br` | Replace `\n` with `<br>` | |
30
+
31
+ ## Array
32
+
33
+ | Filter | Description | Example |
34
+ |:-------|:------------|:--------|
35
+ | `join` | Join array with separator | `{% raw %}{{ arr | join: ", " }}{% endraw %}` |
36
+ | `first` | First element | `{% raw %}{{ arr | first }}{% endraw %}` |
37
+ | `last` | Last element | `{% raw %}{{ arr | last }}{% endraw %}` |
38
+ | `reverse` | Reverse order | |
39
+ | `sort` | Sort ascending (case-sensitive) | |
40
+ | `sort_natural` | Sort ascending (case-insensitive) | |
41
+ | `uniq` | Remove duplicates | |
42
+ | `compact` | Remove nil/falsy values | |
43
+ | `map` | Extract a property from each object | `{% raw %}{{ items | map: "name" }}{% endraw %}` |
44
+ | `where` | Filter objects by property value | `{% raw %}{{ items | where: "active", true }}{% endraw %}` |
45
+ | `concat` | Concatenate two arrays | `{% raw %}{{ arr1 | concat: arr2 }}{% endraw %}` |
46
+ | `slice` | Extract a sub-array | `{% raw %}{{ arr | slice: 1, 3 }}{% endraw %}` |
47
+ | `size` | Length of string or array | `{% raw %}{{ arr | size }}{% endraw %}` |
48
+ | `push` | Append element to array | |
49
+ | `pop` | Remove last element | |
50
+ | `shift` | Remove first element | |
51
+ | `unshift` | Prepend element to array | |
52
+
53
+ ## Number
54
+
55
+ | Filter | Description | Example |
56
+ |:-------|:------------|:--------|
57
+ | `plus` | Add | `{% raw %}{{ 4 | plus: 2 }}{% endraw %}` → `6` |
58
+ | `minus` | Subtract | `{% raw %}{{ 4 | minus: 2 }}{% endraw %}` → `2` |
59
+ | `times` | Multiply | `{% raw %}{{ 4 | times: 2 }}{% endraw %}` → `8` |
60
+ | `divided_by` | Divide | `{% raw %}{{ 10 | divided_by: 2 }}{% endraw %}` → `5` |
61
+ | `modulo` | Modulus | `{% raw %}{{ 10 | modulo: 3 }}{% endraw %}` → `1` |
62
+ | `abs` | Absolute value | `{% raw %}{{ -4 | abs }}{% endraw %}` → `4` |
63
+ | `ceil` | Round up | `{% raw %}{{ 4.1 | ceil }}{% endraw %}` → `5` |
64
+ | `floor` | Round down | `{% raw %}{{ 4.9 | floor }}{% endraw %}` → `4` |
65
+ | `round` | Round to nearest (or N decimal places) | `{% raw %}{{ 4.567 | round: 2 }}{% endraw %}` → `4.57` |
66
+ | `at_least` | Clamp to minimum | `{% raw %}{{ 3 | at_least: 5 }}{% endraw %}` → `5` |
67
+ | `at_most` | Clamp to maximum | `{% raw %}{{ 7 | at_most: 5 }}{% endraw %}` → `5` |
68
+
69
+ ## Other
70
+
71
+ | Filter | Description | Example |
72
+ |:-------|:------------|:--------|
73
+ | `default` | Fallback if nil/empty/false | `{% raw %}{{ val | default: "n/a" }}{% endraw %}` |
74
+ | `date` | Format a date | `{% raw %}{{ "now" | date: "%Y-%m-%d" }}{% endraw %}` |
75
+ | `size` | Length of string or array | `{% raw %}{{ "hello" | size }}{% endraw %}` → `5` |
76
+ | `json` | Serialize to JSON string | `{% raw %}{{ obj | json }}{% endraw %}` |
77
+
78
+ ## Sous Custom Filters
79
+
80
+ | Filter | Description |
81
+ |:-------|:------------|
82
+ | `bulletList` | Convert array to markdown bullet list (`- item` per line) |
@@ -39,6 +39,9 @@ YOU MUST load `about-sous-configuration` when creating or editing the project's
39
39
  config (`sous.config.*`, `conf.d/` layers), defining or debugging config variables, or
40
40
  diagnosing a ConfigError.
41
41
 
42
+ YOU MUST load `about-sous-repos` when working inside a recipe repository (one with a
43
+ `sous.repo.yaml` at its root), and `about-sous-recipes` when creating or changing a recipe.
44
+
42
45
  ## Sous's Shared Recipes
43
46
 
44
47
  The `about-sous`, `about-sous-configuration`, `about-agent-skills` and
@@ -78,7 +81,7 @@ far.
78
81
 
79
82
  ## Source for this Skill
80
83
 
81
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
82
- the output file should not be edited directly.
84
+ This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
85
+ repository. It was compiled from a template, so edit the source, never this output file.
83
86
 
84
87
  - Source Path: {{ sousTemplatePath }}
@@ -69,7 +69,7 @@ prefer the on-disk copies.
69
69
 
70
70
  ## Source for this Skill
71
71
 
72
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
73
- the output file should not be edited directly.
72
+ This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
73
+ repository. It was compiled from a template, so edit the source, never this output file.
74
74
 
75
75
  - Source Path: {{ sousTemplatePath }}
@@ -0,0 +1,179 @@
1
+ ---
2
+ name: about-sous-recipes
3
+ description: >
4
+ YOU MUST load this skill when creating or changing a sous recipe: its sous.recipe.yaml, its
5
+ version, the variables it asks for, the files it publishes, its depends or subscribes lists,
6
+ or a partial or @~ include inside it.
7
+ user-invocable: false
8
+ license: Apache-2.0
9
+ compatibility:
10
+ - claude
11
+ - codex
12
+ ---
13
+
14
+ # About Sous Recipes
15
+
16
+ A **recipe** is a versioned bundle of agent instructions that sous installs into every project
17
+ subscribed to it. It is one folder in a recipe repository, holding a `sous.recipe.yaml` manifest
18
+ beside the files it publishes. For the repository around it (the repo manifest, the index,
19
+ releasing and proposing), YOU MUST load `about-sous-repos`. For writing a skill inside it, YOU MUST
20
+ load `about-agent-skills`.
21
+
22
+ ## The Manifest
23
+
24
+ The manifest is YAML or JSON, never JavaScript, and unknown keys are refused except extension keys
25
+ starting with `x-`. A typical one:
26
+
27
+ ```yaml
28
+ formatVersion: 1
29
+ namespace: review
30
+ name: checklist
31
+ version: 1.2.0
32
+ description: >-
33
+ A pre-merge review checklist, with a command that walks a change through it.
34
+ depends:
35
+ - review/wording
36
+ contents:
37
+ - kind: skills
38
+ include:
39
+ - skills/**/*
40
+ ```
41
+
42
+ The fields in that example:
43
+
44
+ - `formatVersion` is always `1`.
45
+ - `namespace` MUST be declared in the repo manifest, and `name` is unique inside it. Both are
46
+ lowercase kebab-case, and together (`review/checklist`) they are the recipe's identity.
47
+ - `version` is an exact semantic version (see "Raising the Version").
48
+ - `description` is optional; it is copied into the index, and shown by `sous recipe list` and
49
+ `sous repo search`.
50
+ - `contents` is what a subscriber receives: one group per kind (`skills`, `memories`, `prompts` or
51
+ `config`), each with `include` globs and optional `exclude` globs, relative to the recipe folder.
52
+ The subscribing project decides where skills, memories and prompts land. A `config` file becomes
53
+ a config layer in the project, loaded under the project's own config so the project always wins;
54
+ it MUST be `.json`, `.jsonc`, `.yaml` or `.yml`, and every key outside `_aliases`, `_vars`,
55
+ `compilation`, `recipeOutputs`, `runtimeContext`, `store` and `varMappings` is dropped with a
56
+ warning. A recipe that only bundles others may leave `contents` out.
57
+ - `depends`, `subscribes` and `variables` are described below; `submissions` is in
58
+ `about-sous-repos`.
59
+
60
+ A file whose name contains `.tpl.` is rendered through LiquidJS and loses the `.tpl.`; every other
61
+ file is copied as it is. YOU MUST load `about-liquid-templates` before deciding which a file needs.
62
+
63
+ ## Raising the Version
64
+
65
+ Every change to a published recipe raises its version, in the same change. Versions are semantic:
66
+
67
+ - **patch** for wording and fixes;
68
+ - **minor** for new files or a new optional variable;
69
+ - **major** for anything that breaks an existing subscriber: removing or renaming a file, renaming or
70
+ removing a variable, or tightening a variable's validation.
71
+
72
+ The version rises once per release. When several changes to one recipe travel together, it rises
73
+ once, by the largest step any of them needs. A version already raised past the recipe's last tag
74
+ is left alone by every later release run.
75
+
76
+ A contributor proposing a change edits `version` by hand. A maintainer releasing from the
77
+ repository itself may instead commit the change and run
78
+ `sous repo release --recipe <namespace>/<recipe> --bump <level>` (`patch` by default, `minor`,
79
+ `major` or `prerelease`), which raises every changed recipe in scope whose version still equals its
80
+ last tag and commits the raise together with the regenerated index. A proposal MUST NOT carry that
81
+ index change, because `sous repo submit` refuses one that edits `sous.index.json`;
82
+ `about-sous-repos` describes the rest of that run.
83
+
84
+ Example: `review/checklist` is at `1.2.0`, its last tag. One pull request fixes a typo (a patch) and
85
+ adds a command (a minor). The manifest goes to `1.3.0`, once.
86
+
87
+ ## Variables
88
+
89
+ A recipe that needs a value from the project declares a **definition**; the project stores the
90
+ answer in its env files, and a `.tpl.` file renders it by the definition's name.
91
+
92
+ ```yaml
93
+ variables:
94
+ - name: reviewNotesDir
95
+ type: path
96
+ prompt: Where should review notes be kept?
97
+ description: >-
98
+ The checklist command writes one note per branch into this directory. The
99
+ default keeps them inside the project's .sous directory. Any path works,
100
+ relative to the project root or absolute.
101
+ example: docs/review-notes
102
+ default: .sous/review-notes
103
+ scope: shared
104
+ ```
105
+
106
+ The fields in that example, and the ones it leaves out:
107
+
108
+ - `name` is camelCase. `type` is `string`, `number`, `boolean`, `enum`, `path` or `url`; an `enum`
109
+ MUST list its options under `validate.enum`. `required` defaults to true.
110
+ - `description` and `example` are REQUIRED; a manifest missing either is refused. The description
111
+ says, in full sentences, what the setting is for, what the default does, and what else is
112
+ acceptable; the `prompt` is one plain question. An example is only shown beside the question,
113
+ never stored or offered as an answer; a `default` is the answer of last resort. Both MUST fit the
114
+ declared type.
115
+ - Every variable MUST provide a conservative default: the answer that does the least harm when the
116
+ project never chose one. For example, a setting for whether the agent may merge pull requests on
117
+ its own defaults to "only when asked", never to "always".
118
+ - `scope: local` stores the answer in the gitignored `.sous/.env.local`; use it for anything that
119
+ belongs to one person or one machine. `scope: shared`, the default, stores it in the committed
120
+ `.sous/.env`.
121
+ - `secret: true` hides the value wherever sous prints it. A secret MUST also set `scope: local`
122
+ explicitly; a secret left at the default scope is refused.
123
+ - `env` names the environment variable the answer binds to, for example to reuse a `GITHUB_TOKEN`
124
+ the environment already carries. Left out, it is `SOUS_VAR_` plus the name in upper snake case
125
+ (`reviewNotesDir` becomes `SOUS_VAR_REVIEW_NOTES_DIR`). Two definitions of different names MUST
126
+ NOT claim the same environment variable.
127
+ - `validate` takes `pattern`, `minLength`, `maxLength`, `min`, `max` and `enum`. Within a major
128
+ version a definition MAY only loosen; tightening is a major bump, because an upgrade re-checks the
129
+ answers already stored.
130
+
131
+ ## Depends and Subscribes
132
+
133
+ Both lists name other recipes, and they do different things:
134
+
135
+ - `depends` makes the other recipe a **library** for this one. It is fetched and pinned, and its
136
+ files can be included from this recipe's templates, but its files never reach the subscriber's
137
+ output.
138
+ - `subscribes` is a **co-subscription**. The other recipe's files land and its questions are asked,
139
+ as if the project had subscribed to it too.
140
+
141
+ Each entry names its target by where it lives, in one of two forms:
142
+
143
+ - a bare `namespace/recipe` for a sibling in the same repository. With no range it means the
144
+ version released alongside this one; an `@` range (npm's rules) is allowed but uncommon.
145
+ - a locator URL for a recipe in another repository, such as
146
+ `github://example-org/team-recipes/review/wording@^1.0`. The scheme is the provider, and the last
147
+ two segments are always the recipe's namespace and name, never a path on disk.
148
+
149
+ `local://` locators and `repo:` short names are refused: both are one project's private view of a
150
+ repository, and a published manifest is read everywhere.
151
+
152
+ ## Includes and Partials
153
+
154
+ A line holding only `@~<namespace>/<recipe>/<path>.md`, starting at the first column, includes that
155
+ file from a recipe at its pinned version. Inside a recipe it resolves only against the recipe
156
+ itself and the recipes it lists under `depends` or `subscribes`; the path may not be absolute or
157
+ contain `.` or `..` segments, and it MUST end in `.md` (`notes.md` also finds `notes.tpl.md`). A
158
+ line inside a fenced code block is never an include.
159
+
160
+ A **partial** is a short piece of text several skills share, kept in the recipe's `_partials/`
161
+ folder beside `skills/`. As long as no `contents` glob covers `_partials/`, it is never installed on
162
+ its own; it reaches a project only inside the skills that include it:
163
+
164
+ ```markdown
165
+ What to review: $ARGUMENTS
166
+
167
+ @~review/checklist/_partials/review-rules.md
168
+ ```
169
+
170
+ The included text is pasted in before rendering, so any Liquid inside a partial renders only when
171
+ the including file is a `.tpl.` file, and then with that file's variables. A partial SHOULD
172
+ therefore hold no template variables.
173
+
174
+ ## Source for this Skill
175
+
176
+ This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
177
+ repository. It was compiled from a template, so edit the source, never this output file.
178
+
179
+ - Source Path: {{ sousTemplatePath }}
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: about-sous-repos
3
+ description: >
4
+ YOU MUST load this skill when working inside a sous recipe repository (one with a
5
+ sous.repo.yaml at its root), adding a namespace or a recipe to one, running sous repo
6
+ release, sous repo submit, sous repo link, sous repo contribute or sous repo init, or
7
+ deciding what happens to a change when it is proposed, merged or released.
8
+ user-invocable: false
9
+ license: Apache-2.0
10
+ compatibility:
11
+ - claude
12
+ - codex
13
+ metadata:
14
+ version: 1.0.0
15
+ tags: [sous, recipes, repositories]
16
+ ---
17
+
18
+ # About Sous Recipe Repositories
19
+
20
+ A **recipe** is a versioned bundle of agent instructions (skills, memories, prompts and config
21
+ layers) that sous installs into every project subscribed to it. A **recipe repository** is a git
22
+ repository that publishes recipes. It is not a sous project and needs no `.sous/` directory; the
23
+ commands that work on it (`sous repo release`, `sous repo submit`) run from anywhere inside it,
24
+ and find its root by walking up to the first directory holding a repo manifest.
25
+
26
+ Everything here holds for every recipe repository, the official `sous-io/sous-recipes` included.
27
+ For one recipe (its manifest, version, variables, contents and dependencies), YOU MUST load
28
+ `about-sous-recipes`.
29
+
30
+ ## The Files
31
+
32
+ A recipe repository holds three kinds of file that sous reads:
33
+
34
+ - `sous.repo.yaml`, the **repo manifest**, written by hand: the repository's suggested short name,
35
+ an optional description, its namespaces, and the list of recipe folders it publishes. A folder
36
+ is a recipe because this list names it; a new recipe folder MUST be added to the list, or it is
37
+ not published. Every recipe MUST belong to a namespace declared here.
38
+ - `sous.recipe.yaml`, one **recipe manifest** in each recipe folder; see `about-sous-recipes`.
39
+ - `sous.index.json`, the **index**: the catalog projects read, written only by `sous repo release`.
40
+ NEVER edit it by hand.
41
+
42
+ Manifests are YAML (`.yaml`, `.yml`) or JSON (`.json`, `.jsonc`, both allowing comments and
43
+ trailing commas), never JavaScript: sous reads them before anyone has trusted the repository, so
44
+ nothing in them may run. Unknown keys are refused, except extension keys starting with `x-`. A
45
+ recipe folder MUST NOT contain a symbolic link; the release refuses one.
46
+
47
+ ```yaml
48
+ formatVersion: 1
49
+ name: team-recipes
50
+ description: >-
51
+ Review and release recipes for the Harbor Labs engineering team.
52
+ namespaces:
53
+ review:
54
+ description: Recipes about reviewing code.
55
+ recipes:
56
+ - recipes/review/checklist
57
+ contribute: https://github.com/harbor-labs/team-recipes/blob/main/CONTRIBUTING.md
58
+ ```
59
+
60
+ `contribute` is optional: a URL or a sentence saying how changes are sent, printed by
61
+ `sous repo submit` when the repository's host cannot open a proposal for the contributor.
62
+
63
+ ## Versions, Tags and the Index
64
+
65
+ A recipe's version lives in its manifest, which is the source of truth. A version is **published**
66
+ once a git tag shaped `namespace/recipe@version` exists for it (for example
67
+ `review/checklist@1.2.0`). The index records each published version with its content hash, its
68
+ tag and the exact versions of its dependencies at release time; for the length of one release
69
+ commit it also records the version each manifest declares, whose tag that release cuts next. A
70
+ published version never changes: new files need a new version, and a release refuses to publish
71
+ different content under a version already tagged. A lost index is rebuilt from the tags.
72
+
73
+ ## Releasing
74
+
75
+ `sous repo release` publishes. It refuses to run while anything is uncommitted, or while git
76
+ cannot say who is committing. In one run it:
77
+
78
+ 1. validates every manifest;
79
+ 2. finds the recipes whose files changed since the tag that last published them (a recipe never
80
+ tagged is released at the version its manifest declares);
81
+ 3. raises the version of each changed recipe whose version still equals that tag (a patch step by
82
+ default; `--bump minor` or `--bump major` for more);
83
+ 4. regenerates `sous.index.json`;
84
+ 5. commits the manifests and the index together;
85
+ 6. cuts one annotated tag per new version, dependencies first.
86
+
87
+ It prints that plan and asks once. `--yes` answers ahead, `--dry-run` stops after the plan, and
88
+ `--push` pushes the commit and the tags. `--namespace` and `--recipe` narrow the scope, and
89
+ `--include-unchanged` releases unchanged recipes too. On a branch other than the default one it
90
+ raises and commits but cuts no tags, because tags are cut after the merge; `--tag` overrides that.
91
+ Two presets matter most:
92
+
93
+ - `--check` only reads. It validates every manifest, reports how a merge would rewrite the index
94
+ (as information, never as a failure), lists the versions that publish on merge, and fails a
95
+ change to a recipe whose `submissions` block refuses proposals.
96
+ - `--ci` is for automation after a merge. It raises no versions, accepts the plan without asking,
97
+ and fails on any changed recipe whose version still equals its last tag. It does NOT push; pass
98
+ `--push` for that.
99
+
100
+ ## What Happens to a Change
101
+
102
+ A repository made by `sous repo init` carries `.github/workflows/sous-release.yml`, which runs:
103
+
104
+ - on every pull request, `sous repo release --check`;
105
+ - on every push to `main`, `sous repo release --ci --push --yes`, which tags every version that has
106
+ no tag yet, commits the regenerated index when it changed, and pushes both.
107
+
108
+ So a change to a recipe MUST raise that recipe's version in the pull request itself, by editing
109
+ `version` in its `sous.recipe.yaml`: after the merge, `--ci` refuses a recipe whose files changed
110
+ while its version stayed the same. A contributor NEVER runs `sous repo release` on a proposal
111
+ branch (`--check` and `--dry-run` excepted), because it commits a regenerated index and
112
+ `sous repo submit` refuses a change that edits the index.
113
+
114
+ Example: a contributor fixes a typo in `review/checklist`, which is at `1.2.0`, its last tag. The
115
+ pull request changes the skill file and sets `version: 1.2.1`; `--check` passes and lists
116
+ `review/checklist@1.2.1` as publishing on merge; after the merge, `--ci` regenerates the index,
117
+ commits it and cuts the tag `review/checklist@1.2.1`.
118
+
119
+ ## Proposing a Change
120
+
121
+ `sous repo submit` proposes a change to a repository's maintainers and follows the proposal through
122
+ its life: it opens one, pushes new commits to an open one (replacing its title or description when
123
+ new ones are given), reports on one (`--status` only reports), or moves to a new branch once one
124
+ was merged. It runs in the repository's checkout, or from a project that links it (`sous repo
125
+ submit <repo>`). Run on the default branch, it first creates a branch named
126
+ `sous/submit-<date>-<time>`; `--branch` names another.
127
+
128
+ Before sending anything it checks that everything is committed (`--commit` commits it, after
129
+ listing the changes and asking once), validates the repository, and confirms the change leaves
130
+ `sous.index.json` alone. It pushes through a fork on the contributor's own account when the
131
+ contributor cannot push to the repository, and NEVER forces a push. The title and the description
132
+ come from the user (`--title` and `--body`, or asked); sous never borrows a commit message, and
133
+ adds a changelog it builds by comparing the manifests with the default branch.
134
+
135
+ Either manifest can carry a `submissions` block, for recipes whose files are copied in from
136
+ somewhere else; a recipe's own block wins over the repository's:
137
+
138
+ ```yaml
139
+ submissions:
140
+ allowed: false
141
+ instead: Propose changes in harbor-labs/tooling, under recipes/review/.
142
+ ```
143
+
144
+ `submit` warns, prints the `instead` text and asks before going on; `release --check` fails such
145
+ a pull request. The release itself is never restricted.
146
+
147
+ ## Editing From a Project
148
+
149
+ `sous repo link <repo>`, run in a project, points that project at a working copy of the
150
+ repository, cloning it into `.sous/repos/<owner>/<name>` when needed (`--global` uses the
151
+ machine-wide checkout under the user-level sous directory), so an edit shows in the project's next
152
+ build with no release. A path in place of `<repo>` links that checkout where it is. A link never
153
+ changes a checkout on its own: `--branch`, `--create-branch` and `--generate-branch` choose the
154
+ branch, `--from` sets the base of a new one, and `--latest` brings it up to upstream after listing
155
+ anything that would be discarded.
156
+
157
+ `sous repo unlink <repo>` drops the link and goes back to the pinned versions; `--update` also
158
+ moves the pins to the newest released versions their ranges allow, and `--remove` deletes a
159
+ checkout sous cloned. `sous repo contribute <ref>` chains the three commands: it links on a new
160
+ branch (`sous/edit-<date>-<time>`), and with `--finish` it submits whatever no proposal carries
161
+ yet, then unlinks with `--update`.
162
+
163
+ ## Creating a Repository
164
+
165
+ `sous repo init [dir]` writes a new recipe repository into the directory (the current one by
166
+ default): the repo manifest, an empty index, one example recipe with its manifest and a
167
+ placeholder skill, a README, `CONTRIBUTING.md`, `CLAUDE.md` and `AGENTS.md` for agents, the release
168
+ workflow and a `.gitignore`. `sous-io/sous-recipes` is the model a scaffold follows: a new repository
169
+ is as complete as it, minus its recipes. It does not run git: run `git init`, commit, and add a
170
+ remote before the first release, because `sous repo release` reads and writes tags. Every new recipe folder is then listed under `recipes` in `sous.repo.yaml`.
171
+
172
+ ## Source for this Skill
173
+
174
+ This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
175
+ repository. It was compiled from a template, so edit the source, never this output file.
176
+
177
+ - Source Path: {{ sousTemplatePath }}
@@ -74,14 +74,16 @@ variable straight into the output. The footer:
74
74
  ```markdown
75
75
  ## Source for this Skill
76
76
 
77
- This skill was compiled from a template and the output file should not be edited directly.
77
+ This skill belongs to this project and was compiled by sous from a template, so edit the source,
78
+ never this output file.
78
79
 
79
80
  - Source Path: {{ sousTemplatePath }}
80
81
  ```
81
82
  {% endraw %}
82
83
 
83
84
  The `{{ sousTemplatePath }}` variable renders to the absolute path of the source template
84
- at compile time, telling agents where the skill originated.
85
+ at compile time, telling agents where the skill originated. A skill published in a recipe
86
+ names its recipe instead; `about-agent-skills` has that form.
85
87
 
86
88
  For other files in the skill directory (references, scripts, supporting docs), use `.tpl.`
87
89
  naming only when the file genuinely needs LiquidJS processing. YOU MUST load
@@ -107,7 +109,7 @@ load `about-liquid-templates` if any file in your skill needs `.tpl.` processing
107
109
 
108
110
  ## Source for this Skill
109
111
 
110
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
111
- the output file should not be edited directly.
112
+ This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
113
+ repository. It was compiled from a template, so edit the source, never this output file.
112
114
 
113
115
  - Source Path: {{ sousTemplatePath }}
@@ -11,13 +11,13 @@ formatVersion: 1
11
11
 
12
12
  namespace: core
13
13
  name: sous-skills
14
- version: 0.2.21
14
+ version: 0.2.22
15
15
 
16
16
  description: >-
17
17
  The skills that teach an agent what sous is and how it works: which files sous
18
18
  owns and must never be hand-edited, how a sous configuration is written and
19
19
  debugged, the .tpl. template convention and LiquidJS syntax, what an agent
20
- skill is, and how to create one.
20
+ skill is and how to create one, and how a recipe repository and a recipe work.
21
21
 
22
22
  # The copy in sous-io/sous-recipes is overwritten by every sous release, so a
23
23
  # change proposed there would be lost, and a version it tagged would collide
@@ -22,6 +22,9 @@ import { parseRecipeManifest } from "../formats/recipe-manifest.js";
22
22
  import { parseRepoManifest } from "../formats/repo-manifest.js";
23
23
  import { findRepoManifest, loadJsonFile, loadManifestFile } from "../load-manifest.js";
24
24
  import {
25
+ buildAgentsMd,
26
+ buildClaudeMd,
27
+ buildContributing,
25
28
  buildExampleSkill,
26
29
  buildGitignore,
27
30
  buildIndexFile,
@@ -79,9 +82,10 @@ type PlannedFile = {
79
82
  };
80
83
 
81
84
  /**
82
- * Creates a new recipe repository: a repo manifest, one example recipe with a
83
- * placeholder skill, an empty but valid index, a README, release automation and
84
- * a `.gitignore`.
85
+ * Creates a new recipe repository modeled on `sous-io/sous-recipes`: a repo
86
+ * manifest, one example recipe with a placeholder skill, an empty but valid
87
+ * index, a README, a `CONTRIBUTING.md`, a `CLAUDE.md` and an `AGENTS.md` for
88
+ * agents, release automation and a `.gitignore`.
85
89
  *
86
90
  * @param options - What to scaffold, and where.
87
91
  */
@@ -138,10 +142,13 @@ function planFiles(context: ScaffoldContext): PlannedFile[] {
138
142
  contents: buildRecipeManifest(context),
139
143
  },
140
144
  {
141
- relativePath: `${recipeDir}/skills/example-skill/SKILL.md`,
145
+ relativePath: `${recipeDir}/skills/example-skill/SKILL.tpl.md`,
142
146
  contents: buildExampleSkill(context),
143
147
  },
144
148
  { relativePath: "README.md", contents: buildReadme(context) },
149
+ { relativePath: "CONTRIBUTING.md", contents: buildContributing(context) },
150
+ { relativePath: "CLAUDE.md", contents: buildClaudeMd(context) },
151
+ { relativePath: "AGENTS.md", contents: buildAgentsMd() },
145
152
  {
146
153
  relativePath: ".github/workflows/sous-release.yml",
147
154
  contents: buildReleaseWorkflow(),
@@ -59,8 +59,9 @@ description: >-
59
59
  One paragraph saying what this repository publishes and who it is for.
60
60
 
61
61
  # Where to send a change. Shown to anyone whose provider cannot open a proposal
62
- # for them, so a contributor is never left without a route.
63
- # contribute: https://example.com/contributing
62
+ # for them, so a contributor is never left without a route. Point it at this
63
+ # repository's CONTRIBUTING.md once the repository has a public URL.
64
+ # contribute: https://github.com/<owner>/${context.name}/blob/main/CONTRIBUTING.md
64
65
 
65
66
  # Namespaces group recipes. They are not versioned, and a project may subscribe
66
67
  # to a whole namespace, which means every recipe in it, including ones published
@@ -164,7 +165,9 @@ contents:
164
165
  }
165
166
 
166
167
  /**
167
- * The placeholder skill the example recipe contributes.
168
+ * The placeholder skill the example recipe contributes. It is named
169
+ * `SKILL.tpl.md`, as every skill is, because it ends with the footer every
170
+ * skill carries, and the footer's source path is only filled in by a render.
168
171
  *
169
172
  * @param context - The repository being scaffolded.
170
173
  */
@@ -182,14 +185,23 @@ This file is a placeholder written by 'sous repo init'. Replace it with a real
182
185
  skill, or delete it once the ${context.recipe} recipe has content of its own.
183
186
 
184
187
  A skill is ordinary markdown. Everything under this recipe's 'contents' entry is
185
- copied into a subscribing project's agent skill directory, so what you write
186
- here is what an agent reads there.
188
+ compiled into a subscribing project's agent skill directory, so what you write
189
+ here is what an agent reads there. The '.tpl.' in this file's name means it is
190
+ rendered as a LiquidJS template on the way, and the name it is written under
191
+ drops the '.tpl.'.
187
192
 
188
193
  ## What to put here
189
194
 
190
195
  Describe the concept the skill covers, then the rules an agent should follow
191
196
  when it applies. Keep it short enough to be read in full, and point at reference
192
197
  files for anything long.
198
+
199
+ ## Source for this Skill
200
+
201
+ This skill comes from the \`${context.namespace}/${context.recipe}\` recipe, installed by sous from a recipe
202
+ repository. It was compiled from a template, so edit the source, never this output file.
203
+
204
+ - Source Path: {{ sousTemplatePath }}
193
205
  `;
194
206
  }
195
207
 
@@ -292,6 +304,12 @@ To propose a change to a repository you do not maintain, commit it and run
292
304
  \`sous repo submit\`, which validates everything first and then opens a pull
293
305
  request through your provider's own command line tool.
294
306
 
307
+ ## Contributing
308
+
309
+ Improvements are welcome, including new recipes. Contribution instructions in this repository are
310
+ written for agents: \`CONTRIBUTING.md\` says why, and \`CLAUDE.md\` is where an agent without the sous
311
+ core skills starts.
312
+
295
313
  ## Using it
296
314
 
297
315
  In any project that has a sous config:
@@ -316,9 +334,93 @@ sous repo link ${context.name} /path/to/this/checkout
316
334
 
317
335
  Builds say loudly when a repository is linked. Run \`sous repo unlink ${context.name}\`
318
336
  to go back to published versions.
337
+
338
+ ## License
339
+
340
+ This repository has no license yet. Until it has one, others may read it but not reuse it; add a
341
+ \`LICENSE\` file before publishing.
342
+ `;
343
+ }
344
+
345
+ /**
346
+ * `CONTRIBUTING.md`: why this repository publishes no contribution
347
+ * instructions for people, and where an agent starts instead. It is
348
+ * `sous-io/sous-recipes`' own `CONTRIBUTING.md`, word for word apart from the
349
+ * name, because that repository is the model every scaffold follows.
350
+ *
351
+ * @param context - The repository being scaffolded.
352
+ */
353
+ export function buildContributing(context: ScaffoldContext): string {
354
+ return `# Contributing to ${context.name}
355
+
356
+ Sous is built for agentic coding, so this repository does not publish contribution instructions for
357
+ people. Everything here is written for agents to read.
358
+
359
+ Any agent working in this repository is assumed to have, at minimum, the sous core skills, such as
360
+ \`about-sous\`, \`about-agent-skills\`, \`about-sous-repos\` and \`about-sous-recipes\`. They explain
361
+ everything an agent needs to know to contribute: how a recipe is written, how its version is raised,
362
+ and how a change is checked, proposed and released.
363
+
364
+ An agent without those skills starts at \`CLAUDE.md\`, at the root of this repository.
365
+ `;
366
+ }
367
+
368
+ /** Where the sous core skills can be read online, at their latest published version. */
369
+ const CORE_SKILLS_URL =
370
+ "https://github.com/sous-io/sous-recipes/tree/main/recipes/core/sous-skills/skills";
371
+
372
+ /** The official recipe repository every scaffold is modeled on. */
373
+ const MODEL_REPOSITORY_URL = "https://github.com/sous-io/sous-recipes";
374
+
375
+ /**
376
+ * The root `CLAUDE.md`: the entry point for an agent working in the repository.
377
+ * It points at the sous core skills rather than restating them, because they
378
+ * are the source of truth for how a recipe repository works. It follows
379
+ * `sous-io/sous-recipes`' own `CLAUDE.md`, minus what is only true there.
380
+ *
381
+ * @param context - The repository being scaffolded.
382
+ */
383
+ export function buildClaudeMd(context: ScaffoldContext): string {
384
+ return `# ${context.name}
385
+
386
+ This is a sous recipe repository. It publishes recipes: versioned bundles of agent skills, memories
387
+ and partials that sous installs into the projects that subscribe to them.
388
+
389
+ ## Recipe Repositories Are Part of Sous
390
+
391
+ Writing a recipe, raising its version, checking a change, proposing it and releasing it all work
392
+ here exactly as they do in any other sous recipe repository. How a recipe repository works is part
393
+ of sous itself, so the sous core skills teach it, and they are the official source of truth for it.
394
+ This file does not repeat them. The agent MUST read these before changing anything here:
395
+
396
+ - \`about-sous-repos\`: a recipe repository as a whole; the repo manifest, namespaces, the index,
397
+ release tags, what happens to a pull request and a merge, and proposing a change.
398
+ - \`about-sous-recipes\`: one recipe; its manifest, how its version is raised, its variables, its
399
+ contents, its dependencies and its partials.
400
+ - \`about-agent-skills\`: how a skill is written, including the rules every new skill follows.
401
+
402
+ An agent in a project that uses sous already has these skills. An agent that cannot install sous or
403
+ use it reads them directly, as plain markdown files, online, at their latest published version:
404
+
405
+ - ${CORE_SKILLS_URL}
406
+
407
+ ## Where This Repository Came From
408
+
409
+ \`sous repo init\` created this repository, modeled on \`sous-io/sous-recipes\`, the official recipe
410
+ repository. Its files show a complete repository in use:
411
+
412
+ - ${MODEL_REPOSITORY_URL}
319
413
  `;
320
414
  }
321
415
 
416
+ /**
417
+ * The root `AGENTS.md`: one line sending an agent that reads `AGENTS.md`
418
+ * rather than `CLAUDE.md` to the same instructions.
419
+ */
420
+ export function buildAgentsMd(): string {
421
+ return "Read `CLAUDE.md` in this directory; it holds the instructions for agents working in this\nrepository.\n";
422
+ }
423
+
322
424
  /** The release workflow: validate every pull request, publish on merge. */
323
425
  export function buildReleaseWorkflow(): string {
324
426
  return `# Release automation for this sous recipe repository.
@@ -1,82 +0,0 @@
1
- # LiquidJS Built-in Filters
2
-
3
- Standard filters available in all `.tpl.` files. Chained with `|`.
4
-
5
- ## String
6
-
7
- | Filter | Description | Example |
8
- |:-------|:------------|:--------|
9
- | `upcase` | Uppercase | `{{ "hello" | upcase }}` → `HELLO` |
10
- | `downcase` | Lowercase | `{{ "HELLO" | downcase }}` → `hello` |
11
- | `capitalize` | First char uppercase | `{{ "hello world" | capitalize }}` → `Hello world` |
12
- | `strip` | Remove leading/trailing whitespace | `{{ " hi " | strip }}` → `hi` |
13
- | `lstrip` | Remove leading whitespace | |
14
- | `rstrip` | Remove trailing whitespace | |
15
- | `strip_newlines` | Remove all newlines | |
16
- | `strip_html` | Remove HTML tags | |
17
- | `escape` | HTML-escape `<`, `>`, `&`, `"` | |
18
- | `url_encode` | Percent-encode a URL string | |
19
- | `url_decode` | Decode a percent-encoded string | |
20
- | `append` | Append a string | `{{ "foo" | append: "bar" }}` → `foobar` |
21
- | `prepend` | Prepend a string | `{{ "bar" | prepend: "foo" }}` → `foobar` |
22
- | `replace` | Replace all occurrences | `{{ "aabbcc" | replace: "b", "x" }}` → `aaxxcc` |
23
- | `replace_first` | Replace first occurrence | |
24
- | `remove` | Remove all occurrences | `{{ "aabbcc" | remove: "b" }}` → `aacc` |
25
- | `remove_first` | Remove first occurrence | |
26
- | `truncate` | Truncate to N chars (adds `...`) | `{{ "hello world" | truncate: 7 }}` → `hell...` |
27
- | `truncatewords` | Truncate to N words | `{{ "one two three" | truncatewords: 2 }}` → `one two...` |
28
- | `split` | Split string into array | `{{ "a,b,c" | split: "," }}` → `["a","b","c"]` |
29
- | `newline_to_br` | Replace `\n` with `<br>` | |
30
-
31
- ## Array
32
-
33
- | Filter | Description | Example |
34
- |:-------|:------------|:--------|
35
- | `join` | Join array with separator | `{{ arr | join: ", " }}` |
36
- | `first` | First element | `{{ arr | first }}` |
37
- | `last` | Last element | `{{ arr | last }}` |
38
- | `reverse` | Reverse order | |
39
- | `sort` | Sort ascending (case-sensitive) | |
40
- | `sort_natural` | Sort ascending (case-insensitive) | |
41
- | `uniq` | Remove duplicates | |
42
- | `compact` | Remove nil/falsy values | |
43
- | `map` | Extract a property from each object | `{{ items | map: "name" }}` |
44
- | `where` | Filter objects by property value | `{{ items | where: "active", true }}` |
45
- | `concat` | Concatenate two arrays | `{{ arr1 | concat: arr2 }}` |
46
- | `slice` | Extract a sub-array | `{{ arr | slice: 1, 3 }}` |
47
- | `size` | Length of string or array | `{{ arr | size }}` |
48
- | `push` | Append element to array | |
49
- | `pop` | Remove last element | |
50
- | `shift` | Remove first element | |
51
- | `unshift` | Prepend element to array | |
52
-
53
- ## Number
54
-
55
- | Filter | Description | Example |
56
- |:-------|:------------|:--------|
57
- | `plus` | Add | `{{ 4 | plus: 2 }}` → `6` |
58
- | `minus` | Subtract | `{{ 4 | minus: 2 }}` → `2` |
59
- | `times` | Multiply | `{{ 4 | times: 2 }}` → `8` |
60
- | `divided_by` | Divide | `{{ 10 | divided_by: 2 }}` → `5` |
61
- | `modulo` | Modulus | `{{ 10 | modulo: 3 }}` → `1` |
62
- | `abs` | Absolute value | `{{ -4 | abs }}` → `4` |
63
- | `ceil` | Round up | `{{ 4.1 | ceil }}` → `5` |
64
- | `floor` | Round down | `{{ 4.9 | floor }}` → `4` |
65
- | `round` | Round to nearest (or N decimal places) | `{{ 4.567 | round: 2 }}` → `4.57` |
66
- | `at_least` | Clamp to minimum | `{{ 3 | at_least: 5 }}` → `5` |
67
- | `at_most` | Clamp to maximum | `{{ 7 | at_most: 5 }}` → `5` |
68
-
69
- ## Other
70
-
71
- | Filter | Description | Example |
72
- |:-------|:------------|:--------|
73
- | `default` | Fallback if nil/empty/false | `{{ val | default: "n/a" }}` |
74
- | `date` | Format a date | `{{ "now" | date: "%Y-%m-%d" }}` |
75
- | `size` | Length of string or array | `{{ "hello" | size }}` → `5` |
76
- | `json` | Serialize to JSON string | `{{ obj | json }}` |
77
-
78
- ## Sous Custom Filters
79
-
80
- | Filter | Description |
81
- |:-------|:------------|
82
- | `bulletList` | Convert array to markdown bullet list (`- item` per line) |