@caddis/cli 0.4.22 → 0.4.24
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/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/skills/_registry.md +1 -1
- package/bundles/antigravity-plugin/skills/port-to-github/SKILL.md +64 -3
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Get-PortSource.ps1 +30 -2
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Invoke-PortHistoryClean.ps1 +123 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/New-PortRuntimeManifest.ps1 +258 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Publish-PortToGitHub.ps1 +20 -1
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Test-PortContentPolicy.ps1 +218 -0
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/_registry.md +1 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/SKILL.md +174 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/_template/profile.json +55 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/batch-job/profile.json +68 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/dotnet-service/profile.json +67 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/java-service/profile.json +62 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/node-service/profile.json +66 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/php-db/profile.json +75 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/python-web/profile.json +73 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/static-site/profile.json +87 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/references/config.example.json +18 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/references/profile-contract.md +207 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/references/standard.md +304 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/AdoptCommon.ps1 +812 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/Get-AppInventory.ps1 +236 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/New-AdoptionPlan.ps1 +311 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/Resolve-AppProfile.ps1 +246 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/Test-AppConformance.ps1 +343 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/SKILL.md +64 -3
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Get-PortSource.ps1 +30 -2
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Invoke-PortHistoryClean.ps1 +123 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/New-PortRuntimeManifest.ps1 +258 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Publish-PortToGitHub.ps1 +20 -1
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Test-PortContentPolicy.ps1 +218 -0
- package/bundles/manifest.json +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# The adoption standard
|
|
2
|
+
|
|
3
|
+
`Test-AppConformance.ps1` (phase 3) measures an app against 20 items, in the order below - the
|
|
4
|
+
same order the script adds them in. Each one gets a verdict, never a guess:
|
|
5
|
+
|
|
6
|
+
| Verdict | Means |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `PASS` | Observed true, from the inventory or the profile. |
|
|
9
|
+
| `GAP` | Observed false. The evidence is named. |
|
|
10
|
+
| `ASK` | Not observable from files. A person must answer it. |
|
|
11
|
+
| `ANSWERED` | Was `ASK`; a non-empty answer has been recorded for it (see below). Not a fourth independent state - it only exists once an `ASK` has been answered. |
|
|
12
|
+
|
|
13
|
+
Items marked **blocking** below stop the adoption exactly the same way whether they are `GAP` or
|
|
14
|
+
an unanswered `ASK` - a checklist that only tracks blocking failures and lists open questions as
|
|
15
|
+
notes at the bottom lets those questions be skipped, which is the failure this standard exists to
|
|
16
|
+
prevent.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### 1. `git-repo` - The source is in a git repository
|
|
21
|
+
|
|
22
|
+
**Blocking.**
|
|
23
|
+
|
|
24
|
+
**Decided by:** `PASS` if the source folder contains a `.git` directory (`inventory.git.isRepo`
|
|
25
|
+
is true). `GAP` otherwise, with "no `.git` in the source folder" as the evidence.
|
|
26
|
+
|
|
27
|
+
**Why it is on the list:** an app with no repository has no history and no way to see what
|
|
28
|
+
changed. Adoption starts by importing the folder as one commit - but only once the content
|
|
29
|
+
policy (item 4) is settled, because that first commit decides what is in the history forever.
|
|
30
|
+
|
|
31
|
+
### 2. `remote-on-team-host` - The repository lives on the team's host
|
|
32
|
+
|
|
33
|
+
**Blocking.**
|
|
34
|
+
|
|
35
|
+
**Decided by:** `GAP` if there is no repository at all; `GAP` if a repository exists but has zero
|
|
36
|
+
remotes configured ("this folder is the ONLY copy of the history"); `GAP` if
|
|
37
|
+
`expectedRemotePattern` is set in the config and none of the remote URLs match it; `PASS` if a
|
|
38
|
+
remote exists and either no pattern is configured or one remote matches it.
|
|
39
|
+
|
|
40
|
+
**Why it is on the list:** a repository with no remote, or hosted on a personal account, is one
|
|
41
|
+
disk failure or one departure away from disappearing. `expectedRemotePattern` is what lets this
|
|
42
|
+
check tell "the team's host" from "somewhere else" - without it configured, having any remote at
|
|
43
|
+
all is enough to pass, which is a weaker check than it looks like.
|
|
44
|
+
|
|
45
|
+
### 3. `canonical-branch` - Which branch is the truth is written down
|
|
46
|
+
|
|
47
|
+
**Blocking.**
|
|
48
|
+
|
|
49
|
+
**Decided by:** `ASK` when the app has a repository AND (it has no remote, OR it has more than 3
|
|
50
|
+
local branches). `PASS` otherwise. Note the edge case this produces: when there is no repository
|
|
51
|
+
at all, this item reads `PASS` (evidence: "no repository") even though `git-repo` itself is a
|
|
52
|
+
`GAP` for the same app - there being no branches yet is not treated as a question to ask.
|
|
53
|
+
|
|
54
|
+
**Why it is on the list:** with more than a handful of local branches and no shared remote to
|
|
55
|
+
compare against, which one is actually deployed cannot be read from the files. Wiring CI to the
|
|
56
|
+
wrong branch before this is answered means CI builds and tests code nobody ships.
|
|
57
|
+
|
|
58
|
+
### 4. `source-only` - The repository holds source, not runtimes, data or dependencies
|
|
59
|
+
|
|
60
|
+
**Blocking.**
|
|
61
|
+
|
|
62
|
+
**Decided by:** `GAP` if either of these is true: a top-level folder that IS git-tracked also
|
|
63
|
+
matches an excludable content category (dependency, runtime, build-output, cache, db-data,
|
|
64
|
+
archive, data-export, or media - `log` is exempted here) - i.e. something was committed that
|
|
65
|
+
should not have been; or a top-level folder is git-UNTRACKED, is not `.git`, and is at least
|
|
66
|
+
`-UntrackedThresholdMB` (default 10 MB). Otherwise `PASS` - except a `PASS` is downgraded to
|
|
67
|
+
`ASK` if any top-level folder was scanned with `-SkipFolder` (see "Coverage" below).
|
|
68
|
+
|
|
69
|
+
**Why it is on the list:** a `.gitignore` added later does not remove what is already committed -
|
|
70
|
+
that needs a history rewrite. And a folder that is neither committed nor ignored is the genuinely
|
|
71
|
+
dangerous state: it sits there doing nothing until one `git add -A` puts it in the history
|
|
72
|
+
forever. A filename pattern cannot find that state; only asking git can.
|
|
73
|
+
|
|
74
|
+
### 5. `no-secrets` - No credential is in the working tree
|
|
75
|
+
|
|
76
|
+
**Blocking.**
|
|
77
|
+
|
|
78
|
+
**Decided by:** `GAP` if the inventory's `secretsSuspected` list is non-empty (files flagged by
|
|
79
|
+
name, e.g. `.env`, `*.pem`, `id_rsa`, `credentials.json`; or by content, e.g. a line shaped like
|
|
80
|
+
`password = "..."` or a live-looking token, with comments, non-literal values, and template
|
|
81
|
+
references excluded). `PASS` if the heuristics found nothing - which the evidence text says
|
|
82
|
+
explicitly is not proof there is nothing.
|
|
83
|
+
|
|
84
|
+
**Why it is on the list:** history keeps a credential, so the fix is to ROTATE it, not delete it.
|
|
85
|
+
Deleting the file in a later commit does not stop anyone who already has a clone, or ever will,
|
|
86
|
+
from reading the old value out of history. Only rotating the credential itself closes that.
|
|
87
|
+
|
|
88
|
+
### 6. `config-separation` - Configuration is outside the code, per environment
|
|
89
|
+
|
|
90
|
+
**Not blocking.**
|
|
91
|
+
|
|
92
|
+
**Decided by:** `PASS` if at least one config file looks like a committed template (matches a
|
|
93
|
+
name like `.env.example`, `*.sample`, `*.template`, or `*.dist`). `ASK` if the inventory found
|
|
94
|
+
zero config files at all. `GAP` if config files exist but none of them look like a template.
|
|
95
|
+
|
|
96
|
+
**Why it is on the list:** hardcoded, per-environment values in committed config are exactly
|
|
97
|
+
where a real credential tends to leak. A committed template proves the separation exists without
|
|
98
|
+
exposing a real value; its absence does not prove the opposite, which is why zero config files
|
|
99
|
+
reads as a question rather than a pass or a fail.
|
|
100
|
+
|
|
101
|
+
### 7. `deps-pinned` - Dependencies are pinned
|
|
102
|
+
|
|
103
|
+
**Not blocking.**
|
|
104
|
+
|
|
105
|
+
**Decided by:** `PASS` if a recognised lock file is present among the manifests
|
|
106
|
+
(`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `poetry.lock`, `Pipfile.lock`,
|
|
107
|
+
`composer.lock`, `Gemfile.lock`, `Cargo.lock`, `go.sum`). `GAP` otherwise.
|
|
108
|
+
|
|
109
|
+
**Why it is on the list:** without a lock file the build is not reproducible, and a broken deploy
|
|
110
|
+
cannot be told apart from a dependency that moved underneath it between two runs.
|
|
111
|
+
|
|
112
|
+
### 8. `tests` - There is an automated test suite
|
|
113
|
+
|
|
114
|
+
**Not blocking.**
|
|
115
|
+
|
|
116
|
+
**Decided by:** `PASS` if the inventory's test-file count is greater than zero (files under a
|
|
117
|
+
`tests`/`spec`/`__tests__` folder, or matching a test-name convention like `*_test.py` or
|
|
118
|
+
`*.spec.ts`). `GAP` otherwise.
|
|
119
|
+
|
|
120
|
+
**Why it is on the list:** CI that runs no tests only proves the code compiles.
|
|
121
|
+
|
|
122
|
+
### 9. `ci-build` - CI builds and tests on every push
|
|
123
|
+
|
|
124
|
+
**Not blocking.**
|
|
125
|
+
|
|
126
|
+
**Decided by:** `PASS` if any CI file was found (a workflow under `.github`/`.gitea`/`.forgejo`,
|
|
127
|
+
or a known pipeline file like `Jenkinsfile`, `azure-pipelines.yml`, `.travis.yml`,
|
|
128
|
+
`appveyor.yml`, `bitbucket-pipelines.yml`). `GAP` if none was found.
|
|
129
|
+
|
|
130
|
+
**Why it is on the list:** without CI, "it works" means "it worked on one machine, once."
|
|
131
|
+
|
|
132
|
+
### 10. `ci-deploy` - Deployment is reproducible, not manual
|
|
133
|
+
|
|
134
|
+
**Not blocking.**
|
|
135
|
+
|
|
136
|
+
**Decided by:** `ASK` if CI files exist (the file LIST cannot say whether that CI actually
|
|
137
|
+
deploys, only that some pipeline exists). `GAP` if there is no CI at all.
|
|
138
|
+
|
|
139
|
+
**Why it is on the list:** a build pipeline is not the same claim as a deploy pipeline. Whether
|
|
140
|
+
pushing to the default branch actually ships the app has to be answered by a person or by reading
|
|
141
|
+
the workflow's job list, not assumed from its presence.
|
|
142
|
+
|
|
143
|
+
### 11. `runtime-manifest` - Anything excluded from the repository can be got back
|
|
144
|
+
|
|
145
|
+
**Not blocking.**
|
|
146
|
+
|
|
147
|
+
**Decided by:** `PASS` if no ignored folder is 100 MB or larger among the folders that were
|
|
148
|
+
scanned. `ASK` if one or more ignored folders reach that size. A `PASS` is downgraded to `ASK` if
|
|
149
|
+
any top-level folder was scanned with `-SkipFolder` (see "Coverage" below).
|
|
150
|
+
|
|
151
|
+
**Why it is on the list:** excluding a large runtime or dependency tree from the repository is
|
|
152
|
+
correct. It is only SAFE once something says how to get it back - the exact version and where it
|
|
153
|
+
installs from - and a folder nobody looked at cannot be assumed to already have that answer.
|
|
154
|
+
|
|
155
|
+
### 12. `health` - There is a health signal that fails loudly
|
|
156
|
+
|
|
157
|
+
**Not blocking.**
|
|
158
|
+
|
|
159
|
+
**Decided by:** `PASS` if any probed text file contains a line matching `/health`, `/healthz`,
|
|
160
|
+
`/api/health`, or `healthcheck` (case-insensitive). `ASK` if every matched component's profile
|
|
161
|
+
name contains `batch` (a batch job has no HTTP endpoint to check at all). `GAP` otherwise.
|
|
162
|
+
|
|
163
|
+
**Why it is on the list:** a check that returns 200 whatever happens is not a health check.
|
|
164
|
+
Finding the STRING `/health` in the source only proves a route exists - it says nothing about
|
|
165
|
+
whether that route actually fails when the app is broken. That is why the plan step this item
|
|
166
|
+
produces explicitly requires breaking the dependency on purpose and watching the check go red
|
|
167
|
+
before it counts as done.
|
|
168
|
+
|
|
169
|
+
### 13. `run-declared` - How the app runs is declared and version-controlled
|
|
170
|
+
|
|
171
|
+
**Blocking.**
|
|
172
|
+
|
|
173
|
+
**Decided by:** `PASS` if the host's registered services, scheduled tasks, or web sites include
|
|
174
|
+
at least one whose path references this folder (matched on the folder's last path segment,
|
|
175
|
+
against Windows services, scheduled tasks, and IIS sites/applications). `ASK` if nothing on the
|
|
176
|
+
scanned host references it at all.
|
|
177
|
+
|
|
178
|
+
**Why it is on the list:** on a real run, the only "service" `run-declared` found for one app was
|
|
179
|
+
a database engine that happened to be installed inside the app's own folder - not the app's
|
|
180
|
+
process at all. That is why the evidence names each hit individually (`service X`, `task Y`, `site
|
|
181
|
+
Z`): a non-zero count is not proof that any of them is actually running THIS app, and the reader
|
|
182
|
+
has to check, not assume.
|
|
183
|
+
|
|
184
|
+
### 14. `rollback` - There is a way back to the previous version
|
|
185
|
+
|
|
186
|
+
**Not blocking.**
|
|
187
|
+
|
|
188
|
+
**Decided by:** Always `ASK` - the code marks it "not observable from files" unconditionally.
|
|
189
|
+
|
|
190
|
+
**Why it is on the list:** whether the previous release can be restored, by what exact command,
|
|
191
|
+
and whether that command has ever actually been run, is not written down anywhere a scan can
|
|
192
|
+
read. It can only be answered by a person, and then it has to be exercised at least once to be
|
|
193
|
+
believed (see the plan step this item produces).
|
|
194
|
+
|
|
195
|
+
### 15. `owner` - The app has a named owner
|
|
196
|
+
|
|
197
|
+
**Blocking.**
|
|
198
|
+
|
|
199
|
+
**Decided by:** Always `ASK` - "not observable from files" unconditionally.
|
|
200
|
+
|
|
201
|
+
**Why it is on the list:** nothing in a repository can say who is accountable for it. Without a
|
|
202
|
+
named owner there is nobody to decide a change and nobody to call when it breaks, so this has to
|
|
203
|
+
reach a person before anything else proceeds.
|
|
204
|
+
|
|
205
|
+
### 16. `data-recovery` - State can be restored from a backup
|
|
206
|
+
|
|
207
|
+
**Not blocking.**
|
|
208
|
+
|
|
209
|
+
**Decided by:** `PASS` if no ignored folder is 100 MB or larger AND the excludable-content
|
|
210
|
+
summary contains no `db-data` category entry. `ASK` otherwise. A `PASS` is downgraded the same
|
|
211
|
+
way as items 4 and 11 if any top-level folder was scanned with `-SkipFolder`.
|
|
212
|
+
|
|
213
|
+
**Why it is on the list:** excluding a database file or a data dump from the repository is
|
|
214
|
+
correct. It is only safe once a backup schedule exists and a restore from it has actually been
|
|
215
|
+
tested - a repository holding schema and migrations is not itself a backup of the data.
|
|
216
|
+
|
|
217
|
+
### 17. `docs-readme` - A README says what it is and how to run it
|
|
218
|
+
|
|
219
|
+
**Not blocking.**
|
|
220
|
+
|
|
221
|
+
**Decided by:** `PASS` if any doc file's name matches `README*` (case-insensitive). `GAP`
|
|
222
|
+
otherwise.
|
|
223
|
+
|
|
224
|
+
**Why it is on the list:** the first two questions a new person asks - what is this, and how do I
|
|
225
|
+
run it - should not require finding and interrupting someone.
|
|
226
|
+
|
|
227
|
+
### 18. `agent-rules` - A rules file the coding agents read
|
|
228
|
+
|
|
229
|
+
**Not blocking.**
|
|
230
|
+
|
|
231
|
+
**Decided by:** `PASS` if a doc file is named exactly `AGENTS.md` or `CLAUDE.md`
|
|
232
|
+
(case-insensitive). `GAP` otherwise.
|
|
233
|
+
|
|
234
|
+
**Why it is on the list:** without it, every coding agent that touches the app re-derives its
|
|
235
|
+
non-obvious rules from scratch, and different agents derive them differently.
|
|
236
|
+
|
|
237
|
+
### 19. `no-junctions` - No junction or symlink inside the tree git manages
|
|
238
|
+
|
|
239
|
+
**Not blocking.**
|
|
240
|
+
|
|
241
|
+
**Decided by:** `PASS` if no reparse point (junction or symlink) was found outside a dependency
|
|
242
|
+
folder (`node_modules`, `.venv`, `venv`, `vendor`). `GAP` otherwise, naming up to three.
|
|
243
|
+
|
|
244
|
+
**Why it is on the list:** git's own recursive remove (for example `git worktree remove --force`)
|
|
245
|
+
FOLLOWS a junction and deletes the TARGET's contents - this was proven directly, not assumed.
|
|
246
|
+
Ordinary Windows delete operations (`Remove-Item -Recurse`, `cmd /c rmdir /s /q`,
|
|
247
|
+
`[IO.Directory]::Delete`) all correctly refuse to follow a junction, which is exactly what makes
|
|
248
|
+
this trap easy to miss: a delete you tested yourself can look completely safe right up until git
|
|
249
|
+
does the deleting instead.
|
|
250
|
+
|
|
251
|
+
### 20. `profile` - The stack is declared, not assumed
|
|
252
|
+
|
|
253
|
+
**Blocking.**
|
|
254
|
+
|
|
255
|
+
**Decided by:** `PASS` if phase 2 matched at least one component to a profile. `GAP` if it
|
|
256
|
+
matched none.
|
|
257
|
+
|
|
258
|
+
**Why it is on the list:** without a declared stack, every later step - runtimes, build, deploy -
|
|
259
|
+
is a guess dressed up as a plan. "No profile matched" means the rules do not describe this app
|
|
260
|
+
yet; it does not mean the app has no stack.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Coverage: a skipped folder cannot pass a check
|
|
265
|
+
|
|
266
|
+
Three items above (`source-only`, `runtime-manifest`, `data-recovery`) can only reach `PASS`
|
|
267
|
+
through `Limit-ByCoverage`, which downgrades a would-be `PASS` to `ASK` whenever any top-level
|
|
268
|
+
folder was scanned with `-SkipFolder`. A folder passed to `-SkipFolder` is recorded as **not
|
|
269
|
+
scanned**, never as empty and never as clean - it is unknown. On a real run, `-SkipFolder tools`
|
|
270
|
+
hid a 16 GB runtime tree, and without this rule the check for "anything excluded can be got back"
|
|
271
|
+
would have reported `PASS` on a folder nobody had actually looked at. Coverage-dependent checks
|
|
272
|
+
stay open until every folder has been scanned, or the operator explicitly accepts a partial
|
|
273
|
+
inventory (`-AllowPartial` in phase 1).
|
|
274
|
+
|
|
275
|
+
## Answers and waivers
|
|
276
|
+
|
|
277
|
+
**`-Answer id=text`** (phase 3) records a non-empty answer for an item that is currently `ASK`.
|
|
278
|
+
The tool does not judge the content - any non-empty text satisfies it - and the verdict becomes
|
|
279
|
+
`ANSWERED`. Answers persist in `answers.json` inside the app's output folder and are merged with
|
|
280
|
+
any new `-Answer` arguments on every run, so recording one once is enough for every later
|
|
281
|
+
re-check.
|
|
282
|
+
|
|
283
|
+
**`-Waive <id>`** records an accepted exception for an item that is not already `PASS`. It sets a
|
|
284
|
+
`waived` flag; **it never rewrites the verdict itself**. A waived `GAP` is still recorded as
|
|
285
|
+
`GAP`, permanently, with `waived: true` next to it - six months later, that difference between "we
|
|
286
|
+
fixed it" and "we accepted the risk" is the whole value of the record. A waived item is excluded
|
|
287
|
+
from the blocking counts below, so it can no longer stop the exit code, but the report still shows
|
|
288
|
+
what it really is.
|
|
289
|
+
|
|
290
|
+
## Exit codes
|
|
291
|
+
|
|
292
|
+
| Code | Means |
|
|
293
|
+
|---|---|
|
|
294
|
+
| `0` | Every blocking item is `PASS`, `ANSWERED`, or waived. |
|
|
295
|
+
| `1` | At least one blocking item is `GAP` and not waived. Checked first - if both a blocking `GAP` and a blocking unanswered `ASK` exist at once, exit code `1` wins. |
|
|
296
|
+
| `2` | No blocking `GAP` remains, but at least one blocking item is still an unanswered `ASK` and not waived. |
|
|
297
|
+
|
|
298
|
+
The eight blocking items, as the code actually enforces them, are: `git-repo`,
|
|
299
|
+
`remote-on-team-host`, `canonical-branch`, `source-only`, `no-secrets`, `run-declared`, `owner`,
|
|
300
|
+
and `profile`. Which items block is decided in the script, by the `Blocking` argument to each
|
|
301
|
+
item, and not by the project config. An earlier draft of the config carried a `blockingItems`
|
|
302
|
+
list that nothing read and that had drifted out of step with this set; it was removed from the
|
|
303
|
+
code and from `config.example.json` together, rather than documented as a thing that looks
|
|
304
|
+
settable and is not.
|