@loomcli/core 0.5.0 → 0.6.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.
Files changed (77) hide show
  1. package/dist/application.d.ts +18 -2
  2. package/dist/application.js +266 -71
  3. package/dist/bindings.d.ts +15 -10
  4. package/dist/bindings.js +34 -15
  5. package/dist/chain.d.ts +15 -6
  6. package/dist/chain.js +40 -20
  7. package/dist/command-rules.d.ts +55 -0
  8. package/dist/command-rules.js +142 -0
  9. package/dist/command.d.ts +65 -23
  10. package/dist/command.js +695 -226
  11. package/dist/controls.d.ts +8 -0
  12. package/dist/controls.js +23 -0
  13. package/dist/defect.d.ts +18 -0
  14. package/dist/defect.js +272 -0
  15. package/dist/developer.d.ts +24 -0
  16. package/dist/developer.js +52 -0
  17. package/dist/diagnostic-text.d.ts +81 -0
  18. package/dist/diagnostic-text.js +283 -0
  19. package/dist/diagnostic.d.ts +11 -0
  20. package/dist/diagnostic.js +70 -0
  21. package/dist/errors.d.ts +108 -24
  22. package/dist/errors.js +324 -53
  23. package/dist/exit-codes.d.ts +45 -0
  24. package/dist/exit-codes.js +46 -0
  25. package/dist/extension.d.ts +41 -8
  26. package/dist/extension.js +142 -57
  27. package/dist/facts.d.ts +66 -11
  28. package/dist/facts.js +98 -22
  29. package/dist/globals.d.ts +29 -21
  30. package/dist/globals.js +115 -46
  31. package/dist/hints.d.ts +79 -0
  32. package/dist/hints.js +247 -0
  33. package/dist/host.d.ts +13 -0
  34. package/dist/host.js +43 -1
  35. package/dist/identity.d.ts +19 -0
  36. package/dist/identity.js +72 -0
  37. package/dist/index.d.ts +11 -2
  38. package/dist/index.js +5 -0
  39. package/dist/input-rules.d.ts +64 -0
  40. package/dist/input-rules.js +145 -0
  41. package/dist/inspect.d.ts +12 -4
  42. package/dist/inspect.js +88 -28
  43. package/dist/lanes.js +1 -1
  44. package/dist/locate.js +4 -4
  45. package/dist/options.d.ts +23 -2
  46. package/dist/options.js +135 -44
  47. package/dist/output.d.ts +9 -2
  48. package/dist/output.js +18 -2
  49. package/dist/plain.d.ts +6 -0
  50. package/dist/plain.js +12 -0
  51. package/dist/plugin-rules.d.ts +62 -0
  52. package/dist/plugin-rules.js +155 -0
  53. package/dist/plugin.d.ts +22 -10
  54. package/dist/plugin.js +348 -116
  55. package/dist/prototypes.d.ts +7 -0
  56. package/dist/prototypes.js +29 -0
  57. package/dist/rendering.d.ts +6 -1
  58. package/dist/rendering.js +23 -5
  59. package/dist/rules.d.ts +51 -0
  60. package/dist/rules.js +115 -0
  61. package/dist/sequence.js +6 -1
  62. package/dist/sources.d.ts +6 -4
  63. package/dist/sources.js +22 -13
  64. package/dist/style-wire.js +1 -1
  65. package/dist/style.js +1 -1
  66. package/dist/theme.d.ts +4 -0
  67. package/dist/theme.js +25 -5
  68. package/dist/thenable.d.ts +15 -0
  69. package/dist/thenable.js +29 -0
  70. package/dist/translators.d.ts +69 -0
  71. package/dist/translators.js +253 -0
  72. package/dist/types.d.ts +11 -1
  73. package/dist/validation.d.ts +44 -3
  74. package/dist/validation.js +156 -57
  75. package/dist/view.d.ts +48 -14
  76. package/dist/view.js +155 -77
  77. package/package.json +1 -1
package/dist/bindings.js CHANGED
@@ -1,45 +1,64 @@
1
- import { DeclarationError } from './errors.js';
1
+ import { DeclarationError, quoted } from './errors.js';
2
+ import { factFault, siteFinding } from './facts.js';
3
+ import { envName, envOnArgument, envOnMultiple, variableBoundTwice } from './input-rules.js';
2
4
  /** A variable name: a letter or an underscore, then letters, digits, or underscores. */
3
5
  const variableName = /^[A-Za-z_][A-Za-z0-9_]*$/u;
4
6
  /**
5
7
  * The environment binding one option declares, or `undefined` when it declares none. A multiple
6
8
  * option takes its list from the configuration source, so it cannot bind, and a bound name must be
7
- * a variable name. `sentence` names the declaration at the start of the diagnostic.
9
+ * a variable name. The site names the declaration and marks its `env`.
8
10
  */
9
- export function checkEnvBinding(sentence, config) {
11
+ export function checkEnvBinding(site, config) {
10
12
  const env = config.env;
11
13
  if (env === undefined) {
12
14
  return undefined;
13
15
  }
14
16
  if (config.multiple === true) {
15
- throw new DeclarationError(`${sentence} is a multiple option and declares env. Remove env; a list comes from the configuration source.`);
17
+ throw factFault(envOnMultiple, site, {
18
+ correction: 'Remove env; a list comes from the configuration source.',
19
+ fact: 'env',
20
+ sentence: `${site.subject} is a multiple option and declares env.`,
21
+ });
16
22
  }
17
23
  if (typeof env !== 'string' || !variableName.test(env)) {
18
- const quoted = typeof env === 'string' ? ` "${env}"` : '';
19
- throw new DeclarationError(`${sentence} env${quoted} is not a variable name. Use a letter or an underscore, then letters, digits, or underscores.`);
24
+ const named = typeof env === 'string' ? ` ${quoted(env)}` : '';
25
+ throw factFault(envName, site, {
26
+ correction: 'Use a letter or an underscore, then letters, digits, or underscores.',
27
+ fact: 'env',
28
+ sentence: `${site.subject} env${named} is not a variable name.`,
29
+ });
20
30
  }
21
31
  return env;
22
32
  }
23
33
  /** An argument is identified by its place among bare tokens, so no variable can stand in for it. */
24
- export function checkNoArgumentBinding(sentence, config) {
34
+ export function checkNoArgumentBinding(site, config) {
25
35
  if ('env' in config) {
26
- throw new DeclarationError(`${sentence} declares env, which applies to options alone. Remove it.`);
36
+ throw factFault(envOnArgument, site, {
37
+ correction: 'Remove it.',
38
+ fact: 'env',
39
+ sentence: `${site.subject} declares env, which applies to options alone.`,
40
+ });
27
41
  }
28
42
  }
29
43
  /**
30
- * The variables one scope binds, each to the phrase of the option that binds it. Within one
31
- * invocation's scope a variable binds one option, so a second binder is a declaration error that
32
- * names the first binder, in scope order, and then the second. `held` is what an enclosing scope
33
- * already binds, such as the globals table under a Command's own options.
44
+ * The variables one scope binds, each to the option that binds it. Within one invocation's scope a
45
+ * variable binds one option, so a second binder is a declaration error that names the first
46
+ * binder, in scope order, and then the second. `held` is what an enclosing scope already binds,
47
+ * such as the globals table under a Command's own options.
34
48
  */
35
49
  export function claimVariables(bound, held = new Map()) {
36
50
  const claimed = new Map(held);
37
- for (const { site, variable } of bound) {
51
+ for (const binder of bound) {
52
+ const { phrase, variable } = binder;
38
53
  const owner = claimed.get(variable);
39
54
  if (owner !== undefined) {
40
- throw new DeclarationError(`Variable "${variable}" is bound by ${owner} and ${site}. Bind each variable to one option.`);
55
+ throw new DeclarationError(variableBoundTwice, {
56
+ correction: 'Bind each variable to one option.',
57
+ findings: [owner, binder].map(({ site }) => siteFinding(site, `${site.at}.env`)),
58
+ sentence: `Variable ${quoted(variable)} is bound by ${owner.phrase} and ${phrase}.`,
59
+ });
41
60
  }
42
- claimed.set(variable, site);
61
+ claimed.set(variable, binder);
43
62
  }
44
63
  return claimed;
45
64
  }
package/dist/chain.d.ts CHANGED
@@ -40,13 +40,19 @@ interface Invocation {
40
40
  /** The action's own channel, built from the routed Command's declaration when it dispatches. */
41
41
  channel: (binding: ResultBinding) => ActionChannel;
42
42
  defaults: DefaultValues;
43
- facts: {
44
- description: string | undefined;
45
- version: string;
46
- };
47
43
  graph: BuiltGraph;
48
44
  host: Host;
49
- name: string;
45
+ /**
46
+ * The graph `inspect()` returns, built at most once for the run. A configuration source reads
47
+ * its requests from it, the chain reads it after the source, and an `onFailure` hook reads it too.
48
+ */
49
+ inspected: () => CommandGraph;
50
+ /**
51
+ * Offers one throw from the application's work to the translators, and answers with the failure
52
+ * that replaces it, or `undefined` when none does. The chain offers a throw where it leaves the
53
+ * chain, and a configuration source's throw is offered where the resolver's call settles.
54
+ */
55
+ offer: (thrown: unknown) => LoomError | undefined;
50
56
  /**
51
57
  * The invocation's own channel. A middleware reads it as the neutral `Out`, and the action
52
58
  * receives the channel the results lane builds for the Command that was routed.
@@ -57,7 +63,10 @@ interface Invocation {
57
63
  plugins: readonly BuiltPlugin[];
58
64
  /** A fault reported after the primary outcome, which turns a would-be 0 into 1. */
59
65
  report: (fault: LoomError) => void;
60
- /** The routed path, published where routing resolved it, which output names in its own line. */
66
+ /**
67
+ * The path routing walked, published as each name routes, which output names in its own line
68
+ * and a failure view reads. An unknown Command leaves the partial path published.
69
+ */
61
70
  route: (path: readonly string[]) => void;
62
71
  signal: AbortSignal;
63
72
  }
package/dist/chain.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { prepareDispatch, routeInvocation } from './command.js';
2
- import { InternalError, reasonOf, routedSubject, toFailure } from './errors.js';
3
- import { inspectGraph, nodeAt } from './inspect.js';
2
+ import { foreignFailure, InternalError, routedSubject } from './errors.js';
3
+ import { nodeAt } from './inspect.js';
4
4
  import { isSupplied } from './options.js';
5
5
  import { loadDefault, pluginSentence, pluginSpellings, pluginValues } from './plugin.js';
6
+ import { nextMisuse, viewSelection, viewSelectionCorrection } from './rules.js';
6
7
  /**
7
8
  * The view one run selects, which is one value whichever middleware wrote it. The assignment is
8
9
  * kept as it arrived, because a JavaScript caller reaches the setter with any value and the check
@@ -53,13 +54,13 @@ class ViewSelection {
53
54
  const plugin = pluginSentence(assigned.identity);
54
55
  const { name } = assigned;
55
56
  if (!this.#result) {
56
- throw new InternalError(`${plugin} selected view "${String(name)}" on ${routedSubject(path)}, which declares no result.`, undefined);
57
+ throw selectionFault(`${plugin} selected view "${String(name)}" on ${routedSubject(path)}, which declares no result.`);
57
58
  }
58
59
  if (typeof name !== 'string') {
59
- throw new InternalError(`${plugin} selected a view that is not a string on ${routedSubject(path)}.`, undefined);
60
+ throw selectionFault(`${plugin} selected a view that is not a string on ${routedSubject(path)}.`);
60
61
  }
61
62
  if (!this.#result.views.has(name)) {
62
- throw new InternalError(`${plugin} selected view "${name}", which ${routedSubject(path)} does not name.`, undefined);
63
+ throw selectionFault(`${plugin} selected view "${name}", which ${routedSubject(path)} does not name.`);
63
64
  }
64
65
  return name;
65
66
  }
@@ -118,9 +119,21 @@ async function quiet(pending) {
118
119
  function reported(chain, outcome) {
119
120
  return outcome === 'taken-over' && chain.cancelled() ? 'cancelled' : outcome;
120
121
  }
122
+ /** The defect a view selection the routed Command cannot render reports. */
123
+ function selectionFault(sentence) {
124
+ return new InternalError(viewSelection, {
125
+ cause: undefined,
126
+ correction: viewSelectionCorrection,
127
+ sentence,
128
+ });
129
+ }
121
130
  /** A `next()` call that is no longer live: it dispatches nothing and rejects. */
122
131
  function misuse(turn) {
123
- const fault = new InternalError(`${pluginSentence(turn.entry.identity)} called next() ${turn.state.returned ? 'after its middleware returned' : 'twice'}.`, undefined);
132
+ const fault = new InternalError(nextMisuse, {
133
+ cause: undefined,
134
+ correction: 'Call next() once, and await it before the middleware returns.',
135
+ sentence: `${pluginSentence(turn.entry.identity)} called next() ${turn.state.returned ? 'after its middleware returned' : 'twice'}.`,
136
+ });
124
137
  turn.chain.report(fault);
125
138
  const rejected = Promise.reject(fault);
126
139
  void rejected.catch(() => undefined);
@@ -183,7 +196,7 @@ async function settle(turn, thrown) {
183
196
  * again when the middleware let it escape. It keeps the one report it already has.
184
197
  */
185
198
  if (!chain.announced(thrown.value)) {
186
- chain.report(new InternalError(reasonOf(thrown.value), thrown.value));
199
+ chain.report(foreignFailure(thrown.value));
187
200
  }
188
201
  }
189
202
  if (state.calls === 0) {
@@ -217,10 +230,16 @@ async function runEntry(entry, index, chain) {
217
230
  state.returned = true;
218
231
  return settle(turn, thrown);
219
232
  }
220
- /** The whole chain, answering with the failure it raised when a middleware caught that failure. */
233
+ /**
234
+ * The whole chain, answering with the value it raised when a middleware caught that value. The
235
+ * value is kept as it was thrown, so it is offered to the translators once, where it leaves.
236
+ */
221
237
  async function runChain(invocation, routed, prepared) {
222
238
  const entries = activatedEntries(invocation.plugins, prepared.globals);
223
- const run = { invoked: false, raised: undefined };
239
+ const run = {
240
+ invoked: false,
241
+ raised: undefined,
242
+ };
224
243
  const selection = new ViewSelection(prepared.result);
225
244
  /**
226
245
  * The dispatch boundary: the point the chain reaches when its last middleware continues. Core
@@ -271,7 +290,7 @@ async function runChain(invocation, routed, prepared) {
271
290
  }),
272
291
  invoked: () => run.invoked,
273
292
  record: (error) => {
274
- run.raised ??= toFailure(error);
293
+ run.raised ??= { value: error };
275
294
  },
276
295
  report: (fault) => {
277
296
  announced.add(fault);
@@ -300,20 +319,21 @@ async function runChain(invocation, routed, prepared) {
300
319
  * raised and nothing later in the chain runs.
301
320
  */
302
321
  async function runInvocation(invocation) {
303
- const routed = routeInvocation(invocation.graph, invocation.host.argv);
304
- invocation.route(routed.path);
305
- // The graph `inspect()` returns, built at most once for the run.
306
- // A configuration source reads its requests from it, and the chain reads it after the source.
307
- let graph = undefined;
308
- const inspected = () => (graph ??= inspectGraph(invocation.name, invocation.graph, invocation.facts));
309
- const run = { ...invocation, inspected };
310
- const prepared = await prepareDispatch(invocation.graph, routed, run);
311
- const raised = await runChain(run, routed, prepared);
322
+ const routed = routeInvocation(invocation.graph, invocation.host.argv, invocation.route);
323
+ const prepared = await prepareDispatch(invocation.graph, routed, invocation);
324
+ let raised = undefined;
325
+ try {
326
+ raised = await runChain(invocation, routed, prepared);
327
+ }
328
+ catch (error) {
329
+ // A throw leaves the chain here, so a middleware that awaited next() saw it raw.
330
+ throw invocation.offer(error) ?? error;
331
+ }
312
332
  if (raised) {
313
333
  // The chain resolved because a middleware caught the rejection.
314
334
  // The failure it caught still decides the exit code.
315
335
  // That is the rule an action's caught output rejection already follows.
316
- throw raised;
336
+ throw invocation.offer(raised.value) ?? raised.value;
317
337
  }
318
338
  }
319
339
  export { runInvocation };
@@ -0,0 +1,55 @@
1
+ /** An application name, a Command name, or an alias outside the portable name rule. */
2
+ declare const portableName: import("./diagnostic-text.js").DiagnosticRule;
3
+ /** An argument name outside the declared-name rule. */
4
+ declare const declaredName: import("./diagnostic-text.js").DiagnosticRule;
5
+ /** Globals declared on a named Command's options. */
6
+ declare const commandGlobals: import("./diagnostic-text.js").DiagnosticRule;
7
+ /** A declaration call a Command's own `action()` already closed. */
8
+ declare const declaredAfterAction: import("./diagnostic-text.js").DiagnosticRule;
9
+ /** A second `action()` on one Command. */
10
+ declare const multipleActions: import("./diagnostic-text.js").DiagnosticRule;
11
+ /** One Command that declares arguments and attaches children. */
12
+ declare const argumentsBesideChildren: import("./diagnostic-text.js").DiagnosticRule;
13
+ /** Two arguments with one name on one Command. */
14
+ declare const argumentDeclaredTwice: import("./diagnostic-text.js").DiagnosticRule;
15
+ /** A variadic argument that another argument follows. */
16
+ declare const variadicArgumentLast: import("./diagnostic-text.js").DiagnosticRule;
17
+ /** An optional argument that another argument follows. */
18
+ declare const optionalArgumentLast: import("./diagnostic-text.js").DiagnosticRule;
19
+ /** An `alias()` call that names no alias. */
20
+ declare const aliasWithoutNames: import("./diagnostic-text.js").DiagnosticRule;
21
+ /** An alias that repeats its own Command's name or another of its aliases. */
22
+ declare const repeatedAlias: import("./diagnostic-text.js").DiagnosticRule;
23
+ /** A child whose name or alias repeats a sibling's name or alias. */
24
+ declare const siblingNameTaken: import("./diagnostic-text.js").DiagnosticRule;
25
+ /** A child that would sit more than two levels below the root. */
26
+ declare const nestingDepth: import("./diagnostic-text.js").DiagnosticRule;
27
+ /** A value passed to `command()` that is not a Command. */
28
+ declare const notACommand: import("./diagnostic-text.js").DiagnosticRule;
29
+ /** One Command value attached at two places. */
30
+ declare const commandAttachedTwice: import("./diagnostic-text.js").DiagnosticRule;
31
+ /** A Command with neither an action nor children. */
32
+ declare const commandWithoutAction: import("./diagnostic-text.js").DiagnosticRule;
33
+ /** A group, a Command with no action, that declares a local option. */
34
+ declare const groupOption: import("./diagnostic-text.js").DiagnosticRule;
35
+ /** A second `result()` or `rows()` on one Command. */
36
+ declare const multipleResults: import("./diagnostic-text.js").DiagnosticRule;
37
+ /** A `views()` call on a Command that declares no result. */
38
+ declare const viewsWithoutResult: import("./diagnostic-text.js").DiagnosticRule;
39
+ /** A views entry that is not exactly one view. */
40
+ declare const viewShape: import("./diagnostic-text.js").DiagnosticRule;
41
+ /** A row view under a result declared with `result()`. */
42
+ declare const rowViewOnValue: import("./diagnostic-text.js").DiagnosticRule;
43
+ /** A view name outside the declared-name rule, or one that is integer-like. */
44
+ declare const viewName: import("./diagnostic-text.js").DiagnosticRule;
45
+ /** A result on a Command with no action. */
46
+ declare const resultWithoutAction: import("./diagnostic-text.js").DiagnosticRule;
47
+ /** A result whose merged views record holds no view. */
48
+ declare const resultWithoutViews: import("./diagnostic-text.js").DiagnosticRule;
49
+ /** A default view that the merged views record does not hold. */
50
+ declare const unknownDefaultView: import("./diagnostic-text.js").DiagnosticRule;
51
+ /** A description, a deprecated message, or an Application version that is not one line of prose. */
52
+ declare const notOneLine: import("./diagnostic-text.js").DiagnosticRule;
53
+ /** `hidden` or `deprecated` on the Application or on an argument. */
54
+ declare const misplacedListingFact: import("./diagnostic-text.js").DiagnosticRule;
55
+ export { aliasWithoutNames, argumentDeclaredTwice, argumentsBesideChildren, commandAttachedTwice, commandGlobals, commandWithoutAction, declaredAfterAction, declaredName, groupOption, misplacedListingFact, multipleActions, multipleResults, nestingDepth, notOneLine, notACommand, optionalArgumentLast, portableName, repeatedAlias, resultWithoutAction, resultWithoutViews, rowViewOnValue, siblingNameTaken, unknownDefaultView, variadicArgumentLast, viewName, viewShape, viewsWithoutResult, };
@@ -0,0 +1,142 @@
1
+ import { registerRule } from './diagnostic-text.js';
2
+ /*
3
+ * Core's rules for the declaration faults of Commands and their names, aliases, nesting, children,
4
+ * actions, results, and core facts. Each is declared once here and shared by every site that raises
5
+ * it, as a plugin's rules are.
6
+ */
7
+ /** An application name, a Command name, or an alias outside the portable name rule. */
8
+ const portableName = registerRule('@loomcli/core/portable-name', {
9
+ explanation: 'An operator types the application name, each Command name, and each alias as a command at a shell prompt. A character outside the POSIX portable filename set needs quoting there, a leading "-" reads as an option, and a leading "." names a file a shell hides.',
10
+ headline: 'Name not portable',
11
+ });
12
+ /** An argument name outside the declared-name rule. */
13
+ const declaredName = registerRule('@loomcli/core/declared-name', {
14
+ explanation: 'An action reads each argument and option under its name, and help and diagnostics print it. A leading "-" reads as an option, and whitespace or "=" splits the name where the parser reads it.',
15
+ headline: 'Invalid declared name',
16
+ });
17
+ /** Globals declared on a named Command's options. */
18
+ const commandGlobals = registerRule('@loomcli/core/command-globals', {
19
+ explanation: "Global options belong to the Application, which declares them with globalOption() and hands their values to every action. A named Command reads their types from the Application's registered environment.",
20
+ headline: 'Globals on a named Command',
21
+ });
22
+ /** A declaration call a Command's own `action()` already closed. */
23
+ const declaredAfterAction = registerRule('@loomcli/core/declared-after-action', {
24
+ explanation: "action() finishes a Command's declaration: the handler's types read every argument, option, alias, result, and child declared before it, so a later call would declare what the handler never sees.",
25
+ headline: 'Declared after the action',
26
+ });
27
+ /** A second `action()` on one Command. */
28
+ const multipleActions = registerRule('@loomcli/core/multiple-actions', {
29
+ explanation: 'Routing runs one handler for the Command it selects, so a second action would leave one of the two unreachable.',
30
+ headline: 'Second action',
31
+ });
32
+ /** One Command that declares arguments and attaches children. */
33
+ const argumentsBesideChildren = registerRule('@loomcli/core/arguments-beside-children', {
34
+ explanation: "A Command's first bare token either names a child or fills an argument, so a Command that holds both cannot tell which one an operator meant.",
35
+ headline: 'Arguments beside children',
36
+ });
37
+ /** Two arguments with one name on one Command. */
38
+ const argumentDeclaredTwice = registerRule('@loomcli/core/argument-declared-twice', {
39
+ explanation: 'An action reads each argument under its name, so two arguments with one name leave one of them unreadable.',
40
+ headline: 'Argument declared twice',
41
+ });
42
+ /** A variadic argument that another argument follows. */
43
+ const variadicArgumentLast = registerRule('@loomcli/core/variadic-argument-last', {
44
+ explanation: 'A variadic argument takes every positional token that remains, so an argument after it would never receive one.',
45
+ headline: 'Variadic argument not last',
46
+ });
47
+ /** An optional argument that another argument follows. */
48
+ const optionalArgumentLast = registerRule('@loomcli/core/optional-argument-last', {
49
+ explanation: 'Positional tokens fill the arguments in order, and an operator leaves out an optional argument from the end of the line. An argument after an optional one would take the token the optional one was meant to receive.',
50
+ headline: 'Optional argument not last',
51
+ });
52
+ /** An `alias()` call that names no alias. */
53
+ const aliasWithoutNames = registerRule('@loomcli/core/alias-without-names', {
54
+ explanation: 'alias() adds each name it receives to the names that route to its Command, so a call with none adds nothing.',
55
+ headline: 'Alias with no names',
56
+ });
57
+ /** An alias that repeats its own Command's name or another of its aliases. */
58
+ const repeatedAlias = registerRule('@loomcli/core/repeated-alias', {
59
+ explanation: 'A Command answers to its name and to each of its aliases, so an alias that repeats one of them routes nothing new.',
60
+ headline: 'Alias repeats a name',
61
+ });
62
+ /** A child whose name or alias repeats a sibling's name or alias. */
63
+ const siblingNameTaken = registerRule('@loomcli/core/sibling-name-taken', {
64
+ explanation: 'Every canonical name and alias under one parent routes one token to one child, so a name that two siblings share cannot route.',
65
+ headline: 'Name taken by a sibling',
66
+ });
67
+ /** A child that would sit more than two levels below the root. */
68
+ const nestingDepth = registerRule('@loomcli/core/nesting-depth', {
69
+ explanation: 'Each level of nesting adds a token an operator types before a Command runs. Loom keeps every Command at most two levels below the root, so every invocation stays short enough to remember.',
70
+ headline: 'Commands nested too deep',
71
+ });
72
+ /** A value passed to `command()` that is not a Command. */
73
+ const notACommand = registerRule('@loomcli/core/not-a-command', {
74
+ explanation: 'A Command value carries the declaration that routing, parsing, and help read. Any other value carries none.',
75
+ headline: 'Not a Command',
76
+ });
77
+ /** One Command value attached at two places. */
78
+ const commandAttachedTwice = registerRule('@loomcli/core/command-attached-twice', {
79
+ explanation: 'A Command value sits at one place in the tree, where its path, its help page, and its parent read it, so one value attached at two places would have two paths.',
80
+ headline: 'Command attached twice',
81
+ });
82
+ /** A Command with neither an action nor children. */
83
+ const commandWithoutAction = registerRule('@loomcli/core/command-without-action', {
84
+ explanation: "Routing ends at a Command that runs its action, or passes on to one of a group's children. A Command with neither leaves an invocation that reaches it nothing to run.",
85
+ headline: 'Nothing to run',
86
+ });
87
+ /** A group, a Command with no action, that declares a local option. */
88
+ const groupOption = registerRule('@loomcli/core/group-option', {
89
+ explanation: 'A Command with no action is a group, which passes an invocation on to one of its children. A local option is never inherited, so no action reads an option a group declares.',
90
+ headline: 'Option on a group',
91
+ });
92
+ /** A second `result()` or `rows()` on one Command. */
93
+ const multipleResults = registerRule('@loomcli/core/multiple-results', {
94
+ explanation: "A Command's action emits one result through out.results(), declared once, as a value with result() or as rows with rows(), so its consumers read one declaration.",
95
+ headline: 'Second result',
96
+ });
97
+ /** A `views()` call on a Command that declares no result. */
98
+ const viewsWithoutResult = registerRule('@loomcli/core/views-without-result', {
99
+ explanation: 'views() reshapes the views a declared result renders through. A Command that declares no result emits nothing for a view to render.',
100
+ headline: 'Views with no result',
101
+ });
102
+ /** A views entry that is not exactly one view. */
103
+ const viewShape = registerRule('@loomcli/core/view-shape', {
104
+ explanation: 'A views entry is a view with render, which receives the whole result, or a row view with row, which receives one row at a time. Core reads which function it holds to decide how to feed it.',
105
+ headline: 'Not one view',
106
+ });
107
+ /** A row view under a result declared with `result()`. */
108
+ const rowViewOnValue = registerRule('@loomcli/core/row-view-on-value', {
109
+ explanation: 'A row view renders one row at a time, which only a result declared with rows() emits. A value result arrives whole, so a view with render reads it.',
110
+ headline: 'Row view on a value result',
111
+ });
112
+ /** A view name outside the declared-name rule, or one that is integer-like. */
113
+ const viewName = registerRule('@loomcli/core/view-name', {
114
+ explanation: 'An operator and a middleware select a view by its name, so it is a bare token. It is not integer-like either, because an object moves such a key ahead of every other and the views lose the order they were declared in.',
115
+ headline: 'Invalid view name',
116
+ });
117
+ /** A result on a Command with no action. */
118
+ const resultWithoutAction = registerRule('@loomcli/core/result-without-action', {
119
+ explanation: 'A declared result is a promise the action keeps by emitting through out.results(). A Command with no action has nothing to keep it.',
120
+ headline: 'Result with no action',
121
+ });
122
+ /** A result whose merged views record holds no view. */
123
+ const resultWithoutViews = registerRule('@loomcli/core/result-without-views', {
124
+ explanation: 'A result prints through one of its views: the default one, or the one an operator or a middleware selects. A result with none has no way to print.',
125
+ headline: 'Result with no views',
126
+ });
127
+ /** A default view that the merged views record does not hold. */
128
+ const unknownDefaultView = registerRule('@loomcli/core/unknown-default-view', {
129
+ explanation: 'The default view renders a result when nothing selects another, so it names one of the views the result declares.',
130
+ headline: 'Default view not declared',
131
+ });
132
+ /** A description, a deprecated message, or an Application version that is not one line of prose. */
133
+ const notOneLine = registerRule('@loomcli/core/not-one-line', {
134
+ explanation: 'Help, --version, the manifest, and every other listing print a description, a deprecated message, and a version on one line beside what each names, so each holds prose and no line break. An operator or an agent follows a deprecated message to the replacement, so a bare true names none, and a version is a string as the package manifest spells it.',
135
+ headline: 'Text not one line',
136
+ });
137
+ /** `hidden` or `deprecated` on the Application or on an argument. */
138
+ const misplacedListingFact = registerRule('@loomcli/core/misplaced-listing-fact', {
139
+ explanation: 'hidden and deprecated keep a named Command or an option off a listing, or mark it retired. The root is the entry point of every page, and an argument cannot leave the grammar it sits in, so neither carries them.',
140
+ headline: 'Listing fact out of place',
141
+ });
142
+ export { aliasWithoutNames, argumentDeclaredTwice, argumentsBesideChildren, commandAttachedTwice, commandGlobals, commandWithoutAction, declaredAfterAction, declaredName, groupOption, misplacedListingFact, multipleActions, multipleResults, nestingDepth, notOneLine, notACommand, optionalArgumentLast, portableName, repeatedAlias, resultWithoutAction, resultWithoutViews, rowViewOnValue, siblingNameTaken, unknownDefaultView, variadicArgumentLast, viewName, viewShape, viewsWithoutResult, };