@sous-io/sous 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,387 +1,339 @@
1
1
  # Recipe Variables
2
2
 
3
- A recipe that needs a value from your project publishes a **variable definition**: a
4
- specification with a name, a type, a question and a set of constraints. You supply an **answer**.
5
- A "question" is only the interactive moment; sous asks one only when a subscribed recipe needs a
6
- variable and nothing in scope answers it, or when the answer in scope no longer fits.
3
+ A recipe that needs a value from your project publishes a **variable definition**: a name, a type, a question
4
+ and a set of constraints. You supply an **answer**. A "question" is only the interactive moment: sous asks one
5
+ when a recipe needs a variable and nothing in scope answers it, or when what is there no longer fits.
7
6
 
8
- ?> This page is about variables that recipes publish. Your project's own `${var}` configuration
9
- variables are a different, older, deliberately ceremony-free system; see
10
- [Variables](config-variables.md) for those. The two never mix.
7
+ ?> This page is about variables that recipes publish. Your project's own `${var}` configuration variables are
8
+ a separate, ceremony-free system; see [Variables](config-variables.md). The two meet in one place only, under
9
+ [How a template reads an answer](#how-a-template-reads-an-answer).
11
10
 
12
- ## Where answers live
11
+ ## What a definition is, and where it lives
13
12
 
14
- Answers are stored in your project's env files, and nowhere else:
13
+ A definition ships inside the recipe that needs it, in its manifest's `variables:` array, so it versions,
14
+ resolves, pins and trusts like the rest of the recipe; every field it can carry is listed in
15
+ [Variable definitions](repositories-file-formats.md#variable-definitions).
15
16
 
16
- | File | Committed | Holds |
17
- |------|-----------|-------|
18
- | `.sous/.env` | yes | Shared answers (`scope: shared`), the team's defaults |
19
- | `.sous/.env.local` | no, gitignored | Machine-specific answers (`scope: local`) and every secret |
17
+ A definition is in play when your lockfile pins the recipe that declares it, whether you subscribed to that
18
+ recipe or a dependency pulled it in. A recipe the store does not hold yet contributes nothing rather than
19
+ failing, so a fresh clone lists what it can until `sous build` restores the rest.
20
20
 
21
- Sous edits these files the way a careful person would. Exactly one value line is rewritten or
22
- appended; comments, blank lines, ordering and quoting all survive untouched. A newly added entry
23
- gets a short generated header comment above it saying where the value came from:
21
+ A project can also ask questions no recipe publishes: write the same `variables:` array into a YAML or JSON
22
+ file of your own and point `--file` at it. Every `sous vars` command takes that flag, and the definitions in
23
+ it are attributed to a pseudo-recipe in the `local` namespace named after the file, so an `apiUrl` declared in
24
+ `questions.yaml` generates `SOUS_VAR_LOCAL_QUESTIONS_API_URL`, `SOUS_VAR_LOCAL_API_URL` and `SOUS_VAR_API_URL`.
24
25
 
25
- ```bash
26
- # Set by sous for workflow/task-files: Where should task files live?
27
- # One file per git branch is written here.
28
- # Edit freely; sous only rewrites the value line.
29
- SOUS_VAR_TASK_FILE_ROOT=.sous/tasks
30
- ```
26
+ !> A definition's `validate.pattern` is a regular expression published by someone else, so sous runs each
27
+ match on a worker under a fixed budget of 100 milliseconds, and a pattern that exceeds it fails validation
28
+ naming the pattern and the recipe rather than blaming your answer. See
29
+ [A variable pattern that runs out of time](repositories-troubleshooting.md#a-variable-pattern-that-runs-out-of-time).
31
30
 
32
- Those comments are output only. Sous never reads one back, so editing or deleting a comment
33
- changes nothing, and editing the value line is a perfectly normal way to change an answer.
31
+ ## Answer the questions
34
32
 
35
- ## The ladder
33
+ Subscribing asks whatever is unanswered, so in normal use you rarely run `sous vars ask` yourself; reach for
34
+ it after editing an env file by hand, after an upgrade tightened a constraint, or to re-answer with `--all`.
36
35
 
37
- For each variable, sous generates a list of environment variable names and tries them in order,
38
- most specific first. The first name that holds a value wins.
36
+ Every trust decision is settled first: a subscribe resolves the dependency closure and runs the trust
37
+ ceremony for each new repository before the first question prints. Questions then run one recipe at a time,
38
+ the one you asked for and then each recipe it depends on, with a lead-in and per-recipe counts:
39
39
 
40
- | Rung | Name | Example | When it applies |
41
- |------|------|---------|-----------------|
42
- | 1. mapping record | whatever the record names | `TEAM_API_URL` | Only when a record exists for this variable |
43
- | 2. recipe scope | `SOUS_VAR_<NAMESPACE>_<RECIPE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_TASK_FILES_API_URL` | Answers this one recipe's variable and nothing else |
44
- | 3. namespace scope | `SOUS_VAR_<NAMESPACE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_API_URL` | Answers every recipe in the namespace at once |
45
- | 4. shared scope | `SOUS_VAR_<VARIABLE>` | `SOUS_VAR_API_URL` | Answers every recipe that declares that variable name |
46
- | 5. declared name | the definition's own `env` field | `GITHUB_TOKEN` | How a recipe binds a value the environment already carries |
47
-
48
- Read from the bottom up, the ladder is a story about sharing. One `SOUS_VAR_API_URL` answers
49
- every recipe that wants an `apiUrl`, which is what you want most of the time. When two recipes
50
- want the same name and mean different things, move one answer up a rung to the namespace or the
51
- recipe form, and the more specific name wins for that recipe alone. When even that is not enough,
52
- a mapping record settles it.
53
-
54
- !> Candidate names are only ever GENERATED and looked up, never parsed back into scopes. The
55
- underscore is both the delimiter and a legal identifier character, so no parse of a name would be
56
- trustworthy: `SOUS_VAR_TASK_FILES_ROOT` could be three different things. Sous therefore builds the
57
- five candidates it knows are correct and asks the environment about each one.
58
-
59
- ## The three sources, within a rung
60
-
61
- Each rung is looked up in three places, in this order:
62
-
63
- 1. **The real shell environment.** `SOUS_VAR_API_URL=... sous build` beats both files.
64
- 2. **`.sous/.env.local`.** Gitignored; your machine, your secrets.
65
- 3. **`.sous/.env`.** Committed; the team's shared defaults.
66
-
67
- No load ever overwrites a value that is already set, so the first writer wins. This is the same
68
- precedence the rest of sous uses for env files; see
69
- [Discovery and overrides](config-discovery.md).
70
-
71
- ## Mapping records
72
-
73
- A mapping record binds one environment variable, of any name at all, to one fully qualified
74
- variable. It is the top rung and the universal conflict resolver: two recipes wanting the same
75
- name, or a name that already means something else in your environment.
76
-
77
- ```json
78
- {
79
- "varMappings": {
80
- "TEAM_API_URL": "sous-recipes:misc/stuff/apiUrl"
81
- }
82
- }
83
- ```
40
+ ```text
41
+ workflow/task-files needs 4 answers, and workflow/sub-agent-delegation, which it depends on, needs 2.
84
42
 
85
- A target is written `namespace/recipe/variableName`, optionally qualified as
86
- `repo:namespace/recipe/variableName`. Records live under the top-level `varMappings` config key.
87
- Sous writes the ones it creates into `.sous/conf.d/520-var-mappings.jsonc`, editing one record at
88
- a time so each name has exactly one, and you may hand-write `varMappings` in your primary config
89
- too; the two merge like any other config layer.
43
+ workflow/task-files needs 4 answers before it can be used.
44
+ ```
90
45
 
91
- You rarely write one yourself, because sous offers one at the moment the conflict appears. When
92
- the name an answer would use already holds a value that does not fit the definition, you are
93
- asked where the answer should go:
46
+ Each question prints its header, the publisher's description wrapped to your terminal, and four labeled facts:
94
47
 
95
48
  ```text
96
- SERVICE_TOKEN already holds a value that does not fit serviceToken. Where should this answer go?
97
- SOUS_VAR_TOOLING_DEPLOY_SERVICE_TOKEN, with a mapping record (recommended)
98
- SERVICE_TOKEN, replacing what is there
99
- ```
100
-
101
- Choosing the record writes both the answer under the scoped name and the record binding it, and
102
- the run reports the pair. Where there is no terminal, the record is written, because overwriting
103
- a value something else is already using would be the worse of the two guesses.
49
+ Question 1 of 4: taskFileRoot
104
50
 
105
- ## `sous vars list`
51
+ This recipe mandates the creation of task files that are stored locally and, in
52
+ general, should not be committed. The default stores them in the project's .sous
53
+ directory; any local path works, relative to the project root or absolute.
106
54
 
107
- Lists every variable in play: its name, the recipe that published it, the environment variable
108
- that answered it, the value, and where the value came from. A secret's value is hidden.
55
+ default : .sous/tasks
56
+ example : ~/my-task-files
57
+ stored-as : SOUS_VAR_TASK_FILE_ROOT
58
+ storage-path: /home/you/project/.sous/.env
109
59
 
110
- ```term
111
- $ sous vars list
112
- Variable Recipe Answered by Value Source
113
- apiUrl workflow/task-files SOUS_VAR_API_URL https://api.example.com shared scope, the .env file
114
- taskFileRoot workflow/task-files SOUS_VAR_TASK_... .sous/tasks shared scope, the .env file
115
- serviceToken tooling/deploy nothing yet
60
+ ? Where should task files be stored? (.sous/tasks):
61
+ ⏎ accept default • ⇥ advanced
116
62
  ```
117
63
 
118
- `--file <path>` reads definitions from a standalone definitions file instead of the project's
119
- recipes. The file holds the same `variables:` array a recipe manifest carries, which is how a
120
- project asks questions no recipe publishes yet.
64
+ The legend under the input line names the keys that do anything: Enter accepts what is typed, or the default
65
+ when there is one; a list question names the arrow keys instead, and a yes-or-no question the two letters.
121
66
 
122
- ## `sous vars show <name>`
67
+ ### Tab opens the advanced view
123
68
 
124
- Shows one variable in full: its question, the publisher's description and example, the recipe and
125
- version that published it, the value in scope and whether it fits, then the same labeled facts the
126
- advanced view of a question prints, and finally every name on the ladder with the rung that
127
- actually answered.
69
+ Tab works at every kind of question. The advanced view repeats the header and description, prints every fact
70
+ about the variable through the renderer `sous vars show` uses, and offers a menu:
128
71
 
129
- ```term
130
- $ sous vars show apiUrl
131
- Question : Which API should sous talk to?
132
- About : Every request this recipe generates is sent to one deployment of
133
- the API, and this setting says which one. The default points at
134
- the public production host, but any deployment you can reach works.
135
- For example: https://api.example.com
136
- Recipe : workflow/task-files version 1.2.0 from sous-recipes
137
- Stored in : .env
138
- Value : https://api.example.com
139
-
140
- example : https://api.example.com
72
+ ```text
73
+ [Advanced Variable Settings]
74
+
75
+ Question 1 of 4: taskFileRoot
76
+
77
+ default : .sous/tasks
78
+ example : ~/my-task-files
141
79
  required-by : workflow/task-files https://example.com/owner/recipes/workflow/task-files
142
80
  defined-by : workflow/task-files https://example.com/owner/recipes/workflow/task-files
143
81
  storage-path: /home/you/project/.sous/.env
144
- stored-as : SOUS_VAR_API_URL
145
- constraints : • must be a value of the type url (type: url)
82
+ stored-as : SOUS_VAR_TASK_FILE_ROOT
83
+ constraints : • must be a value of the type path (type: path)
146
84
 
147
- Environment variable Rung Status
148
- SOUS_VAR_WORKFLOW_TASK_FILES_API_URL recipe scope not set
149
- SOUS_VAR_WORKFLOW_API_URL namespace scope not set
150
- SOUS_VAR_API_URL shared scope answered it, from the .env file
85
+ ? What would you like to do?
86
+ Return to value entry
87
+ Change the storage file
88
+ Change the stored variable name
151
89
  ```
152
90
 
153
- `required-by` and `defined-by` are the same links the advanced view of a question shows, so the
154
- two views say the same thing in the same words.
91
+ `required-by` names the recipe whose closure pulled this variable in, and spells out the chain when it
92
+ arrived through a dependency; `defined-by` names the recipe that declares it. Both carry the location beside
93
+ the recipe key, in muted grey: a repository URL, or a filesystem path for a repository on this machine.
155
94
 
156
- The name may be the bare variable name or its full `namespace/recipe.name` key, which is what you
157
- use when two recipes publish the same name.
95
+ **Changing the stored variable name** offers every rung the ladder looks up, sous's choice preselected, plus
96
+ a name you type; one the ladder never looks at is bound with a [mapping record](#when-names-collide-mapping-records).
158
97
 
159
- ?> Three names cannot be reached by the `sous vars <name>` shorthand: `list`, `show` and `ask`,
160
- because each of those is a subcommand. A variable with one of those names is reached the long
161
- way, as `sous vars show ask`.
98
+ **Changing the storage file** offers the committed `.sous/.env` and the gitignored `.sous/.env.local`.
99
+ Pointing a secret, or a variable the publisher declared machine-specific, at the committed file is allowed;
100
+ sous says what that means and asks you to confirm, which is informed consent rather than a locked door:
162
101
 
163
- ## `sous vars ask`
102
+ ```text
103
+ workflow/task-files declared this variable a secret, and .env is committed to git. An answer stored
104
+ there enters your project's git history, is pushed with every clone, and is visible to everyone who
105
+ can read the repository.
164
106
 
165
- Asks the questions the project's definitions imply and stores the answers.
107
+ ? Store this answer in .env anyway? (y/N)
108
+ ```
166
109
 
167
- | Invocation | What it does |
168
- |------------|--------------|
169
- | `sous vars ask` | Asks only what is unanswered, or what no longer fits its definition |
170
- | `sous vars ask <name>` | Asks everything the name covers; see the forms below |
171
- | `sous vars ask --repo <name>` | Asks every variable one repository publishes |
172
- | `sous vars ask --namespace <name>` | Asks every variable one namespace publishes |
173
- | `sous vars ask --var <name>` | Asks one variable; repeat it for each variable |
174
- | `sous vars ask --accept-first` | When the name matches several things, takes the first one listed |
175
- | `sous vars ask --all` | Asks every variable again, including the ones already answered |
176
- | `sous vars ask --file <path>` | Reads definitions from a standalone definitions file |
177
- | `sous vars ask --answer <name>=<value>` | Answers one question ahead of time; repeat it for each answer |
178
- | `sous vars ask --answers-file <path>` | Reads answers from a YAML or JSON file of `name: value` pairs |
179
- | `sous vars ask --dry-run` | Reports what would be asked and written, without writing anything |
110
+ Once anything has changed, the first menu item becomes "Save changes and return to value entry" and a
111
+ "Discard changes and return to value entry" item joins it; returning prints the question again with the
112
+ updated facts. When an answer is entered, two lines say what was stored and where; a secret is never printed:
113
+
114
+ ```text
115
+ Answer : SOUS_VAR_TASK_FILE_ROOT=.sous/tasks
116
+ Saved to: /home/you/project/.sous/.env
117
+ ```
180
118
 
181
- ### What the name may be
119
+ Every run ends with a three-part report: `Answers already in scope:`, `Answers stored:`, `Left unanswered:`.
182
120
 
183
- The name is a reference, resolved the way every sous command resolves one. It may name a
184
- variable, an environment variable that answers one, a recipe, a namespace or a repository, and
185
- anything larger than a variable asks every question it publishes:
121
+ ## Where answers are stored
186
122
 
187
- ```term
188
- $ sous vars ask taskFileRoot
189
- // one variable, by the name its recipe gave it
190
- $ sous vars ask SOUS_VAR_TASK_FILE_ROOT
191
- // the same variable, by an environment variable in use that answers it
192
- $ sous vars ask workflow/task-files.taskFileRoot
193
- // the same variable again, spelled out
194
- $ sous vars ask task-files
195
- // every question that one recipe asks
196
- $ sous vars ask workflow
197
- // every question every recipe in that namespace asks
198
- $ sous vars ask sous-recipes
199
- // every question that repository's recipes ask
123
+ Answers live in your project's env files, and nowhere else:
124
+
125
+ | File | Committed | Holds |
126
+ |------|-----------|-------|
127
+ | `.sous/.env` | yes | Shared answers (`scope: shared`), the team's defaults |
128
+ | `.sous/.env.local` | no, gitignored | Machine-specific answers (`scope: local`) and every secret |
129
+
130
+ Sous edits them the way a careful person would: exactly one value line is rewritten or appended, and
131
+ comments, blank lines, ordering and quoting survive untouched. A new entry gets a generated header comment:
132
+
133
+ ```bash
134
+ # Set by sous for workflow/qa-variables: Where should a review keep its working files?
135
+ # A run writes partial output, logs and diffs somewhere before it assembles the note.
136
+ # Edit freely; sous only rewrites the value line.
137
+ SOUS_VAR_QA_SCRATCH_DIR=/var/tmp/qa-review
200
138
  ```
201
139
 
202
- Every reference may be written at any level of qualification, up to the fully qualified
203
- `repository:namespace/recipe.variable`, and matching is case-sensitive. An environment variable
204
- name resolves when the definition declares it (a recipe may bind an existing variable such as
205
- `GITHUB_TOKEN`) or when it is one of the generated ladder names that `.sous/.env` or
206
- `.sous/.env.local` actually sets.
140
+ Comments are output only; sous never reads one back, and editing the value line changes the answer.
207
141
 
208
- `--repo`, `--namespace` and `--var` say outright which kind of thing is meant, and narrow the
209
- same way. Each one resolves against what the one before it left, so
210
- `sous vars ask --namespace workflow apiUrl` asks about that namespace's `apiUrl` even when
211
- another namespace publishes one too.
142
+ ## How sous finds an answer: the ladder
212
143
 
213
- When a name matches more than one thing, sous lists what it could have meant and asks which one
214
- you meant. `--accept-first` takes the first one listed, and a run with no terminal fails naming
215
- that flag rather than guessing.
144
+ For each variable, sous tries these names in order; the first one holding a value wins.
216
145
 
217
- ### How the questions run
146
+ | Rung | Name | Example | When it applies |
147
+ |------|------|---------|-----------------|
148
+ | 1. mapping record | whatever the record names | `TEAM_TOKEN` | Only when a record exists for this variable |
149
+ | 2. recipe scope | `SOUS_VAR_<NAMESPACE>_<RECIPE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_QA_VARIABLES_QA_SERVICE_TOKEN` | Answers this one recipe's variable and nothing else |
150
+ | 3. namespace scope | `SOUS_VAR_<NAMESPACE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_QA_SERVICE_TOKEN` | Answers every recipe in the namespace at once |
151
+ | 4. shared scope | `SOUS_VAR_<VARIABLE>` | `SOUS_VAR_QA_SERVICE_TOKEN` | Answers every recipe that declares that variable name |
152
+ | 5. declared name | the definition's own `env` field | `QA_SERVICE_TOKEN` | How a recipe binds a value the environment already carries |
218
153
 
219
- Every trust decision is settled first. A subscribe resolves the whole dependency closure and runs
220
- the trust ceremony for each new repository before the first question is printed, so a question is
221
- never interleaved with a decision about who you are trusting.
154
+ Read from the bottom up, the ladder is a story about sharing. One `SOUS_VAR_API_URL` answers every recipe
155
+ that wants an `apiUrl`, which is what you want most of the time; when two recipes want the same name and
156
+ mean different things, move one answer up a rung and the more specific name wins for that recipe alone.
222
157
 
223
- Questions then run one recipe at a time: the recipe you subscribed to first, then each recipe it
224
- depends on, each opening with how many answers it needs. When the closure covers more than one
225
- recipe, a single lead-in says so before anything is asked:
158
+ Each rung is looked up in three places, in this order: the real shell environment, then `.sous/.env.local`,
159
+ then `.sous/.env`. So `SOUS_VAR_API_URL=... sous build` beats both files, and a machine-specific answer beats
160
+ the team's committed default; see [Discovery and overrides](config-discovery.md).
226
161
 
227
- ```term
228
- workflow/task-files needs 4 answers, and workflow/sub-agent-delegation, which it depends on, needs 2.
229
- ```
162
+ !> Candidate names are only ever GENERATED and looked up, never parsed back into scopes. The underscore is
163
+ both the delimiter and a legal identifier character, so no parse would be trustworthy.
230
164
 
231
- Each question then prints its own view: the header, the publisher's description wrapped to your
232
- terminal, and four labeled facts (the default, the example, and exactly where the answer will be
233
- stored and under what name). The keys that do anything are named by the legend the question draws
234
- under its own input line, in the style the stock prompts use. Those facts are drawn by the renderer
235
- the advanced view uses, so the same label means the same thing and lines up the same way on both.
165
+ `sous vars show` prints the whole ladder, which is the fastest way to see why an answer did or did not take
166
+ effect. Here `qaServiceToken` declared `env: QA_SERVICE_TOKEN`, so the bottom rung is that name:
236
167
 
237
168
  ```term
238
- workflow/task-files needs 4 answers before it can be used.
169
+ $ sous vars show qaServiceToken
170
+ ➔ Environment variables sous looks at, most specific first:
171
+
172
+ Environment variable Rung Status
173
+ ----------------------------------------------- --------------- -------------
174
+ SOUS_VAR_WORKFLOW_QA_VARIABLES_QA_SERVICE_TOKEN recipe scope not set
175
+ SOUS_VAR_WORKFLOW_QA_SERVICE_TOKEN namespace scope not set
176
+ SOUS_VAR_QA_SERVICE_TOKEN shared scope not set
177
+ QA_SERVICE_TOKEN declared name not set
178
+ ```
239
179
 
240
- Question 1 of 4: taskFileRoot
180
+ ## When names collide: mapping records
241
181
 
242
- This recipe mandates the creation of task files that are stored locally and, in
243
- general, should not be committed. This setting dictates the path in which agents
244
- will store and search for your task files. The default value stores task files in
245
- the project's .sous directory, but you can specify any local path, either relative
246
- to the project root or absolute.
182
+ A mapping record binds one environment variable, of any name at all, to one fully qualified variable. It is
183
+ the top rung and the universal conflict resolver: two recipes wanting the same name, or a name already in use.
247
184
 
248
- default : .sous/tasks
249
- example : ~/my-task-files
250
- stored-as : SOUS_VAR_TASK_FILE_ROOT
251
- storage-path: /home/you/project/.sous/.env
185
+ You rarely write one by hand, because sous offers one when the conflict appears: when the name an answer
186
+ would use already holds a value that does not fit the definition, you are asked where the answer should go:
252
187
 
253
- ? Where should task files be stored? (.sous/tasks):
254
- ⏎ accept default • ⇥ advanced
188
+ ```text
189
+ ? SERVICE_TOKEN already holds a value that does not fit serviceToken. Where should this answer go?
190
+ > SOUS_VAR_TOOLING_DEPLOY_SERVICE_TOKEN, with a mapping record (recommended)
191
+ SERVICE_TOKEN, replacing what is there
255
192
  ```
256
193
 
257
- Enter accepts what is typed, or the default when nothing is. A question with no default drops
258
- the Enter half of the legend, since there is nothing for Enter alone to accept, and shows
259
- `⇥ advanced` alone. A question answered from a list rather than typed names the arrow keys
260
- instead:
194
+ Choosing the record writes both the answer under the scoped name and the record binding it, and the run
195
+ reports the pair. Where there is no terminal the record is written, since overwriting a value in use is worse.
261
196
 
262
- ```term
263
- ? Which ticket system do you use?
264
- > github
265
- jira
266
- linear
267
- ↑↓ navigate • ⏎ select • ⇥ advanced
268
- ```
197
+ Sous writes the records it creates into `.sous/conf.d/520-var-mappings.jsonc`, one at a time, so each name
198
+ has exactly one. The record's shape and the rest of the managed layers are under
199
+ [Configuration keys](repositories-file-formats.md#configuration-keys) and
200
+ [Managed config layers](repositories-file-formats.md#managed-config-layers); hand-write one if you prefer.
269
201
 
270
- Tab opens the advanced view from every kind of question: a typed one, a pick-one list, and a
271
- yes-or-no confirmation alike. The word is always "Advanced". Once an answer is stored, two lines
272
- say what was stored and where:
202
+ ## See and answer variables from the command line
203
+
204
+ `sous vars list` prints every variable in play, what answered it, and where the value came from.
273
205
 
274
206
  ```term
275
- Answer : SOUS_VAR_TASK_FILE_ROOT=.sous/tasks
276
- Saved to: /home/you/project/.sous/.env
207
+ $ sous vars list
208
+ Variable Recipe Answered by Value Source
209
+ -------------- --------------------- ----------------------- -------------- --------------------------------
210
+ qaScratchDir workflow/qa-variables SOUS_VAR_QA_SCRATCH_DIR /var/tmp/qa shared scope, the .env.local file
211
+ qaServiceToken workflow/qa-variables (unanswered) nothing yet
212
+ qaTaskRoot workflow/qa-variables SOUS_VAR_QA_TASK_ROOT .sous/qa-notes shared scope, the .env file
277
213
  ```
278
214
 
279
- An answer that was already in scope is never asked about again; it is reported with its scope and
280
- its source instead.
215
+ `sous vars show <name>` prints one variable in full: the question, the publisher's description and example,
216
+ the recipe and version that published it, the value in scope and whether it fits, the labeled facts the
217
+ advanced view draws, and the ladder table above. The name may be bare or the full `namespace/recipe.name`.
281
218
 
282
- ### The advanced view
219
+ `sous vars ask [name]` asks the questions the definitions imply and stores the answers.
283
220
 
284
- Tab opens the advanced view of the same question: every fact about the variable, laid out by the
285
- same renderer `sous vars show` uses, and a menu for changing where the answer goes.
221
+ | Invocation | What it does |
222
+ |------------|--------------|
223
+ | `sous vars ask` | Asks only what is unanswered, or what no longer fits its definition |
224
+ | `sous vars ask <name>` | Asks everything the name covers; see the forms below |
225
+ | `sous vars ask --repo <name>` | Asks every variable one repository publishes |
226
+ | `sous vars ask --namespace <name>` | Asks every variable one namespace publishes |
227
+ | `sous vars ask --var <name>` | Asks one variable; repeat the flag for each one |
228
+ | `sous vars ask --all` | Asks every variable again, including the ones already answered |
229
+ | `sous vars ask --accept-first` | When the name matches several things, takes the first one listed |
230
+ | `sous vars ask --file <path>` | Reads definitions from a standalone definitions file |
231
+ | `sous vars ask --answer <name>=<value>` | Answers one question ahead of time; repeat it for each answer |
232
+ | `sous vars ask --answers-file <path>` | Reads answers from a YAML or JSON file of `name: value` pairs |
233
+ | `sous vars ask --dry-run` | Reports what would be asked and written, without writing anything |
234
+ | `sous vars ask --non-interactive` | Never asks; fails instead, naming the environment variable or flag that would answer each question |
286
235
 
287
- ```term
288
- [Advanced Variable Settings]
236
+ `<name>` is a reference, resolved like any sous ref; anything larger than a variable asks all its questions:
289
237
 
290
- Question 1 of 4: taskFileRoot
238
+ ```bash
239
+ sous vars ask taskFileRoot # one variable, by the name its recipe gave it
240
+ sous vars ask SOUS_VAR_TASK_FILE_ROOT # the same one, by a name in use that answers it
241
+ sous vars ask workflow/task-files.taskFileRoot # the same one, spelled out in full
242
+ sous vars ask task-files # every question one recipe asks
243
+ sous vars ask sous-recipes # every question that repository's recipes ask
244
+ ```
291
245
 
292
- This recipe mandates the creation of task files that are stored locally and, in
293
- general, should not be committed. This setting dictates the path in which agents
294
- will store and search for your task files. The default value stores task files in
295
- the project's .sous directory, but you can specify any local path, either relative
296
- to the project root or absolute.
246
+ Matching is case-sensitive, and an environment variable name resolves when a definition declares it or an
247
+ env file sets it. `--repo`, `--namespace` and `--var` say which kind is meant, each narrowing what the one
248
+ before left, so `sous vars ask --namespace workflow apiUrl` asks about that namespace's `apiUrl` alone. When
249
+ a name matches several things, sous lists them and asks; `--accept-first` takes the first, and a run with no
250
+ terminal fails naming it.
297
251
 
298
- default : .sous/tasks
299
- example : ~/my-task-files
300
- required-by : workflow/task-files https://example.com/owner/recipes/workflow/task-files
301
- defined-by : workflow/task-files https://example.com/owner/recipes/workflow/task-files
302
- storage-path: /home/you/project/.sous/.env
303
- stored-as : SOUS_VAR_TASK_FILE_ROOT
304
- constraints : • must be a value of the type path (type: path)
305
- • must be at least 1 character long (minLength: 1)
252
+ ?> Bare `sous vars` is shorthand for `vars list`, and `sous vars <name>` for `vars show <name>`, which prints
253
+ the same report (`vars show` spells the storage path out in full); `sous var list`, `sous var show` and
254
+ `sous var ask` work too. A variable named `list`, `show` or `ask` collides with the subcommand, so reach it
255
+ the long way: `sous vars show ask`.
306
256
 
307
- ? What would you like to do?
308
- Return to value entry
309
- Change the storage file
310
- Change the stored variable name
311
- ```
257
+ ### Answering ahead of the questions
312
258
 
313
- `required-by` names the recipe you subscribed to whose closure pulled this variable in, and spells
314
- out the chain when it arrived through a dependency; `defined-by` names the recipe that declares
315
- the definition. Both carry the location beside the recipe key, in muted grey: a repository URL with
316
- the recipe's folder for a hosted repository, and a filesystem path for one read from this machine.
259
+ `--answer <name>=<value>` answers a question before it is asked and repeats as often as needed, and
260
+ `--answers-file <path>` reads the same pairs from YAML or JSON; an `--answer` wins over the same name in the
261
+ file. Pair either with `--dry-run` first, as in
262
+ `sous vars ask --var qaScratchDir --answer qaScratchDir=/var/tmp/qa-review --dry-run`. Both work the same way
263
+ on `sous subscription add`; see [Answer the questions](repositories-consuming.md#answer-the-questions).
317
264
 
318
- Changing the stored name offers every rung the ladder looks up, with the one sous would use
319
- already selected, plus a name of your own; a name the ladder would never look at is bound with a
320
- mapping record, so resolution still finds it. Changing the storage file offers the committed
321
- `.sous/.env` and the gitignored `.sous/.env.local`. Once anything has changed, the first menu item
322
- becomes "Save changes and return to value entry" and a "Discard changes" item joins it.
265
+ Every supplied answer is checked before anything is written, so a value that does not fit fails the run
266
+ naming the constraint, and an unknown name fails naming every variable in play. An answer replacing a stored
267
+ one is written where that answer lives, and a value the shell supplies is never rewritten:
323
268
 
324
- A secret, or a variable the publisher declared machine-specific, may still be pointed at the
325
- committed file. Sous does not prevent it; it says plainly that the value would enter your git
326
- history and asks you to confirm, which is informed consent rather than a locked door. Returning to
327
- the value question prints its view again, with the updated `stored-as` and `storage-path`
328
- facts.
269
+ ```text
270
+ Answers stored:
271
+ qaParallelAgents: 4 SOUS_VAR_QA_PARALLEL_AGENTS in .env
272
+ replaced the answer already there: 99
273
+ SOUS_VAR_QA_PARALLEL_AGENTS is set in your shell environment and answers
274
+ this variable first; unset it for the stored answer to take effect
275
+ ```
329
276
 
330
- Every run ends with the same three-part report: what was inherited from an answer already in
331
- scope, what was stored and under which name in which file, and what was left unanswered and why.
277
+ ## Answer without a terminal
332
278
 
333
- Subscribing runs the same machinery, so in normal use you rarely invoke `vars ask` by hand;
334
- reach for it after editing an env file, after a recipe upgrade tightened a constraint, or when
335
- you want to re-answer something deliberately with `--all`.
279
+ Sous never hangs waiting on a prompt it cannot show; what counts as "no terminal" is listed under
280
+ [Run sous in CI, or from an agent](repositories-consuming.md#run-sous-in-ci-or-from-an-agent). Such a run
281
+ fails on the unanswered required variables, naming every environment variable that would satisfy each one:
336
282
 
337
- ### Answering ahead of the questions
283
+ ```text
284
+ Error: 1 variable still needs an answer, and there is no terminal to ask on.
338
285
 
339
- `--answer <name>=<value>` answers a question before it is asked, and repeats for as many answers
340
- as there are questions; `--answers-file <path>` reads the same pairs from a YAML or JSON file
341
- (comments allowed), and an `--answer` wins over the same name in the file. Both flags work the
342
- same way on `sous subscription add`, which is where a run with no terminal usually meets them;
343
- [Consuming recipes](repositories-consuming.md#answering-questions-ahead-of-time) walks through
344
- that flow, dry run first.
286
+ Set one of the environment variables listed under each variable, or run
287
+ 'sous vars ask' from a terminal.
345
288
 
346
- ```bash
347
- sous vars ask --answer taskFileRoot=.sous/tasks --answer apiUrl=https://api.example.com
348
- sous vars ask --answers-file ./answers.yaml
289
+ qaTaskRoot (workflow/qa-variables): Where should the review notes be stored?
290
+ SOUS_VAR_WORKFLOW_QA_VARIABLES_QA_TASK_ROOT (recipe scope)
291
+ SOUS_VAR_WORKFLOW_QA_TASK_ROOT (namespace scope)
292
+ SOUS_VAR_QA_TASK_ROOT (shared scope)
349
293
  ```
350
294
 
351
- The name is spelled exactly as the recipe declares it, in camelCase, or as its full
352
- `namespace/recipe.name` key; everything after the first `=` is the answer. Every supplied answer
353
- is checked against its definition before anything is written, so a value that does not fit fails
354
- the run naming the constraint and the publisher's example, and a name no recipe declares fails
355
- naming every variable that is in play. An answer for a variable that already has one replaces it,
356
- where that answer lives, and the report says what it replaced. Whatever is left over is asked for
357
- as usual.
295
+ Set one of those names in your pipeline and the build proceeds. For an answer the whole team shares, and
296
+ nothing about it sensitive, committing it to `.sous/.env` is simpler: a fresh clone needs no pipeline setup.
358
297
 
359
- ## Without a terminal
298
+ ## How a template reads an answer
360
299
 
361
- Sous never hangs waiting on a prompt it cannot show. A run with no terminal and an unanswered
362
- required variable fails, and the failure names every environment variable that would satisfy it,
363
- most specific first, which is the message a continuous integration log needs to be useful:
300
+ A build lays the answers into the template scope itself. For every variable a subscribed recipe publishes,
301
+ the build walks the ladder above, takes the first value it finds, and adds it to the scope under the
302
+ variable's own name. So once `taskFileRoot` is answered, `{{ taskFileRoot }}` renders in the recipe's own
303
+ skills and in any template this project compiles, and `${taskFileRoot}` works in `_vars` and every other
304
+ config value. Nothing has to be mapped by hand.
364
305
 
365
- ```text
366
- One variable still needs an answer, and there is no terminal to ask on.
306
+ The answers sit under your config, not over it. The scope a template renders with is assembled in this
307
+ order, each layer overriding the one before:
367
308
 
368
- Set one of the environment variables listed under each variable, or run
369
- 'sous vars ask' from a terminal.
309
+ 1. The auto-injected `sous*` variables.
310
+ 2. The recipe answers, found through the ladder.
311
+ 3. Your `_env` block.
312
+ 4. Your `_vars` block.
370
313
 
371
- apiUrl (workflow/task-files): Where does the API live?
372
- SOUS_VAR_WORKFLOW_TASK_FILES_API_URL (recipe scope)
373
- SOUS_VAR_WORKFLOW_API_URL (namespace scope)
374
- SOUS_VAR_API_URL (shared scope)
375
- ```
314
+ So a project that already carries an answer in `_vars`, or maps one through `_env`, keeps rendering exactly
315
+ what it did; the answer in the env files is simply shadowed, and `sous vars list` still reports it.
316
+
317
+ When no rung answers, the definition's own `default` is what renders, because the description a publisher
318
+ writes promises what the default does. A required variable with no answer and no default renders as an
319
+ empty string, and the build says so before it compiles, naming each such variable, the recipe that asks for
320
+ it, and `sous vars ask` as the way to answer. The build still succeeds; an unanswered question is
321
+ something to tell you about, not a reason to refuse the rest of the project.
322
+
323
+ ?> Two recipes may ask the same question. Their shared answer renders in both, and in your own templates.
324
+ When the recipe-scoped name gives one of them a different answer, that recipe's own files render its own
325
+ answer while everything else, your templates included, renders the first definition's; `sous vars show`
326
+ tells you which names are in play.
327
+
328
+ An answer is laid in exactly as it is stored: a path stays the string you typed, relative or absolute, and a
329
+ number stays text. A template that needs an absolute path from a relative answer composes one under another
330
+ name in `_vars`, for instance `taskFileDir: "${sousDir}/../${taskFileRoot}"`.
376
331
 
377
- Set one of those names as a secret or a variable in your pipeline and the build proceeds. For an
378
- answer the whole team shares and nothing about it is sensitive, committing it to `.sous/.env` is
379
- simpler still: a fresh clone then needs no pipeline configuration at all.
332
+ The `_env` block is still the way to reach any environment variable no recipe asks about.
380
333
 
381
334
  ## Where to go next
382
335
 
383
336
  - [Consuming recipes](repositories-consuming.md): subscribing, building, and what lands where
384
- - [Authoring a repository](repositories-authoring.md): declaring the definitions this page
385
- resolves
386
- - [Variable definitions](repositories-file-formats.md#variable-definitions): the full field table
337
+ - [Authoring a repository](repositories-authoring.md): declaring the definitions this page resolves
338
+ - [Troubleshooting](repositories-troubleshooting.md): a question sous cannot ask, a pattern that runs out of time
387
339
  - [Command reference](commands.md): every command and flag