patram 0.10.0 → 0.12.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 (117) hide show
  1. package/bin/patram.js +4 -4
  2. package/lib/cli/arguments.types.d.ts +1 -0
  3. package/lib/cli/commands/check.js +27 -15
  4. package/lib/cli/commands/fields.js +0 -4
  5. package/lib/cli/commands/queries.js +179 -1
  6. package/lib/cli/commands/query.js +1 -8
  7. package/lib/cli/commands/refs.js +3 -10
  8. package/lib/cli/commands/show.js +1 -8
  9. package/lib/cli/help-metadata.js +106 -111
  10. package/lib/cli/main.js +10 -10
  11. package/lib/cli/parse-arguments-helpers.js +416 -66
  12. package/lib/cli/parse-arguments.js +4 -4
  13. package/lib/cli/render-help.js +10 -4
  14. package/lib/config/defaults.js +33 -25
  15. package/lib/config/load-patram-config.d.ts +19 -33
  16. package/lib/config/load-patram-config.js +18 -121
  17. package/lib/config/load-patram-config.types.d.ts +3 -40
  18. package/lib/config/manage-stored-queries-helpers.d.ts +69 -0
  19. package/lib/config/manage-stored-queries-helpers.js +320 -0
  20. package/lib/config/manage-stored-queries-jsonc.d.ts +31 -0
  21. package/lib/config/manage-stored-queries-jsonc.js +95 -0
  22. package/lib/config/manage-stored-queries.d.ts +77 -0
  23. package/lib/config/manage-stored-queries.js +300 -0
  24. package/lib/config/patram-config.d.ts +34 -34
  25. package/lib/config/patram-config.js +3 -3
  26. package/lib/config/patram-config.types.d.ts +5 -11
  27. package/lib/config/resolve-patram-graph-config.d.ts +5 -1
  28. package/lib/config/resolve-patram-graph-config.js +3 -119
  29. package/lib/config/schema.d.ts +158 -269
  30. package/lib/config/schema.js +72 -210
  31. package/lib/config/validate-patram-config-value.d.ts +13 -0
  32. package/lib/config/validate-patram-config-value.js +94 -0
  33. package/lib/config/validation.d.ts +2 -12
  34. package/lib/config/validation.js +125 -483
  35. package/lib/find-close-match.d.ts +4 -1
  36. package/lib/graph/build-graph-identity.d.ts +1 -32
  37. package/lib/graph/build-graph-identity.js +5 -269
  38. package/lib/graph/build-graph.d.ts +13 -4
  39. package/lib/graph/build-graph.js +347 -488
  40. package/lib/graph/build-graph.types.d.ts +8 -9
  41. package/lib/graph/check-directive-metadata-helpers.d.ts +30 -0
  42. package/lib/graph/check-directive-metadata-helpers.js +126 -0
  43. package/lib/graph/check-directive-metadata.d.ts +8 -9
  44. package/lib/graph/check-directive-metadata.js +70 -561
  45. package/lib/graph/check-directive-path-target.d.ts +6 -13
  46. package/lib/graph/check-directive-path-target.js +26 -57
  47. package/lib/graph/check-directive-value.d.ts +1 -5
  48. package/lib/graph/check-directive-value.js +40 -180
  49. package/lib/graph/check-graph.d.ts +5 -5
  50. package/lib/graph/check-graph.js +8 -6
  51. package/lib/graph/document-node-identity.d.ts +23 -7
  52. package/lib/graph/document-node-identity.js +417 -160
  53. package/lib/graph/graph-node.d.ts +42 -0
  54. package/lib/graph/graph-node.js +83 -0
  55. package/lib/graph/inspect-reverse-references.js +16 -11
  56. package/lib/graph/load-project-graph.d.ts +7 -7
  57. package/lib/graph/load-project-graph.js +7 -7
  58. package/lib/graph/parse-where-clause.types.d.ts +3 -2
  59. package/lib/graph/query/cypher-reader.d.ts +59 -0
  60. package/lib/graph/query/cypher-reader.js +151 -0
  61. package/lib/graph/query/cypher-support.d.ts +79 -0
  62. package/lib/graph/query/cypher-support.js +213 -0
  63. package/lib/graph/query/cypher-tokenize.d.ts +13 -0
  64. package/lib/graph/query/cypher-tokenize.js +225 -0
  65. package/lib/graph/query/cypher.types.d.ts +43 -0
  66. package/lib/graph/query/execute.d.ts +7 -7
  67. package/lib/graph/query/execute.js +71 -33
  68. package/lib/graph/query/inspect.js +58 -24
  69. package/lib/graph/query/parse-cypher-patterns.d.ts +27 -0
  70. package/lib/graph/query/parse-cypher-patterns.js +382 -0
  71. package/lib/graph/query/parse-cypher.d.ts +7 -0
  72. package/lib/graph/query/parse-cypher.js +580 -0
  73. package/lib/graph/query/parse-query.d.ts +13 -0
  74. package/lib/graph/query/parse-query.js +97 -0
  75. package/lib/graph/query/resolve.d.ts +6 -0
  76. package/lib/graph/query/resolve.js +81 -24
  77. package/lib/output/command-output.js +12 -5
  78. package/lib/output/compact-layout.js +221 -0
  79. package/lib/output/format-output-item-block.js +31 -1
  80. package/lib/output/format-output-metadata.js +16 -29
  81. package/lib/output/format-stored-query-block.js +95 -0
  82. package/lib/output/layout-incoming-references.js +101 -19
  83. package/lib/output/layout-stored-queries.js +23 -330
  84. package/lib/output/list-queries.js +1 -1
  85. package/lib/output/render-field-discovery.js +11 -2
  86. package/lib/output/render-output-view.js +9 -5
  87. package/lib/output/renderers/json.js +5 -26
  88. package/lib/output/renderers/plain.js +155 -35
  89. package/lib/output/renderers/rich.js +250 -36
  90. package/lib/output/resolve-check-target.js +120 -11
  91. package/lib/output/resolved-link-layout.js +43 -0
  92. package/lib/output/rich-source/render.js +193 -35
  93. package/lib/output/show-document.js +25 -18
  94. package/lib/output/view-model/index.js +124 -103
  95. package/lib/parse/jsdoc/parse-jsdoc-blocks.js +1 -1
  96. package/lib/parse/jsdoc/parse-jsdoc-claims.js +12 -6
  97. package/lib/parse/markdown/parse-markdown-claims.js +99 -62
  98. package/lib/parse/markdown/parse-markdown-directives.d.ts +10 -6
  99. package/lib/parse/markdown/parse-markdown-directives.js +104 -18
  100. package/lib/parse/markdown/parse-markdown-prose.d.ts +27 -0
  101. package/lib/parse/markdown/parse-markdown-prose.js +243 -0
  102. package/lib/parse/parse-claims.d.ts +2 -6
  103. package/lib/parse/parse-claims.js +11 -53
  104. package/lib/parse/tagged-fenced/tagged-fenced-blocks.d.ts +4 -4
  105. package/lib/parse/tagged-fenced/tagged-fenced-blocks.js +4 -4
  106. package/lib/parse/yaml/parse-yaml-claims.js +4 -4
  107. package/lib/patram.d.ts +9 -3
  108. package/lib/patram.js +1 -1
  109. package/lib/scan/discover-fields.js +194 -55
  110. package/lib/scan/list-source-files.d.ts +4 -4
  111. package/lib/scan/list-source-files.js +4 -4
  112. package/package.json +2 -1
  113. package/lib/directive-validation-test-helpers.js +0 -87
  114. package/lib/graph/query/parse.d.ts +0 -75
  115. package/lib/graph/query/parse.js +0 -1064
  116. package/lib/output/derived-summary.js +0 -280
  117. package/lib/output/format-derived-summary-row.js +0 -9
@@ -5,7 +5,7 @@
5
5
  * @typedef {import('./arguments.types.ts').CliParseError} CliParseError
6
6
  * @typedef {import('./arguments.types.ts').ParsedCliHelpRequest} ParsedCliHelpRequest
7
7
  * @typedef {{ kind: string, name?: string, rawName?: string, value?: string | boolean }} CliOptionToken
8
- * @typedef {{ color?: string, explain?: boolean, help?: boolean, json?: boolean, limit?: string, lint?: boolean, 'no-color'?: boolean, offset?: string, plain?: boolean, where?: string }} CliOptionValues
8
+ * @typedef {{ color?: string, cypher?: string, desc?: string, explain?: boolean, help?: boolean, json?: boolean, limit?: string, lint?: boolean, name?: string, 'no-color'?: boolean, offset?: string, plain?: boolean, where?: string }} CliOptionValues
9
9
  * @typedef {{ option_tokens: CliOptionToken[], positionals: string[], values: CliOptionValues }} ParsedCommandLine
10
10
  */
11
11
 
@@ -23,11 +23,14 @@ import { findInvalidQueryPagination } from './query-pagination.js';
23
23
 
24
24
  export const CLI_OPTIONS = /** @type {const} */ ({
25
25
  color: { type: 'string' },
26
+ cypher: { type: 'string' },
27
+ desc: { type: 'string' },
26
28
  explain: { type: 'boolean' },
27
29
  help: { type: 'boolean' },
28
30
  json: { type: 'boolean' },
29
31
  limit: { type: 'string' },
30
32
  lint: { type: 'boolean' },
33
+ name: { type: 'string' },
31
34
  'no-color': { type: 'boolean' },
32
35
  offset: { type: 'string' },
33
36
  plain: { type: 'boolean' },
@@ -96,34 +99,15 @@ export function validateParsedCommand(command_name, command_line) {
96
99
  const command_positionals = command_line.positionals.slice(1);
97
100
 
98
101
  if (
99
- command_name === 'query' &&
100
- command_line.values.where !== undefined &&
101
- command_positionals.length === 0
102
+ hasAdHocQueryCommand(command_name, command_line.values, command_positionals)
102
103
  ) {
103
- return (
104
- createMessageParseError(
105
- findInvalidQueryPagination(command_line.option_tokens),
106
- ) ??
107
- findInvalidQueryMode(
108
- command_name,
109
- command_line.values,
110
- command_positionals,
111
- )
112
- );
104
+ return validateAdHocQueryCommand(command_line, command_positionals);
113
105
  }
114
106
 
115
- return (
116
- findInvalidCommandOption(command_name, command_line.option_tokens) ??
117
- createMessageParseError(
118
- findInvalidQueryPagination(command_line.option_tokens),
119
- ) ??
120
- findInvalidQueryInspection(command_name, command_line.values) ??
121
- findInvalidQueryMode(
122
- command_name,
123
- command_line.values,
124
- command_positionals,
125
- ) ??
126
- validateCommandPositionals(command_name, command_positionals)
107
+ return validateStandardCommand(
108
+ command_name,
109
+ command_line,
110
+ command_positionals,
127
111
  );
128
112
  }
129
113
 
@@ -154,17 +138,134 @@ export function buildCommandArguments(
154
138
  command_positionals,
155
139
  parsed_values,
156
140
  ) {
157
- if (command_name === 'query' && parsed_values.where !== undefined) {
158
- return ['--where', parsed_values.where];
141
+ if (command_name === 'query' || command_name === 'refs') {
142
+ return buildAdHocQueryArguments(
143
+ command_name,
144
+ command_positionals,
145
+ parsed_values,
146
+ );
159
147
  }
160
148
 
161
- if (command_name === 'refs' && parsed_values.where !== undefined) {
162
- return [command_positionals[0], '--where', parsed_values.where];
149
+ if (command_name === 'queries') {
150
+ return buildStoredQueryArguments(command_positionals, parsed_values);
151
+ }
152
+
153
+ return command_positionals;
154
+ }
155
+
156
+ /**
157
+ * @param {CliCommandName} command_name
158
+ * @param {CliOptionValues} parsed_values
159
+ * @param {string[]} command_positionals
160
+ * @returns {boolean}
161
+ */
162
+ function hasAdHocQueryCommand(
163
+ command_name,
164
+ parsed_values,
165
+ command_positionals,
166
+ ) {
167
+ return (
168
+ command_name === 'query' &&
169
+ parsed_values.cypher !== undefined &&
170
+ command_positionals.length === 0
171
+ );
172
+ }
173
+
174
+ /**
175
+ * @param {ParsedCommandLine} command_line
176
+ * @param {string[]} command_positionals
177
+ * @returns {CliParseError | null}
178
+ */
179
+ function validateAdHocQueryCommand(command_line, command_positionals) {
180
+ return (
181
+ findInvalidCommandOption('query', command_line.option_tokens) ??
182
+ createMessageParseError(
183
+ findInvalidQueryPagination(command_line.option_tokens),
184
+ ) ??
185
+ findInvalidQueryMode('query', command_line.values, command_positionals)
186
+ );
187
+ }
188
+
189
+ /**
190
+ * @param {CliCommandName} command_name
191
+ * @param {ParsedCommandLine} command_line
192
+ * @param {string[]} command_positionals
193
+ * @returns {CliParseError | null}
194
+ */
195
+ function validateStandardCommand(
196
+ command_name,
197
+ command_line,
198
+ command_positionals,
199
+ ) {
200
+ return (
201
+ findInvalidCommandOption(command_name, command_line.option_tokens) ??
202
+ createMessageParseError(
203
+ findInvalidQueryPagination(command_line.option_tokens),
204
+ ) ??
205
+ findInvalidQueryInspection(command_name, command_line.values) ??
206
+ findInvalidQueryMode(
207
+ command_name,
208
+ command_line.values,
209
+ command_positionals,
210
+ ) ??
211
+ findInvalidQueriesMutation(
212
+ command_name,
213
+ command_line.values,
214
+ command_positionals,
215
+ ) ??
216
+ validateCommandPositionals(command_name, command_positionals)
217
+ );
218
+ }
219
+
220
+ /**
221
+ * @param {'query' | 'refs'} command_name
222
+ * @param {string[]} command_positionals
223
+ * @param {CliOptionValues} parsed_values
224
+ * @returns {string[]}
225
+ */
226
+ function buildAdHocQueryArguments(
227
+ command_name,
228
+ command_positionals,
229
+ parsed_values,
230
+ ) {
231
+ if (parsed_values.cypher !== undefined) {
232
+ return command_name === 'query'
233
+ ? ['--cypher', parsed_values.cypher]
234
+ : [command_positionals[0], '--cypher', parsed_values.cypher];
163
235
  }
164
236
 
165
237
  return command_positionals;
166
238
  }
167
239
 
240
+ /**
241
+ * @param {string[]} command_positionals
242
+ * @param {CliOptionValues} parsed_values
243
+ * @returns {string[]}
244
+ */
245
+ function buildStoredQueryArguments(command_positionals, parsed_values) {
246
+ /** @type {string[]} */
247
+ const command_arguments = [...command_positionals];
248
+
249
+ appendOptionValue(command_arguments, '--name', parsed_values.name);
250
+ appendOptionValue(command_arguments, '--cypher', parsed_values.cypher);
251
+ appendOptionValue(command_arguments, '--desc', parsed_values.desc);
252
+
253
+ return command_arguments;
254
+ }
255
+
256
+ /**
257
+ * @param {string[]} command_arguments
258
+ * @param {string} option_name
259
+ * @param {string | undefined} option_value
260
+ */
261
+ function appendOptionValue(command_arguments, option_name, option_value) {
262
+ if (option_value === undefined) {
263
+ return;
264
+ }
265
+
266
+ command_arguments.push(option_name, option_value);
267
+ }
268
+
168
269
  /**
169
270
  * @param {string | undefined} help_target
170
271
  * @returns {CliParseError}
@@ -305,24 +406,10 @@ function findInvalidCommandOption(command_name, option_tokens) {
305
406
  */
306
407
  function findMissingOptionValue(option_tokens) {
307
408
  for (const token of option_tokens) {
308
- if (token.name === 'where' && typeof token.value !== 'string') {
309
- return {
310
- argument_label: "<name> or --where '<clause>'",
311
- code: 'missing_required_argument',
312
- command_name: 'query',
313
- };
314
- }
409
+ const missing_value_error = findMissingOptionValueError(token);
315
410
 
316
- if (token.name === 'offset' && typeof token.value !== 'string') {
317
- return createMessageParseError('Offset requires a value.');
318
- }
319
-
320
- if (token.name === 'limit' && typeof token.value !== 'string') {
321
- return createMessageParseError('Limit requires a value.');
322
- }
323
-
324
- if (token.name === 'color' && typeof token.value !== 'string') {
325
- return createMessageParseError('Color requires a value.');
411
+ if (missing_value_error) {
412
+ return missing_value_error;
326
413
  }
327
414
  }
328
415
 
@@ -356,14 +443,38 @@ function findInvalidQueryMode(
356
443
  ) {
357
444
  if (
358
445
  command_name === 'query' &&
359
- parsed_values.where !== undefined &&
446
+ parsed_values.cypher !== undefined &&
360
447
  command_positionals.length > 0
361
448
  ) {
362
449
  return createMessageParseError(
363
- 'Query accepts either "--where" or a stored query name.',
450
+ 'Query accepts either "--cypher" or a stored query name.',
364
451
  );
365
452
  }
366
453
 
454
+ if (command_name === 'query' && parsed_values.where !== undefined) {
455
+ return {
456
+ code: 'option_not_valid_for_command',
457
+ command_name: 'query',
458
+ token: '--where',
459
+ };
460
+ }
461
+
462
+ if (command_name === 'queries' && parsed_values.where !== undefined) {
463
+ return {
464
+ code: 'option_not_valid_for_command',
465
+ command_name: 'queries',
466
+ token: '--where',
467
+ };
468
+ }
469
+
470
+ if (command_name === 'refs' && parsed_values.where !== undefined) {
471
+ return {
472
+ code: 'option_not_valid_for_command',
473
+ command_name: 'refs',
474
+ token: '--where',
475
+ };
476
+ }
477
+
367
478
  return null;
368
479
  }
369
480
 
@@ -386,6 +497,42 @@ function findInvalidQueryInspection(command_name, parsed_values) {
386
497
  return null;
387
498
  }
388
499
 
500
+ /**
501
+ * @param {CliCommandName} command_name
502
+ * @param {CliOptionValues} parsed_values
503
+ * @param {string[]} command_positionals
504
+ * @returns {CliParseError | null}
505
+ */
506
+ function findInvalidQueriesMutation(
507
+ command_name,
508
+ parsed_values,
509
+ command_positionals,
510
+ ) {
511
+ if (command_name !== 'queries' || command_positionals.length === 0) {
512
+ return null;
513
+ }
514
+
515
+ const subcommand_name = command_positionals[0];
516
+
517
+ if (
518
+ subcommand_name !== 'add' &&
519
+ subcommand_name !== 'remove' &&
520
+ subcommand_name !== 'update'
521
+ ) {
522
+ return createUnexpectedArgumentError('queries', subcommand_name);
523
+ }
524
+
525
+ if (subcommand_name === 'add') {
526
+ return validateQueriesAddMutation(parsed_values, command_positionals);
527
+ }
528
+
529
+ if (subcommand_name === 'remove') {
530
+ return validateQueriesRemoveMutation(parsed_values, command_positionals);
531
+ }
532
+
533
+ return validateQueriesUpdateMutation(parsed_values, command_positionals);
534
+ }
535
+
389
536
  /**
390
537
  * @param {CliCommandName} command_name
391
538
  * @param {string[]} command_positionals
@@ -394,24 +541,18 @@ function findInvalidQueryInspection(command_name, parsed_values) {
394
541
  function validateCommandPositionals(command_name, command_positionals) {
395
542
  const command_definition = getCommandDefinition(command_name);
396
543
 
544
+ if (allowsQueriesMutationPositionals(command_name, command_positionals)) {
545
+ return null;
546
+ }
547
+
397
548
  if (command_positionals.length < command_definition.min_positionals) {
398
- if (command_name === 'query' && command_definition.missing_argument_label) {
399
- return {
400
- argument_label: command_definition.missing_argument_label,
401
- code: 'missing_required_argument',
402
- command_name: 'query',
403
- };
404
- }
549
+ const missing_argument_error = createMissingArgumentError(
550
+ command_name,
551
+ command_definition.missing_argument_label,
552
+ );
405
553
 
406
- if (
407
- (command_name === 'refs' || command_name === 'show') &&
408
- command_definition.missing_argument_label
409
- ) {
410
- return {
411
- argument_label: command_definition.missing_argument_label,
412
- code: 'missing_required_argument',
413
- command_name,
414
- };
554
+ if (missing_argument_error) {
555
+ return missing_argument_error;
415
556
  }
416
557
 
417
558
  return createMessageParseError(
@@ -428,7 +569,7 @@ function validateCommandPositionals(command_name, command_positionals) {
428
569
 
429
570
  if (command_name === 'query' && command_positionals.length === 0) {
430
571
  return {
431
- argument_label: "<name> or --where '<clause>'",
572
+ argument_label: "<name> or --cypher '<query>'",
432
573
  code: 'missing_required_argument',
433
574
  command_name: 'query',
434
575
  };
@@ -443,14 +584,223 @@ function validateCommandPositionals(command_name, command_positionals) {
443
584
  */
444
585
  function isKnownCommandOptionName(option_name) {
445
586
  return (
587
+ option_name === 'desc' ||
588
+ option_name === 'cypher' ||
446
589
  option_name === 'explain' ||
447
590
  option_name === 'limit' ||
448
591
  option_name === 'lint' ||
592
+ option_name === 'name' ||
449
593
  option_name === 'offset' ||
594
+ option_name === 'query' ||
450
595
  option_name === 'where'
451
596
  );
452
597
  }
453
598
 
599
+ /**
600
+ * @param {CliOptionToken} option_token
601
+ * @returns {CliParseError | null}
602
+ */
603
+ function findMissingOptionValueError(option_token) {
604
+ if (typeof option_token.value === 'string') {
605
+ return null;
606
+ }
607
+
608
+ if (option_token.name === 'where') {
609
+ return createMessageParseError('Where requires a value.');
610
+ }
611
+
612
+ if (option_token.name === 'cypher') {
613
+ return {
614
+ argument_label: "<name> or --cypher '<query>'",
615
+ code: 'missing_required_argument',
616
+ command_name: 'query',
617
+ };
618
+ }
619
+
620
+ const message = getMissingOptionValueMessage(option_token.name);
621
+
622
+ if (!message) {
623
+ return null;
624
+ }
625
+
626
+ return createMessageParseError(message);
627
+ }
628
+
629
+ /**
630
+ * @param {string | undefined} option_name
631
+ * @returns {string | null}
632
+ */
633
+ function getMissingOptionValueMessage(option_name) {
634
+ /** @type {Record<string, string>} */
635
+ const option_messages = {
636
+ color: 'Color requires a value.',
637
+ cypher: 'Cypher requires a value.',
638
+ desc: 'Desc requires a value.',
639
+ limit: 'Limit requires a value.',
640
+ name: 'Name requires a value.',
641
+ offset: 'Offset requires a value.',
642
+ query: 'Query requires a value.',
643
+ where: 'Where requires a value.',
644
+ };
645
+
646
+ if (!option_name || !Object.hasOwn(option_messages, option_name)) {
647
+ return null;
648
+ }
649
+
650
+ return option_messages[option_name];
651
+ }
652
+
653
+ /**
654
+ * @param {CliOptionValues} parsed_values
655
+ * @param {string[]} command_positionals
656
+ * @returns {CliParseError | null}
657
+ */
658
+ function validateQueriesAddMutation(parsed_values, command_positionals) {
659
+ const positional_error = validateQueriesMutationPositionals(
660
+ command_positionals,
661
+ 'Queries add requires a stored query name.',
662
+ );
663
+
664
+ if (positional_error) {
665
+ return positional_error;
666
+ }
667
+
668
+ if (parsed_values.name !== undefined) {
669
+ return createMessageParseError('Queries add does not accept "--name".');
670
+ }
671
+
672
+ if (parsed_values.cypher === undefined) {
673
+ return createMessageParseError('Queries add requires "--cypher <query>".');
674
+ }
675
+
676
+ return null;
677
+ }
678
+
679
+ /**
680
+ * @param {CliOptionValues} parsed_values
681
+ * @param {string[]} command_positionals
682
+ * @returns {CliParseError | null}
683
+ */
684
+ function validateQueriesRemoveMutation(parsed_values, command_positionals) {
685
+ const positional_error = validateQueriesMutationPositionals(
686
+ command_positionals,
687
+ 'Queries remove requires a stored query name.',
688
+ );
689
+
690
+ if (positional_error) {
691
+ return positional_error;
692
+ }
693
+
694
+ if (
695
+ parsed_values.desc !== undefined ||
696
+ parsed_values.cypher !== undefined ||
697
+ parsed_values.name !== undefined ||
698
+ parsed_values.where !== undefined
699
+ ) {
700
+ return createMessageParseError(
701
+ 'Queries remove does not accept mutation options.',
702
+ );
703
+ }
704
+
705
+ return null;
706
+ }
707
+
708
+ /**
709
+ * @param {CliOptionValues} parsed_values
710
+ * @param {string[]} command_positionals
711
+ * @returns {CliParseError | null}
712
+ */
713
+ function validateQueriesUpdateMutation(parsed_values, command_positionals) {
714
+ const positional_error = validateQueriesMutationPositionals(
715
+ command_positionals,
716
+ 'Queries update requires a stored query name.',
717
+ );
718
+
719
+ if (positional_error) {
720
+ return positional_error;
721
+ }
722
+
723
+ if (
724
+ parsed_values.desc === undefined &&
725
+ parsed_values.cypher === undefined &&
726
+ parsed_values.name === undefined
727
+ ) {
728
+ return createMessageParseError(
729
+ 'Queries update requires at least one of "--name", "--cypher", or "--desc".',
730
+ );
731
+ }
732
+
733
+ return null;
734
+ }
735
+
736
+ /**
737
+ * @param {string[]} command_positionals
738
+ * @param {string} missing_name_message
739
+ * @returns {CliParseError | null}
740
+ */
741
+ function validateQueriesMutationPositionals(
742
+ command_positionals,
743
+ missing_name_message,
744
+ ) {
745
+ if (command_positionals.length < 2) {
746
+ return createMessageParseError(missing_name_message);
747
+ }
748
+
749
+ if (command_positionals.length > 2) {
750
+ return createUnexpectedArgumentError('queries', command_positionals[2]);
751
+ }
752
+
753
+ return null;
754
+ }
755
+
756
+ /**
757
+ * @param {CliCommandName} command_name
758
+ * @param {string[]} command_positionals
759
+ * @returns {boolean}
760
+ */
761
+ function allowsQueriesMutationPositionals(command_name, command_positionals) {
762
+ if (command_name !== 'queries' || command_positionals.length === 0) {
763
+ return false;
764
+ }
765
+
766
+ const subcommand_name = command_positionals[0];
767
+
768
+ return (
769
+ subcommand_name === 'add' ||
770
+ subcommand_name === 'remove' ||
771
+ subcommand_name === 'update'
772
+ );
773
+ }
774
+
775
+ /**
776
+ * @param {CliCommandName} command_name
777
+ * @param {string | null} missing_argument_label
778
+ * @returns {CliParseError | null}
779
+ */
780
+ function createMissingArgumentError(command_name, missing_argument_label) {
781
+ if (!missing_argument_label) {
782
+ return null;
783
+ }
784
+
785
+ if (command_name === 'query') {
786
+ return {
787
+ argument_label: missing_argument_label,
788
+ code: 'missing_required_argument',
789
+ command_name: 'query',
790
+ };
791
+ }
792
+
793
+ if (command_name === 'refs' || command_name === 'show') {
794
+ return {
795
+ argument_label: missing_argument_label,
796
+ code: 'missing_required_argument',
797
+ command_name,
798
+ };
799
+ }
800
+
801
+ return null;
802
+ }
803
+
454
804
  /**
455
805
  * @param {'help' | CliCommandName} command_name
456
806
  * @param {string | undefined} token
@@ -34,10 +34,10 @@ import { buildQueryPagination } from './query-pagination.js';
34
34
  * Normalizes raw argv into one validated Patram command plus shared output and
35
35
  * pagination options.
36
36
  *
37
- * Kind: cli
38
- * Status: active
39
- * Tracked in: ../../docs/plans/v0/source-anchor-dogfooding.md
40
- * Decided by: ../../docs/decisions/cli-argument-parser.md
37
+ * kind: cli
38
+ * status: active
39
+ * tracked_in: ../../docs/plans/v0/source-anchor-dogfooding.md
40
+ * decided_by: ../../docs/decisions/cli-argument-parser.md
41
41
  * @patram
42
42
  * @see {@link ./main.js}
43
43
  * @see {@link ../../docs/decisions/cli-argument-parser.md}
@@ -85,6 +85,7 @@ export function renderCliParseError(parse_error) {
85
85
  if (parse_error.code === 'unknown_stored_query') {
86
86
  return renderUnknownStoredQueryError(
87
87
  parse_error.name,
88
+ parse_error.next_path,
88
89
  parse_error.suggestion,
89
90
  );
90
91
  }
@@ -100,7 +101,7 @@ export function renderInvalidWhereDiagnostic(diagnostic) {
100
101
  const diagnostic_line = formatDiagnostic(diagnostic);
101
102
 
102
103
  return joinOutputLines([
103
- 'Invalid where clause:',
104
+ 'Invalid query:',
104
105
  ` ${diagnostic_line}`,
105
106
  '',
106
107
  'Next:',
@@ -156,7 +157,7 @@ function renderCommandHelp(command_name) {
156
157
  ) {
157
158
  output_lines.push(
158
159
  '',
159
- 'Where clause:',
160
+ 'Query syntax:',
160
161
  ...indentLines(command_definition.syntax_lines),
161
162
  );
162
163
  }
@@ -359,10 +360,15 @@ function renderUnexpectedArgumentError(command_name, invalid_token) {
359
360
 
360
361
  /**
361
362
  * @param {string} stored_query_name
363
+ * @param {string | undefined} next_path
362
364
  * @param {string | undefined} suggestion
363
365
  * @returns {string}
364
366
  */
365
- function renderUnknownStoredQueryError(stored_query_name, suggestion) {
367
+ function renderUnknownStoredQueryError(
368
+ stored_query_name,
369
+ next_path,
370
+ suggestion,
371
+ ) {
366
372
  if (suggestion) {
367
373
  return joinOutputLines([
368
374
  `Unknown stored query: ${stored_query_name}`,
@@ -371,7 +377,7 @@ function renderUnknownStoredQueryError(stored_query_name, suggestion) {
371
377
  ` ${suggestion}`,
372
378
  '',
373
379
  'Next:',
374
- ` patram query ${suggestion}`,
380
+ ` ${next_path ?? `patram query ${suggestion}`}`,
375
381
  ]);
376
382
  }
377
383