@optique/core 1.4.0-dev.2899 → 1.4.0-dev.2935

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.
@@ -2,6 +2,108 @@ const require_usage = require('./usage.cjs');
2
2
 
3
3
  //#region src/usage-internals.ts
4
4
  /**
5
+ * Projects augmented runner usage onto a recorded canonical command path.
6
+ * Unsupported or ambiguous metadata returns undefined, preserving root usage.
7
+ * No parser, documentation, or usage-override callbacks are evaluated.
8
+ * @param usage The runner's original augmented usage.
9
+ * @param path The canonical path recorded by the failed parse.
10
+ * @param meta The known augmentation boundary, absent without alternatives.
11
+ * @returns The selected grammar, or undefined when projection is uncertain.
12
+ * @internal
13
+ */
14
+ function projectCommandUsage(usage, path, meta) {
15
+ if (path.length === 0) return void 0;
16
+ if (meta == null) return projectCommandTerms(usage, path, 0);
17
+ const top = usage[0];
18
+ if (usage.length !== 1 || top.type !== "exclusive" || top.terms.length !== meta.alternativeCount + 1) return void 0;
19
+ const selected = projectCommandTerms(top.terms[meta.optionCount], path, 0);
20
+ if (selected == null) return void 0;
21
+ const options = top.terms.slice(0, meta.optionCount).filter((branch) => branch.some((term) => term.type === "option" && !require_usage.isUsageHidden(term.hidden)));
22
+ return options.length === 0 ? selected : [{
23
+ type: "exclusive",
24
+ terms: [...options, selected]
25
+ }];
26
+ }
27
+ function containsCommand(usage) {
28
+ return usage.some((term) => {
29
+ switch (term.type) {
30
+ case "command": return true;
31
+ case "exclusive": return term.terms.some(containsCommand);
32
+ case "optional":
33
+ case "multiple":
34
+ case "sequence": return containsCommand(term.terms);
35
+ default: return false;
36
+ }
37
+ });
38
+ }
39
+ /** Undefined distinguishes unsupported metadata from a command-free branch. */
40
+ function firstCommandNames(usage) {
41
+ const hosts = usage.filter((term) => containsCommand([term]));
42
+ const first = hosts[0];
43
+ if (first == null) return [];
44
+ if (first.type === "command") return [first.name];
45
+ if (hosts.length !== 1) return void 0;
46
+ if (first.type === "sequence") return firstCommandNames(first.terms);
47
+ if (first.type !== "exclusive") return void 0;
48
+ const names = [];
49
+ for (const branch of first.terms) {
50
+ const found = firstCommandNames(branch);
51
+ if (found == null) return void 0;
52
+ names.push(...found);
53
+ }
54
+ return names;
55
+ }
56
+ function projectCommandTerms(usage, path, offset) {
57
+ if (offset === path.length) return usage;
58
+ const index = usage.findIndex((term) => containsCommand([term]));
59
+ if (index < 0) return void 0;
60
+ const host = usage[index];
61
+ const prefix = usage.slice(0, index);
62
+ const suffix = usage.slice(index + 1);
63
+ const orderedPrefix = prefix.some((term) => term.type === "option" || term.type === "argument" || term.type === "literal");
64
+ const preserveOrder = (terms$1) => orderedPrefix ? [{
65
+ type: "sequence",
66
+ terms: terms$1
67
+ }] : terms$1;
68
+ if (host.type === "command") {
69
+ if (host.name !== path[offset] || require_usage.isUsageHidden(host.hidden)) return void 0;
70
+ const rest = projectCommandTerms(suffix, path, offset + 1);
71
+ return rest == null ? void 0 : preserveOrder([
72
+ ...prefix,
73
+ host,
74
+ ...rest
75
+ ]);
76
+ }
77
+ if (containsCommand(suffix)) return void 0;
78
+ if (host.type === "sequence") {
79
+ const terms$1 = projectCommandTerms(host.terms, path, offset);
80
+ return terms$1 == null ? void 0 : preserveOrder([
81
+ ...prefix,
82
+ {
83
+ ...host,
84
+ terms: terms$1
85
+ },
86
+ ...suffix
87
+ ]);
88
+ }
89
+ if (host.type !== "exclusive") return void 0;
90
+ let selected;
91
+ for (const branch of host.terms) {
92
+ const names = firstCommandNames(branch);
93
+ if (names == null) return void 0;
94
+ if (!names.includes(path[offset])) continue;
95
+ if (selected != null) return void 0;
96
+ selected = branch;
97
+ }
98
+ if (selected == null) return void 0;
99
+ const terms = projectCommandTerms(selected, path, offset);
100
+ return terms == null ? void 0 : preserveOrder([
101
+ ...prefix,
102
+ ...terms,
103
+ ...suffix
104
+ ]);
105
+ }
106
+ /**
5
107
  * Collects option names and command names that are valid as the *immediate*
6
108
  * next token at the current parse position ("leading candidates").
7
109
  *
@@ -152,4 +254,5 @@ exports.collectActiveOptionNames = collectActiveOptionNames;
152
254
  exports.collectLeadingCandidates = collectLeadingCandidates;
153
255
  exports.collectOptionNamesAtCurrentCommandDepth = collectOptionNamesAtCurrentCommandDepth;
154
256
  exports.collectRootOptionNames = collectRootOptionNames;
155
- exports.extractLeadingCommandNames = extractLeadingCommandNames;
257
+ exports.extractLeadingCommandNames = extractLeadingCommandNames;
258
+ exports.projectCommandUsage = projectCommandUsage;
@@ -1,7 +1,109 @@
1
- import { isSuggestionHidden } from "./usage.js";
1
+ import { isSuggestionHidden, isUsageHidden } from "./usage.js";
2
2
 
3
3
  //#region src/usage-internals.ts
4
4
  /**
5
+ * Projects augmented runner usage onto a recorded canonical command path.
6
+ * Unsupported or ambiguous metadata returns undefined, preserving root usage.
7
+ * No parser, documentation, or usage-override callbacks are evaluated.
8
+ * @param usage The runner's original augmented usage.
9
+ * @param path The canonical path recorded by the failed parse.
10
+ * @param meta The known augmentation boundary, absent without alternatives.
11
+ * @returns The selected grammar, or undefined when projection is uncertain.
12
+ * @internal
13
+ */
14
+ function projectCommandUsage(usage, path, meta) {
15
+ if (path.length === 0) return void 0;
16
+ if (meta == null) return projectCommandTerms(usage, path, 0);
17
+ const top = usage[0];
18
+ if (usage.length !== 1 || top.type !== "exclusive" || top.terms.length !== meta.alternativeCount + 1) return void 0;
19
+ const selected = projectCommandTerms(top.terms[meta.optionCount], path, 0);
20
+ if (selected == null) return void 0;
21
+ const options = top.terms.slice(0, meta.optionCount).filter((branch) => branch.some((term) => term.type === "option" && !isUsageHidden(term.hidden)));
22
+ return options.length === 0 ? selected : [{
23
+ type: "exclusive",
24
+ terms: [...options, selected]
25
+ }];
26
+ }
27
+ function containsCommand(usage) {
28
+ return usage.some((term) => {
29
+ switch (term.type) {
30
+ case "command": return true;
31
+ case "exclusive": return term.terms.some(containsCommand);
32
+ case "optional":
33
+ case "multiple":
34
+ case "sequence": return containsCommand(term.terms);
35
+ default: return false;
36
+ }
37
+ });
38
+ }
39
+ /** Undefined distinguishes unsupported metadata from a command-free branch. */
40
+ function firstCommandNames(usage) {
41
+ const hosts = usage.filter((term) => containsCommand([term]));
42
+ const first = hosts[0];
43
+ if (first == null) return [];
44
+ if (first.type === "command") return [first.name];
45
+ if (hosts.length !== 1) return void 0;
46
+ if (first.type === "sequence") return firstCommandNames(first.terms);
47
+ if (first.type !== "exclusive") return void 0;
48
+ const names = [];
49
+ for (const branch of first.terms) {
50
+ const found = firstCommandNames(branch);
51
+ if (found == null) return void 0;
52
+ names.push(...found);
53
+ }
54
+ return names;
55
+ }
56
+ function projectCommandTerms(usage, path, offset) {
57
+ if (offset === path.length) return usage;
58
+ const index = usage.findIndex((term) => containsCommand([term]));
59
+ if (index < 0) return void 0;
60
+ const host = usage[index];
61
+ const prefix = usage.slice(0, index);
62
+ const suffix = usage.slice(index + 1);
63
+ const orderedPrefix = prefix.some((term) => term.type === "option" || term.type === "argument" || term.type === "literal");
64
+ const preserveOrder = (terms$1) => orderedPrefix ? [{
65
+ type: "sequence",
66
+ terms: terms$1
67
+ }] : terms$1;
68
+ if (host.type === "command") {
69
+ if (host.name !== path[offset] || isUsageHidden(host.hidden)) return void 0;
70
+ const rest = projectCommandTerms(suffix, path, offset + 1);
71
+ return rest == null ? void 0 : preserveOrder([
72
+ ...prefix,
73
+ host,
74
+ ...rest
75
+ ]);
76
+ }
77
+ if (containsCommand(suffix)) return void 0;
78
+ if (host.type === "sequence") {
79
+ const terms$1 = projectCommandTerms(host.terms, path, offset);
80
+ return terms$1 == null ? void 0 : preserveOrder([
81
+ ...prefix,
82
+ {
83
+ ...host,
84
+ terms: terms$1
85
+ },
86
+ ...suffix
87
+ ]);
88
+ }
89
+ if (host.type !== "exclusive") return void 0;
90
+ let selected;
91
+ for (const branch of host.terms) {
92
+ const names = firstCommandNames(branch);
93
+ if (names == null) return void 0;
94
+ if (!names.includes(path[offset])) continue;
95
+ if (selected != null) return void 0;
96
+ selected = branch;
97
+ }
98
+ if (selected == null) return void 0;
99
+ const terms = projectCommandTerms(selected, path, offset);
100
+ return terms == null ? void 0 : preserveOrder([
101
+ ...prefix,
102
+ ...terms,
103
+ ...suffix
104
+ ]);
105
+ }
106
+ /**
5
107
  * Collects option names and command names that are valid as the *immediate*
6
108
  * next token at the current parse position ("leading candidates").
7
109
  *
@@ -148,4 +250,4 @@ function collectRootOptionNamesFromTerm(term, names, rootLeadingNames) {
148
250
  }
149
251
 
150
252
  //#endregion
151
- export { collectActiveOptionNames, collectLeadingCandidates, collectOptionNamesAtCurrentCommandDepth, collectRootOptionNames, extractLeadingCommandNames };
253
+ export { collectActiveOptionNames, collectLeadingCandidates, collectOptionNamesAtCurrentCommandDepth, collectRootOptionNames, extractLeadingCommandNames, projectCommandUsage };
@@ -1,6 +1,7 @@
1
1
  const require_message = require('./message.cjs');
2
2
  const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
3
3
  const require_internal_dependency = require('./internal/dependency.cjs');
4
+ const require_internal_doc_metadata = require('./internal/doc-metadata.cjs');
4
5
  const require_suggestion = require('./suggestion.cjs');
5
6
  const require_nonempty = require('./nonempty.cjs');
6
7
 
@@ -92,7 +93,7 @@ function choice(choices, options = {}) {
92
93
  const numberInvalidChoice = options.errors?.invalidChoice;
93
94
  const numberStrings = numberChoices.map((v) => Object.is(v, -0) ? "-0" : String(v));
94
95
  const frozenNumberChoices = Object.freeze(numberChoices);
95
- return {
96
+ return require_internal_doc_metadata.captureDocValue({
96
97
  mode: "sync",
97
98
  metavar: metavar$1,
98
99
  placeholder: choices[0],
@@ -156,7 +157,12 @@ function choice(choices, options = {}) {
156
157
  text: valueStr
157
158
  }));
158
159
  }
159
- };
160
+ }, {
161
+ kind: "number",
162
+ choices: numberChoices,
163
+ preserveNegativeZero: true,
164
+ canFormatDefault: true
165
+ });
160
166
  }
161
167
  const stringChoices = Object.freeze([...new Set(choices)]);
162
168
  const stringOptions = options;
@@ -203,7 +209,7 @@ function choice(choices, options = {}) {
203
209
  maxDistance: stringSuggest.maxDistance,
204
210
  maxSuggestions: stringSuggest.maxSuggestions
205
211
  } : stringSuggest;
206
- return {
212
+ return require_internal_doc_metadata.captureDocValue({
207
213
  mode: "sync",
208
214
  metavar: metavar$1,
209
215
  placeholder: choices[0],
@@ -240,7 +246,11 @@ function choice(choices, options = {}) {
240
246
  text: value
241
247
  }));
242
248
  }
243
- };
249
+ }, {
250
+ kind: "string",
251
+ choices: stringChoices,
252
+ canFormatDefault: customKey === void 0 && !caseInsensitive
253
+ });
244
254
  }
245
255
  function biject(mapping) {
246
256
  if (mapping === null || typeof mapping !== "object") throw new TypeError("Expected object.");
@@ -723,7 +733,7 @@ function string(options = {}) {
723
733
  const patternSource = options.pattern?.source ?? null;
724
734
  const patternFlags = options.pattern?.flags ?? null;
725
735
  const patternMismatch = options.errors?.patternMismatch;
726
- return {
736
+ return require_internal_doc_metadata.captureDocValue({
727
737
  mode: "sync",
728
738
  metavar: metavar$1,
729
739
  placeholder: options.placeholder ?? "",
@@ -747,7 +757,7 @@ function string(options = {}) {
747
757
  format(value) {
748
758
  return value;
749
759
  }
750
- };
760
+ }, { kind: "string" });
751
761
  }
752
762
  /**
753
763
  * Creates a {@link ValueParser} that compiles regular expression sources.
@@ -1179,7 +1189,7 @@ function integer(options) {
1179
1189
  if (options?.type === "bigint") {
1180
1190
  const metavar$2 = options.metavar ?? "INTEGER";
1181
1191
  require_nonempty.ensureNonEmptyString(metavar$2);
1182
- return {
1192
+ return require_internal_doc_metadata.captureDocValue({
1183
1193
  mode: "sync",
1184
1194
  metavar: metavar$2,
1185
1195
  placeholder: options?.placeholder ?? (options?.min != null && options.min > 0n ? options.min : options?.max != null && options.max < 0n ? options.max : 0n),
@@ -1205,7 +1215,7 @@ function integer(options) {
1205
1215
  format(value) {
1206
1216
  return value.toString();
1207
1217
  }
1208
- };
1218
+ }, { kind: "bigint" });
1209
1219
  }
1210
1220
  const metavar$1 = options?.metavar ?? "INTEGER";
1211
1221
  require_nonempty.ensureNonEmptyString(metavar$1);
@@ -1223,7 +1233,7 @@ function integer(options) {
1223
1233
  error: unsafeIntegerError ? typeof unsafeIntegerError === "function" ? unsafeIntegerError(input) : unsafeIntegerError : require_message.message`Expected a safe integer between ${require_message.text(Number.MIN_SAFE_INTEGER.toLocaleString("en"))} and ${require_message.text(Number.MAX_SAFE_INTEGER.toLocaleString("en"))}, but got ${input}. Use type: "bigint" for large values.`
1224
1234
  };
1225
1235
  }
1226
- return {
1236
+ return require_internal_doc_metadata.captureDocValue({
1227
1237
  mode: "sync",
1228
1238
  metavar: metavar$1,
1229
1239
  placeholder: options?.placeholder ?? (firstAllowed > 0 ? firstAllowed : lastAllowed < 0 ? lastAllowed : 0),
@@ -1256,7 +1266,7 @@ function integer(options) {
1256
1266
  format(value) {
1257
1267
  return value.toString();
1258
1268
  }
1259
- };
1269
+ }, { kind: "number" });
1260
1270
  }
1261
1271
  /**
1262
1272
  * Creates a {@link ValueParser} for floating-point numbers.
@@ -1,6 +1,7 @@
1
1
  import { cloneMessage, lineBreak, message, metavar, text, valueSet } from "./message.js";
2
2
  import { mapMaybePromiseByMode, wrapForMode, wrapIterableForMode } from "./internal/mode-dispatch.js";
3
3
  import { defaultValues, dependencyId, dependencyIds, derivedValueParserMarker, getSnapshottedDefaultDependencyValues, isDependencySource, isDerivedValueParser, parseWithDependency, singleDefaultValue, snapshotDefaultDependencyValues, suggestWithDependency } from "./internal/dependency.js";
4
+ import { captureDocValue } from "./internal/doc-metadata.js";
4
5
  import { appendValueHint, appendValueSuggestions, deduplicateSuggestions } from "./suggestion.js";
5
6
  import { ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
6
7
 
@@ -92,7 +93,7 @@ function choice(choices, options = {}) {
92
93
  const numberInvalidChoice = options.errors?.invalidChoice;
93
94
  const numberStrings = numberChoices.map((v) => Object.is(v, -0) ? "-0" : String(v));
94
95
  const frozenNumberChoices = Object.freeze(numberChoices);
95
- return {
96
+ return captureDocValue({
96
97
  mode: "sync",
97
98
  metavar: metavar$1,
98
99
  placeholder: choices[0],
@@ -156,7 +157,12 @@ function choice(choices, options = {}) {
156
157
  text: valueStr
157
158
  }));
158
159
  }
159
- };
160
+ }, {
161
+ kind: "number",
162
+ choices: numberChoices,
163
+ preserveNegativeZero: true,
164
+ canFormatDefault: true
165
+ });
160
166
  }
161
167
  const stringChoices = Object.freeze([...new Set(choices)]);
162
168
  const stringOptions = options;
@@ -203,7 +209,7 @@ function choice(choices, options = {}) {
203
209
  maxDistance: stringSuggest.maxDistance,
204
210
  maxSuggestions: stringSuggest.maxSuggestions
205
211
  } : stringSuggest;
206
- return {
212
+ return captureDocValue({
207
213
  mode: "sync",
208
214
  metavar: metavar$1,
209
215
  placeholder: choices[0],
@@ -240,7 +246,11 @@ function choice(choices, options = {}) {
240
246
  text: value
241
247
  }));
242
248
  }
243
- };
249
+ }, {
250
+ kind: "string",
251
+ choices: stringChoices,
252
+ canFormatDefault: customKey === void 0 && !caseInsensitive
253
+ });
244
254
  }
245
255
  function biject(mapping) {
246
256
  if (mapping === null || typeof mapping !== "object") throw new TypeError("Expected object.");
@@ -723,7 +733,7 @@ function string(options = {}) {
723
733
  const patternSource = options.pattern?.source ?? null;
724
734
  const patternFlags = options.pattern?.flags ?? null;
725
735
  const patternMismatch = options.errors?.patternMismatch;
726
- return {
736
+ return captureDocValue({
727
737
  mode: "sync",
728
738
  metavar: metavar$1,
729
739
  placeholder: options.placeholder ?? "",
@@ -747,7 +757,7 @@ function string(options = {}) {
747
757
  format(value) {
748
758
  return value;
749
759
  }
750
- };
760
+ }, { kind: "string" });
751
761
  }
752
762
  /**
753
763
  * Creates a {@link ValueParser} that compiles regular expression sources.
@@ -1179,7 +1189,7 @@ function integer(options) {
1179
1189
  if (options?.type === "bigint") {
1180
1190
  const metavar$2 = options.metavar ?? "INTEGER";
1181
1191
  ensureNonEmptyString(metavar$2);
1182
- return {
1192
+ return captureDocValue({
1183
1193
  mode: "sync",
1184
1194
  metavar: metavar$2,
1185
1195
  placeholder: options?.placeholder ?? (options?.min != null && options.min > 0n ? options.min : options?.max != null && options.max < 0n ? options.max : 0n),
@@ -1205,7 +1215,7 @@ function integer(options) {
1205
1215
  format(value) {
1206
1216
  return value.toString();
1207
1217
  }
1208
- };
1218
+ }, { kind: "bigint" });
1209
1219
  }
1210
1220
  const metavar$1 = options?.metavar ?? "INTEGER";
1211
1221
  ensureNonEmptyString(metavar$1);
@@ -1223,7 +1233,7 @@ function integer(options) {
1223
1233
  error: unsafeIntegerError ? typeof unsafeIntegerError === "function" ? unsafeIntegerError(input) : unsafeIntegerError : message`Expected a safe integer between ${text(Number.MIN_SAFE_INTEGER.toLocaleString("en"))} and ${text(Number.MAX_SAFE_INTEGER.toLocaleString("en"))}, but got ${input}. Use type: "bigint" for large values.`
1224
1234
  };
1225
1235
  }
1226
- return {
1236
+ return captureDocValue({
1227
1237
  mode: "sync",
1228
1238
  metavar: metavar$1,
1229
1239
  placeholder: options?.placeholder ?? (firstAllowed > 0 ? firstAllowed : lastAllowed < 0 ? lastAllowed : 0),
@@ -1256,7 +1266,7 @@ function integer(options) {
1256
1266
  format(value) {
1257
1267
  return value.toString();
1258
1268
  }
1259
- };
1269
+ }, { kind: "number" });
1260
1270
  }
1261
1271
  /**
1262
1272
  * Creates a {@link ValueParser} for floating-point numbers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.4.0-dev.2899",
3
+ "version": "1.4.0-dev.2935",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -251,6 +251,14 @@
251
251
  },
252
252
  "import": "./dist/internal/help-request.js",
253
253
  "require": "./dist/internal/help-request.cjs"
254
+ },
255
+ "./doc-pages": {
256
+ "types": {
257
+ "import": "./dist/doc-pages.d.ts",
258
+ "require": "./dist/doc-pages.d.cts"
259
+ },
260
+ "import": "./dist/doc-pages.js",
261
+ "require": "./dist/doc-pages.cjs"
254
262
  }
255
263
  },
256
264
  "imports": {
@@ -261,7 +269,7 @@
261
269
  },
262
270
  "sideEffects": false,
263
271
  "devDependencies": {
264
- "@optique/env": "1.4.0-dev.2899+9c2973b0",
272
+ "@optique/env": "1.4.0-dev.2935+fc126fd1",
265
273
  "@types/node": "^24.0.0",
266
274
  "fast-check": "^4.7.0",
267
275
  "tsdown": "^0.13.0",
@@ -80,9 +80,9 @@ Core rules
80
80
  Show hidden `aliases` in help with `showAliases` on `command()` or runner.
81
81
  - Use `run(parser, { completion: "both" })` for completion, or the object form
82
82
  with `completion.errors` for custom shell errors. Do not hand-write scripts.
83
- - Use `usageLine: [{ type: "ellipsis" }]` in runner options when a large root
84
- synopsis should become a compact `Usage: myapp ...` line. This applies only
85
- to root full help; use `command()`'s `usageLine` for subcommand help.
83
+ - Use `usageLine: [{ type: "ellipsis" }]` for compact root full help and the
84
+ override on `command()` for subcommand help. `errorUsage: "command"` scopes
85
+ usage above errors with root fallback, ignoring help-only overrides.
86
86
  - Use `showUsage: false` in runner options when full help should show the
87
87
  brief and sections without `Usage:`. Set `command()`'s `showUsage: true`
88
88
  to restore it; children inherit the nearest explicit ancestor setting.
@@ -243,15 +243,13 @@ Common mistakes checklist
243
243
  - Wrap required `flag("--x")` in `optional()` or `withDefault(..., false)`.
244
244
  - Do not expect `multiple(p)` to fail when absent; it returns `[]`. Wrap with
245
245
  `nonEmpty()` when at least one value is required.
246
- - Do not confuse free-order parsing with `seq()`. Most constructs let child
247
- parsers compete by priority; use `seq()` only for truly ordered grammars.
246
+ - Use `seq()` only for ordered grammars; most constructs use parser priority.
248
247
  - Use structured `message` values for errors and descriptions.
249
248
  - Register contexts for `bindEnv()`, `bindConfig()`, `bindDerivedDefault()`,
250
249
  and `bindKeyring()` in the runner's `contexts` option.
251
250
  - Use `bindEnv().readFallback()` for env/default in error handlers.
252
251
  - Enable `showEnvironment` for env-only help; set `documentation.description`.
253
- - Keep multi-level dependency graphs with `dependency()` rather than
254
- duplicating one-level factories.
252
+ - Keep multi-level `dependency()` graphs; avoid duplicate one-level factories.
255
253
  - Put runtime prompt checks in `when`, with typed `otherwise`; evaluate them
256
254
  only at fallback, never eagerly during construction.
257
255
 
@@ -272,8 +270,10 @@ Reference links
272
270
  - Structured messages: <https://optique.dev/concepts/messages.md>
273
271
  - Shell completion: <https://optique.dev/concepts/completion.md>
274
272
  - Command discovery: <https://optique.dev/concepts/discover.md>
275
- - CLI: <https://github.com/dahlia/optique/tree/main/examples/file-inspect>
276
- - Man pages: <https://optique.dev/concepts/man.md>
273
+ - [CLI recipe][file-inspect]
274
+ - Markdown references: <https://optique.dev/concepts/markdown-reference.md>
275
+
276
+ [file-inspect]: https://github.com/dahlia/optique/tree/main/examples/file-inspect
277
277
 
278
278
 
279
279
  Integration packages
@@ -294,6 +294,6 @@ Integration packages
294
294
  | `@optique/temporal` | Temporal date and time parsers | <https://optique.dev/integrations/temporal.md> |
295
295
  | `@optique/git` | Async Git reference validation | <https://optique.dev/integrations/git.md> |
296
296
  | `@optique/logtape` | LogTape levels and formatter/output options | <https://optique.dev/integrations/logtape.md> |
297
- | `@optique/man` | Man page generation | <https://optique.dev/concepts/man.md> |
297
+ | `@optique/man` | Man pages and Markdown references | <https://optique.dev/concepts/man.md> |
298
298
  | `@optique/discover` | Command discovery; opt-in `help.bareGroup` | <https://optique.dev/concepts/discover.md> |
299
299
  | `@optique/testing` | Layered CLI testing at a chosen boundary | <https://optique.dev/concepts/testing.md> |