1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
# SAMPLE.toml — Reference for all supported TOML command definition fields.
#
# This file is not loaded by safe-chains. It documents every field the
# registry understands, when to use each one, and how they compose.
# Copy the pattern that matches your command and fill in the data.
#
# CORE PRINCIPLE: This is an allowlist. Only what you list is allowed.
# Omitted flags, subcommands, and arguments are implicitly not allowed.
# Never describe what is blocked — just list what is permitted.
#
# FIELD DEFAULTS (omit when the default is what you want):
# level = "Inert" (alternatives: "SafeRead", "SafeWrite")
# bare = true (command can run with no arguments)
# tolerate_unknown_short = false (unknown -X tokens are rejected; if true,
# accepted as positional. Use for tools
# like `pdftotext -help`, `sample -mayDie`)
# tolerate_unknown_long = false (unknown --foo tokens are rejected; if
# true, accepted as positional. AVOID
# unless the tool's long-flag surface is
# genuinely unbounded — most modern
# destructive flags are double-dash, so
# this silently passes things like --repair,
# --delete, --to-remote, etc.)
#
# `positional_style = true` is the legacy single-bool form of these two; it
# was removed because removing a flag from the allowlist did NOT deny it
# while positional_style was on (the unknown form still slipped through as
# a positional). The build panics if any TOML still sets it.
#
# CHOOSING THE RIGHT PATTERN:
#
# Is the command a simple tool with flags and positional args?
# → Use "Flat command" (grep, cat, jq, bat, etc.)
#
# Does the command have subcommands (git log, cargo build, etc.)?
# → Use "Structured command with subcommands"
#
# Does a subcommand require a specific flag to be safe (cargo fmt --check)?
# → Use "guard" on the sub
#
# Does a subcommand have its own sub-subcommands (npm config get)?
# → Use nested [[command.sub.sub]]
#
# Does a subcommand delegate to an inner command (rustup run stable echo)?
# → Use "delegate_skip" or "delegate_after"
#
# Does a subcommand accept anything (git help)?
# → Use "allow_all = true"
#
# Does a subcommand only allow specific argument values (npm run test)?
# → Use "first_arg" with patterns
#
# Does a subcommand require one of several flags (conda config --show)?
# → Use "require_any"
#
# Does a flag promote the safety level (sk --history → SafeWrite)?
# → Use "write_flags"
#
# Does the command have global flags before subcommands (jj --no-pager log)?
# → Use "wrapper" on a structured command to strip them first
#
# Does the command wrap and delegate to an inner command (timeout, nice)?
# → Use "wrapper" with skip flags and positional_skip
#
# Does the command need Rust code for validation (curl, perl, fzf)?
# → Use "handler" to reference a named Rust function
#
# Was a subcommand considered but deliberately not approved?
# → Use "candidate = true" to record the decision
#
# ─────────────────────────────────────────────────────────────────────
# FLAT COMMAND — simple tool with flags and positional args
# ─────────────────────────────────────────────────────────────────────
#
# Use for: grep, cat, jq, ls, bat, wc, and most CLI tools.
# A flat command has no subcommands — just flags and file arguments.
# [[command]]
# name = "grep"
# aliases = ["egrep", "fgrep"] # optional; registers additional names
# url = "https://www.gnu.org/software/grep/manual/grep.html"
# level = "Inert" # Inert | SafeRead | SafeWrite
# bare = false # false = requires at least one argument
# # max_positional = 2 # optional; limits positional arg count
# # tolerate_unknown_short = true # optional; treats unknown -X / -word
# # tokens as positional. Use for
# # tools like `pdftotext -help`.
# # tolerate_unknown_long = true # optional; treats unknown --foo
# # tokens as positional. DANGEROUS:
# # most destructive flags are
# # double-dash. Reserve for tools
# # like AWS CLI whose long-flag
# # surface is genuinely unbounded.
# # numeric_dash = true # optional; accepts -NUMBER shorthand
# # (e.g. head -20 ≡ head -n 20).
# # Only for commands with POSIX -number legacy syntax.
# standalone = [ # flags that take no value
# "--count", "--help", "--recursive", "--version",
# "-c", "-h", "-i", "-r", "-V",
# ]
# valued = [ # flags that consume the next token as value
# "--after-context", "--max-count", # also accepts --max-count=5 syntax
# "-A", "-m",
# ]
# ─────────────────────────────────────────────────────────────────────
# STRUCTURED COMMAND — has subcommands
# ─────────────────────────────────────────────────────────────────────
#
# Use for: cargo, git, docker, npm, brew, etc.
# bare_flags are accepted when the command is invoked alone (e.g. cargo --help).
# Each [[command.sub]] defines one allowed subcommand.
# [[command]]
# name = "cargo"
# url = "https://doc.rust-lang.org/cargo/commands/"
# bare_flags = ["--help", "--version", "-V", "-h"] # allowed with just the flag
#
# [[command.sub]]
# name = "test"
# level = "SafeRead"
# standalone = ["--release", "--no-fail-fast", "-h"]
# valued = ["--jobs", "--package", "-j", "-p"]
#
# [[command.sub]]
# name = "build"
# level = "SafeWrite"
# standalone = ["--release", "-h"]
# valued = ["--jobs", "--target", "-j"]
# ─────────────────────────────────────────────────────────────────────
# GUARDED SUBCOMMAND — requires a specific flag to be present
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand is only safe with a particular flag.
# Example: "cargo fmt" is a write operation, but "cargo fmt --check" is read-only.
# Without the guard flag, the subcommand is not allowed.
#
# Internally, guard is a shorthand for require_any with a single flag.
# The difference: guard also accepts bare "help" as a positional argument
# (e.g. "cargo fmt help"), while require_any only accepts --help/-h.
# Use guard when there is exactly one required flag; use require_any
# when multiple flags are acceptable alternatives.
# [[command.sub]]
# name = "fmt"
# guard = "--check" # long flag that must be present
# # guard_short = "-c" # optional short form of the guard
# level = "Inert"
# bare = false # must have at least the guard flag
# standalone = ["--all", "--check", "-h"]
# valued = ["--package", "-p"]
# ─────────────────────────────────────────────────────────────────────
# NESTED SUBCOMMANDS — subcommand has its own subcommands
# ─────────────────────────────────────────────────────────────────────
#
# Use for: npm config get, docker compose ps, mise config ls, etc.
# The parent sub has no flags of its own — it just groups child subs.
# Bare invocation of the parent (e.g. "npm config") is rejected by default.
#
# Set nested_bare = true if bare invocation and --help/-h should be
# accepted. Use this for commands like "mise settings" that show output
# when invoked without a subcommand. This also allows --help/-h on the
# parent without routing to a child sub.
#
# If the parent sub accepts flags that can appear before the child sub
# (e.g. "git config --local --list"), add standalone/valued on the parent.
# These are skipped when locating the child sub name.
# [[command.sub]]
# name = "config"
# # nested_bare = true # set if "tool config" alone is safe
# # standalone = ["--local", "--global"] # flags allowed before the child sub
# # valued = ["--file", "-f"] # valued flags allowed before the child sub
#
# [[command.sub.sub]]
# name = "get"
# standalone = ["--help", "--json", "-h"]
#
# [[command.sub.sub]]
# name = "list"
# standalone = ["--help", "--json", "-h"]
# ─────────────────────────────────────────────────────────────────────
# FIRST ARG FILTER — restrict which argument values are accepted
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand accepts an argument but only specific values are
# safe. The first positional argument after the subcommand name must
# match one of the patterns. Supports trailing * for prefix matching.
# Example: "npm run test" and "npm run test:unit" are safe, but
# "npm run build" is not.
# --help/-h is always allowed without matching.
# [[command.sub]]
# name = "run"
# first_arg = ["test", "test:*"] # "test" exact or "test:..." prefix
# level = "SafeRead"
# ─────────────────────────────────────────────────────────────────────
# REQUIRE ANY — subcommand needs at least one of several flags
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand is only safe if at least one of several specific
# flags is present. Internally the same dispatch as guard, but accepts
# multiple alternatives. Use guard when there is exactly one required
# flag; use require_any when several flags are acceptable.
# Example: "conda config" is only safe with --show or --show-sources.
# Without one of those, it could be used for --set or --add operations.
# --help/-h is always allowed without the required flag.
# [[command.sub]]
# name = "config"
# bare = false
# require_any = ["--show", "--show-sources"]
# standalone = ["--help", "--json", "--show", "--show-sources", "-h"]
# valued = ["--file", "--name", "-f", "-n"]
# ─────────────────────────────────────────────────────────────────────
# ALLOW ALL — subcommand that accepts anything
# ─────────────────────────────────────────────────────────────────────
#
# Use for: help subcommands that accept any topic as an argument.
# Internally equivalent to a policy with both `tolerate_unknown_short` and
# `tolerate_unknown_long` set and no flags listed, but allow_all is clearer
# about intent.
# [[command.sub]]
# name = "help"
# allow_all = true
# level = "Inert"
# ─────────────────────────────────────────────────────────────────────
# WRITE-FLAGGED — specific flags promote the safety level
# ─────────────────────────────────────────────────────────────────────
#
# Use when a command is normally Inert but certain flags cause writes.
# Example: sk (skim) is Inert, but --history writes to a file → SafeWrite.
# The base level applies when none of the write_flags are present.
# [[command.sub]]
# name = "run"
# write_flags = ["--history"] # if any of these appear → SafeWrite
# # level = "Inert" # base level when write_flags absent
# standalone = ["--help", "-h"]
# valued = ["--history", "--query", "-q"]
# ─────────────────────────────────────────────────────────────────────
# DELEGATION — subcommand wraps an inner command
# ─────────────────────────────────────────────────────────────────────
#
# delegate_skip: Skip N tokens after the subcommand name, then validate
# the rest as a complete command. Used for "rustup run stable echo hello"
# where skip=2 skips the toolchain name.
#
# delegate_after: Find a separator token (usually "--"), then validate
# everything after it as a complete command. Used for
# "mise exec -- git status" where the separator is "--".
# [[command.sub]]
# name = "run"
# delegate_skip = 2 # skip 2 tokens, validate the rest
#
# [[command.sub]]
# name = "exec"
# delegate_after = "--" # validate everything after "--"
# ─────────────────────────────────────────────────────────────────────
# STRUCTURED + WRAPPER — global flags stripped before sub dispatch
# ─────────────────────────────────────────────────────────────────────
#
# Use for: commands with global flags that appear before the subcommand.
# Example: "jj --no-pager log" or "xcrun --sdk macosx --find clang".
# The wrapper flags are stripped from the front, then the remaining
# tokens are dispatched to subcommands as normal.
# This reuses the wrapper concept on a structured command.
# [[command]]
# name = "jj"
# bare_flags = ["--help", "--version", "-h"]
# [command.wrapper]
# standalone = ["--no-pager", "--quiet", "--verbose"]
# valued = ["--color", "--repository", "-R"]
#
# [[command.sub]]
# name = "log"
# standalone = ["--help", "-h"]
#
# [[command.sub]]
# name = "diff"
# standalone = ["--help", "-h"]
# ─────────────────────────────────────────────────────────────────────
# WRAPPER — command that wraps an inner command
# ─────────────────────────────────────────────────────────────────────
#
# Use for: timeout, time, nice, ionice, dotenv, and similar commands
# that accept their own flags, optionally skip positional args (like a
# duration or priority), then delegate everything remaining as a
# complete inner command to be validated recursively.
#
# Fields:
# standalone — wrapper's own flags that take no value
# valued — wrapper's own flags that consume the next token
# positional_skip — number of positional args to skip before the
# inner command (e.g., 1 for timeout's duration)
# separator — stop scanning flags at this token (e.g., "--")
# bare_ok — if true, running the wrapper with no inner command
# is allowed (e.g., "env" alone lists variables)
# [[command]]
# name = "timeout"
# url = "https://www.gnu.org/software/coreutils/..."
# [command.wrapper]
# standalone = ["--preserve-status"]
# valued = ["--signal", "--kill-after", "-s", "-k"]
# positional_skip = 1 # skip the duration argument
#
# [[command]]
# name = "dotenv"
# url = "https://github.com/bkeepers/dotenv"
# [command.wrapper]
# valued = ["-c", "-e", "-f", "-v"]
# separator = "--" # stop flag scanning at "--"
# ─────────────────────────────────────────────────────────────────────
# CUSTOM HANDLER — requires Rust code for validation
# ─────────────────────────────────────────────────────────────────────
#
# Use as a last resort when the command needs logic that can't be
# expressed with the fields above. The handler name references a Rust
# function registered in custom_cmd_handlers() or custom_sub_handlers()
# in src/handlers/mod.rs. The function receives the token slice and
# returns a Verdict.
#
# Works at both levels:
# - Command level: the entire command uses a Rust handler
# - Sub level: one subcommand uses a Rust handler while siblings
# use declarative TOML fields
#
# Before reaching for this: can you express the rule as a guard, a
# nested sub, a delegation, or simply by listing the right flags?
# Most commands that seem to need custom code can be modeled with the
# declarative fields above.
# Command-level handler (entire command validated by Rust):
# [[command]]
# name = "curl"
# handler = "curl" # references Rust handler by name
# url = "https://curl.se/docs/manpage.html"
# Sub-level handler (one sub uses Rust, siblings use TOML):
# [[command.sub]]
# name = "exec"
# handler = "bundle_exec" # references Rust handler by name
# When a command (or sub) uses a handler, the auto-generated docs
# can't introspect the Rust logic, so add a doc_body field with the
# allowed surface in prose. Markdown bullet lines (lines starting
# with "- ") are kept as-is; other lines get a "- " prefix added.
# Follow the documentation style rules: list what is allowed in
# positive terms; do not say "denied", "blocked", "no flags", etc.
#
# [[command]]
# name = "php"
# handler = "php"
# doc_body = """
# - Allowed standalone flags: --help, --version
# - Subcommands: artisan, please
# """
#
# [[command.sub]]
# name = "cache:clear"
# handler = "laravel_cache_clear"
# doc_body = "requires --store with one of file, array, null"
# Handlers should hold LOGIC, not DATA. If your handler wants a static
# `WordSet` of allowed flags or sub names, that data belongs in TOML.
# A handler-using command can declare `[[command.sub]]` blocks (the
# handler dispatches them via `registry::try_sub_dispatch(name, tokens)`)
# and a `[command.fallback]` block — an alternate grammar engaged when
# no sub matches (dispatched via `registry::try_fallback_grammar(name, tokens)`).
# `commands/tools/tilt.toml` and `src/handlers/tilt.rs` are the canonical
# example: tilt's K8s subs live in TOML, the Ruby template-engine grammar
# is the fallback, and the Rust handler is a few lines of dispatch.
# ─────────────────────────────────────────────────────────────────────
# COMMAND.FALLBACK — alternate grammar for handler-using commands
# ─────────────────────────────────────────────────────────────────────
#
# Engaged when the handler calls `registry::try_fallback_grammar(name, tokens)`
# and `[[command.sub]]` dispatch found no match. Mirrors the flat-policy
# fields available on a sub block, plus `positional_shape`.
#
# [command.fallback]
# level = "Inert" # SafetyLevel for matching invocations
# bare = true # is bare invocation allowed
# max_positional = 1 # cap on non-flag arguments
# positional_shape = "path" # predicate over the first positional;
# # only "path" is supported today —
# # accepts tokens shaped like a file
# # path (contain `/`, contain `.`, or
# # are `-` for stdin) and rejects flags
# standalone = ["--help", "--list", "-h", "-l"]
# valued = ["--type", "-t"]
# tolerate_unknown_short = false
# tolerate_unknown_long = false
# numeric_dash = false
# ─────────────────────────────────────────────────────────────────────
# HANDLER POLICIES / MATRIX — data the handler walks
# ─────────────────────────────────────────────────────────────────────
#
# For handlers whose dispatch logic genuinely can't move to TOML (e.g.
# gh's sub × action matrix) but whose per-policy WordSets should still
# be data, not Rust constants.
#
# `[command.handler_policy.NAME]` — a named flag policy. The handler
# reads it via `registry::check_handler_policy(cmd, key, tokens)`.
# Same fields as [command.fallback]: standalone, valued, bare,
# max_positional, tolerate_unknown_short, tolerate_unknown_long,
# numeric_dash.
#
# `[[command.matrix]]` — parent × action → policy mappings. Each
# block says "for these parent subcommand names, this action verb
# maps to this `handler_policy` and validates at this safety level."
# Walked by `registry::try_matrix_dispatch(cmd, tokens)`. Lets a
# handler express its dispatch table as data instead of `match` arms.
#
# Shorthand form (no guard required):
# [command.matrix.actions]
# list = "list_policy"
#
# Detailed form (action requires a specific flag to be present):
# [command.matrix.actions.download]
# policy = "download_policy"
# guard = "--output"
# guard_short = "-O"
#
# `policy = "key"` on a [[command.sub]] — references a
# [command.handler_policy.KEY] by name and copies its flag list into
# this sub's effective policy. Lets a single-sub form (no action
# verb, optionally with a guard) share the same WordSets a matrix
# entry would use, without duplicating them. Mutually exclusive with
# inline `standalone` / `valued` on the sub.
#
# [[command.sub]]
# name = "browse"
# policy = "browse"
# guard = "--no-browser"
# guard_short = "-n"
# level = "Inert"
# ─────────────────────────────────────────────────────────────────────
# RESEARCHED_VERSION — record which upstream version was last vetted
# ─────────────────────────────────────────────────────────────────────
#
# Optional free-form string on [[command]]. Records the upstream
# version of the underlying tool that was last researched when this
# TOML was written or revised. Internal use only — not rendered in
# docs or used at runtime; surfaces as a tripwire when re-researching
# a tool to see what's changed since.
#
# Common forms:
# researched_version = "1.9.0" # semver tag
# researched_version = "v5.10.3 (April 2026)" # tag + release date
# researched_version = "@northflank/cli 0.10.15" # package@version
# researched_version = "github.com/foo/bar @ master, 2026-05-08" # snapshot
# researched_version = "POSIX kill(1); surface frozen" # truly stable surface
#
# When ADDING or REVISING a TOML, set this field to whatever the
# upstream's *latest* released version is — verified by checking
# the project's GitHub releases / npm / crates.io / official docs,
# NOT just `<tool> --version` on the local machine, which can be
# stale. If the local install and the online docs disagree, follow
# the online docs (the latest version) and treat any local-only
# behavior as a follow-up to revisit.
#
# When backfill is impossible (older TOMLs that pre-date this field)
# leave it absent rather than guess. Re-researching is the right
# moment to fill it in, not before.
# ─────────────────────────────────────────────────────────────────────
# DENY — shortcut for "every invocation of this command is denied"
# ─────────────────────────────────────────────────────────────────────
#
# Use for: locking down a built-in command from a custom TOML
# (.safe-chains.toml). One field replaces a whole spec.
#
# [[command]]
# name = "gh"
# deny = true
#
# Equivalent to writing a spec with bare = false, max_positional = 0,
# and no flags or subs — just shorter.
# ─────────────────────────────────────────────────────────────────────
# CANDIDATE — record that a command was considered but not approved
# ─────────────────────────────────────────────────────────────────────
#
# Use for: subcommands or commands that were reviewed and deliberately
# left out of the allowlist. Candidates are parsed but skipped during
# registry building — they won't be dispatched or appear in docs.
# They serve as a record so future contributors don't re-evaluate
# the same commands.
#
# [[command.sub.sub]]
# name = "push"
# candidate = true
#
# [[command.sub.sub]]
# name = "pop"
# candidate = true
#
# Works on [[command]] and [[command.sub]] too:
#
# [[command]]
# name = "dangerous-tool"
# candidate = true
# url = "https://example.com"
# ─────────────────────────────────────────────────────────────────────
# SAFETY LEVELS
# ─────────────────────────────────────────────────────────────────────
#
# "Inert" — No side effects. Read-only or purely informational.
# Examples: ls, cat, grep, git log, cargo tree
#
# "SafeRead" — Runs code but doesn't produce build artifacts.
# Examples: cargo test, npm test, go vet, linters
#
# "SafeWrite" — Produces artifacts or modifies the working tree.
# Examples: cargo build, go build, cargo doc
#
# When commands are piped or chained, the highest level wins.
# Unlisted commands are always denied regardless of level.