patram 0.8.0 → 0.10.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 (143) hide show
  1. package/bin/patram.js +2 -2
  2. package/lib/cli/arguments.types.d.ts +63 -0
  3. package/lib/{parse-cli-color-options.js → cli/color-options.js} +2 -2
  4. package/lib/cli/command-helpers.js +35 -0
  5. package/lib/cli/commands/check.js +73 -0
  6. package/lib/cli/commands/fields.js +57 -0
  7. package/lib/cli/commands/queries.js +41 -0
  8. package/lib/cli/commands/query.js +242 -0
  9. package/lib/cli/commands/refs.js +72 -0
  10. package/lib/cli/commands/show.js +58 -0
  11. package/lib/{cli-help-metadata.js → cli/help-metadata.js} +7 -102
  12. package/lib/cli/main.js +76 -0
  13. package/lib/{parse-cli-arguments-helpers.js → cli/parse-arguments-helpers.js} +24 -12
  14. package/lib/{parse-cli-arguments.js → cli/parse-arguments.js} +12 -12
  15. package/lib/{parse-cli-query-pagination.js → cli/query-pagination.js} +2 -2
  16. package/lib/{render-cli-help.js → cli/render-help.js} +84 -3
  17. package/lib/{resolve-output-mode.js → cli/resolve-output-mode.js} +2 -2
  18. package/lib/cli/test-helpers.js +30 -0
  19. package/lib/config/defaults.d.ts +10 -0
  20. package/lib/config/defaults.js +80 -0
  21. package/lib/config/load-patram-config.d.ts +76 -0
  22. package/lib/config/load-patram-config.js +315 -0
  23. package/lib/config/load-patram-config.types.d.ts +45 -0
  24. package/lib/{patram-config.d.ts → config/patram-config.d.ts} +31 -31
  25. package/lib/{patram-config.js → config/patram-config.js} +3 -3
  26. package/lib/{patram-config.types.d.ts → config/patram-config.types.d.ts} +1 -1
  27. package/lib/{resolve-patram-graph-config.d.ts → config/resolve-patram-graph-config.d.ts} +2 -2
  28. package/lib/{resolve-patram-graph-config.js → config/resolve-patram-graph-config.js} +3 -3
  29. package/lib/{load-patram-config.d.ts → config/schema.d.ts} +149 -191
  30. package/lib/config/schema.js +328 -0
  31. package/lib/{source-file-defaults.d.ts → config/source-file-defaults.d.ts} +0 -1
  32. package/lib/{source-file-defaults.js → config/source-file-defaults.js} +1 -1
  33. package/lib/config/validation.d.ts +27 -0
  34. package/lib/config/validation.js +615 -0
  35. package/lib/directive-validation-test-helpers.js +1 -1
  36. package/lib/find-close-match.d.ts +8 -0
  37. package/lib/find-close-match.js +98 -0
  38. package/lib/{build-graph-identity.d.ts → graph/build-graph-identity.d.ts} +2 -2
  39. package/lib/{build-graph-identity.js → graph/build-graph-identity.js} +1 -1
  40. package/lib/{build-graph.d.ts → graph/build-graph.d.ts} +3 -3
  41. package/lib/{build-graph.js → graph/build-graph.js} +17 -13
  42. package/lib/{build-graph.types.d.ts → graph/build-graph.types.d.ts} +1 -1
  43. package/lib/graph/check-directive-metadata.d.ts +23 -0
  44. package/lib/{check-directive-metadata.js → graph/check-directive-metadata.js} +7 -7
  45. package/lib/graph/check-directive-path-target.d.ts +32 -0
  46. package/lib/{check-directive-path-target.js → graph/check-directive-path-target.js} +4 -4
  47. package/lib/graph/check-directive-value.d.ts +19 -0
  48. package/lib/{check-directive-value.js → graph/check-directive-value.js} +3 -3
  49. package/lib/graph/check-graph.d.ts +29 -0
  50. package/lib/{check-graph.js → graph/check-graph.js} +6 -6
  51. package/lib/graph/directive-diagnostics.d.ts +20 -0
  52. package/lib/{directive-diagnostics.js → graph/directive-diagnostics.js} +2 -2
  53. package/lib/graph/directive-type-rules.d.ts +18 -0
  54. package/lib/{directive-type-rules.js → graph/directive-type-rules.js} +3 -3
  55. package/lib/{document-node-identity.d.ts → graph/document-node-identity.d.ts} +2 -2
  56. package/lib/{document-node-identity.js → graph/document-node-identity.js} +2 -2
  57. package/lib/graph/inspect-reverse-references.d.ts +22 -0
  58. package/lib/{inspect-reverse-references.js → graph/inspect-reverse-references.js} +2 -2
  59. package/lib/{load-project-graph.d.ts → graph/load-project-graph.d.ts} +10 -10
  60. package/lib/{load-project-graph.js → graph/load-project-graph.js} +12 -12
  61. package/lib/{parse-where-clause.types.d.ts → graph/parse-where-clause.types.d.ts} +1 -1
  62. package/lib/{query-graph.d.ts → graph/query/execute.d.ts} +11 -11
  63. package/lib/{query-graph.js → graph/query/execute.js} +12 -12
  64. package/lib/{query-inspection.d.ts → graph/query/inspect.d.ts} +10 -8
  65. package/lib/{query-inspection.js → graph/query/inspect.js} +16 -17
  66. package/lib/{parse-where-clause.d.ts → graph/query/parse.d.ts} +6 -6
  67. package/lib/{parse-where-clause.js → graph/query/parse.js} +2 -2
  68. package/lib/graph/query/resolve.d.ts +28 -0
  69. package/lib/{resolve-where-clause.js → graph/query/resolve.js} +39 -5
  70. package/lib/graph/reverse-reference-test-helpers.d.ts +55 -0
  71. package/lib/{command-output.js → output/command-output.js} +5 -5
  72. package/lib/{derived-summary.js → output/derived-summary.js} +7 -7
  73. package/lib/{layout-incoming-references.js → output/layout-incoming-references.js} +4 -4
  74. package/lib/{layout-incoming-summary-lines.js → output/layout-incoming-summary-lines.js} +0 -5
  75. package/lib/{layout-stored-queries.js → output/layout-stored-queries.js} +27 -11
  76. package/lib/{list-queries.js → output/list-queries.js} +3 -2
  77. package/lib/{render-check-output.js → output/render-check-output.js} +1 -1
  78. package/lib/{render-field-discovery.js → output/render-field-discovery.js} +3 -3
  79. package/lib/output/render-output-view.js +56 -0
  80. package/lib/{render-json-output.js → output/renderers/json.js} +10 -6
  81. package/lib/{render-plain-output.js → output/renderers/plain.js} +34 -33
  82. package/lib/{render-rich-output.js → output/renderers/rich.js} +44 -32
  83. package/lib/{resolve-check-target.js → output/resolve-check-target.js} +1 -1
  84. package/lib/{render-rich-source.js → output/rich-source/render.js} +6 -6
  85. package/lib/{show-document.js → output/show-document.js} +12 -12
  86. package/lib/{render-output-view.js → output/view-model/index.js} +11 -70
  87. package/lib/{write-paged-output.js → output/write-paged-output.js} +9 -5
  88. package/lib/{claim-helpers.d.ts → parse/claim-helpers.d.ts} +2 -2
  89. package/lib/{parse-jsdoc-claims.d.ts → parse/jsdoc/parse-jsdoc-claims.d.ts} +2 -2
  90. package/lib/{parse-jsdoc-claims.js → parse/jsdoc/parse-jsdoc-claims.js} +9 -9
  91. package/lib/{parse-jsdoc-prose.d.ts → parse/jsdoc/parse-jsdoc-prose.d.ts} +1 -1
  92. package/lib/{parse-jsdoc-prose.js → parse/jsdoc/parse-jsdoc-prose.js} +1 -1
  93. package/lib/{parse-markdown-claims.d.ts → parse/markdown/parse-markdown-claims.d.ts} +3 -3
  94. package/lib/{parse-markdown-claims.js → parse/markdown/parse-markdown-claims.js} +8 -8
  95. package/lib/{parse-markdown-directives.d.ts → parse/markdown/parse-markdown-directives.d.ts} +2 -2
  96. package/lib/{parse-markdown-directives.js → parse/markdown/parse-markdown-directives.js} +3 -3
  97. package/lib/{parse-claims.d.ts → parse/parse-claims.d.ts} +4 -13
  98. package/lib/{parse-claims.js → parse/parse-claims.js} +18 -26
  99. package/lib/{parse-claims.types.d.ts → parse/parse-claims.types.d.ts} +1 -1
  100. package/lib/{tagged-fenced-block-error.d.ts → parse/tagged-fenced/tagged-fenced-block-error.d.ts} +2 -2
  101. package/lib/{tagged-fenced-block-parser.d.ts → parse/tagged-fenced/tagged-fenced-block-parser.d.ts} +3 -3
  102. package/lib/{tagged-fenced-blocks.d.ts → parse/tagged-fenced/tagged-fenced-blocks.d.ts} +7 -7
  103. package/lib/{tagged-fenced-blocks.js → parse/tagged-fenced/tagged-fenced-blocks.js} +3 -3
  104. package/lib/{parse-yaml-claims.d.ts → parse/yaml/parse-yaml-claims.d.ts} +4 -4
  105. package/lib/{parse-yaml-claims.js → parse/yaml/parse-yaml-claims.js} +22 -13
  106. package/lib/patram.d.ts +29 -28
  107. package/lib/patram.js +5 -6
  108. package/lib/{discover-fields.js → scan/discover-fields.js} +145 -18
  109. package/lib/scan/list-repo-files.d.ts +16 -0
  110. package/lib/{list-source-files.js → scan/list-repo-files.js} +2 -35
  111. package/lib/{list-source-files.d.ts → scan/list-source-files.d.ts} +4 -11
  112. package/lib/scan/list-source-files.js +45 -0
  113. package/package.json +8 -7
  114. package/lib/build-graph.types.ts +0 -27
  115. package/lib/discover-fields.types.ts +0 -52
  116. package/lib/load-patram-config.js +0 -1215
  117. package/lib/load-patram-config.types.d.ts +0 -45
  118. package/lib/load-patram-config.types.ts +0 -56
  119. package/lib/output-view.types.d.ts +0 -88
  120. package/lib/output-view.types.ts +0 -113
  121. package/lib/overlay-graph.d.ts +0 -43
  122. package/lib/overlay-graph.js +0 -191
  123. package/lib/parse-claims.types.ts +0 -41
  124. package/lib/parse-cli-arguments.types.ts +0 -75
  125. package/lib/parse-where-clause.types.ts +0 -87
  126. package/lib/patram-cli.js +0 -593
  127. package/lib/patram-config.types.ts +0 -22
  128. package/lib/tagged-fenced-blocks.types.ts +0 -38
  129. /package/lib/{reverse-reference-test-helpers.js → graph/reverse-reference-test-helpers.js} +0 -0
  130. /package/lib/{format-derived-summary-row.js → output/format-derived-summary-row.js} +0 -0
  131. /package/lib/{format-node-header.js → output/format-node-header.js} +0 -0
  132. /package/lib/{format-output-item-block.js → output/format-output-item-block.js} +0 -0
  133. /package/lib/{format-output-metadata.js → output/format-output-metadata.js} +0 -0
  134. /package/lib/{claim-helpers.js → parse/claim-helpers.js} +0 -0
  135. /package/lib/{parse-jsdoc-blocks.d.ts → parse/jsdoc/parse-jsdoc-blocks.d.ts} +0 -0
  136. /package/lib/{parse-jsdoc-blocks.js → parse/jsdoc/parse-jsdoc-blocks.js} +0 -0
  137. /package/lib/{tagged-fenced-block-error.js → parse/tagged-fenced/tagged-fenced-block-error.js} +0 -0
  138. /package/lib/{tagged-fenced-block-markdown.d.ts → parse/tagged-fenced/tagged-fenced-block-markdown.d.ts} +0 -0
  139. /package/lib/{tagged-fenced-block-markdown.js → parse/tagged-fenced/tagged-fenced-block-markdown.js} +0 -0
  140. /package/lib/{tagged-fenced-block-metadata.d.ts → parse/tagged-fenced/tagged-fenced-block-metadata.d.ts} +0 -0
  141. /package/lib/{tagged-fenced-block-metadata.js → parse/tagged-fenced/tagged-fenced-block-metadata.js} +0 -0
  142. /package/lib/{tagged-fenced-block-parser.js → parse/tagged-fenced/tagged-fenced-block-parser.js} +0 -0
  143. /package/lib/{tagged-fenced-blocks.types.d.ts → parse/tagged-fenced/tagged-fenced-blocks.types.d.ts} +0 -0
@@ -1,5 +1,4 @@
1
1
  export const MARKDOWN_SOURCE_FILE_EXTENSIONS: string[];
2
2
  export const YAML_SOURCE_FILE_EXTENSIONS: string[];
3
3
  export const JSDOC_SOURCE_FILE_EXTENSIONS: string[];
4
- export const SUPPORTED_SOURCE_FILE_EXTENSIONS: string[];
5
4
  export const DEFAULT_INCLUDE_PATTERNS: string[];
@@ -13,7 +13,7 @@ export const JSDOC_SOURCE_FILE_EXTENSIONS = [
13
13
  '.tsx',
14
14
  ];
15
15
 
16
- export const SUPPORTED_SOURCE_FILE_EXTENSIONS = [
16
+ const SUPPORTED_SOURCE_FILE_EXTENSIONS = [
17
17
  ...JSDOC_SOURCE_FILE_EXTENSIONS,
18
18
  ...MARKDOWN_SOURCE_FILE_EXTENSIONS,
19
19
  ...YAML_SOURCE_FILE_EXTENSIONS,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @param {unknown} config_value
3
+ * @returns {PatramDiagnostic[]}
4
+ */
5
+ export function validateLegacyConfigShape(config_value: unknown): PatramDiagnostic[];
6
+ /**
7
+ * @param {PatramRepoConfig} repo_config
8
+ * @returns {PatramDiagnostic[]}
9
+ */
10
+ export function validateGraphSchema(repo_config: PatramRepoConfig): PatramDiagnostic[];
11
+ /**
12
+ * @param {PatramRepoConfig} repo_config
13
+ * @returns {PatramDiagnostic[]}
14
+ */
15
+ export function validateFieldSchemaConfig(repo_config: PatramRepoConfig): PatramDiagnostic[];
16
+ /**
17
+ * @param {PatramRepoConfig} repo_config
18
+ * @returns {PatramDiagnostic[]}
19
+ */
20
+ export function validateStoredQueries(repo_config: PatramRepoConfig): PatramDiagnostic[];
21
+ /**
22
+ * @param {PatramRepoConfig} repo_config
23
+ * @returns {PatramDiagnostic[]}
24
+ */
25
+ export function validateDerivedSummaries(repo_config: PatramRepoConfig): PatramDiagnostic[];
26
+ import type { PatramDiagnostic } from './load-patram-config.js';
27
+ import type { PatramRepoConfig } from './schema.js';
@@ -0,0 +1,615 @@
1
+ /**
2
+ * @import { ClassDefinition } from './patram-config.js';
3
+ * @import {
4
+ * DerivedSummaryFieldConfig,
5
+ * MetadataFieldConfig,
6
+ * PatramRepoConfig,
7
+ * } from './schema.js';
8
+ * @import { PatramDiagnostic } from './load-patram-config.js';
9
+ */
10
+ /* eslint-disable max-lines */
11
+
12
+ import { z } from 'zod';
13
+
14
+ import { parsePatramConfig } from './patram-config.js';
15
+ import { getQuerySemanticDiagnostics } from '../graph/query/inspect.js';
16
+ import { parseWhereClause } from '../graph/query/parse.js';
17
+ import { resolvePatramGraphConfig } from './resolve-patram-graph-config.js';
18
+ import {
19
+ CONFIG_FILE_NAME,
20
+ isKnownMarkdownStyle,
21
+ isMixedStyleValue,
22
+ isReservedStructuralFieldName,
23
+ } from './schema.js';
24
+
25
+ /**
26
+ * @param {unknown} config_value
27
+ * @returns {PatramDiagnostic[]}
28
+ */
29
+ export function validateLegacyConfigShape(config_value) {
30
+ if (
31
+ config_value === null ||
32
+ typeof config_value !== 'object' ||
33
+ !Object.hasOwn(config_value, 'class_schemas')
34
+ ) {
35
+ return [];
36
+ }
37
+
38
+ return [
39
+ createConfigDiagnostic(
40
+ 'class_schemas',
41
+ 'Top-level "class_schemas" is not supported. Move entries into classes.<name>.schema.',
42
+ ),
43
+ ];
44
+ }
45
+
46
+ /**
47
+ * @param {PatramRepoConfig} repo_config
48
+ * @returns {PatramDiagnostic[]}
49
+ */
50
+ export function validateGraphSchema(repo_config) {
51
+ if (
52
+ repo_config.classes === undefined &&
53
+ repo_config.mappings === undefined &&
54
+ repo_config.relations === undefined
55
+ ) {
56
+ return [];
57
+ }
58
+
59
+ try {
60
+ parsePatramConfig({
61
+ classes: collectGraphClassDefinitions(repo_config.classes),
62
+ mappings: repo_config.mappings ?? {},
63
+ relations: repo_config.relations ?? {},
64
+ });
65
+ } catch (error) {
66
+ if (error instanceof z.ZodError) {
67
+ return error.issues.map(createValidationDiagnostic);
68
+ }
69
+
70
+ throw error;
71
+ }
72
+
73
+ return [];
74
+ }
75
+
76
+ /**
77
+ * @param {PatramRepoConfig} repo_config
78
+ * @returns {PatramDiagnostic[]}
79
+ */
80
+ export function validateFieldSchemaConfig(repo_config) {
81
+ const path_classes = repo_config.path_classes ?? {};
82
+ const classes = repo_config.classes ?? {};
83
+ const fields = repo_config.fields ?? {};
84
+ /** @type {PatramDiagnostic[]} */
85
+ const diagnostics = [];
86
+
87
+ collectFieldConfigDiagnostics(diagnostics, path_classes, fields);
88
+ collectClassSchemaConfigDiagnostics(
89
+ diagnostics,
90
+ path_classes,
91
+ fields,
92
+ classes,
93
+ );
94
+
95
+ return diagnostics;
96
+ }
97
+
98
+ /**
99
+ * @param {PatramRepoConfig} repo_config
100
+ * @returns {PatramDiagnostic[]}
101
+ */
102
+ export function validateStoredQueries(repo_config) {
103
+ /** @type {PatramDiagnostic[]} */
104
+ const diagnostics = [];
105
+
106
+ for (const [query_name, stored_query] of Object.entries(
107
+ repo_config.queries,
108
+ )) {
109
+ collectWhereClauseDiagnostics(
110
+ diagnostics,
111
+ repo_config,
112
+ stored_query.where,
113
+ `queries.${query_name}.where`,
114
+ );
115
+ }
116
+
117
+ return diagnostics;
118
+ }
119
+
120
+ /**
121
+ * @param {PatramRepoConfig} repo_config
122
+ * @returns {PatramDiagnostic[]}
123
+ */
124
+ export function validateDerivedSummaries(repo_config) {
125
+ if (!repo_config.derived_summaries) {
126
+ return [];
127
+ }
128
+
129
+ const graph_config = resolvePatramGraphConfig(repo_config);
130
+ const known_relation_names = new Set(Object.keys(graph_config.relations));
131
+ /** @type {PatramDiagnostic[]} */
132
+ const diagnostics = [];
133
+ /** @type {Map<string, string>} */
134
+ const class_coverage = new Map();
135
+
136
+ for (const [summary_name, summary_definition] of Object.entries(
137
+ repo_config.derived_summaries,
138
+ )) {
139
+ collectDuplicateClassDiagnostics(
140
+ diagnostics,
141
+ class_coverage,
142
+ summary_definition.classes,
143
+ summary_name,
144
+ );
145
+ collectDerivedSummaryFieldDiagnostics(
146
+ diagnostics,
147
+ known_relation_names,
148
+ repo_config,
149
+ summary_name,
150
+ summary_definition.fields,
151
+ );
152
+ }
153
+
154
+ return diagnostics;
155
+ }
156
+
157
+ /**
158
+ * @param {import('zod').core.$ZodIssue} issue
159
+ * @returns {PatramDiagnostic}
160
+ */
161
+ function createValidationDiagnostic(issue) {
162
+ const issue_path = formatIssuePath(issue.path);
163
+
164
+ if (issue_path) {
165
+ return {
166
+ code: 'config.invalid',
167
+ column: 1,
168
+ level: 'error',
169
+ line: 1,
170
+ message: `Invalid config at "${issue_path}": ${issue.message}`,
171
+ path: CONFIG_FILE_NAME,
172
+ };
173
+ }
174
+
175
+ return {
176
+ code: 'config.invalid',
177
+ column: 1,
178
+ level: 'error',
179
+ line: 1,
180
+ message: `Invalid config: ${issue.message}`,
181
+ path: CONFIG_FILE_NAME,
182
+ };
183
+ }
184
+
185
+ /**
186
+ * @param {PatramDiagnostic[]} diagnostics
187
+ * @param {Map<string, string>} class_coverage
188
+ * @param {string[]} class_names
189
+ * @param {string} summary_name
190
+ */
191
+ function collectDuplicateClassDiagnostics(
192
+ diagnostics,
193
+ class_coverage,
194
+ class_names,
195
+ summary_name,
196
+ ) {
197
+ for (const class_name of class_names) {
198
+ const existing_summary_name = class_coverage.get(class_name);
199
+
200
+ if (!existing_summary_name) {
201
+ class_coverage.set(class_name, summary_name);
202
+ continue;
203
+ }
204
+
205
+ diagnostics.push(
206
+ createConfigDiagnostic(
207
+ `derived_summaries.${summary_name}.classes`,
208
+ `Class "${class_name}" is already covered by derived summary "${existing_summary_name}".`,
209
+ ),
210
+ );
211
+ }
212
+ }
213
+
214
+ /**
215
+ * @param {PatramDiagnostic[]} diagnostics
216
+ * @param {Set<string>} known_relation_names
217
+ * @param {PatramRepoConfig} repo_config
218
+ * @param {string} summary_name
219
+ * @param {DerivedSummaryFieldConfig[]} field_definitions
220
+ */
221
+ function collectDerivedSummaryFieldDiagnostics(
222
+ diagnostics,
223
+ known_relation_names,
224
+ repo_config,
225
+ summary_name,
226
+ field_definitions,
227
+ ) {
228
+ for (const [field_index, field_definition] of field_definitions.entries()) {
229
+ if ('count' in field_definition) {
230
+ collectTraversalDiagnostic(
231
+ diagnostics,
232
+ field_definition.count.traversal,
233
+ known_relation_names,
234
+ `derived_summaries.${summary_name}.fields.${field_index}.count.traversal`,
235
+ );
236
+ collectWhereClauseDiagnostics(
237
+ diagnostics,
238
+ repo_config,
239
+ field_definition.count.where,
240
+ `derived_summaries.${summary_name}.fields.${field_index}.count.where`,
241
+ );
242
+ continue;
243
+ }
244
+
245
+ for (const [case_index, select_case] of field_definition.select.entries()) {
246
+ collectWhereClauseDiagnostics(
247
+ diagnostics,
248
+ repo_config,
249
+ select_case.when,
250
+ `derived_summaries.${summary_name}.fields.${field_index}.select.${case_index}.when`,
251
+ );
252
+ }
253
+ }
254
+ }
255
+
256
+ /**
257
+ * @param {PatramDiagnostic[]} diagnostics
258
+ * @param {string} traversal_text
259
+ * @param {Set<string>} known_relation_names
260
+ * @param {string} diagnostic_path
261
+ */
262
+ function collectTraversalDiagnostic(
263
+ diagnostics,
264
+ traversal_text,
265
+ known_relation_names,
266
+ diagnostic_path,
267
+ ) {
268
+ const traversal_match =
269
+ /^(?<direction>in|out):(?<relation_name>[a-zA-Z0-9_]+)$/du.exec(
270
+ traversal_text,
271
+ );
272
+
273
+ if (!traversal_match?.groups?.relation_name) {
274
+ diagnostics.push(
275
+ createConfigDiagnostic(
276
+ diagnostic_path,
277
+ 'Derived summary traversal must use "in:<relation>" or "out:<relation>".',
278
+ ),
279
+ );
280
+
281
+ return;
282
+ }
283
+
284
+ if (known_relation_names.has(traversal_match.groups.relation_name)) {
285
+ return;
286
+ }
287
+
288
+ diagnostics.push(
289
+ createConfigDiagnostic(
290
+ diagnostic_path,
291
+ `Unknown relation "${traversal_match.groups.relation_name}" in derived summary traversal.`,
292
+ ),
293
+ );
294
+ }
295
+
296
+ /**
297
+ * @param {PatramDiagnostic[]} diagnostics
298
+ * @param {Record<string, { prefixes: string[] }>} path_classes
299
+ * @param {Record<string, MetadataFieldConfig>} fields
300
+ */
301
+ function collectFieldConfigDiagnostics(diagnostics, path_classes, fields) {
302
+ for (const [field_name, field_definition] of Object.entries(fields)) {
303
+ if (collectReservedFieldDiagnostic(diagnostics, field_name)) {
304
+ continue;
305
+ }
306
+
307
+ collectDisplayOrderDiagnostic(diagnostics, field_name, field_definition);
308
+ collectFieldPathClassDiagnostic(
309
+ diagnostics,
310
+ path_classes,
311
+ field_name,
312
+ field_definition,
313
+ );
314
+ }
315
+ }
316
+
317
+ /**
318
+ * @param {PatramDiagnostic[]} diagnostics
319
+ * @param {string} field_name
320
+ * @returns {boolean}
321
+ */
322
+ function collectReservedFieldDiagnostic(diagnostics, field_name) {
323
+ if (
324
+ !field_name.startsWith('$') ||
325
+ !isReservedStructuralFieldName(field_name)
326
+ ) {
327
+ return false;
328
+ }
329
+
330
+ diagnostics.push(
331
+ createConfigDiagnostic(
332
+ `fields.${field_name}`,
333
+ 'Metadata field names must not start with "$".',
334
+ ),
335
+ );
336
+
337
+ return true;
338
+ }
339
+
340
+ /**
341
+ * @param {PatramDiagnostic[]} diagnostics
342
+ * @param {string} field_name
343
+ * @param {MetadataFieldConfig} field_definition
344
+ */
345
+ function collectDisplayOrderDiagnostic(
346
+ diagnostics,
347
+ field_name,
348
+ field_definition,
349
+ ) {
350
+ if (
351
+ field_definition.display?.order === undefined ||
352
+ (Number.isInteger(field_definition.display.order) &&
353
+ field_definition.display.order >= 0)
354
+ ) {
355
+ return;
356
+ }
357
+
358
+ diagnostics.push(
359
+ createConfigDiagnostic(
360
+ `fields.${field_name}.display.order`,
361
+ 'Display order must be a non-negative integer.',
362
+ ),
363
+ );
364
+ }
365
+
366
+ /**
367
+ * @param {PatramDiagnostic[]} diagnostics
368
+ * @param {Record<string, { prefixes: string[] }>} path_classes
369
+ * @param {string} field_name
370
+ * @param {MetadataFieldConfig} field_definition
371
+ */
372
+ function collectFieldPathClassDiagnostic(
373
+ diagnostics,
374
+ path_classes,
375
+ field_name,
376
+ field_definition,
377
+ ) {
378
+ if (
379
+ !('path_class' in field_definition) ||
380
+ field_definition.path_class === undefined
381
+ ) {
382
+ return;
383
+ }
384
+
385
+ if (field_definition.type !== 'path') {
386
+ diagnostics.push(
387
+ createConfigDiagnostic(
388
+ `fields.${field_name}.path_class`,
389
+ 'Path classes are only valid for path fields.',
390
+ ),
391
+ );
392
+
393
+ return;
394
+ }
395
+
396
+ if (path_classes[field_definition.path_class]) {
397
+ return;
398
+ }
399
+
400
+ diagnostics.push(
401
+ createConfigDiagnostic(
402
+ `fields.${field_name}.path_class`,
403
+ `Unknown path class "${field_definition.path_class}".`,
404
+ ),
405
+ );
406
+ }
407
+
408
+ /**
409
+ * @param {PatramDiagnostic[]} diagnostics
410
+ * @param {Record<string, { prefixes: string[] }>} path_classes
411
+ * @param {Record<string, MetadataFieldConfig>} fields
412
+ * @param {NonNullable<PatramRepoConfig['classes']>} classes
413
+ */
414
+ function collectClassSchemaConfigDiagnostics(
415
+ diagnostics,
416
+ path_classes,
417
+ fields,
418
+ classes,
419
+ ) {
420
+ for (const [class_name, class_definition] of Object.entries(classes)) {
421
+ const schema_definition = class_definition.schema;
422
+
423
+ if (!schema_definition) {
424
+ continue;
425
+ }
426
+
427
+ collectMarkdownStylesDiagnostic(
428
+ diagnostics,
429
+ `classes.${class_name}.schema.markdown_styles`,
430
+ schema_definition.markdown_styles,
431
+ );
432
+ collectMixedStylesDiagnostic(
433
+ diagnostics,
434
+ `classes.${class_name}.schema.mixed_styles`,
435
+ schema_definition.mixed_styles,
436
+ );
437
+
438
+ for (const field_name of Object.keys(schema_definition.fields)) {
439
+ if (fields[field_name]) {
440
+ collectMarkdownStylesDiagnostic(
441
+ diagnostics,
442
+ `classes.${class_name}.schema.fields.${field_name}.markdown_styles`,
443
+ schema_definition.fields[field_name].markdown_styles,
444
+ );
445
+ } else {
446
+ diagnostics.push(
447
+ createConfigDiagnostic(
448
+ `classes.${class_name}.schema.fields.${field_name}`,
449
+ `Unknown field "${field_name}".`,
450
+ ),
451
+ );
452
+ }
453
+ }
454
+ }
455
+
456
+ for (const [class_name, class_definition] of Object.entries(classes)) {
457
+ const schema_definition = class_definition.schema;
458
+
459
+ if (!schema_definition) {
460
+ continue;
461
+ }
462
+
463
+ if (
464
+ schema_definition.document_path_class === undefined ||
465
+ path_classes[schema_definition.document_path_class]
466
+ ) {
467
+ continue;
468
+ }
469
+
470
+ diagnostics.push(
471
+ createConfigDiagnostic(
472
+ `classes.${class_name}.schema.document_path_class`,
473
+ `Unknown path class "${schema_definition.document_path_class}".`,
474
+ ),
475
+ );
476
+ }
477
+ }
478
+
479
+ /**
480
+ * @param {PatramDiagnostic[]} diagnostics
481
+ * @param {string} diagnostic_path
482
+ * @param {string[] | undefined} markdown_styles
483
+ */
484
+ function collectMarkdownStylesDiagnostic(
485
+ diagnostics,
486
+ diagnostic_path,
487
+ markdown_styles,
488
+ ) {
489
+ if (markdown_styles === undefined) {
490
+ return;
491
+ }
492
+
493
+ if (markdown_styles.length === 0) {
494
+ diagnostics.push(
495
+ createConfigDiagnostic(
496
+ diagnostic_path,
497
+ 'Markdown styles must contain at least one style.',
498
+ ),
499
+ );
500
+
501
+ return;
502
+ }
503
+
504
+ for (const markdown_style of markdown_styles) {
505
+ if (isKnownMarkdownStyle(markdown_style)) {
506
+ continue;
507
+ }
508
+
509
+ diagnostics.push(
510
+ createConfigDiagnostic(
511
+ diagnostic_path,
512
+ `Unknown markdown style "${markdown_style}".`,
513
+ ),
514
+ );
515
+ }
516
+ }
517
+
518
+ /**
519
+ * @param {PatramDiagnostic[]} diagnostics
520
+ * @param {string} diagnostic_path
521
+ * @param {string | undefined} mixed_styles
522
+ */
523
+ function collectMixedStylesDiagnostic(
524
+ diagnostics,
525
+ diagnostic_path,
526
+ mixed_styles,
527
+ ) {
528
+ if (mixed_styles === undefined || isMixedStyleValue(mixed_styles)) {
529
+ return;
530
+ }
531
+
532
+ diagnostics.push(
533
+ createConfigDiagnostic(
534
+ diagnostic_path,
535
+ 'Mixed styles must be "ignore" or "error".',
536
+ ),
537
+ );
538
+ }
539
+
540
+ /**
541
+ * @param {PatramRepoConfig['classes']} classes
542
+ * @returns {Record<string, ClassDefinition>}
543
+ */
544
+ function collectGraphClassDefinitions(classes) {
545
+ /** @type {Record<string, ClassDefinition>} */
546
+ const graph_class_definitions = {};
547
+
548
+ for (const [class_name, class_definition] of Object.entries(classes ?? {})) {
549
+ graph_class_definitions[class_name] = {
550
+ builtin: class_definition.builtin,
551
+ label: class_definition.label,
552
+ };
553
+ }
554
+
555
+ return graph_class_definitions;
556
+ }
557
+
558
+ /**
559
+ * @param {PatramDiagnostic[]} diagnostics
560
+ * @param {PatramRepoConfig} repo_config
561
+ * @param {string} where_clause
562
+ * @param {string} diagnostic_path
563
+ */
564
+ function collectWhereClauseDiagnostics(
565
+ diagnostics,
566
+ repo_config,
567
+ where_clause,
568
+ diagnostic_path,
569
+ ) {
570
+ const parse_result = parseWhereClause(where_clause);
571
+
572
+ if (!parse_result.success) {
573
+ diagnostics.push(
574
+ createConfigDiagnostic(diagnostic_path, parse_result.diagnostic.message),
575
+ );
576
+
577
+ return;
578
+ }
579
+
580
+ const semantic_diagnostics = getQuerySemanticDiagnostics(
581
+ repo_config,
582
+ { kind: 'ad_hoc' },
583
+ parse_result.expression,
584
+ );
585
+
586
+ for (const semantic_diagnostic of semantic_diagnostics) {
587
+ diagnostics.push(
588
+ createConfigDiagnostic(diagnostic_path, semantic_diagnostic.message),
589
+ );
590
+ }
591
+ }
592
+
593
+ /**
594
+ * @param {string} issue_path
595
+ * @param {string} message
596
+ * @returns {PatramDiagnostic}
597
+ */
598
+ function createConfigDiagnostic(issue_path, message) {
599
+ return {
600
+ code: 'config.invalid',
601
+ column: 1,
602
+ level: 'error',
603
+ line: 1,
604
+ message: `Invalid config at "${issue_path}": ${message}`,
605
+ path: CONFIG_FILE_NAME,
606
+ };
607
+ }
608
+
609
+ /**
610
+ * @param {(string | number | symbol | undefined)[]} issue_path
611
+ * @returns {string}
612
+ */
613
+ function formatIssuePath(issue_path) {
614
+ return issue_path.map(String).join('.');
615
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @import { MappingDefinition } from './patram-config.types.ts';
2
+ * @import { MappingDefinition } from './config/patram-config.types.ts';
3
3
  */
4
4
 
5
5
  /**
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Find the closest candidate above the shared suggestion threshold.
3
+ *
4
+ * @param {string} input_text
5
+ * @param {readonly string[]} candidates
6
+ * @returns {string | undefined}
7
+ */
8
+ export function findCloseMatch(input_text: string, candidates: readonly string[]): string | undefined;