@wirecat/cli-base 0.40.3 → 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.
@@ -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)