@sous-io/sous 0.2.31 → 0.2.33
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/README.md +7 -6
- package/docs/markdown/_sidebar.md +2 -0
- package/docs/markdown/commands.md +8 -4
- package/docs/markdown/configuration.md +28 -4
- package/docs/markdown/repositories-authoring.md +22 -2
- package/docs/markdown/repositories-consuming.md +40 -11
- package/docs/markdown/repositories-file-formats.md +28 -13
- package/docs/markdown/repositories-quickstart.md +1 -1
- package/docs/markdown/repositories.md +14 -9
- package/package.json +2 -1
- package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +32 -17
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +8 -2
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +4 -0
- package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +29 -9
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/sous.config.schema.json +27 -15
- package/src/commands/lock/rebuild.ts +3 -6
- package/src/commands/namespace/show.ts +2 -2
- package/src/commands/recipe/show.ts +26 -9
- package/src/commands/repo/contribute.ts +32 -23
- package/src/commands/repo/gc.ts +1 -1
- package/src/commands/repo/remove.ts +1 -1
- package/src/commands/repo/search.ts +1 -1
- package/src/commands/vars/ask.ts +75 -54
- package/src/lib/build-service.ts +85 -11
- package/src/lib/config-schema.ts +59 -19
- package/src/lib/include-resolver.ts +307 -69
- package/src/lib/markdown-compiler.ts +273 -81
- package/src/lib/project-scaffold/templates.ts +19 -11
- package/src/lib/repos/catalog-inputs.ts +1 -1
- package/src/lib/repos/catalog.ts +98 -72
- package/src/lib/repos/declarations.ts +22 -12
- package/src/lib/repos/formats/index-file.ts +1 -1
- package/src/lib/repos/formats/recipe-manifest.ts +3 -4
- package/src/lib/repos/lock-service.ts +1 -1
- package/src/lib/repos/locked-namespace-resolver.ts +14 -6
- package/src/lib/repos/locked-recipe-lookup.ts +123 -0
- package/src/lib/repos/locked-recipes.ts +1 -1
- package/src/lib/repos/memories-hash-name.ts +33 -0
- package/src/lib/repos/namespace-resolver.ts +198 -119
- package/src/lib/repos/recipe-config-layers.ts +29 -1
- package/src/lib/repos/recipe-key-matcher.ts +62 -0
- package/src/lib/repos/recipe-memories.ts +175 -0
- package/src/lib/repos/recipe-targets.ts +27 -28
- package/src/lib/repos/ref-search.ts +1 -1
- package/src/lib/repos/release/index-builder.ts +23 -15
- package/src/lib/repos/release/plan.ts +14 -7
- package/src/lib/repos/release/settle.ts +38 -121
- package/src/lib/repos/release/submit-checkout.ts +42 -23
- package/src/lib/repos/release/tags.ts +1 -1
- package/src/lib/repos/resolver.ts +90 -72
- package/src/lib/repos/subscription-service.ts +192 -170
- package/src/lib/settings.ts +37 -33
- package/src/lib/vars/ask.ts +3 -3
- package/src/lib/vars/mappings.ts +17 -9
- package/src/services/ref-resolver/container.ts +107 -0
- package/src/services/ref-resolver/dependency.ts +60 -0
- package/src/services/ref-resolver/format.ts +197 -0
- package/src/services/ref-resolver/glob.ts +115 -0
- package/src/services/ref-resolver/hash-names.ts +155 -0
- package/src/services/ref-resolver/index.ts +55 -0
- package/src/services/ref-resolver/injectable.ts +36 -0
- package/src/services/ref-resolver/location.ts +62 -0
- package/src/services/ref-resolver/lookups/cached-index-lookup.ts +53 -0
- package/src/services/ref-resolver/lookups/catalog-lookup.ts +23 -0
- package/src/services/ref-resolver/lookups/catalog-matcher.ts +261 -0
- package/src/services/ref-resolver/lookups/chained-lookup.ts +27 -0
- package/src/services/ref-resolver/lookups/env-var-lookup.ts +65 -0
- package/src/services/ref-resolver/lookups/fetched-index-lookup.ts +150 -0
- package/src/services/ref-resolver/lookups/recorded-dependency-lookup.ts +62 -0
- package/src/services/ref-resolver/lookups/ref-lookup.ts +54 -0
- package/src/services/ref-resolver/lookups/variable-lookup.ts +123 -0
- package/src/services/ref-resolver/named-refs.ts +144 -0
- package/src/services/ref-resolver/parser/env-var-splitter.ts +27 -0
- package/src/services/ref-resolver/parser/location-splitter.ts +239 -0
- package/src/services/ref-resolver/parser/name-path-splitter.ts +200 -0
- package/src/services/ref-resolver/parser/partial-ref.ts +84 -0
- package/src/services/ref-resolver/parser/query-splitter.ts +61 -0
- package/src/services/ref-resolver/parser/range-splitter.ts +52 -0
- package/src/services/ref-resolver/parser/ref-parser.ts +115 -0
- package/src/services/ref-resolver/parser/repo-qualifier-splitter.ts +51 -0
- package/src/services/ref-resolver/parts.ts +115 -0
- package/src/services/ref-resolver/pruners/command-line-pruner.ts +47 -0
- package/src/services/ref-resolver/pruners/include-pruner.ts +66 -0
- package/src/services/ref-resolver/pruners/manifest-pruner.ts +130 -0
- package/src/services/ref-resolver/pruners/ref-pruner.ts +135 -0
- package/src/services/ref-resolver/pruners/rule-helpers.ts +44 -0
- package/src/services/ref-resolver/pruners/stored-key-pruner.ts +136 -0
- package/src/services/ref-resolver/ref-helpers.ts +62 -0
- package/src/services/ref-resolver/ref-picker-service.ts +204 -0
- package/src/{lib/repos/reference-report.ts → services/ref-resolver/ref-report.ts} +2 -40
- package/src/services/ref-resolver/ref-resolve-arguments.ts +47 -0
- package/src/services/ref-resolver/ref-resolve-result.ts +36 -0
- package/src/services/ref-resolver/ref-resolver-service.ts +323 -0
- package/src/services/ref-resolver/source.ts +48 -0
- package/src/services/ref-resolver/tokens.ts +25 -0
- package/src/services/ref-resolver/types.ts +155 -0
- package/src/templating/init-liquid-engine.ts +25 -18
- package/src/lib/refs/find.ts +0 -770
- package/src/lib/refs/index.ts +0 -14
- package/src/lib/refs/parse.ts +0 -844
- package/src/lib/refs/pick.ts +0 -147
- package/src/lib/refs/scopes.ts +0 -95
- package/src/lib/refs/settle.ts +0 -219
package/README.md
CHANGED
|
@@ -70,8 +70,8 @@ sous init
|
|
|
70
70
|
|
|
71
71
|
`sous init` writes the project's `.sous/` directory and runs the first build. It creates a
|
|
72
72
|
commented `sous.config.js` (pass `--format json` for a JSON config bound to the shipped
|
|
73
|
-
schema), a starter prompt at `.sous/memories/AGENTS.md` that the config compiles to `AGENTS.md`
|
|
74
|
-
at the project root, the two env files described below, and a `.sous/.gitignore` covering the
|
|
73
|
+
schema), a starter prompt at `.sous/memories/AGENTS.tpl.md` that the config compiles to `AGENTS.md`
|
|
74
|
+
at the project root (it includes every memory your subscribed recipes publish, with one line, `@#memories/**/*.md`), the two env files described below, and a `.sous/.gitignore` covering the
|
|
75
75
|
files sous keeps local to one machine. A project with a `package.json` also gains `@sous-io/sous`
|
|
76
76
|
as a devDependency at the running version, so the project pins the sous it builds with; run
|
|
77
77
|
your package manager's install afterwards. The first build compiles the starter prompt and the
|
|
@@ -87,12 +87,12 @@ export const config = {
|
|
|
87
87
|
compilation: {
|
|
88
88
|
targets: [
|
|
89
89
|
{
|
|
90
|
-
entryPoint: "${sousDir}/memories/AGENTS.md",
|
|
90
|
+
entryPoint: "${sousDir}/memories/AGENTS.tpl.md",
|
|
91
91
|
outputs: [{ destinationFile: "${projectRoot}/AGENTS.md" }],
|
|
92
92
|
},
|
|
93
93
|
],
|
|
94
94
|
},
|
|
95
|
-
|
|
95
|
+
recipes: { skills: { outputs: ["${projectRoot}/.claude/skills"] } },
|
|
96
96
|
};
|
|
97
97
|
```
|
|
98
98
|
|
|
@@ -179,8 +179,9 @@ A recipe's files are reachable without knowing where anything is installed. An `
|
|
|
179
179
|
path may name a recipe by its namespace, so
|
|
180
180
|
`@~workflow/task-files/_partials/resume-task.md` composes a block published by the recipe
|
|
181
181
|
`workflow/task-files`, at the version your project has pinned, into your own instruction
|
|
182
|
-
file.
|
|
183
|
-
can define your own aliases with an `_aliases` block.
|
|
182
|
+
file. `@#project/...` names your project's root, `@~/...` names your home directory, and you
|
|
183
|
+
can define your own aliases with an `_aliases` block. A path may be a glob, and
|
|
184
|
+
`@#memories/**/*.md` includes the memories of every recipe your project subscribes to. Either spelling of a file finds it:
|
|
184
185
|
`@shared.md` also finds `shared.tpl.md`, and the other way round.
|
|
185
186
|
|
|
186
187
|
Sous records every file and directory it writes in a state file, `.sous/sous.state.json` by
|
|
@@ -30,3 +30,5 @@
|
|
|
30
30
|
- [0009: One whole build, everywhere](adrs/0009-whole-build-everywhere.md)
|
|
31
31
|
- [0010: The index describes each version's dependencies and questions](adrs/0010-index-describes-recipes.md)
|
|
32
32
|
- [0011: `sous update` updates sous through the package managers](adrs/0011-self-update.md)
|
|
33
|
+
- [0012: Recipes do not install Sous plugins](adrs/0012-recipes-and-plugins.md)
|
|
34
|
+
- [0013: One ref resolver, and recipe memories through a `#memories` view](adrs/0013-one-ref-resolver.md)
|
|
@@ -67,8 +67,8 @@ Every topic answers to both spellings of its name: `repo` and `repos`, `subscrip
|
|
|
67
67
|
### `sous init [DIRECTORY]`
|
|
68
68
|
Sets a project up for sous: writes its `.sous/` directory, then runs the first build. It is the one command that
|
|
69
69
|
runs before a config exists, and the starting point for a project that has never used sous. It writes a commented
|
|
70
|
-
primary config, a starter prompt at `.sous/memories/AGENTS.md` that the config compiles to `AGENTS.md` at the
|
|
71
|
-
project root, `.sous/.env` and `.sous/.env.local.example`, and the sous-managed block in `.sous/.gitignore`. A
|
|
70
|
+
primary config, a starter prompt at `.sous/memories/AGENTS.tpl.md` that the config compiles to `AGENTS.md` at the
|
|
71
|
+
project root (it holds the one line, `@#memories/**/*.md`, that includes every memory your subscribed recipes publish), `.sous/.env` and `.sous/.env.local.example`, and the sous-managed block in `.sous/.gitignore`. A
|
|
72
72
|
project that has a `package.json` also gains `@sous-io/sous` in its `devDependencies`, at exactly the running
|
|
73
73
|
version, unless it already depends on it; nothing is installed, so run your package manager's install afterwards.
|
|
74
74
|
The first build compiles the starter prompt and the `core` skills, and pins them in `.sous/sous.lock.json`.
|
|
@@ -103,10 +103,14 @@ repositories that ask for newer versions. Every command that builds (`init`, `bu
|
|
|
103
103
|
`repo remove`, `repo unlink`, `subscription add`, `subscription remove`, `subscription update`) runs this same
|
|
104
104
|
build, so each one prints the same things.
|
|
105
105
|
|
|
106
|
-
A compile error (a missing or circular include, a
|
|
107
|
-
the build. Every target is still compiled and every error is listed, then the build exits `1`. An output whose
|
|
106
|
+
A compile error (a missing or circular include, a line that looks like an include but cannot be read as one, a
|
|
107
|
+
template that fails to render, a file sous cannot read) fails the build. Every target is still compiled and every error is listed, then the build exits `1`. An output whose
|
|
108
108
|
target had an error is not written, so the copy from the last good build stays in place.
|
|
109
109
|
|
|
110
|
+
A build also warns about every memory an active recipe publishes that no output includes, naming the line to add
|
|
111
|
+
(`@#memories/**/*.md`) and the `recipes.memories.exclude` opt-out; see
|
|
112
|
+
[Recipe memories](repositories-consuming.md#recipe-memories).
|
|
113
|
+
|
|
110
114
|
A build also lists each recipe this project uses that has a newer version within the range
|
|
111
115
|
declared for it, beside the version pinned. It moves no pin; only always-pull moves one. The build reads upstream
|
|
112
116
|
for this at most once per freshness window (`store.freshnessSeconds`, five minutes by default), gives a
|
|
@@ -47,10 +47,34 @@ are resolved at different stages and never mix.
|
|
|
47
47
|
|
|
48
48
|
## Templates and the `.tpl.` convention
|
|
49
49
|
|
|
50
|
-
A file's name decides whether sous renders it. A file with `.tpl.` in its name is rendered
|
|
51
|
-
LiquidJS and lands with that segment stripped, so `SKILL.tpl.md` compiles to `SKILL.md`; a file
|
|
52
|
-
without it is
|
|
53
|
-
|
|
50
|
+
A file's own name decides whether sous renders it, every time. A file with `.tpl.` in its name is rendered
|
|
51
|
+
through LiquidJS and lands with that segment stripped, so `SKILL.tpl.md` compiles to `SKILL.md`; a file
|
|
52
|
+
without it is never rendered, however much it looks like a template, even when a template includes it. A
|
|
53
|
+
`.tpl.` file included from a plain file still renders, by its own name, before its text is placed. The
|
|
54
|
+
convention applies to `entryPoint` and `entryGlob` targets alike, and to the files a recipe brings into your
|
|
55
|
+
project.
|
|
56
|
+
|
|
57
|
+
## Include syntax
|
|
58
|
+
|
|
59
|
+
A line holding only `@path` includes that file's content. A path may be:
|
|
60
|
+
|
|
61
|
+
- relative to the including file (`@sections/context.md`);
|
|
62
|
+
- `${var}` substituted (`@${projectRoot}/prompts/x.md`);
|
|
63
|
+
- home-relative (`@~/notes/private.md`);
|
|
64
|
+
- an alias from `_aliases`, by its first segment (`@myAlias/doc.md`);
|
|
65
|
+
- a name sous or a plugin registers, written with the `#` sigil: `#project` is the project root
|
|
66
|
+
(`@#project/prompts/intro.md`);
|
|
67
|
+
- a recipe file, written with the `~` sigil and the recipe's namespace (`@~workflow/task-files/_partials/resume.md`);
|
|
68
|
+
- a view: `@#memories/**/*.md` includes every active recipe's memories (see
|
|
69
|
+
[Recipe memories](repositories-consuming.md#recipe-memories)).
|
|
70
|
+
|
|
71
|
+
A path may use glob syntax (`*`, `**`, `?`, `[..]`, `{a,b}`) and then includes every file it matches, in bytewise
|
|
72
|
+
path order. The same file may be included in more than one place; a circular include is an error, as is a line
|
|
73
|
+
that looks like an include (`@docs/notes.txt`, `@~workflow/missing`) but cannot be resolved, which fails the
|
|
74
|
+
build naming the file and the line. A line inside a fenced code block is left as written. An alias name may not
|
|
75
|
+
start with `~` or `#`. Reads of recipe files go through the
|
|
76
|
+
[ref resolver](adrs/0013-one-ref-resolver.md); [Including recipe files in your own templates](repositories.md#including-recipe-files-in-your-own-templates)
|
|
77
|
+
has the namespace rules.
|
|
54
78
|
|
|
55
79
|
## Composition
|
|
56
80
|
|
|
@@ -68,11 +68,31 @@ contents:
|
|
|
68
68
|
|
|
69
69
|
`contents` groups are what a subscribing project receives, one group per kind, each with `include` glob patterns
|
|
70
70
|
relative to the recipe folder (and optional `exclude` patterns). The four kinds are `skills`, `memories`,
|
|
71
|
-
`prompts` and `config`; the kind decides
|
|
72
|
-
[`
|
|
71
|
+
`prompts` and `config`; the kind decides what happens to them in the subscriber's project, which the subscriber shapes with
|
|
72
|
+
[`recipes`](repositories-file-formats.md#recipes-what-each-content-kind-does). Field tables are in the
|
|
73
73
|
[recipe manifest](repositories-file-formats.md#sousrecipeyaml-the-recipe-manifest) reference, and recipe
|
|
74
74
|
metadata is the source of truth for the version: never edit `sous.index.json` by hand.
|
|
75
75
|
|
|
76
|
+
### Shipping memories
|
|
77
|
+
|
|
78
|
+
A memory is a fragment of an agent's always-loaded instructions. A recipe ships one with a `contents` group of
|
|
79
|
+
`kind: memories`:
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
contents:
|
|
83
|
+
- kind: memories
|
|
84
|
+
include:
|
|
85
|
+
- memories/**/*.md
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Plain `.md` files are copied as they are; a file named `*.tpl.md` renders through LiquidJS with the subscriber's
|
|
89
|
+
answers in scope. The recipe never says where a memory lands. A subscriber includes every memory of every recipe
|
|
90
|
+
it holds directly with one line, `@#memories/**/*.md`, which lists a file at
|
|
91
|
+
`#memories/<namespace>/<recipe>/<path under the pattern's static base>`. A recipe held only through `depends` is
|
|
92
|
+
a library and contributes none. The build warns a subscriber whose output includes none of a memory it
|
|
93
|
+
publishes, so a memory is never silently lost. See
|
|
94
|
+
[Recipe memories](repositories-consuming.md#recipe-memories) for the subscriber's side.
|
|
95
|
+
|
|
76
96
|
A recipe's files compile exactly the way a project's own `entryGlob` target does, so the
|
|
77
97
|
[`.tpl.` convention](configuration.md#templates-and-the-tpl-convention) applies unchanged inside a recipe. A
|
|
78
98
|
template may include a file from a recipe this one depends on through the reserved `~` sigil, resolved against
|
|
@@ -173,17 +173,46 @@ like `workflow/qa-helper` above, is fetched and pinned but stays out of your out
|
|
|
173
173
|
|
|
174
174
|
## Choose where the files land
|
|
175
175
|
|
|
176
|
-
Recipe files compile the
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
176
|
+
Recipe files compile under the [`.tpl.` convention](configuration.md#templates-and-the-tpl-convention): a file
|
|
177
|
+
with `.tpl.` in its name is rendered through LiquidJS and loses `.tpl.` on the way out, and anything else is copied
|
|
178
|
+
verbatim. Skills are written to the directories named by the
|
|
179
|
+
[`recipes`](repositories-file-formats.md#recipes-what-each-content-kind-does) config key; for example
|
|
180
|
+
`recipes: { skills: { outputs: ["${projectRoot}/.claude/skills", "${projectRoot}/.codex/skills"] } }`. The
|
|
181
|
+
default is `<project root>/.claude/skills`, because that is where every agent looks. Written skills are tracked
|
|
182
|
+
like every other file sous writes, so `sous prune` removes what an unsubscribed recipe used to write and
|
|
183
|
+
`sous clear` removes all of it; neither reaches into a linked checkout.
|
|
184
|
+
|
|
185
|
+
## Recipe memories
|
|
186
|
+
|
|
187
|
+
A memory is a recipe's fragment of an agent's instructions. Sous does not copy memories anywhere; your
|
|
188
|
+
instruction source includes them. Put one line in the file your `AGENTS.md` or `CLAUDE.md` is built from:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
@#memories/**/*.md
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`#memories` is a view: it lists the memories of every recipe you hold directly (a subscription to it, to its
|
|
195
|
+
namespace, or a co-subscription of a recipe you hold) at the virtual path
|
|
196
|
+
`#memories/<namespace>/<recipe>/<path>`, and the line includes each one. A recipe held only through `depends`
|
|
197
|
+
adds none. `sous init` writes this line into its starter, `.sous/memories/AGENTS.tpl.md`.
|
|
198
|
+
|
|
199
|
+
Memories come in dependency order (a recipe after the recipes it depends on, ties by recipe key). Two config
|
|
200
|
+
lists under `recipes.memories` shape the rest, each entry a glob over `namespace/recipe` keys or a regular
|
|
201
|
+
expression written `/.../`:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
recipes: {
|
|
205
|
+
memories: { first: ["communication/*"], exclude: ["tool-usage/automated-browser-tasks"] },
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
- `first`: those recipes' memories lead.
|
|
210
|
+
- `exclude`: those recipes' memories are left out; this is how you opt a recipe out on purpose.
|
|
211
|
+
|
|
212
|
+
A glob narrows the view as it does any path: `@#memories/communication/**/*.md` includes only that namespace.
|
|
213
|
+
When a build finds a memory an active recipe publishes that no include of any output reached (by exact path, glob
|
|
214
|
+
or the view) and `exclude` does not name, it warns, naming the memory and the line to add. Subscribing alone
|
|
215
|
+
includes nothing, and the warning is how that is never silent.
|
|
187
216
|
|
|
188
217
|
## Answer the questions
|
|
189
218
|
|
|
@@ -80,9 +80,11 @@ contents: # what a subscriber actually receives
|
|
|
80
80
|
| `contents`, `variables` | no | Contents default to an empty list, which is what a curated bundle publishes; a duplicated variable name, or two definitions claiming one `env`, is an error |
|
|
81
81
|
| `submissions` | no | Whether this recipe takes proposed changes, winning over the repository's block; see [The submissions block](#the-submissions-block) |
|
|
82
82
|
|
|
83
|
-
`contents[].kind` is `skills`, `memories`, `prompts` or `config
|
|
84
|
-
destinations named by [`
|
|
85
|
-
|
|
83
|
+
`contents[].kind` is `skills`, `memories`, `prompts` or `config`; there is no kind for a sous plugin. `skills` are
|
|
84
|
+
written to the destinations named by [`recipes`](#recipes-what-each-content-kind-does); `memories` are included
|
|
85
|
+
into your instruction files by one line (see [Recipe memories](repositories-consuming.md#recipe-memories));
|
|
86
|
+
`config` entries become config layers; nothing takes `prompts` yet, and the build warns about a recipe that
|
|
87
|
+
publishes them. `include` needs at least one glob, and both lists are recipe-relative.
|
|
86
88
|
|
|
87
89
|
### The submissions block
|
|
88
90
|
|
|
@@ -124,9 +126,10 @@ two readings that both publish what is named fail it too, naming the spellings t
|
|
|
124
126
|
|
|
125
127
|
### Ref forms
|
|
126
128
|
|
|
127
|
-
Every place a ref is written reads it with one
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
Every place a ref is written reads it with one ref resolver (`src/services/ref-resolver/`): a parser that
|
|
130
|
+
returns every reading of the text, a pruner for the place that drops the readings the place refuses, and a
|
|
131
|
+
lookup that keeps the readings that exist. These forms are recognized everywhere; the table after them says
|
|
132
|
+
which each place allows. A refused form is an error saying what to write there instead.
|
|
130
133
|
|
|
131
134
|
| Form | Example | Names |
|
|
132
135
|
| --- | --- | --- |
|
|
@@ -153,6 +156,7 @@ end of the project path with `/-/`, so it reads one way.
|
|
|
153
156
|
| A config file's subscription keys | `namespace` or `namespace/recipe`, lowercase; the repository is recorded in the lockfile and a range in the entry's `range` field |
|
|
154
157
|
| A recipe manifest's `depends` and `subscribes` | every form naming a namespace or a recipe, except `repo:` and a local location |
|
|
155
158
|
| A key sous stores (the lockfile, the index, the store) | `namespace` or `namespace/recipe`, lowercase |
|
|
159
|
+
| An include line, after its `~` | `namespace/recipe/path`, any part a glob, optionally starting with a `repo:` qualifier; see [Include syntax](configuration.md#include-syntax) |
|
|
156
160
|
|
|
157
161
|
Matching tries the exact spelling first, then ignores case; a name that still matches several things is a
|
|
158
162
|
question, answered by `--accept-first` or by choosing. What sous writes (config layers, the lockfile, the
|
|
@@ -368,8 +372,9 @@ store: # every value is a default you can change
|
|
|
368
372
|
maxBytes: 1073741824 # one gigabyte; past it, 'sous repo gc' evicts unpinned entries
|
|
369
373
|
freshnessSeconds: 300 # how long a fetched index stays fresh
|
|
370
374
|
watchPollSeconds: 300 # how often watch mode polls upstream
|
|
371
|
-
|
|
372
|
-
skills: ["${projectRoot}/.claude/skills", "${projectRoot}/.codex/skills"]
|
|
375
|
+
recipes: # what each content kind a subscribed recipe publishes does
|
|
376
|
+
skills: { outputs: ["${projectRoot}/.claude/skills", "${projectRoot}/.codex/skills"] }
|
|
377
|
+
memories: { first: ["communication/*"], exclude: ["tool-usage/automated-browser-tasks"] }
|
|
373
378
|
varMappings:
|
|
374
379
|
QA_SERVICE_TOKEN: workflow/qa-variables/qaServiceToken
|
|
375
380
|
```
|
|
@@ -380,7 +385,7 @@ varMappings:
|
|
|
380
385
|
| `subscriptions` | a ref key | A bare namespace (every recipe in it, including ones published later) or `namespace/recipe`. No repo qualifier and no range in the key |
|
|
381
386
|
| `enabled: false` | either of the two above | The opt-out for the entries sous provides itself, the `sous-recipes` repository and the `core` subscription |
|
|
382
387
|
| `store` | fixed fields | Plain numbers, all optional; the values sous ships are defaults, not assumptions |
|
|
383
|
-
| `
|
|
388
|
+
| `recipes` | content kind | What each kind does; see [recipes: what each content kind does](#recipes-what-each-content-kind-does) |
|
|
384
389
|
| `varMappings` | environment variable name | Binds one name to one recipe variable, written `namespace/recipe/variableName` with an optional `repo:` qualifier |
|
|
385
390
|
|
|
386
391
|
A repository's `url` may be a URL or an absolute path to one on this machine, read by the `local` provider
|
|
@@ -388,11 +393,21 @@ with identical trust. A hand-written entry is laid over sous's default field by
|
|
|
388
393
|
`{ enabled: false }` is a complete opt-out, and a written `url` repoints the repository while the rest of
|
|
389
394
|
the default stands.
|
|
390
395
|
|
|
391
|
-
###
|
|
396
|
+
### recipes: what each content kind does
|
|
392
397
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
One block per content kind a subscribed recipe publishes. A recipe's `config` contents become layers, not files,
|
|
399
|
+
and a recipe's own config layer may set `recipes.skills` only; `recipes.memories` chooses what the project
|
|
400
|
+
includes, so it is dropped from a recipe's layer with a warning.
|
|
401
|
+
|
|
402
|
+
- `recipes.skills.outputs` (`string[]`, default `["<project root>/.claude/skills"]`): the directories recipe skill
|
|
403
|
+
bundles are written into, `${var}` substituted as in any config path. Name several to feed more than one agent.
|
|
404
|
+
- `recipes.memories.first` (`string[]`, default none): recipes whose memories lead the `#memories` view. An entry
|
|
405
|
+
is a glob over `namespace/recipe` keys (`communication/*`), or a regular expression written between two slashes
|
|
406
|
+
(`/^tool-usage\//`); a string, because a config layer is JSON and cannot hold a `RegExp`.
|
|
407
|
+
- `recipes.memories.exclude` (`string[]`, default none): recipes whose memories the view leaves out, and the
|
|
408
|
+
build's unincluded-memory warning never lists. The same entry forms as `first`.
|
|
409
|
+
|
|
410
|
+
There is no `prompts` block.
|
|
396
411
|
|
|
397
412
|
## Managed config layers
|
|
398
413
|
|
|
@@ -145,7 +145,7 @@ its name is rendered through LiquidJS and loses that segment (`SKILL.tpl.md` bec
|
|
|
145
145
|
and any other file is copied verbatim. They are tracked like every file sous writes, so
|
|
146
146
|
`sous prune` removes them when you unsubscribe. Where each kind lands is your project's decision,
|
|
147
147
|
under
|
|
148
|
-
[`
|
|
148
|
+
[`recipes`](repositories-file-formats.md#recipes-what-each-content-kind-does).
|
|
149
149
|
|
|
150
150
|
## 6. Start a repository of your own
|
|
151
151
|
|
|
@@ -47,7 +47,8 @@ $ sous namespace list
|
|
|
47
47
|
|
|
48
48
|
A recipe is the unit you subscribe to and the only thing that carries a version. It may hold
|
|
49
49
|
skills, memories, prompts, config layers and variable definitions in any mixture, and
|
|
50
|
-
subscribing to it gets all of them.
|
|
50
|
+
subscribing to it gets all of them. A recipe may ship a plugin for a coding tool (a Claude Code
|
|
51
|
+
plugin, say), but never a plugin for sous itself; those come only with sous or as npm packages.
|
|
51
52
|
|
|
52
53
|
```yaml
|
|
53
54
|
# recipes/workflow/task-files/sous.recipe.yaml
|
|
@@ -193,10 +194,10 @@ this machine.
|
|
|
193
194
|
Subscribing decides what is switched on, and it is not a second security gate. A recipe is
|
|
194
195
|
**active** when the project subscribes to it directly or through its namespace, or when an active
|
|
195
196
|
recipe lists it under `subscribes`; only an active recipe registers entry points of its own, such as
|
|
196
|
-
output files, skills and
|
|
197
|
-
may include, call or import its files, but it registers nothing itself. Activation
|
|
198
|
-
from running what it has no use for and keeps unrelated recipes from colliding;
|
|
199
|
-
trusted repository is already trusted.
|
|
197
|
+
output files, skills, tool hooks and tool plugins. A recipe held only through `depends` is a library:
|
|
198
|
+
an active recipe may include, call or import its files, but it registers nothing itself. Activation
|
|
199
|
+
keeps a project from running what it has no use for and keeps unrelated recipes from colliding;
|
|
200
|
+
everything in a trusted repository is already trusted.
|
|
200
201
|
|
|
201
202
|
## The official repository, and the built-in core
|
|
202
203
|
|
|
@@ -279,8 +280,10 @@ $ sous build
|
|
|
279
280
|
✓ /home/me/my-project/.claude/skills/start-task/SKILL.md (~1,996 tokens)
|
|
280
281
|
```
|
|
281
282
|
|
|
282
|
-
Skills default to `<project root>/.claude/skills
|
|
283
|
-
|
|
283
|
+
Skills default to `<project root>/.claude/skills`, and the
|
|
284
|
+
[`recipes`](repositories-file-formats.md#recipes-what-each-content-kind-does) block changes that. Memories are
|
|
285
|
+
not copied anywhere; one include line in your instruction source pulls them in (see
|
|
286
|
+
[Recipe memories](repositories-consuming.md#recipe-memories)).
|
|
284
287
|
|
|
285
288
|
By default a build uses what the lockfile pins and does not talk to the network. Two things
|
|
286
289
|
change that: **always-pull**, which installs a newer in-range version whenever one exists, and
|
|
@@ -324,13 +327,15 @@ README. None is ever overwritten, and directories holding rendered output are le
|
|
|
324
327
|
## Including recipe files in your own templates
|
|
325
328
|
|
|
326
329
|
A template may pull in a file from a recipe through the reserved `~` sigil, naming the recipe's
|
|
327
|
-
published identity and then the path inside it, on a line of its own
|
|
330
|
+
published identity and then the path inside it, on a line of its own. Any part may be a glob, and a
|
|
331
|
+
`repo:` qualifier may start the path to say which repository when two publish the same namespace:
|
|
328
332
|
|
|
329
333
|
```text
|
|
330
334
|
@~workflow/qa-helper/_partials/review-steps.md
|
|
335
|
+
@~workflow/qa-helper/_partials/*.md
|
|
331
336
|
```
|
|
332
337
|
|
|
333
|
-
The `~` is required. A bare `@path` is always a relative path or a declared alias, so an include
|
|
338
|
+
A glob includes every file it matches, in bytewise path order. The `~` is required. A bare `@path` is always a relative path or a declared alias, so an include
|
|
334
339
|
line can never quietly stop meaning a file on disk. In your own templates, `~<namespace>`
|
|
335
340
|
resolves against every recipe the lockfile pins, whatever brought it in: a recipe you subscribe
|
|
336
341
|
to, a member of a set you subscribe to, or a library another recipe depends on. Inside a recipe,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sous-io/sous",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.33",
|
|
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",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@oclif/core": "^4",
|
|
58
58
|
"chokidar": "^5.0.0",
|
|
59
59
|
"glob": "^13.0.6",
|
|
60
|
+
"inversify": "^8.2.3",
|
|
60
61
|
"jsonc-parser": "^3.3.1",
|
|
61
62
|
"liquidjs": "^10.25.0",
|
|
62
63
|
"minimatch": "^10.2.4",
|
|
@@ -92,7 +92,7 @@ Include another file at render time (path **relative to the template file's dire
|
|
|
92
92
|
{% endraw %}
|
|
93
93
|
|
|
94
94
|
`render` resolves paths relative to the template file. For files outside that tree, use
|
|
95
|
-
a recipe reference (`@~<namespace>/<recipe>/...`), a path **alias** (
|
|
95
|
+
a recipe reference (`@~<namespace>/<recipe>/...`), a path **alias** (`@#project/...`, or a
|
|
96
96
|
user-defined alias), a home-relative path (`@~/...`) or a `@`-prefixed `${var}` path; the
|
|
97
97
|
same resolution as `@include` (see below) works in `render` too.
|
|
98
98
|
|
|
@@ -107,23 +107,24 @@ The `@path` syntax is processed by the build system before LiquidJS runs. It inc
|
|
|
107
107
|
a file's content inline. Write `@` immediately followed by a `.md` path on its own line
|
|
108
108
|
with nothing else on that line.
|
|
109
109
|
|
|
110
|
-
`@include` works in both `.tpl.` and plain `.md` files.
|
|
111
|
-
|
|
112
|
-
file
|
|
110
|
+
`@include` works in both `.tpl.` and plain `.md` files. A file's own name decides whether it
|
|
111
|
+
renders, every time: a `.tpl.` file always renders as Liquid and any other file never does, wherever
|
|
112
|
+
it is included from. Each included file renders on its own, by its own name, with the including
|
|
113
|
+
output's variables, and its text is then placed where the line was. A plain `.md` file that holds
|
|
114
|
+
{% raw %}`{{ tags }}`{% endraw %} therefore keeps them verbatim; rename it `.tpl.md` when it needs rendering.
|
|
113
115
|
|
|
114
116
|
The engine sets both `strictVariables: false` and `strictFilters: false`. An undefined
|
|
115
117
|
variable renders as an empty string, and an **unknown filter silently no-ops**, passing
|
|
116
118
|
its input through unchanged. Neither mistake raises an error, so a typo in a variable or
|
|
117
119
|
filter name shows up only as missing or unfiltered output.
|
|
118
120
|
|
|
119
|
-
###
|
|
121
|
+
### Fenced code blocks, repeats and malformed lines
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
prefix it with a word, or wrap it in backticks inline).
|
|
123
|
+
A line inside a fenced code block (``` or ~~~) is never an include, so an `@path.md` example
|
|
124
|
+
inside a fence shows as written. The same file may be included in more than one place; a circular
|
|
125
|
+
include is an error. A line that looks like an include (one word starting with `@`, holding a `/` or
|
|
126
|
+
`.md`, or starting with `~`, `#`, `.` or `$`) but cannot be resolved fails the build, naming the file
|
|
127
|
+
and the line; a line like `@alice thanks` is plain text.
|
|
127
128
|
|
|
128
129
|
### Path forms
|
|
129
130
|
|
|
@@ -133,12 +134,14 @@ A `@`-path may be any of:
|
|
|
133
134
|
- **Variable-substituted**: `@${projectRoot}/prompts/x.md`; `${var}` is
|
|
134
135
|
substituted before resolving; if the result is absolute it is used directly.
|
|
135
136
|
- **Aliased**: `@<alias>/rest.md`, where the first segment names a registered alias.
|
|
137
|
+
- **A glob**: `*`, `**`, `?`, `[..]` and `{a,b}` after substitution, as in `entryGlob`; it
|
|
138
|
+
includes every file it matches, in bytewise path order.
|
|
136
139
|
|
|
137
140
|
### Recipe references and path aliases
|
|
138
141
|
|
|
139
142
|
The first path segment, up to the first `/` or `:` (both separators work, so `@a/b.md`
|
|
140
|
-
is the same as `@a:b.md`), is matched first against the alias registry and then, when it begins
|
|
141
|
-
`~`, against the recipe namespaces this project or recipe can address.
|
|
143
|
+
is the same as `@a:b.md`), is matched first against the alias registry (which holds the `#` names) and then, when it begins
|
|
144
|
+
with `~`, against the recipe namespaces this project or recipe can address.
|
|
142
145
|
|
|
143
146
|
The normal way to reach a file that another recipe publishes is a **recipe reference**,
|
|
144
147
|
written `@~<namespace>/<recipe>/<path inside that recipe>`:
|
|
@@ -152,10 +155,15 @@ may address every recipe the project's lockfile pins, whatever brought it in. An
|
|
|
152
155
|
is an error naming what was missing, so a reference can never quietly pick up a recipe
|
|
153
156
|
nobody asked for.
|
|
154
157
|
|
|
155
|
-
Built-in
|
|
156
|
-
|
|
158
|
+
Built-in names are reserved, begin with `#` (names sous or a plugin registers), and are consulted
|
|
159
|
+
before recipe namespaces:
|
|
157
160
|
|
|
158
|
-
-
|
|
161
|
+
- `@#project/...` → the consuming project's root.
|
|
162
|
+
- `@#memories/**/*.md` → a **view**: every memory published by a recipe this project holds
|
|
163
|
+
directly (a subscription, or a co-subscription of one), at the virtual path
|
|
164
|
+
`#memories/<namespace>/<recipe>/<path>`, in dependency order. A recipe held only through
|
|
165
|
+
`depends` contributes none. The `recipes.memories.first` and `recipes.memories.exclude` config
|
|
166
|
+
lists reorder and filter it.
|
|
159
167
|
|
|
160
168
|
A bare `@~/...` is not an alias: the sigil followed only by a separator is the home
|
|
161
169
|
directory, so `@~/notes/context.md` includes a file from your home directory.
|
|
@@ -168,7 +176,7 @@ Everything else sous once shipped inside its own package is published as a recip
|
|
|
168
176
|
a recipe reference is what reaches it.
|
|
169
177
|
|
|
170
178
|
Projects register their own aliases in settings via an `_aliases` block (root and/or
|
|
171
|
-
project level); names may **not** start with `~` (reserved). An alias value is a string
|
|
179
|
+
project level); names may **not** start with `~` or `#` (reserved). An alias value is a string
|
|
172
180
|
or an array of strings (each may use `${var}`):
|
|
173
181
|
|
|
174
182
|
```
|
|
@@ -188,6 +196,13 @@ wins**; if none exist, the build errors listing every path tried:
|
|
|
188
196
|
|
|
189
197
|
## Custom Tags
|
|
190
198
|
|
|
199
|
+
The tags below are built into sous. A recipe cannot add a tag or a filter, since that would be a
|
|
200
|
+
Sous plugin; a Sous plugin comes only from sous's built-ins or an npm-style module.
|
|
201
|
+
|
|
202
|
+
> "recipes will not be allowed to install _Sous_ plugins. All Sous plugins, besides any built-ins that ship with Sous, itself, will come from a NPM repo (or similar) as modules. Recipes _will_ be allowed to install tool plugins (e.g. Claude Code Plugins)."
|
|
203
|
+
>
|
|
204
|
+
> -- **Luke Chavers** in an agent session (2026-09-29)
|
|
205
|
+
|
|
191
206
|
**`showVars`**: dumps all variables currently in scope as a fenced JSON block.
|
|
192
207
|
Useful during development to see exactly what variables are available at a given point
|
|
193
208
|
in a template. Remove before finalizing.
|
|
@@ -65,8 +65,14 @@ trust question reads only its index.
|
|
|
65
65
|
Subscribing decides what is switched on, not what is safe. A recipe is active when the
|
|
66
66
|
project subscribes to it, directly or through its namespace, or when an active recipe
|
|
67
67
|
lists it under `subscribes`, and only an active recipe registers entry points of its own
|
|
68
|
-
(output files, skills, hooks). A recipe held only through `depends` is a
|
|
69
|
-
recipe may include, call or import its files, but it registers nothing
|
|
68
|
+
(output files, skills, tool hooks and tool plugins). A recipe held only through `depends` is a
|
|
69
|
+
library: an active recipe may include, call or import its files, but it registers nothing
|
|
70
|
+
itself. No recipe installs a Sous plugin; Sous plugins come only from sous's built-ins and from
|
|
71
|
+
npm-style modules.
|
|
72
|
+
|
|
73
|
+
> "recipes will not be allowed to install _Sous_ plugins. All Sous plugins, besides any built-ins that ship with Sous, itself, will come from a NPM repo (or similar) as modules. Recipes _will_ be allowed to install tool plugins (e.g. Claude Code Plugins)."
|
|
74
|
+
>
|
|
75
|
+
> -- **Luke Chavers** in an agent session (2026-09-29)
|
|
70
76
|
|
|
71
77
|
## Sous's Own Documentation
|
|
72
78
|
|
|
@@ -59,6 +59,10 @@ prefer the on-disk copies.
|
|
|
59
59
|
- Hand-written config belongs in the primary file or your own `conf.d/` layers. Never
|
|
60
60
|
hand-edit `conf.d/500-*` through `conf.d/599-*`; that band is reserved for layers the sous
|
|
61
61
|
CLI writes.
|
|
62
|
+
- The top-level `recipes` key says what the project does with each content kind its subscribed
|
|
63
|
+
recipes publish: `recipes.skills.outputs` lists the directories skills are written to, and
|
|
64
|
+
`recipes.memories.first` and `recipes.memories.exclude` (globs over `namespace/recipe`, or
|
|
65
|
+
`/regex/` strings) order and filter the memories the `@#memories/**/*.md` include pulls in.
|
|
62
66
|
- Any config problem halts sous with a ConfigError naming the offending file; fix the named
|
|
63
67
|
file rather than working around it.
|
|
64
68
|
- Configs use `${var}`; template files use LiquidJS double-brace syntax. The two resolve at
|
|
@@ -49,11 +49,21 @@ The fields in that example:
|
|
|
49
49
|
`sous repo search`.
|
|
50
50
|
- `contents` is what a subscriber receives: one group per kind (`skills`, `memories`, `prompts` or
|
|
51
51
|
`config`), each with `include` globs and optional `exclude` globs, relative to the recipe folder.
|
|
52
|
-
The subscribing project decides where skills
|
|
52
|
+
The subscribing project decides where skills land; memories are included into its instruction
|
|
53
|
+
files by one line, `@#memories/**/*.md` (see "Shipping Memories" below), and nothing takes
|
|
54
|
+
prompts yet. A `config` file becomes
|
|
53
55
|
a config layer in the project, loaded under the project's own config so the project always wins;
|
|
54
56
|
it MUST be `.json`, `.jsonc`, `.yaml` or `.yml`, and every key outside `_aliases`, `_vars`,
|
|
55
|
-
`compilation`, `
|
|
57
|
+
`compilation`, `recipes`, `runtimeContext`, `store` and `varMappings` is dropped with a
|
|
56
58
|
warning. A recipe that only bundles others may leave `contents` out.
|
|
59
|
+
- A recipe MAY install tool plugins (a plugin for a coding agent tool, such as a Claude Code
|
|
60
|
+
plugin) and MUST NOT install a Sous plugin (one that extends sous itself). Sous plugins come only
|
|
61
|
+
from the built-ins that ship with sous and from npm-style modules. A `config` file is
|
|
62
|
+
configuration, not a Sous plugin.
|
|
63
|
+
|
|
64
|
+
> "recipes will not be allowed to install _Sous_ plugins. All Sous plugins, besides any built-ins that ship with Sous, itself, will come from a NPM repo (or similar) as modules. Recipes _will_ be allowed to install tool plugins (e.g. Claude Code Plugins)."
|
|
65
|
+
>
|
|
66
|
+
> -- **Luke Chavers** in an agent session (2026-09-29)
|
|
57
67
|
- `depends`, `subscribes` and `variables` are described below; `submissions` is in
|
|
58
68
|
`about-sous-repos`.
|
|
59
69
|
|
|
@@ -153,13 +163,24 @@ Each entry names its target by where it lives:
|
|
|
153
163
|
`local://` locators and `repo:` short names are refused: both are one project's private view of a
|
|
154
164
|
repository, and a published manifest is read everywhere.
|
|
155
165
|
|
|
166
|
+
## Shipping Memories
|
|
167
|
+
|
|
168
|
+
A memory is a fragment of an agent's always-loaded instructions. Publish one with a `contents`
|
|
169
|
+
group of `kind: memories`, for example `include: [memories/**/*.md]`. A plain `.md` file is copied
|
|
170
|
+
as it is; name a file `*.tpl.md` when it needs the subscriber's answers. The recipe never says where
|
|
171
|
+
a memory lands: a subscribing project includes the memories of every recipe it holds directly with
|
|
172
|
+
one line, `@#memories/**/*.md`, at the virtual path `#memories/<namespace>/<recipe>/<path>` (the path
|
|
173
|
+
being relative to the pattern's static base). A recipe held only through `depends` is a library and
|
|
174
|
+
contributes none, and the subscriber's build warns about a published memory no output includes.
|
|
175
|
+
|
|
156
176
|
## Includes and Partials
|
|
157
177
|
|
|
158
178
|
A line holding only `@~<namespace>/<recipe>/<path>.md`, starting at the first column, includes that
|
|
159
179
|
file from a recipe at its pinned version. Inside a recipe it resolves only against the recipe
|
|
160
180
|
itself and the recipes it lists under `depends` or `subscribes`; the path may not be absolute or
|
|
161
|
-
contain `.` or `..` segments, and it MUST end in `.md` (`notes.md` also finds `notes.tpl.md`).
|
|
162
|
-
line inside a fenced code block is never an
|
|
181
|
+
contain `.` or `..` segments, and it MUST end in `.md` (`notes.md` also finds `notes.tpl.md`). Any
|
|
182
|
+
part may be a glob, which includes every match. A line inside a fenced code block is never an
|
|
183
|
+
include, and a line that looks like an include but cannot be read as one fails the build.
|
|
163
184
|
|
|
164
185
|
A **partial** is a short piece of text several skills share, kept in the recipe's `_partials/`
|
|
165
186
|
folder beside `skills/`. As long as no `contents` glob covers `_partials/`, it is never installed on
|
|
@@ -171,12 +192,11 @@ What to review: $ARGUMENTS
|
|
|
171
192
|
@~review/checklist/_partials/review-rules.md
|
|
172
193
|
```
|
|
173
194
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
195
|
+
A file's own name decides whether it renders: a `.tpl.` file always renders as Liquid, and any other
|
|
196
|
+
file never does, wherever it is included from. So a partial that uses template variables MUST be named
|
|
197
|
+
`*.tpl.md`, and it renders with the including output's variables in every file that includes it. For
|
|
198
|
+
example, a partial holding
|
|
178
199
|
`{% raw %}Task files live in {{ taskFileRoot }}.{% endraw %}` renders with the project's answer wherever it is included.
|
|
179
|
-
Name a new partial `*.tpl.md` too.
|
|
180
200
|
|
|
181
201
|
## Source for this Skill
|
|
182
202
|
|