From aba787c7892be0b6ae59834fdce87d2d82f30dd4 Mon Sep 17 00:00:00 2001 From: "warp-factories[bot]" <243557089+warp-factories[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 22:31:07 +0000 Subject: [PATCH 1/2] docs: add option alias pairing convention (#374) --- .agents/skills/add-command-spec/SKILL.md | 2 +- README.md | 57 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/.agents/skills/add-command-spec/SKILL.md b/.agents/skills/add-command-spec/SKILL.md index 1fdb1f7a..f77aada9 100644 --- a/.agents/skills/add-command-spec/SKILL.md +++ b/.agents/skills/add-command-spec/SKILL.md @@ -42,7 +42,7 @@ The Fig autocomplete repo at https://github.com/withfig/autocomplete/tree/master ## Step 2: Implement the Spec -1. **Create the JSON spec**: `command-signatures/json/.json` following Fig's completion spec schema and the reference examples. +1. **Create the JSON spec**: `command-signatures/json/.json` following Fig's completion spec schema and the reference examples. Before writing option `name` or `exclusiveOn` fields, read the ["Option aliases and exclusivity" convention](../../../README.md#option-aliases-and-exclusivity) in the root README — it defines when to group spellings into one option and how to reference aliases from `exclusiveOn`. 2. **Create generators** (if needed): Add `command-signatures/src/generators/.rs`, define a `generator()` function returning `CommandSignatureGenerators`, and register it in `generators/mod.rs` ### Platform Compatibility diff --git a/README.md b/README.md index 02a80aba..1c4ac8d6 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,63 @@ Options are therefore matched by the `OptionOverrides::name` field and positiona At the time of writing, _only_ `template` is supported. Others will need to be added as needed. +### Option aliases and exclusivity + +An option's `name` field accepts either a single string or an array of strings (`CommandOption.name` deserializes both forms into a `Vec`, see `completion-metadata/src/fig_types.rs`). Use this convention to decide which form fits a given option, and how to reference aliases from `exclusiveOn`. + +#### When to use a scalar vs. an array + +An option entry represents one semantic option: + +1. Use a plain string when the target command and platform expose only one documented spelling for that option. +2. Use an array when multiple documented spellings are fully equivalent, i.e. they share the same meaning, argument shape, repeatability, and other metadata. List short aliases first, followed by long aliases in the command's documented order. +3. Include multiple long synonyms in that same array when they are equivalent. `head.json` groups a short flag with two long synonyms this way: + ```json + { + "name": ["-q", "--quiet", "--silent"], + "description": "Never print headers giving file names" + } + ``` +4. Use separate option entries when spellings differ in semantics, accepted arguments, applicability, or metadata. A similar description or name alone does not make two spellings aliases of each other. +5. Do not invent aliases. A BSD/macOS-oriented spec may legitimately keep a short-only string when the targeted command has no GNU long form for that platform (`du.json`'s `-d` is short-only for this reason). Pair a long form only after verifying it is supported on the platform(s) the spec targets; `df.json` shows both styles side by side because only some of its options have a documented long form. + +Do not duplicate equivalent aliases as separate entries just to give each spelling its own `name` string. + +#### Referencing aliases from `exclusiveOn` + +`exclusiveOn` holds literal option spellings, not logical option identifiers, so it is deserialized the same way as `name` but is never carried onto the runtime `Opt` — nothing reads it after deserialization today (see "Currently unenforced" below). Because it is literal, each entry must list every alias a user could type for each conflicting option, not just one. + +`grep.json` shows the convention for two mutually exclusive paired options: +```json +[ + { + "name": ["-L", "--files-without-match"], + "exclusiveOn": ["-l", "--files-with-matches"] + }, + { + "name": ["-l", "--files-with-matches"], + "exclusiveOn": ["-L", "--files-without-match"] + } +] +``` + +When adding a verified long form to an existing short-only option, work through these steps in order: + +1. Replace the option's scalar `name` with an array containing the existing short form and the new long form. +2. Search the same command or subcommand for every `exclusiveOn` that references the existing short form, and add the new long form beside it. +3. Review the changed option's own `exclusiveOn` list and make sure it includes every alias of each option it conflicts with. +4. Keep mutual relationships symmetric when the command describes the options as mutually exclusive; do not infer symmetry when the command's actual behavior is directional. + +These updates are local to the option being changed and its direct relationships. You are not required to normalize unrelated options or files while doing this. + +#### Migration is opportunistic + +This convention does not require a corpus-wide rewrite. Existing specs may still mix scalar and array forms, or reference only some aliases in `exclusiveOn`; normalize an entry only when you are already touching it and have verified the command's actual behavior. Follow the convention for anything you add or edit. + +#### Currently unenforced + +`exclusiveOn` is parsed from JSON today, but Warp does not enforce it at runtime: the value is not carried onto the runtime `Opt` representation, so nothing currently rejects or suppresses conflicting options a user types together. Treat a complete `exclusiveOn` list as authored, future-compatible metadata, not as a promise of current runtime behavior. + ## License This project is licensed under the MIT License. See the LICENSE file for details. Many of the signatures were adapted from Fig (https://github.com/withfig/autocomplete), which is also licensed under the MIT License. From 85a3965c52324ae600be748bf1062d01109bb677 Mon Sep 17 00:00:00 2001 From: "warp-factories[bot]" <243557089+warp-factories[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 22:38:10 +0000 Subject: [PATCH 2/2] docs: fix exclusiveOn deserialization and df.json claims --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c4ac8d6..a2c33449 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,13 @@ An option entry represents one semantic option: } ``` 4. Use separate option entries when spellings differ in semantics, accepted arguments, applicability, or metadata. A similar description or name alone does not make two spellings aliases of each other. -5. Do not invent aliases. A BSD/macOS-oriented spec may legitimately keep a short-only string when the targeted command has no GNU long form for that platform (`du.json`'s `-d` is short-only for this reason). Pair a long form only after verifying it is supported on the platform(s) the spec targets; `df.json` shows both styles side by side because only some of its options have a documented long form. +5. Do not invent aliases. A BSD/macOS-oriented spec may legitimately keep a short-only string when the targeted command has no GNU long form for that platform (`du.json`'s `-d` is short-only for this reason). Pair a long form only after verifying it is supported on the platform(s) the spec targets. Do not duplicate equivalent aliases as separate entries just to give each spelling its own `name` string. #### Referencing aliases from `exclusiveOn` -`exclusiveOn` holds literal option spellings, not logical option identifiers, so it is deserialized the same way as `name` but is never carried onto the runtime `Opt` — nothing reads it after deserialization today (see "Currently unenforced" below). Because it is literal, each entry must list every alias a user could type for each conflicting option, not just one. +`exclusiveOn` holds literal option spellings, not logical option identifiers. Unlike `name`, `exclusiveOn` must always be written as an array of strings; it is never carried onto the runtime `Opt` — nothing reads it after deserialization today (see "Currently unenforced" below). Because it is literal, each entry must list every alias a user could type for each conflicting option, not just one. `grep.json` shows the convention for two mutually exclusive paired options: ```json