@sous-io/sous 0.2.21 → 0.2.23
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.
- package/docs/markdown/_sidebar.md +1 -0
- package/docs/markdown/repositories-authoring.md +9 -2
- package/docs/markdown/repositories-contributing.md +1 -1
- package/docs/markdown/repositories-quickstart.md +9 -4
- package/package.json +1 -1
- package/recipes/core/sous-skills/skills/about-agent-skills/SKILL.tpl.md +87 -19
- package/recipes/core/sous-skills/skills/about-agent-skills/examples/{do-something.md → do-something.tpl.md} +1 -3
- package/recipes/core/sous-skills/skills/about-agent-skills/references/{commands.md → commands.tpl.md} +4 -0
- package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +2 -2
- package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md +82 -0
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +5 -2
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +2 -2
- package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +182 -0
- package/recipes/core/sous-skills/skills/about-sous-repos/SKILL.tpl.md +177 -0
- package/recipes/core/sous-skills/skills/create-skill/SKILL.tpl.md +6 -4
- package/recipes/core/sous-skills/sous.recipe.yaml +2 -2
- package/src/lib/repos/scaffold/index.ts +11 -4
- package/src/lib/repos/scaffold/templates.ts +107 -5
- package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.md +0 -82
- /package/recipes/core/sous-skills/skills/about-agent-skills/examples/{about-something.md → about-something.tpl.md} +0 -0
- /package/recipes/core/sous-skills/skills/about-agent-skills/references/{advanced-patterns.md → advanced-patterns.tpl.md} +0 -0
- /package/recipes/core/sous-skills/skills/about-agent-skills/references/{frontmatter.md → frontmatter.tpl.md} +0 -0
- /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.
|
|
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
|
|
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
|
|
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
|
@@ -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.
|
|
52
|
-
language: open with "YOU MUST load this skill
|
|
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.**
|
|
120
|
-
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
|
150
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
|
175
|
-
|
|
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
|
|
288
|
-
|
|
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 }}
|
package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md
ADDED
|
@@ -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
|
|
82
|
-
|
|
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
|
|
73
|
-
|
|
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,182 @@
|
|
|
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. Every compiled source in
|
|
172
|
+
the official sous repositories is named `*.tpl.md`, partials included, so a partial MAY use template
|
|
173
|
+
variables: they render in every file that includes it. For example, a partial holding
|
|
174
|
+
`{% raw %}Task files live in {{ taskFileRoot }}.{% endraw %}` renders with the project's answer wherever it is included.
|
|
175
|
+
Name a new partial `*.tpl.md` too.
|
|
176
|
+
|
|
177
|
+
## Source for this Skill
|
|
178
|
+
|
|
179
|
+
This skill comes from the `core/sous-skills` recipe, installed by sous from a recipe
|
|
180
|
+
repository. It was compiled from a template, so edit the source, never this output file.
|
|
181
|
+
|
|
182
|
+
- 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
|
|
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
|
|
111
|
-
|
|
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.
|
|
14
|
+
version: 0.2.23
|
|
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
|
|
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
|
|
83
|
-
* placeholder skill, an empty but valid
|
|
84
|
-
* a
|
|
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
|
-
#
|
|
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
|
-
|
|
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) |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|