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
@@ -1,22 +1,26 @@
1
1
  /**
2
- * @import { OutputNodeItem } from './output-view.types.ts';
2
+ * @import { RefsOutputView, OutputNodeItem } from './output-view.types.ts';
3
3
  */
4
4
 
5
- import { formatOutputNodeMetadataRows } from './format-output-metadata.js';
5
+ import { formatCompactMetadataLabel } from './compact-layout.js';
6
6
  import { formatNodeHeader } from './format-node-header.js';
7
- import { formatOutputItemBlock } from './format-output-item-block.js';
7
+ import { formatOutputNodeStoredMetadataRow } from './format-output-metadata.js';
8
+
9
+ const INCOMING_TREE_PREFIX_WIDTH = 5;
8
10
 
9
11
  /**
10
12
  * Layout grouped incoming references as plain text lines.
11
13
  *
12
14
  * @param {Record<string, OutputNodeItem[]>} incoming
13
15
  * @param {{
16
+ * format_node_block?: (output_item: OutputNodeItem) => string,
14
17
  * format_node_header?: (output_item: OutputNodeItem) => string,
15
18
  * format_relation_header?: (relation_name: string, relation_count: number) => string,
16
19
  * }} layout_options
17
20
  * @returns {string[]}
18
21
  */
19
22
  export function layoutIncomingReferenceLines(incoming, layout_options = {}) {
23
+ const formatNodeBlock = layout_options.format_node_block;
20
24
  const formatNodeHeader =
21
25
  layout_options.format_node_header ?? defaultNodeHeaderFormatter;
22
26
  const formatRelationHeader =
@@ -35,13 +39,51 @@ export function layoutIncomingReferenceLines(incoming, layout_options = {}) {
35
39
  formatRelationHeader(relation_name, relation_sources.length),
36
40
  );
37
41
  output_lines.push(
38
- ...layoutIncomingRelationSources(relation_sources, formatNodeHeader),
42
+ ...layoutIncomingRelationSources(
43
+ relation_sources,
44
+ formatNodeBlock ?? createDefaultNodeBlockFormatter(formatNodeHeader),
45
+ ),
39
46
  );
40
47
  }
41
48
 
42
49
  return output_lines;
43
50
  }
44
51
 
52
+ /**
53
+ * @param {RefsOutputView} output_view
54
+ * @returns {string[]}
55
+ */
56
+ export function collectIncomingRefHeaders(output_view) {
57
+ /** @type {string[]} */
58
+ const headers = [formatNodeHeader(output_view.node)];
59
+
60
+ for (const incoming_items of Object.values(output_view.incoming)) {
61
+ for (const output_item of incoming_items) {
62
+ headers.push(formatNodeHeader(output_item));
63
+ }
64
+ }
65
+
66
+ return headers;
67
+ }
68
+
69
+ /**
70
+ * @param {{ is_tty?: boolean, terminal_width?: number }} render_options
71
+ * @returns {{ is_tty?: boolean, terminal_width?: number }}
72
+ */
73
+ export function createIncomingTreeRenderOptions(render_options) {
74
+ if (typeof render_options.terminal_width !== 'number') {
75
+ return render_options;
76
+ }
77
+
78
+ return {
79
+ ...render_options,
80
+ terminal_width: Math.max(
81
+ 1,
82
+ render_options.terminal_width - INCOMING_TREE_PREFIX_WIDTH,
83
+ ),
84
+ };
85
+ }
86
+
45
87
  /**
46
88
  * @param {OutputNodeItem} output_item
47
89
  * @returns {string}
@@ -61,21 +103,18 @@ function defaultRelationHeaderFormatter(relation_name, relation_count) {
61
103
 
62
104
  /**
63
105
  * @param {OutputNodeItem[]} relation_sources
64
- * @param {(output_item: OutputNodeItem) => string} format_node_header
106
+ * @param {(output_item: OutputNodeItem) => string} format_node_block
65
107
  * @returns {string[]}
66
108
  */
67
- function layoutIncomingRelationSources(relation_sources, format_node_header) {
109
+ function layoutIncomingRelationSources(relation_sources, format_node_block) {
68
110
  /** @type {string[]} */
69
111
  const output_lines = [];
70
112
 
71
113
  for (const [item_index, output_item] of relation_sources.entries()) {
72
- if (item_index > 0) {
73
- output_lines.push('');
74
- }
75
-
76
114
  output_lines.push(
77
- ...indentIncomingNodeBlock(
78
- formatIncomingNodeBlock(output_item, format_node_header),
115
+ ...layoutIncomingNodeBlock(
116
+ format_node_block(output_item),
117
+ item_index === relation_sources.length - 1,
79
118
  ),
80
119
  );
81
120
  }
@@ -89,17 +128,60 @@ function layoutIncomingRelationSources(relation_sources, format_node_header) {
89
128
  * @returns {string}
90
129
  */
91
130
  function formatIncomingNodeBlock(output_item, format_node_header) {
92
- return formatOutputItemBlock({
93
- header: format_node_header(output_item),
94
- metadata_rows: formatOutputNodeMetadataRows(output_item),
95
- title: output_item.title,
96
- });
131
+ const stored_metadata_row = formatOutputNodeStoredMetadataRow(output_item);
132
+ const metadata_label = stored_metadata_row
133
+ ? formatCompactMetadataLabel([stored_metadata_row])
134
+ : undefined;
135
+ const header = metadata_label
136
+ ? `${format_node_header(output_item)} ${metadata_label}`
137
+ : format_node_header(output_item);
138
+ /** @type {string[]} */
139
+ const output_lines = [header];
140
+
141
+ output_lines.push(output_item.title);
142
+
143
+ if (output_item.description) {
144
+ output_lines.push(...output_item.description.split('\n'));
145
+ }
146
+
147
+ return output_lines.join('\n');
148
+ }
149
+
150
+ /**
151
+ * @param {(output_item: OutputNodeItem) => string} format_node_header
152
+ * @returns {(output_item: OutputNodeItem) => string}
153
+ */
154
+ function createDefaultNodeBlockFormatter(format_node_header) {
155
+ return (output_item) =>
156
+ formatIncomingNodeBlock(output_item, format_node_header);
97
157
  }
98
158
 
99
159
  /**
100
160
  * @param {string} block
161
+ * @param {boolean} is_last_item
101
162
  * @returns {string[]}
102
163
  */
103
- function indentIncomingNodeBlock(block) {
104
- return block.split('\n').map((line) => (line.length > 0 ? ` ${line}` : ''));
164
+ function layoutIncomingNodeBlock(block, is_last_item) {
165
+ const header_prefix = is_last_item ? ' └─ ' : ' ├─ ';
166
+ const continuation_prefix = is_last_item ? ' ' : ' │ ';
167
+
168
+ return block.split('\n').map((line, line_index) => {
169
+ if (line_index === 0) {
170
+ return `${header_prefix}${line}`;
171
+ }
172
+
173
+ if (line.length === 0) {
174
+ return '';
175
+ }
176
+
177
+ return `${continuation_prefix}${trimIncomingContinuationIndent(line)}`;
178
+ });
179
+ }
180
+
181
+ /**
182
+ * @param {string} line
183
+ * @returns {string}
184
+ */
185
+ function trimIncomingContinuationIndent(line) {
186
+ return line.startsWith(' ') ? line.slice(2) : line;
105
187
  }
@@ -1,14 +1,7 @@
1
- /* eslint-disable max-lines */
2
1
  /**
3
2
  * @import { OutputStoredQueryItem } from './output-view.types.ts';
4
3
  */
5
4
 
6
- import { parseWhereClause } from '../graph/query/parse.js';
7
-
8
- const MAX_STORED_QUERY_WIDTH = 100;
9
- const MIN_TERM_COLUMN_WIDTH = 20;
10
- const STORED_QUERY_COLUMN_GAP = 2;
11
-
12
5
  /**
13
6
  * @typedef {'description' | 'field_name' | 'keyword' | 'literal' | 'name' | 'operator' | 'plain'} StoredQuerySegmentKind
14
7
  */
@@ -18,336 +11,29 @@ const STORED_QUERY_COLUMN_GAP = 2;
18
11
  */
19
12
 
20
13
  /**
21
- * Layout stored queries into styled lines shared by plain and rich renderers.
22
- *
23
- * @param {OutputStoredQueryItem[]} output_items
14
+ * @param {string} where_clause
15
+ * @param {number} first_line_width
16
+ * @param {number} continuation_width
24
17
  * @returns {StoredQuerySegment[][]}
25
18
  */
26
- export function layoutStoredQueries(output_items) {
27
- if (output_items.length === 0) {
28
- return [];
29
- }
30
-
31
- const name_column_width = Math.max(
32
- ...output_items.map((output_item) => output_item.name.length),
33
- );
34
- const term_column_width = Math.max(
35
- MIN_TERM_COLUMN_WIDTH,
36
- MAX_STORED_QUERY_WIDTH - name_column_width - STORED_QUERY_COLUMN_GAP,
37
- );
38
-
39
- return output_items.flatMap((output_item) =>
40
- layoutStoredQuery(output_item, name_column_width, term_column_width),
19
+ export function layoutStoredQueryRow(
20
+ where_clause,
21
+ first_line_width,
22
+ continuation_width,
23
+ ) {
24
+ return wrapPhrasesWithLineWidths(
25
+ createStoredQueryPhrases(where_clause),
26
+ first_line_width,
27
+ continuation_width,
41
28
  );
42
29
  }
43
30
 
44
- /**
45
- * @param {OutputStoredQueryItem} output_item
46
- * @param {number} name_column_width
47
- * @param {number} term_column_width
48
- * @returns {StoredQuerySegment[][]}
49
- */
50
- function layoutStoredQuery(output_item, name_column_width, term_column_width) {
51
- const term_lines = wrapPhrases(
52
- createStoredQueryPhrases(output_item.where),
53
- term_column_width,
54
- );
55
- const continuation_prefix = ' '.repeat(
56
- name_column_width + STORED_QUERY_COLUMN_GAP,
57
- );
58
-
59
- /** @type {StoredQuerySegment[][]} */
60
- const output_lines = term_lines.map((line_segments, line_index) => {
61
- if (line_index === 0) {
62
- return [
63
- {
64
- kind: 'name',
65
- text: output_item.name.padEnd(name_column_width, ' '),
66
- },
67
- {
68
- kind: 'plain',
69
- text: ' '.repeat(STORED_QUERY_COLUMN_GAP),
70
- },
71
- ...line_segments,
72
- ];
73
- }
74
-
75
- return [
76
- {
77
- kind: 'plain',
78
- text: continuation_prefix,
79
- },
80
- ...line_segments,
81
- ];
82
- });
83
-
84
- if (output_item.description) {
85
- for (const description_line of output_item.description.split('\n')) {
86
- output_lines.push([
87
- {
88
- kind: 'description',
89
- text: `${' '.repeat(name_column_width + STORED_QUERY_COLUMN_GAP)}${description_line}`,
90
- },
91
- ]);
92
- }
93
- }
94
-
95
- output_lines.push([]);
96
-
97
- return output_lines;
98
- }
99
-
100
31
  /**
101
32
  * @param {string} where_clause
102
33
  * @returns {StoredQuerySegment[][]}
103
34
  */
104
35
  function createStoredQueryPhrases(where_clause) {
105
- const parse_result = parseWhereClause(where_clause);
106
-
107
- if (!parse_result.success) {
108
- return createFallbackPhrases(where_clause);
109
- }
110
-
111
- return createExpressionPhrases(parse_result.expression);
112
- }
113
-
114
- /**
115
- * @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
116
- * @returns {StoredQuerySegment[][]}
117
- */
118
- function createExpressionPhrases(expression) {
119
- if (expression.kind !== 'and' && expression.kind !== 'or') {
120
- return [[...createExpressionSegments(expression, 0)]];
121
- }
122
-
123
- return expression.expressions.map((subexpression, expression_index) =>
124
- createExpressionPhrase(
125
- subexpression,
126
- expression.kind,
127
- expression_index > 0,
128
- ),
129
- );
130
- }
131
-
132
- /**
133
- * @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
134
- * @param {'and' | 'or'} operator
135
- * @param {boolean} should_prefix_operator
136
- * @returns {StoredQuerySegment[]}
137
- */
138
- function createExpressionPhrase(expression, operator, should_prefix_operator) {
139
- /** @type {StoredQuerySegment[]} */
140
- const phrase = [];
141
-
142
- if (should_prefix_operator) {
143
- phrase.push({ kind: 'keyword', text: operator });
144
- phrase.push({ kind: 'plain', text: ' ' });
145
- }
146
-
147
- phrase.push(
148
- ...createExpressionSegments(
149
- expression,
150
- getBooleanExpressionPrecedence(operator),
151
- ),
152
- );
153
-
154
- return phrase;
155
- }
156
-
157
- /**
158
- * @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
159
- * @param {number} parent_precedence
160
- * @returns {StoredQuerySegment[]}
161
- */
162
- function createExpressionSegments(expression, parent_precedence) {
163
- const expression_precedence = getExpressionPrecedence(expression);
164
- const expression_segments = createRawExpressionSegments(expression);
165
-
166
- if (expression_precedence >= parent_precedence) {
167
- return expression_segments;
168
- }
169
-
170
- return [
171
- { kind: 'operator', text: '(' },
172
- ...expression_segments,
173
- { kind: 'operator', text: ')' },
174
- ];
175
- }
176
-
177
- /**
178
- * @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
179
- * @returns {StoredQuerySegment[]}
180
- */
181
- function createRawExpressionSegments(expression) {
182
- if (expression.kind === 'and' || expression.kind === 'or') {
183
- return expression.expressions.flatMap((subexpression, expression_index) => {
184
- const subexpression_segments = createExpressionSegments(
185
- subexpression,
186
- getExpressionPrecedence(expression),
187
- );
188
-
189
- if (expression_index === 0) {
190
- return subexpression_segments;
191
- }
192
-
193
- return [
194
- { kind: 'plain', text: ' ' },
195
- { kind: 'keyword', text: expression.kind },
196
- { kind: 'plain', text: ' ' },
197
- ...subexpression_segments,
198
- ];
199
- });
200
- }
201
-
202
- if (expression.kind === 'not') {
203
- return [
204
- { kind: 'keyword', text: 'not' },
205
- { kind: 'plain', text: ' ' },
206
- ...createExpressionSegments(
207
- expression.expression,
208
- getExpressionPrecedence(expression),
209
- ),
210
- ];
211
- }
212
-
213
- if (expression.kind === 'term') {
214
- return createTermSegments(expression.term);
215
- }
216
-
217
- throw new Error('Unsupported stored-query expression.');
218
- }
219
-
220
- /**
221
- * @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
222
- * @returns {number}
223
- */
224
- function getExpressionPrecedence(expression) {
225
- if (expression.kind === 'or') {
226
- return 1;
227
- }
228
-
229
- if (expression.kind === 'and') {
230
- return 2;
231
- }
232
-
233
- if (expression.kind === 'not') {
234
- return 3;
235
- }
236
-
237
- return 4;
238
- }
239
-
240
- /**
241
- * @param {'and' | 'or'} operator
242
- * @returns {number}
243
- */
244
- function getBooleanExpressionPrecedence(operator) {
245
- return operator === 'or' ? 1 : 2;
246
- }
247
-
248
- /**
249
- * @param {import('../graph/parse-where-clause.types.ts').ParsedTerm} term
250
- * @returns {StoredQuerySegment[]}
251
- */
252
- function createTermSegments(term) {
253
- if (term.kind === 'aggregate') {
254
- return createAggregateSegments(term);
255
- }
256
-
257
- if (term.kind === 'field') {
258
- return [
259
- { kind: 'field_name', text: term.field_name },
260
- { kind: 'operator', text: term.operator },
261
- { kind: 'literal', text: term.value },
262
- ];
263
- }
264
-
265
- if (term.kind === 'field_set') {
266
- return createFieldSetSegments(term);
267
- }
268
-
269
- if (term.kind === 'relation_target') {
270
- return [
271
- { kind: 'field_name', text: term.relation_name },
272
- { kind: 'operator', text: '=' },
273
- { kind: 'literal', text: term.target_id },
274
- ];
275
- }
276
-
277
- return [
278
- { kind: 'field_name', text: term.relation_name },
279
- { kind: 'operator', text: ':*' },
280
- ];
281
- }
282
-
283
- /**
284
- * @param {import('../graph/parse-where-clause.types.ts').ParsedFieldSetTerm} term
285
- * @returns {StoredQuerySegment[]}
286
- */
287
- function createFieldSetSegments(term) {
288
- return [
289
- { kind: 'field_name', text: term.field_name },
290
- { kind: 'plain', text: ' ' },
291
- { kind: 'operator', text: term.operator },
292
- { kind: 'plain', text: ' ' },
293
- { kind: 'operator', text: '[' },
294
- ...createListSegments(term.values),
295
- { kind: 'operator', text: ']' },
296
- ];
297
- }
298
-
299
- /**
300
- * @param {import('../graph/parse-where-clause.types.ts').ParsedAggregateTerm} term
301
- * @returns {StoredQuerySegment[]}
302
- */
303
- function createAggregateSegments(term) {
304
- /** @type {StoredQuerySegment[]} */
305
- const segments = [
306
- { kind: 'field_name', text: term.aggregate_name },
307
- { kind: 'operator', text: '(' },
308
- ...createTraversalSegments(term.traversal),
309
- { kind: 'operator', text: ', ' },
310
- ...createExpressionSegments(term.expression, 0),
311
- { kind: 'operator', text: ')' },
312
- ];
313
-
314
- if (term.aggregate_name === 'count') {
315
- segments.push({ kind: 'plain', text: ' ' });
316
- segments.push({ kind: 'operator', text: term.comparison ?? '=' });
317
- segments.push({ kind: 'plain', text: ' ' });
318
- segments.push({ kind: 'literal', text: String(term.value ?? 0) });
319
- }
320
-
321
- return segments;
322
- }
323
-
324
- /**
325
- * @param {{ direction: 'in' | 'out', relation_name: string }} traversal
326
- * @returns {StoredQuerySegment[]}
327
- */
328
- function createTraversalSegments(traversal) {
329
- return [
330
- { kind: 'field_name', text: traversal.direction },
331
- { kind: 'operator', text: ':' },
332
- { kind: 'field_name', text: traversal.relation_name },
333
- ];
334
- }
335
-
336
- /**
337
- * @param {string[]} values
338
- * @returns {StoredQuerySegment[]}
339
- */
340
- function createListSegments(values) {
341
- return values.flatMap((value, value_index) => {
342
- if (value_index === 0) {
343
- return [{ kind: 'literal', text: value }];
344
- }
345
-
346
- return [
347
- { kind: 'operator', text: ', ' },
348
- { kind: 'literal', text: value },
349
- ];
350
- });
36
+ return createFallbackPhrases(where_clause);
351
37
  }
352
38
 
353
39
  /**
@@ -366,15 +52,21 @@ function createFallbackPhrases(where_clause) {
366
52
 
367
53
  /**
368
54
  * @param {StoredQuerySegment[][]} phrases
369
- * @param {number} term_column_width
55
+ * @param {number} first_line_width
56
+ * @param {number} continuation_width
370
57
  * @returns {StoredQuerySegment[][]}
371
58
  */
372
- function wrapPhrases(phrases, term_column_width) {
59
+ function wrapPhrasesWithLineWidths(
60
+ phrases,
61
+ first_line_width,
62
+ continuation_width,
63
+ ) {
373
64
  /** @type {StoredQuerySegment[][]} */
374
65
  const lines = [];
375
66
  /** @type {StoredQuerySegment[]} */
376
67
  let current_line = [];
377
68
  let current_width = 0;
69
+ let current_line_width = first_line_width;
378
70
 
379
71
  for (const phrase of phrases) {
380
72
  const phrase_width = measureSegments(phrase);
@@ -385,10 +77,11 @@ function wrapPhrases(phrases, term_column_width) {
385
77
  continue;
386
78
  }
387
79
 
388
- if (current_width + 1 + phrase_width > term_column_width) {
80
+ if (current_width + 1 + phrase_width > current_line_width) {
389
81
  lines.push(current_line);
390
82
  current_line = [...phrase];
391
83
  current_width = phrase_width;
84
+ current_line_width = continuation_width;
392
85
  continue;
393
86
  }
394
87
 
@@ -14,6 +14,6 @@ export function listQueries(stored_queries) {
14
14
  .map(([name, stored_query]) => ({
15
15
  description: stored_query.description,
16
16
  name,
17
- where: stored_query.where,
17
+ where: stored_query.cypher ?? '',
18
18
  }));
19
19
  }
@@ -42,8 +42,9 @@ function formatJsonFieldSuggestion(field_suggestion) {
42
42
  confidence: field_suggestion.confidence,
43
43
  conflicting_evidence: field_suggestion.conflicting_evidence,
44
44
  evidence_references: field_suggestion.evidence_references,
45
- likely_class_usage: field_suggestion.likely_class_usage,
46
45
  likely_multiplicity: field_suggestion.likely_multiplicity,
46
+ likely_on: field_suggestion.likely_on,
47
+ likely_to: field_suggestion.likely_to,
47
48
  likely_type: field_suggestion.likely_type,
48
49
  name: field_suggestion.name,
49
50
  };
@@ -106,10 +107,18 @@ function formatTextFieldSuggestion(field_suggestion, render_options) {
106
107
  lines.push(
107
108
  `${render_options.label(' likely type:')} ${field_suggestion.likely_type.name}`,
108
109
  `${render_options.label(' likely multiplicity:')} ${field_suggestion.likely_multiplicity.name}`,
109
- `${render_options.label(' likely class usage:')} ${field_suggestion.likely_class_usage.classes.join(', ')}`,
110
+ `${render_options.label(' likely on:')} ${field_suggestion.likely_on.types.join(', ')}`,
110
111
  `${render_options.label(' confidence:')} ${formatConfidence(field_suggestion.confidence)}`,
111
112
  );
112
113
 
114
+ if (field_suggestion.likely_to) {
115
+ lines.splice(
116
+ 3,
117
+ 0,
118
+ `${render_options.label(' likely to:')} ${field_suggestion.likely_to.type}`,
119
+ );
120
+ }
121
+
113
122
  if (field_suggestion.evidence_references.length > 0) {
114
123
  lines.push(
115
124
  ...formatTextEvidenceSection(
@@ -13,10 +13,10 @@ import { renderRichOutput } from './renderers/rich.js';
13
13
  * Normalizes `query`, `queries`, and `show` results into renderer-specific
14
14
  * output models.
15
15
  *
16
- * Kind: output
17
- * Status: active
18
- * Tracked in: ../../docs/plans/v0/source-anchor-dogfooding.md
19
- * Decided by: ../../docs/decisions/cli-output-architecture.md
16
+ * kind: output
17
+ * status: active
18
+ * tracked_in: ../../docs/plans/v0/source-anchor-dogfooding.md
19
+ * decided_by: ../../docs/decisions/cli-output-architecture.md
20
20
  * @patram
21
21
  * @see {@link ./show-document.js}
22
22
  * @see {@link ../../docs/decisions/cli-output-architecture.md}
@@ -34,23 +34,27 @@ export {
34
34
  * @param {OutputView} output_view
35
35
  * @param {ResolvedOutputMode} output_mode
36
36
  * @param {ParsedCliArguments} parsed_arguments
37
+ * @param {{ is_tty?: boolean, terminal_width?: number }=} render_options
37
38
  * @returns {Promise<string>}
38
39
  */
39
40
  export async function renderOutputView(
40
41
  output_view,
41
42
  output_mode,
42
43
  parsed_arguments,
44
+ render_options = {},
43
45
  ) {
44
46
  if (output_mode.renderer_name === 'json') {
45
47
  return renderJsonOutput(output_view);
46
48
  }
47
49
 
48
50
  if (output_mode.renderer_name === 'plain') {
49
- return renderPlainOutput(output_view);
51
+ return renderPlainOutput(output_view, render_options);
50
52
  }
51
53
 
52
54
  return renderRichOutput(output_view, {
53
55
  color_enabled: output_mode.color_enabled,
54
56
  color_mode: parsed_arguments.color_mode,
57
+ is_tty: render_options.is_tty,
58
+ terminal_width: render_options.terminal_width,
55
59
  });
56
60
  }