@wirecat/cli-base 0.40.4 → 0.41.0
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 +1 -0
- package/docs/standards/README.md +111 -0
- package/docs/standards/accounts.md +148 -0
- package/docs/standards/architecture.md +214 -0
- package/docs/standards/ci.md +159 -0
- package/docs/standards/commands.md +302 -0
- package/docs/standards/docs.md +214 -0
- package/docs/standards/layout.md +128 -0
- package/docs/standards/lifecycle.md +143 -0
- package/docs/standards/mcp.md +207 -0
- package/docs/standards/new-cli.md +111 -0
- package/docs/standards/output.md +256 -0
- package/docs/standards/permissions.md +295 -0
- package/docs/standards/releases.md +191 -0
- package/docs/standards/security.md +260 -0
- package/docs/standards/store.md +177 -0
- package/docs/standards/style.md +119 -0
- package/docs/standards/testing.md +222 -0
- package/docs/standards/toolchain.md +232 -0
- package/package.json +4 -3
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Commands
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
How a command is shaped: its path and the verb and resource words it uses, flags and arguments, the effects and
|
|
6
|
+
lifetime it declares, the entity it prints, the query language for lists, the `mcp` setting and help text.
|
|
7
|
+
|
|
8
|
+
Rule ids on this page are `WC-CMD-<n>`. How to read a rule, how ids work and which rule wins are in the
|
|
9
|
+
[index](README.md). What a command prints is on the [output](output.md) page. How long it runs and how it ends is on
|
|
10
|
+
the [lifecycle](lifecycle.md) page.
|
|
11
|
+
|
|
12
|
+
The words themselves are not copied here. They live in the cli-base registries, and `cli-dev lint` reads them from
|
|
13
|
+
there:
|
|
14
|
+
|
|
15
|
+
- verbs, views, resources and root words: [`src/standards/vocabulary.ts`](https://github.com/WireCatLabs/cli-base/blob/main/src/standards/vocabulary.ts);
|
|
16
|
+
- flags with one meaning in every CLI, and the option roles: [`src/standards/flags-how.ts`](https://github.com/WireCatLabs/cli-base/blob/main/src/standards/flags-how.ts)
|
|
17
|
+
and [`src/standards/flags-what.ts`](https://github.com/WireCatLabs/cli-base/blob/main/src/standards/flags-what.ts);
|
|
18
|
+
- the lint rules: [`src/standards/lint.ts`](https://github.com/WireCatLabs/cli-base/blob/main/src/standards/lint.ts); the audit rules and the required roots:
|
|
19
|
+
[`src/standards/audit.ts`](https://github.com/WireCatLabs/cli-base/blob/main/src/standards/audit.ts).
|
|
20
|
+
|
|
21
|
+
A "messenger CLI" below is one of the CLIs that share cli-core's messenger commands: tg, max and wa. Their option
|
|
22
|
+
catalogue and parity manifest stay in cli-core
|
|
23
|
+
[`docs/dev/STANDARD.md`](https://github.com/WireCatLabs/cli-core/blob/main/docs/dev/STANDARD.md).
|
|
24
|
+
|
|
25
|
+
## Paths and words
|
|
26
|
+
|
|
27
|
+
### WC-CMD-1 · Build every path as resource first, then a verb or a view
|
|
28
|
+
|
|
29
|
+
- **Why:** an agent that knows two commands can guess the third.
|
|
30
|
+
- **Enforced by:** `unknown-verb` · `unknown-resource`.
|
|
31
|
+
- **Example:** `<tool> [profile] [namespace] <resource> [subresource ...] <verb-or-view> [arguments]`, such as
|
|
32
|
+
`tg work chats members list <chat>`. A collection is plural (`chats`). A thing a profile has exactly one of is
|
|
33
|
+
singular (`account`). A group is never a verb.
|
|
34
|
+
- **Exceptions:** a root word stands alone (`doctor`, `upgrade`). Generated commands (`bot api`) mirror the official
|
|
35
|
+
API's names.
|
|
36
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-48, NEED-417.
|
|
37
|
+
|
|
38
|
+
### WC-CMD-2 · Take every verb, view, resource and root word from the cli-base vocabulary
|
|
39
|
+
|
|
40
|
+
- **Why:** one word has one meaning in every CLI, so `show` never means "list" somewhere else.
|
|
41
|
+
- **Enforced by:** `unknown-verb` · `unknown-resource`.
|
|
42
|
+
- **Example:** `show`, not `get` or `view`. Groups and channels are both `chats`. A new word goes into the registry in
|
|
43
|
+
its own pull request first, and the owner approves the wording. Then the command uses it.
|
|
44
|
+
- **Exceptions:** none.
|
|
45
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-199, NEED-417.
|
|
46
|
+
|
|
47
|
+
### WC-CMD-3 · Give a group without a leaf only its help, never a hidden default action
|
|
48
|
+
|
|
49
|
+
- **Why:** a group that runs something when its leaf is missing does work nobody asked for.
|
|
50
|
+
- **Enforced by:** `cli-core: src/cli/program.test.ts`.
|
|
51
|
+
- **Example:** `tg chats` prints the commands under `chats`. At a terminal the help goes to stderr and the exit code
|
|
52
|
+
is 2. In machine mode the answer is a `validation_error` that names `--help`.
|
|
53
|
+
- **Exceptions:** none.
|
|
54
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
55
|
+
|
|
56
|
+
### WC-CMD-4 · Add a root word only when no resource or namespace can hold the command
|
|
57
|
+
|
|
58
|
+
- **Why:** every root word is one more place an agent must look.
|
|
59
|
+
- **Enforced by:** `unknown-verb`.
|
|
60
|
+
- **Example:** statistics are `stats messages top`, not a new root `top`. The pull request that adds a root says which
|
|
61
|
+
group could not hold it and which commands it groups. One new report never earns a root of its own.
|
|
62
|
+
- **Exceptions:** none.
|
|
63
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
64
|
+
|
|
65
|
+
### WC-CMD-5 · Give every CLI the required root commands
|
|
66
|
+
|
|
67
|
+
- **Why:** agents and the cross-CLI audit start from the same entry points in every CLI.
|
|
68
|
+
- **Enforced by:** `required-root` (weekly audit; it does not block a pull request yet).
|
|
69
|
+
- **Example:** `commands`, `doctor`, `skill show`, `skill install`, `mcp`, `upgrade`.
|
|
70
|
+
- **Exceptions:** none.
|
|
71
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
72
|
+
|
|
73
|
+
### WC-CMD-6 · Give one action or one report one leaf command
|
|
74
|
+
|
|
75
|
+
- **Why:** an option that turns a command into a different report hides that report from anyone reading the tree.
|
|
76
|
+
- **Enforced by:** review only.
|
|
77
|
+
- **Example:** ranking is `stats messages top`, not a ranking mode on `search messages`. Options choose the scope,
|
|
78
|
+
measure, order or format of the same action. No command both shows and changes something.
|
|
79
|
+
- **Exceptions:** a documented preparatory fetch such as `--sync-first`. It keeps its own permissions and reports
|
|
80
|
+
its outcome.
|
|
81
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
82
|
+
|
|
83
|
+
### WC-CMD-7 · Never let two commands overlap
|
|
84
|
+
|
|
85
|
+
- **Why:** an agent that learns the narrow command never finds the wide one.
|
|
86
|
+
- **Enforced by:** review only.
|
|
87
|
+
- **Example:** a command whose main result is a search sits under `search <resource>` (`search all`,
|
|
88
|
+
`search messages`), and every statistic under `stats <resource>`. A narrower scope is an option or a resource of
|
|
89
|
+
the same command, not a sibling elsewhere. There is no `messages search` beside `search messages`.
|
|
90
|
+
- **Exceptions:** none.
|
|
91
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-846, NEED-847, NEED-1087.
|
|
92
|
+
|
|
93
|
+
### WC-CMD-8 · Keep no aliases: a renamed path stops working in the release that renames it
|
|
94
|
+
|
|
95
|
+
- **Why:** two names for one command split the docs, the tests and what agents learn.
|
|
96
|
+
- **Enforced by:** review only.
|
|
97
|
+
- **Example:** the release notes give the old and the new path under "Changed — may break callers". Help, completion,
|
|
98
|
+
generated pages, the skill, MCP names and permission keys move in the same change.
|
|
99
|
+
- **Exceptions:** none.
|
|
100
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-417.
|
|
101
|
+
|
|
102
|
+
### WC-CMD-9 · Answer the naming questions in the pull request before you build a new command
|
|
103
|
+
|
|
104
|
+
- **Why:** a name is cheap to change before code, docs and agents depend on it.
|
|
105
|
+
- **Enforced by:** review only.
|
|
106
|
+
- **Example:** the pull request says what the user wants and which resource owns it, whether it is a new action or
|
|
107
|
+
an option of an existing one, how its path maps to its MCP name, what it answers and what it may change, and what
|
|
108
|
+
it moves or replaces.
|
|
109
|
+
- **Exceptions:** none.
|
|
110
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-417.
|
|
111
|
+
|
|
112
|
+
## Flags and arguments
|
|
113
|
+
|
|
114
|
+
### WC-CMD-10 · Use a vocabulary flag for its meaning, and give one flag one kind of value everywhere
|
|
115
|
+
|
|
116
|
+
- **Why:** `--limit` must mean "how many items" in every command of every CLI.
|
|
117
|
+
- **Enforced by:** `flag-synonym` · `flag-two-meanings` · `same-flag-meaning` (weekly audit; it does not block a pull
|
|
118
|
+
request yet).
|
|
119
|
+
- **Example:** `--limit <n>`, never `--max`, `--take` or `--count`. `--cursor <cursor>`, never `--offset`. How much
|
|
120
|
+
to read is `--limit`, `--page` and `--page-size`. A count of pages is never an option.
|
|
121
|
+
- **Exceptions:** generated commands (`bot api`) are exempt from the naming rules.
|
|
122
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
123
|
+
|
|
124
|
+
### WC-CMD-11 · Name an option in plain words, with the kind of value in the name
|
|
125
|
+
|
|
126
|
+
- **Why:** a wire field name means nothing to a person, and an option that guesses its value's kind guesses wrong.
|
|
127
|
+
- **Enforced by:** review only.
|
|
128
|
+
- **Example:** `--operation-id`, not `--cid`. `--before-id` takes a message id, `--after-time` a time, `--before-n` a
|
|
129
|
+
count: two kinds are two options. A length of time is a `<duration>` with a unit (`500ms`, `30s`, `2m`, `4h`, `1d`),
|
|
130
|
+
never a bare number.
|
|
131
|
+
- **Exceptions:** none.
|
|
132
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
133
|
+
|
|
134
|
+
### WC-CMD-12 · Give every option a role from the vocabulary, and filter a list with its query
|
|
135
|
+
|
|
136
|
+
- **Why:** a filter flag per field grows without end and differs between CLIs. One query language does not.
|
|
137
|
+
- **Enforced by:** `option-role` · `filter-flag`.
|
|
138
|
+
- **Example:** the roles are paging, output, sort, mode, action, budget, scope, target, value and query. A list of open
|
|
139
|
+
tasks is `tasks list state:open`, not `tasks list --state open`.
|
|
140
|
+
- **Exceptions:** `scope` flags pick accounts, providers or sources. They stay flags, because a scope is never picked
|
|
141
|
+
for you.
|
|
142
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
143
|
+
|
|
144
|
+
### WC-CMD-13 · Name arguments with a singular noun of the thing they take
|
|
145
|
+
|
|
146
|
+
- **Why:** the same thing has the same placeholder in every command, so an agent knows what to pass.
|
|
147
|
+
- **Enforced by:** review only.
|
|
148
|
+
- **Example:** messenger CLIs use `<chat>`, `<message>`, `<person>`, `<text>`, `<link>`, `<file>`, `<job>` and `<task>`.
|
|
149
|
+
- **Exceptions:** none.
|
|
150
|
+
- **Applies to:** all CLIs; the fixed list applies to messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
151
|
+
|
|
152
|
+
### WC-CMD-14 · Keep the shared global flags at their meaning
|
|
153
|
+
|
|
154
|
+
- **Why:** a script that works with one CLI works with the next.
|
|
155
|
+
- **Enforced by:** review only.
|
|
156
|
+
- **Example:** `--json` and `--jsonl` choose machine output. `--quiet` turns diagnostics off, but a failure is still
|
|
157
|
+
said. `-v` and `-vv` add detail, `-V` prints the version, `--trace` writes request diagnostics, `--debug` adds the
|
|
158
|
+
whole failure record. `--timeout <duration>` bounds the command. `--no-input` never prompts. `--dry-run` previews.
|
|
159
|
+
`--yes` answers the question an `ask` permission level puts before a write.
|
|
160
|
+
- **Exceptions:** none.
|
|
161
|
+
- **Applies to:** CLIs that run on cli-core's `run()`. **Since:** cli-base 0.41.0. **Rulings:** NEED-125.
|
|
162
|
+
|
|
163
|
+
### WC-CMD-15 · Never prompt when no person can answer
|
|
164
|
+
|
|
165
|
+
- **Why:** a prompt in a pipe or under an agent waits for ever.
|
|
166
|
+
- **Enforced by:** review only.
|
|
167
|
+
- **Example:** `--no-input`, `--json`, `--jsonl`, a `CI` variable or a stdout that is not a terminal each switch prompts
|
|
168
|
+
and browser logins off. Data piped on stdin for an explicit command still works. Buffered stdin stops at
|
|
169
|
+
`--max-input-bytes` (16 MiB by default).
|
|
170
|
+
- **Exceptions:** none. `--yes` confirms a write; it never lifts a `deny`
|
|
171
|
+
([WC-PERM-10](permissions.md#wc-perm-10--a-flag-confirms-it-never-grants)).
|
|
172
|
+
- **Applies to:** CLIs that run on cli-core's `run()`. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
173
|
+
|
|
174
|
+
### WC-CMD-16 · Offer only ids in shell completion, with titles as their descriptions, from local data
|
|
175
|
+
|
|
176
|
+
- **Why:** the shell calls completion on every Tab, so it must be fast and must not touch the network.
|
|
177
|
+
- **Enforced by:** review only.
|
|
178
|
+
- **Example:** `tg messages show <Tab>` offers chat ids, each with the chat's title beside it. The values come from a
|
|
179
|
+
local cache through `suggest` in `@wirecat/cli-base/completion`.
|
|
180
|
+
- **Exceptions:** none.
|
|
181
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-270.
|
|
182
|
+
|
|
183
|
+
## What a command declares
|
|
184
|
+
|
|
185
|
+
Each of these is set with `annotate(command, { … })` from `@wirecat/cli-base/commands` and appears in
|
|
186
|
+
`commands --json`.
|
|
187
|
+
|
|
188
|
+
### WC-CMD-17 · Declare `effects` on every command
|
|
189
|
+
|
|
190
|
+
- **Why:** retries and unasked next steps are judged on effects, and a command without them counts as a write.
|
|
191
|
+
- **Enforced by:** `cli-core: src/cli/effects.test.ts` · `cli-memo: src/effects.test.ts`.
|
|
192
|
+
- **Example:** `annotate(send, { effects: { remote: "write", local: "write" } })`. Each side is `none`, `read` or
|
|
193
|
+
`write`. A sync that reads the service and writes the local store is `{ remote: "read", local: "write" }`.
|
|
194
|
+
- **Exceptions:** none. Without `effects`, the error handler never marks a failure safe to retry, and a next step
|
|
195
|
+
that runs the command is never safe to run unasked.
|
|
196
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
197
|
+
|
|
198
|
+
### WC-CMD-18 · Declare `lifetime` and `cooperative` instead of matching command names
|
|
199
|
+
|
|
200
|
+
- **Why:** the runner decides the deadline, the signal handling and the exit code from the metadata, never from the
|
|
201
|
+
name, so a renamed command keeps its behaviour.
|
|
202
|
+
- **Enforced by:** review only.
|
|
203
|
+
- **Example:** `annotate(serve, { lifetime: "persistent" })` for a command that runs until stopped,
|
|
204
|
+
`annotate(login, { lifetime: "interactive" })` for a login that waits for a person, `cooperative: true` for a long
|
|
205
|
+
fetch that stops at its own pace. A runner reads them with `metaOf(command)`. What each does at run time is on the
|
|
206
|
+
[lifecycle](lifecycle.md) page.
|
|
207
|
+
- **Exceptions:** none.
|
|
208
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
209
|
+
|
|
210
|
+
### WC-CMD-19 · Declare the entity a command answers with
|
|
211
|
+
|
|
212
|
+
- **Why:** field projection, the query fields and the cross-CLI schema check all start from the entity.
|
|
213
|
+
- **Enforced by:** review only.
|
|
214
|
+
- **Example:** `annotate(list, { output: { entity: "meeting" } })`, or `{ entity: "contact", view: "profile" }` for a
|
|
215
|
+
view of one.
|
|
216
|
+
- **Exceptions:** a command whose answer is not about the owner's data, such as `commands --json` or a `--dry-run`
|
|
217
|
+
preview.
|
|
218
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
219
|
+
|
|
220
|
+
### WC-CMD-20 · Give a list one query over its entity's fields, as its last argument
|
|
221
|
+
|
|
222
|
+
- **Why:** one query language for every list means an agent learns filtering once.
|
|
223
|
+
- **Enforced by:** `list-without-query` · `query-example`.
|
|
224
|
+
- **Example:** `tasks list state:open priority>1`. The list takes `[query...]` last. The fields come from the entity's
|
|
225
|
+
Valibot schema through `queryFieldsOf`, and every field example and default term parses and validates. The list
|
|
226
|
+
filters where its data lives, in the store or at the provider, never in memory.
|
|
227
|
+
- **Exceptions:** none.
|
|
228
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
229
|
+
|
|
230
|
+
### WC-CMD-21 · Set `mcp: false` on a command or group that MCP servers must not offer
|
|
231
|
+
|
|
232
|
+
- **Why:** the MCP tools are generated from the command tree, so the tree says what stays off it.
|
|
233
|
+
- **Enforced by:** `src/commands/commands.test.ts`.
|
|
234
|
+
- **Example:** `annotate(flood, { mcp: false })` keeps `flood` and every command under it off MCP servers. The CLI
|
|
235
|
+
still runs it. `commands --json` shows `mcp: false` on that command and each one below, and leaves the key out
|
|
236
|
+
everywhere else. Which commands get it is on the [MCP](mcp.md) page.
|
|
237
|
+
- **Exceptions:** cli-base has had the field on commands and groups since 0.40.4; it has none on options yet.
|
|
238
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-1411.
|
|
239
|
+
|
|
240
|
+
## Help and discovery
|
|
241
|
+
|
|
242
|
+
### WC-CMD-22 · Describe every command, argument and option, with a summary of at most 80 characters
|
|
243
|
+
|
|
244
|
+
- **Why:** help and `commands --json` are where people and agents learn the command.
|
|
245
|
+
- **Enforced by:** `missing-command-help` · `missing-argument-help` · `missing-option-help` · `command-help-too-long`.
|
|
246
|
+
- **Example:** `.description("stored meetings, newest first")`.
|
|
247
|
+
- **Exceptions:** none.
|
|
248
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
249
|
+
|
|
250
|
+
### WC-CMD-23 · Write help as the user sees the command: one line, lower case, no full stop, no internal term
|
|
251
|
+
|
|
252
|
+
- **Why:** a wire field, a port or an adapter name means nothing to the person reading help.
|
|
253
|
+
- **Enforced by:** review only.
|
|
254
|
+
- **Example:** an option says what it changes and its unit: `--pause <duration>` "wait this long between pages".
|
|
255
|
+
- **Exceptions:** none.
|
|
256
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
257
|
+
|
|
258
|
+
### WC-CMD-24 · Give every leaf command one to three examples that run as written
|
|
259
|
+
|
|
260
|
+
- **Why:** an example an agent copies must work.
|
|
261
|
+
- **Enforced by:** `command-example-count` · `faulty-help-example-parameters`.
|
|
262
|
+
- **Example:** `annotate(list, { examples: ["max chats list --limit 20"] })`. Each example starts with the tool, runs
|
|
263
|
+
its own command, and gives only arguments and options that command takes.
|
|
264
|
+
- **Exceptions:** generated commands (`bot api`) need no examples.
|
|
265
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
266
|
+
|
|
267
|
+
### WC-CMD-25 · Give a shared command and a shared option the same sentence in every CLI of its group
|
|
268
|
+
|
|
269
|
+
- **Why:** they are one command, so they read as one.
|
|
270
|
+
- **Enforced by:** `same-path-contract` (weekly audit; it does not block a pull request yet).
|
|
271
|
+
- **Example:** the shared factory in cli-core writes the description. A CLI changes it only to name its messenger.
|
|
272
|
+
- **Exceptions:** a sentence may differ where it names something the messenger's own way. The option catalogue in
|
|
273
|
+
cli-core says why.
|
|
274
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
275
|
+
|
|
276
|
+
### WC-CMD-26 · Print the command tree with cli-base's `commandsDocument`, and keep every key snake_case
|
|
277
|
+
|
|
278
|
+
- **Why:** agents, `cli-dev lint` and the audit read one shape from every CLI.
|
|
279
|
+
- **Enforced by:** `key-casing`.
|
|
280
|
+
- **Example:** `max commands --json` prints `{ cli, version, contract, description, global_options, … }`.
|
|
281
|
+
`max commands search messages --json` describes one command and adds `scope` and `inherited_options`. An unknown
|
|
282
|
+
or hidden path is a `validation_error` (exit 2) that lists the valid commands at that level.
|
|
283
|
+
- **Exceptions:** none.
|
|
284
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
285
|
+
|
|
286
|
+
### WC-CMD-27 · Refuse a command path that arrived as one word
|
|
287
|
+
|
|
288
|
+
- **Why:** a shell loop that quotes `"projects add"` would otherwise get the root help and no reason.
|
|
289
|
+
- **Enforced by:** `src/commands/commands.test.ts` · `cli-memo: src/program.test.ts`.
|
|
290
|
+
- **Example:** call `refuseJoinedPath(program, args)` before `parseAsync`. It throws a `validation_error` that names
|
|
291
|
+
the words to type.
|
|
292
|
+
- **Exceptions:** none. Only memo calls `refuseJoinedPath` today; cli-core and the other CLIs do not yet.
|
|
293
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
294
|
+
|
|
295
|
+
## Sources
|
|
296
|
+
|
|
297
|
+
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
298
|
+
holds.
|
|
299
|
+
|
|
300
|
+
- [cli-core `STANDARD.md`](https://github.com/WireCatLabs/cli-core/blob/main/docs/dev/STANDARD.md): command names, agent
|
|
301
|
+
execution contract, help text
|
|
302
|
+
- [cli-base API reference](https://github.com/WireCatLabs/cli-base/blob/main/docs/REFERENCE.md)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Documents
|
|
2
|
+
|
|
3
|
+
<!-- cspell:ignore whatsapp -->
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
How documents are written: user pages and developer pages, generated pages, current facts only, languages, and the
|
|
8
|
+
changelog.
|
|
9
|
+
|
|
10
|
+
Rule ids on this page are `WC-DOC-<n>`. How to read a rule, how ids work and which rule wins are in the
|
|
11
|
+
[index](README.md).
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
### WC-DOC-1 · Keep user pages and developer pages apart
|
|
16
|
+
|
|
17
|
+
- **Why:** a user page is read to do a task; a developer page is read to change the code. Mixed pages serve neither.
|
|
18
|
+
- **Enforced by:** review only.
|
|
19
|
+
- **Example:** `README.md` and `docs/*.md` are user pages. `docs/dev/*.md`, `CLAUDE.md`, `AGENTS.md` and
|
|
20
|
+
`SECURITY.md` are developer pages.
|
|
21
|
+
- **Exceptions:** `CHANGELOG.md` is read by users and has its own rules (WC-DOC-14 to WC-DOC-20).
|
|
22
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
23
|
+
|
|
24
|
+
### WC-DOC-2 · A user page has no correction marks, no struck-out text and no internal ids
|
|
25
|
+
|
|
26
|
+
- **Why:** the reader wants the current behaviour, not the history of a page or a journal number they cannot open.
|
|
27
|
+
- **Enforced by:** `tg-cli: pnpm docs:check` · `max-cli: pnpm docs:check` · `whatsapp-cli: pnpm docs:check` ·
|
|
28
|
+
`zoom-cli: pnpm docs:check`.
|
|
29
|
+
- **Example:** write "`--limit` defaults to 50", not "~~20~~ 50 (correction 2026-10-01, NEED-12)".
|
|
30
|
+
- **Exceptions:** none. `cli-dev docs-check` checks this when the CLI's rules module sets `userPage`. tg, max, wa
|
|
31
|
+
and zoom set it; memo, drive and cli-base's default check do not.
|
|
32
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
33
|
+
|
|
34
|
+
### WC-DOC-3 · Every document states current facts; rewrite a wrong sentence in place
|
|
35
|
+
|
|
36
|
+
- **Why:** a confidently stale document causes a wrong decision; git already keeps the old text.
|
|
37
|
+
- **Enforced by:** review only.
|
|
38
|
+
- **Example:** a claim that turns out wrong is rewritten to the current fact, with no "correction" note and no dated
|
|
39
|
+
"measured on" trail. A finished plan or handoff is deleted. A done backlog or cleanup line is deleted.
|
|
40
|
+
- **Exceptions:** `CHANGELOG.md` is the one place with version history.
|
|
41
|
+
- **Applies to:** all CLIs, user and developer pages. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
42
|
+
|
|
43
|
+
### WC-DOC-4 · A generated page is never edited by hand
|
|
44
|
+
|
|
45
|
+
- **Why:** the next generation overwrites a hand edit, and the page then disagrees with the program until it does.
|
|
46
|
+
- **Enforced by:** `cli-memo: pnpm generate:check` · `drive-cli: pnpm generate:check` ·
|
|
47
|
+
`zoom-cli: pnpm generate:check` · `tg-cli: ci.yml:ci` · `max-cli: ci.yml:ci` · `whatsapp-cli: ci.yml:ci`.
|
|
48
|
+
- **Example:** `docs/commands.md` comes from the program. Change the command's help text, then run `pnpm generate`.
|
|
49
|
+
- **Exceptions:** none. memo, drive and zoom check this with `pnpm generate:check`. tg, max and wa run the generator
|
|
50
|
+
and then `git diff --exit-code` in full validation.
|
|
51
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
52
|
+
|
|
53
|
+
### WC-DOC-5 · Every relative link and anchor resolves
|
|
54
|
+
|
|
55
|
+
- **Why:** a broken link sends the reader nowhere at the moment they need the next step.
|
|
56
|
+
- **Enforced by:** `pnpm docs:check`.
|
|
57
|
+
- **Example:** `cli-dev docs-check` fails on `[setup](usage.md#setup)` when `usage.md` has no "Setup" heading.
|
|
58
|
+
- **Exceptions:** a link into a private checkout that CI does not have (for example `docs_ai/`) is skipped by the CLI's
|
|
59
|
+
rules module.
|
|
60
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
61
|
+
|
|
62
|
+
### WC-DOC-6 · The `docs/` folder matches `docs/meta.json` and has the shared page set
|
|
63
|
+
|
|
64
|
+
- **Why:** every tool has the same pages at the same addresses, so a reader who knows one tool knows them all.
|
|
65
|
+
- **Enforced by:** `max-cli: pnpm docs:check` · `whatsapp-cli: pnpm docs:check` · `tg-cli: pnpm docs:check`.
|
|
66
|
+
- **Example:** `docs/meta.json` lists every page once, in sidebar order. The folder has `index`, `installation`,
|
|
67
|
+
`usage`, `commands`, `configuration`, `troubleshooting` and `security`. Each page has exactly one `#` heading.
|
|
68
|
+
- **Exceptions:** `changelog` in `meta.json` comes from the root `CHANGELOG.md`. `cli-dev docs-check --pages` checks
|
|
69
|
+
this. max and wa run it, and tg runs the same check in its own `scripts/docs-check.ts`. memo, drive and zoom do not
|
|
70
|
+
yet.
|
|
71
|
+
- **Applies to:** every CLI in the docs portal. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
72
|
+
|
|
73
|
+
### WC-DOC-7 · `README.md` is the full introduction to the tool
|
|
74
|
+
|
|
75
|
+
- **Why:** most readers open only the README; it must take them from "what is this" to a first result.
|
|
76
|
+
- **Enforced by:** review only.
|
|
77
|
+
- **Example:** follow max-cli's README order: quick start, what you can do, for AI agents, why this tool, the main
|
|
78
|
+
features, sign-in and profiles, settings and permissions, limits, security, when it does not work, documentation,
|
|
79
|
+
development.
|
|
80
|
+
- **Exceptions:** none.
|
|
81
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-500.
|
|
82
|
+
|
|
83
|
+
### WC-DOC-8 · A user guide follows the portal's authoring and terminology rules
|
|
84
|
+
|
|
85
|
+
- **Why:** one vocabulary and one page shape across tools; the portal copies these pages as they are.
|
|
86
|
+
- **Enforced by:** review only.
|
|
87
|
+
- **Example:** a guide opens with when to use it and what result the reader gets, then the steps. It says "agent",
|
|
88
|
+
not "assistant" or "model". See cli-docs `AUTHORING.md` and `TERMINOLOGY.md`.
|
|
89
|
+
- **Exceptions:** reference pages, such as the generated `commands.md`, follow their generator.
|
|
90
|
+
- **Applies to:** every CLI in the docs portal. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
91
|
+
|
|
92
|
+
### WC-DOC-9 · A tool's security page links the shared page and keeps only what is its own
|
|
93
|
+
|
|
94
|
+
- **Why:** shared behaviour written in each tool drifts; the reader needs only what differs for this tool.
|
|
95
|
+
- **Enforced by:** review only.
|
|
96
|
+
- **Example:** `docs/security.md` links the portal's shared security page, then states what this messenger stores and
|
|
97
|
+
sends.
|
|
98
|
+
- **Exceptions:** none.
|
|
99
|
+
- **Applies to:** every CLI in the docs portal. **Since:** cli-base 0.41.0. **Rulings:** NEED-556.
|
|
100
|
+
|
|
101
|
+
### WC-DOC-10 · One fact has one home; every other document links to it
|
|
102
|
+
|
|
103
|
+
- **Why:** two copies of a fact drift, and the reader cannot tell which one is current.
|
|
104
|
+
- **Enforced by:** review only.
|
|
105
|
+
- **Example:** a rule lives on its handbook page; a repository's `CLAUDE.md` links the page instead of restating the
|
|
106
|
+
rule. A task lives in the backlog; a plan links it.
|
|
107
|
+
- **Exceptions:** none.
|
|
108
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
109
|
+
|
|
110
|
+
### WC-DOC-11 · A developer page anchors each claim and says how it was verified
|
|
111
|
+
|
|
112
|
+
- **Why:** a claim with no source cannot be checked when the code or the service changes.
|
|
113
|
+
- **Enforced by:** review only.
|
|
114
|
+
- **Example:** cite `src/store/open.ts:42` for our code, a URL for anything outside, a commit for history. Write "ran
|
|
115
|
+
X, saw Y" for what you ran, and "the docs describe X" for what you only read. Say at the top what is not verified.
|
|
116
|
+
- **Exceptions:** user pages state behaviour and do not carry code anchors.
|
|
117
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
118
|
+
|
|
119
|
+
### WC-DOC-12 · A runnable command in a document has no placeholder in it
|
|
120
|
+
|
|
121
|
+
- **Why:** a placeholder such as `<your-chat-id>` gets pasted as it is and runs against the wrong thing.
|
|
122
|
+
- **Enforced by:** review only.
|
|
123
|
+
- **Example:** paste a real value from a real run, or an obviously synthetic one that is labelled as such. Ask for a
|
|
124
|
+
value in a plain sentence before the command.
|
|
125
|
+
- **Exceptions:** syntax lines in a reference page, which are not meant to be run.
|
|
126
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
127
|
+
|
|
128
|
+
### WC-DOC-13 · Each file is in one language, and each repository has a fixed document language
|
|
129
|
+
|
|
130
|
+
- **Why:** a reader and the spell checker both handle one language per file.
|
|
131
|
+
- **Enforced by:** review only.
|
|
132
|
+
- **Example:** English everywhere, except max-cli: its user pages, its changelog and its new documents are Russian.
|
|
133
|
+
A section added to an existing file follows that file's language.
|
|
134
|
+
- **Exceptions:** command names, option names and quoted output stay as the program prints them.
|
|
135
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
136
|
+
|
|
137
|
+
### WC-DOC-14 · `CHANGELOG.md` has one section per version, newest first, under a pending section
|
|
138
|
+
|
|
139
|
+
- **Why:** the GitHub release and the release scripts read the changelog by its headings.
|
|
140
|
+
- **Enforced by:** `pnpm docs:check` · `changelog-guard.yml`.
|
|
141
|
+
- **Example:** `## Unreleased` at the top while work is merged; a release dates it as `## 0.41.0 — 17.10.2026`. Each
|
|
142
|
+
version heading and the pending heading appear once.
|
|
143
|
+
- **Exceptions:** max-cli names the pending section `## Не выпущено`.
|
|
144
|
+
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
145
|
+
|
|
146
|
+
### WC-DOC-15 · Changelog entries go under five fixed headings, each at most once per section
|
|
147
|
+
|
|
148
|
+
- **Why:** one heading set lets a reader, an agent and one guard read every repository the same way.
|
|
149
|
+
- **Enforced by:** `pnpm docs:check` · `changelog-guard.yml`.
|
|
150
|
+
- **Example:** `### Added`, `### Changed — may break callers`, `### Fixed`, `### Security`, `### Removed`. max-cli
|
|
151
|
+
uses this fixed Russian rendering: `Что нового`, `Изменено — может сломать скрипты`, `Исправлено`, `Безопасность`,
|
|
152
|
+
`Удалено`.
|
|
153
|
+
- **Exceptions:** none. tg-cli and whatsapp-cli still check `What's new` and `Changed — may break scripts`, and memo
|
|
154
|
+
and drive have used `Changed` and `Documentation`. They move to this set.
|
|
155
|
+
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
156
|
+
|
|
157
|
+
### WC-DOC-16 · A change a caller may notice goes under "Changed — may break callers"
|
|
158
|
+
|
|
159
|
+
- **Why:** this heading is what a reader scans before an upgrade.
|
|
160
|
+
- **Enforced by:** review only.
|
|
161
|
+
- **Example:** a changed output field, exit code, option, permission or config key, or a changed exported signature
|
|
162
|
+
in a shared library. The entry says what each caller has to do.
|
|
163
|
+
- **Exceptions:** none.
|
|
164
|
+
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
165
|
+
|
|
166
|
+
### WC-DOC-17 · Each entry says what changed for the caller, why, and what to watch for
|
|
167
|
+
|
|
168
|
+
- **Why:** a line that only names the change leaves the reader unable to decide whether it touches them.
|
|
169
|
+
- **Enforced by:** review only.
|
|
170
|
+
- **Example:** "**`chats list` sorts by the last message.** It sorted by name before. A script that needs the old
|
|
171
|
+
order passes `--sort name`." The bold lead is a whole sentence. No file paths or internal names a caller cannot
|
|
172
|
+
see; link the page that describes it.
|
|
173
|
+
- **Exceptions:** "why" is left out when it is obvious, such as a new command.
|
|
174
|
+
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
175
|
+
|
|
176
|
+
### WC-DOC-18 · The changelog has no internal ids
|
|
177
|
+
|
|
178
|
+
- **Why:** a journal, backlog or ruling id means nothing to a reader outside the project.
|
|
179
|
+
- **Enforced by:** `pnpm docs:check`.
|
|
180
|
+
- **Example:** `cli-dev docs-check` fails on `NEED-12` or `TASK-3` anywhere in `CHANGELOG.md`.
|
|
181
|
+
- **Exceptions:** none.
|
|
182
|
+
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
183
|
+
|
|
184
|
+
### WC-DOC-19 · A pull request with a breaking title adds an entry to the pending section
|
|
185
|
+
|
|
186
|
+
- **Why:** a breaking change with no changelog line reaches callers with no warning.
|
|
187
|
+
- **Enforced by:** `changelog-guard.yml`.
|
|
188
|
+
- **Example:** a title `feat(store)!: …` fails the guard unless the pull request adds a line under `## Unreleased`.
|
|
189
|
+
- **Exceptions:** none.
|
|
190
|
+
- **Applies to:** every repository that calls the changelog guard. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
191
|
+
|
|
192
|
+
### WC-DOC-20 · A released section is frozen, and a new entry goes under an existing heading
|
|
193
|
+
|
|
194
|
+
- **Why:** a release's notes must match what shipped, and one section with two `### Fixed` headings reads as two
|
|
195
|
+
lists.
|
|
196
|
+
- **Enforced by:** `changelog-guard.yml`.
|
|
197
|
+
- **Example:** the guard fails a change to a section whose `v<version>` tag exists, unless the change restores the
|
|
198
|
+
exact text in that tag. It also fails a new repeated `###` heading in any section that has no tag yet.
|
|
199
|
+
- **Exceptions:** a dated section with no tag is still open to fixes; a release is tagged only after npm has it.
|
|
200
|
+
- **Applies to:** every repository that calls the changelog guard. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
201
|
+
|
|
202
|
+
## Sources
|
|
203
|
+
|
|
204
|
+
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
205
|
+
holds.
|
|
206
|
+
|
|
207
|
+
- [cli-base `CONVENTIONS.md`](https://github.com/WireCatLabs/cli-base/blob/main/docs/dev/CONVENTIONS.md)
|
|
208
|
+
- [max-cli `CONVENTIONS.md`](https://github.com/WireCatLabs/max-cli/blob/main/docs/dev/CONVENTIONS.md), sections
|
|
209
|
+
"Documents" and "The changelog"
|
|
210
|
+
- [cli-docs `STRUCTURE.md`](https://github.com/WireCatLabs/cli-docs/blob/main/docs/STRUCTURE.md),
|
|
211
|
+
[`AUTHORING.md`](https://github.com/WireCatLabs/cli-docs/blob/main/docs/AUTHORING.md),
|
|
212
|
+
[`TERMINOLOGY.md`](https://github.com/WireCatLabs/cli-docs/blob/main/docs/TERMINOLOGY.md) and
|
|
213
|
+
[`REVIEWING.md`](https://github.com/WireCatLabs/cli-docs/blob/main/docs/REVIEWING.md). These stay the contract
|
|
214
|
+
of the docs portal. This page links them and does not repeat them.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Layout
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
Where things live in a CLI repository: the folders, the entry points, what the package exports, how large a file may
|
|
6
|
+
grow, one job per file, and how a large unit is split.
|
|
7
|
+
|
|
8
|
+
Rule ids on this page are `WC-LAY-<n>`. How to read a rule, how ids work and which rule wins are in the
|
|
9
|
+
[index](README.md). Which layer may import which, and what goes in cli-base, cli-core or the CLI, is on the
|
|
10
|
+
[Architecture page](architecture.md). The hard file size cap is a lint rule on the [Toolchain page](toolchain.md).
|
|
11
|
+
|
|
12
|
+
## Rules
|
|
13
|
+
|
|
14
|
+
### WC-LAY-1 · Use the shared top-level layout
|
|
15
|
+
|
|
16
|
+
- **Why:** an agent that knows one WireCat CLI finds its way in the next one without a map.
|
|
17
|
+
- **Enforced by:** review only.
|
|
18
|
+
- **Example:** a CLI repository has these at its root:
|
|
19
|
+
- `src/`: all code, tests beside it;
|
|
20
|
+
- `bin/`: the repository's own scripts, such as `bin/release` and a wrapper that runs the local build;
|
|
21
|
+
- `scripts/`: development scripts in TypeScript or JavaScript;
|
|
22
|
+
- `docs/`: user pages; `docs/dev/`: developer pages;
|
|
23
|
+
- `lint/`: `baseline/` and `overrides.json` for the command lint;
|
|
24
|
+
- `contracts/`: the offline contract plan and its snapshots;
|
|
25
|
+
- `config/wirecat/`: the shared presets;
|
|
26
|
+
- `skills/`: the agent skill the package ships;
|
|
27
|
+
- `CHANGELOG.md`, `CLAUDE.md`, `CLEANUP.md`, `SECURITY.md`, `README.md`, `LICENSE`.
|
|
28
|
+
- **Exceptions:** a folder the CLI has no use for may be missing; memo has no `contracts/` yet. A product folder may
|
|
29
|
+
be added, such as `spec/` for a protocol or API description.
|
|
30
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
31
|
+
|
|
32
|
+
### WC-LAY-2 · Start the command from `src/bin/<command>.ts`, and keep that file thin
|
|
33
|
+
|
|
34
|
+
- **Why:** the start file runs before any error handling exists, so every line in it is a line that can crash without
|
|
35
|
+
a useful message.
|
|
36
|
+
- **Enforced by:** review only.
|
|
37
|
+
- **Example:** `src/bin/max.ts` is built to `dist/bin/max.js`, which `package.json` names in `"bin"`. It sets up
|
|
38
|
+
crash handling and a quiet exit on a closed pipe, then imports `../program.js` and sets `process.exitCode` to the
|
|
39
|
+
result of `run(argv)`.
|
|
40
|
+
- **Exceptions:** none.
|
|
41
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
42
|
+
|
|
43
|
+
### WC-LAY-3 · Export `createProgram` from `src/program.ts`
|
|
44
|
+
|
|
45
|
+
- **Why:** the test matrix, the reference page and the tests build the command tree from this one function, without
|
|
46
|
+
a process.
|
|
47
|
+
- **Enforced by:** review only.
|
|
48
|
+
- **Example:** `cli-dev test-matrix --program dist/program.js` loads `createProgram` from the built file. Tests call
|
|
49
|
+
`run()` from the same module.
|
|
50
|
+
- **Exceptions:** none.
|
|
51
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
52
|
+
|
|
53
|
+
### WC-LAY-4 · Put test-only helpers outside the published folder
|
|
54
|
+
|
|
55
|
+
- **Why:** everything under `src/` of a library is compiled into `dist/` and published, so a test helper there ships
|
|
56
|
+
to every user.
|
|
57
|
+
- **Enforced by:** review only.
|
|
58
|
+
- **Example:** cli-base keeps its test sandbox in `test/sandbox.ts`, not in `src/`. A helper that callers also use in
|
|
59
|
+
their own tests is a public entry point instead, such as `@wirecat/cli-base/testing`.
|
|
60
|
+
- **Exceptions:** a CLI's colocated `*.test.ts` files stay in `src/`, because its `tsconfig.json` excludes them from
|
|
61
|
+
the build.
|
|
62
|
+
- **Applies to:** cli-base, cli-core and cli-testing. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
63
|
+
|
|
64
|
+
### WC-LAY-5 · Add an entry point as an `exports` entry built from `src/<name>/index.ts`
|
|
65
|
+
|
|
66
|
+
- **Why:** a caller can import only what `exports` names, so each entry point is a public promise that needs its own
|
|
67
|
+
door.
|
|
68
|
+
- **Enforced by:** review only.
|
|
69
|
+
- **Example:** `"./query": { "types": "./dist/query/index.d.ts", "default": "./dist/query/index.js" }`, built from
|
|
70
|
+
`src/query/index.ts`. The same pull request adds a line to `scripts/smoke.ts` that loads it under Bun.
|
|
71
|
+
- **Exceptions:** a CLI that is used only as a command has no `exports`, or only `"."`.
|
|
72
|
+
- **Applies to:** cli-base, cli-core and cli-testing. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
73
|
+
|
|
74
|
+
### WC-LAY-6 · Keep generated code in a `generated/` folder, and commit it
|
|
75
|
+
|
|
76
|
+
- **Why:** a reader and the lint must tell generated code from written code, and a stale generated file must show as
|
|
77
|
+
a diff.
|
|
78
|
+
- **Enforced by:** review only.
|
|
79
|
+
- **Example:** max keeps its generated protocol wrappers in `src/generated/`. Full validation runs `pnpm generate`
|
|
80
|
+
and then `git diff --exit-code`. The size cap and the secret lint skip `**/generated/**` by path.
|
|
81
|
+
- **Exceptions:** a single generated file may use a `.generated.ts` suffix, such as cli-core's
|
|
82
|
+
`migrations.generated.ts`, and is then excluded from Biome by name.
|
|
83
|
+
- **Applies to:** all CLIs and libraries that generate code. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
84
|
+
|
|
85
|
+
### WC-LAY-7 · Treat 300 and 500 lines as review signals
|
|
86
|
+
|
|
87
|
+
- **Why:** size is a sign of mixed jobs, not a proof, so it starts a review question instead of failing a build.
|
|
88
|
+
- **Enforced by:** review only.
|
|
89
|
+
- **Example:** at about 300 lines of handwritten production code, look for jobs that can be separated. Above 500,
|
|
90
|
+
split them, or say in the pull request why keeping them together is clearer. The hard cap of 1,000 lines is
|
|
91
|
+
[WC-TOOL-13](toolchain.md#wc-tool-13--cap-every-file-at-1000-lines-with-biome).
|
|
92
|
+
- **Exceptions:** generated code, declarative registries and cohesive test scenarios get their own judgment. Do not
|
|
93
|
+
meet the number with a new barrel file, an operation scattered into trivial files or a class around each function.
|
|
94
|
+
- **Applies to:** all CLIs and libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
95
|
+
|
|
96
|
+
### WC-LAY-8 · Give each module one reason to change
|
|
97
|
+
|
|
98
|
+
- **Why:** a module that mixes a domain rule, a use case and a transport mapping changes for three reasons, and
|
|
99
|
+
every change risks the other two.
|
|
100
|
+
- **Enforced by:** review only.
|
|
101
|
+
- **Example:** split when unrelated policies, different effects or separate consumers collect in one file. Keep
|
|
102
|
+
validation, orchestration and output mapping apart when each has real logic. Do not write a catch-all `utils`
|
|
103
|
+
module, a boolean flag that selects unrelated workflows, or a command handler that also holds SQL. Split a large
|
|
104
|
+
unit along a named seam, and dispatch by lookup, not by a branch per case:
|
|
105
|
+
- the tg adapter is a thin class over feature modules (folders, topics, groups, contacts, login, watch) that share a
|
|
106
|
+
small feature context;
|
|
107
|
+
- the cli-core store is a set of areas, each in its own file ([Store](store.md));
|
|
108
|
+
- the Lucene compiler has one compiler per field, picked from the field registry by name.
|
|
109
|
+
- **Exceptions:** do not scatter one operation into trivial files, add a barrel or wrap each function in a class only
|
|
110
|
+
to meet a size limit.
|
|
111
|
+
- **Applies to:** all CLIs and the shared packages. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
112
|
+
|
|
113
|
+
### WC-LAY-9 · Do not add a new job to a large file because it is already there
|
|
114
|
+
|
|
115
|
+
- **Why:** an existing large file is not evidence that a mixed responsibility belongs in it.
|
|
116
|
+
- **Enforced by:** review only.
|
|
117
|
+
- **Example:** extract the part a change needs when that makes its boundary clearer, keep its behaviour with focused
|
|
118
|
+
tests, and record other structural debt in the backlog instead of fixing it in the same pull request.
|
|
119
|
+
- **Exceptions:** none.
|
|
120
|
+
- **Applies to:** all CLIs and libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
121
|
+
|
|
122
|
+
## Sources
|
|
123
|
+
|
|
124
|
+
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
125
|
+
holds.
|
|
126
|
+
|
|
127
|
+
- [cli-base `MODULARITY.md`](https://github.com/WireCatLabs/cli-base/blob/main/docs/dev/MODULARITY.md)
|
|
128
|
+
- [cli-base `ARCHITECTURE.md`](https://github.com/WireCatLabs/cli-base/blob/main/docs/dev/ARCHITECTURE.md)
|