patram 0.0.2 → 0.2.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 (67) hide show
  1. package/bin/patram.js +25 -147
  2. package/lib/build-graph-identity.js +270 -0
  3. package/lib/build-graph.js +156 -77
  4. package/lib/check-graph.js +23 -7
  5. package/lib/claim-helpers.js +55 -0
  6. package/lib/cli-help-metadata.js +552 -0
  7. package/lib/command-output.js +83 -0
  8. package/lib/derived-summary.js +278 -0
  9. package/lib/format-derived-summary-row.js +9 -0
  10. package/lib/format-node-header.js +19 -0
  11. package/lib/format-output-item-block.js +22 -0
  12. package/lib/format-output-metadata.js +62 -0
  13. package/lib/layout-stored-queries.js +361 -0
  14. package/lib/list-queries.js +18 -0
  15. package/lib/list-source-files.js +50 -15
  16. package/lib/load-patram-config.js +505 -18
  17. package/lib/load-patram-config.types.ts +40 -0
  18. package/lib/load-project-graph.js +124 -0
  19. package/lib/output-view.types.ts +88 -0
  20. package/lib/parse-claims.js +38 -158
  21. package/lib/parse-claims.types.ts +7 -0
  22. package/lib/parse-cli-arguments-helpers.js +446 -0
  23. package/lib/parse-cli-arguments.js +266 -0
  24. package/lib/parse-cli-arguments.types.ts +69 -0
  25. package/lib/parse-cli-color-options.js +44 -0
  26. package/lib/parse-cli-query-pagination.js +49 -0
  27. package/lib/parse-jsdoc-blocks.js +184 -0
  28. package/lib/parse-jsdoc-claims.js +280 -0
  29. package/lib/parse-jsdoc-prose.js +111 -0
  30. package/lib/parse-markdown-claims.js +242 -0
  31. package/lib/parse-markdown-directives.js +136 -0
  32. package/lib/parse-where-clause.js +707 -0
  33. package/lib/parse-where-clause.types.ts +70 -0
  34. package/lib/patram-cli.js +464 -0
  35. package/lib/patram-config.js +3 -1
  36. package/lib/patram-config.types.ts +2 -1
  37. package/lib/patram.js +6 -0
  38. package/lib/query-graph.js +368 -0
  39. package/lib/query-inspection.js +523 -0
  40. package/lib/render-check-output.js +315 -0
  41. package/lib/render-cli-help.js +419 -0
  42. package/lib/render-json-output.js +161 -0
  43. package/lib/render-output-view.js +222 -0
  44. package/lib/render-plain-output.js +182 -0
  45. package/lib/render-rich-output.js +240 -0
  46. package/lib/render-rich-source.js +1333 -0
  47. package/lib/resolve-check-target.js +190 -0
  48. package/lib/resolve-output-mode.js +60 -0
  49. package/lib/resolve-patram-graph-config.js +88 -0
  50. package/lib/resolve-where-clause.js +66 -0
  51. package/lib/show-document.js +311 -0
  52. package/lib/source-file-defaults.js +28 -0
  53. package/lib/tagged-fenced-block-error.js +17 -0
  54. package/lib/tagged-fenced-block-markdown.js +111 -0
  55. package/lib/tagged-fenced-block-metadata.js +97 -0
  56. package/lib/tagged-fenced-block-parser.js +292 -0
  57. package/lib/tagged-fenced-blocks.js +100 -0
  58. package/lib/tagged-fenced-blocks.types.ts +38 -0
  59. package/lib/write-paged-output.js +87 -0
  60. package/package.json +28 -12
  61. package/bin/patram.test.js +0 -184
  62. package/lib/build-graph.test.js +0 -141
  63. package/lib/check-graph.test.js +0 -103
  64. package/lib/list-source-files.test.js +0 -101
  65. package/lib/load-patram-config.test.js +0 -211
  66. package/lib/parse-claims.test.js +0 -113
  67. package/lib/patram-config.test.js +0 -147
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  /**
2
3
  * @import { LoadPatramConfigResult, PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
3
4
  */
@@ -8,6 +9,27 @@ import process from 'node:process';
8
9
 
9
10
  import { z } from 'zod';
10
11
 
12
+ import { parsePatramConfig } from './patram-config.js';
13
+ import { parseWhereClause } from './parse-where-clause.js';
14
+ import { resolvePatramGraphConfig } from './resolve-patram-graph-config.js';
15
+ import { DEFAULT_INCLUDE_PATTERNS } from './source-file-defaults.js';
16
+
17
+ /**
18
+ * Repo config loading.
19
+ *
20
+ * Reads `.patram.json`, applies defaults, and validates repo config and graph
21
+ * schema before command execution.
22
+ *
23
+ * Kind: config
24
+ * Status: active
25
+ * Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
26
+ * Decided by: ../docs/decisions/single-config-file.md
27
+ * Decided by: ../docs/decisions/optional-config-default-scan.md
28
+ * @patram
29
+ * @see {@link ./resolve-patram-graph-config.js}
30
+ * @see {@link ../docs/decisions/single-config-file.md}
31
+ */
32
+
11
33
  const CONFIG_FILE_NAME = '.patram.json';
12
34
 
13
35
  const stored_query_schema = z
@@ -16,12 +38,71 @@ const stored_query_schema = z
16
38
  })
17
39
  .strict();
18
40
 
41
+ const derived_summary_scalar_schema = z.union([
42
+ z.boolean(),
43
+ z.number(),
44
+ z.string(),
45
+ z.null(),
46
+ ]);
47
+
48
+ const derived_summary_count_schema = z
49
+ .object({
50
+ traversal: z
51
+ .string()
52
+ .min(1, 'Derived summary count "traversal" must not be empty.'),
53
+ where: z
54
+ .string()
55
+ .min(1, 'Derived summary count "where" must not be empty.'),
56
+ })
57
+ .strict();
58
+
59
+ const derived_summary_select_case_schema = z
60
+ .object({
61
+ value: derived_summary_scalar_schema,
62
+ when: z.string().min(1, 'Derived summary select "when" must not be empty.'),
63
+ })
64
+ .strict();
65
+
66
+ const derived_summary_field_schema = z
67
+ .object({
68
+ count: derived_summary_count_schema.optional(),
69
+ default: derived_summary_scalar_schema.optional(),
70
+ name: z
71
+ .string()
72
+ .regex(
73
+ /^[a-z][a-z0-9_]*$/du,
74
+ 'Derived summary field names must use lower_snake_case.',
75
+ ),
76
+ select: z.array(derived_summary_select_case_schema).optional(),
77
+ })
78
+ .strict()
79
+ .superRefine(validateDerivedSummaryFieldDefinition);
80
+
81
+ const derived_summary_schema = z
82
+ .object({
83
+ fields: z
84
+ .array(derived_summary_field_schema)
85
+ .min(1, 'Derived summary "fields" must contain at least one field.'),
86
+ kinds: z
87
+ .array(z.string().min(1))
88
+ .min(1, 'Derived summary "kinds" must contain at least one kind.'),
89
+ })
90
+ .strict()
91
+ .superRefine(validateDerivedSummaryDefinition);
92
+
19
93
  const patram_repo_config_schema = z
20
94
  .object({
95
+ derived_summaries: z
96
+ .record(z.string().min(1), derived_summary_schema)
97
+ .optional(),
21
98
  include: z
22
99
  .array(z.string().min(1, 'Include globs must not be empty.'))
23
- .min(1, 'Include must contain at least one glob.'),
24
- queries: z.record(z.string().min(1), stored_query_schema),
100
+ .min(1, 'Include must contain at least one glob.')
101
+ .default(DEFAULT_INCLUDE_PATTERNS),
102
+ kinds: z.unknown().optional(),
103
+ mappings: z.unknown().optional(),
104
+ queries: z.record(z.string().min(1), stored_query_schema).default({}),
105
+ relations: z.unknown().optional(),
25
106
  })
26
107
  .strict();
27
108
 
@@ -36,7 +117,7 @@ export async function loadPatramConfig(project_directory = process.cwd()) {
36
117
  const config_source = await readConfigSource(config_file_path);
37
118
 
38
119
  if (config_source === null) {
39
- return createLoadResult(null, [createMissingConfigDiagnostic()]);
120
+ return createLoadResult(createDefaultRepoConfig(), []);
40
121
  }
41
122
 
42
123
  const parse_result = parseConfigJson(config_source);
@@ -54,7 +135,21 @@ export async function loadPatramConfig(project_directory = process.cwd()) {
54
135
  );
55
136
  }
56
137
 
57
- return createLoadResult(config_result.data, []);
138
+ const graph_schema_diagnostics = validateGraphSchema(config_result.data);
139
+
140
+ if (graph_schema_diagnostics.length > 0) {
141
+ return createLoadResult(null, graph_schema_diagnostics);
142
+ }
143
+
144
+ const normalized_config = normalizeRepoConfig(config_result.data);
145
+ const derived_summary_diagnostics =
146
+ validateDerivedSummaries(normalized_config);
147
+
148
+ if (derived_summary_diagnostics.length > 0) {
149
+ return createLoadResult(null, derived_summary_diagnostics);
150
+ }
151
+
152
+ return createLoadResult(normalized_config, []);
58
153
  }
59
154
 
60
155
  /**
@@ -108,20 +203,6 @@ function createLoadResult(config, diagnostics) {
108
203
  };
109
204
  }
110
205
 
111
- /**
112
- * @returns {PatramDiagnostic}
113
- */
114
- function createMissingConfigDiagnostic() {
115
- return {
116
- code: 'config.not_found',
117
- column: 1,
118
- level: 'error',
119
- line: 1,
120
- message: 'Config file ".patram.json" was not found.',
121
- path: CONFIG_FILE_NAME,
122
- };
123
- }
124
-
125
206
  /**
126
207
  * @param {string} config_source
127
208
  * @param {SyntaxError} error
@@ -168,6 +249,412 @@ function createValidationDiagnostic(issue) {
168
249
  };
169
250
  }
170
251
 
252
+ /**
253
+ * @param {{ count?: unknown, default?: unknown, select?: unknown }} field_definition
254
+ * @param {import('zod').RefinementCtx} refinement_context
255
+ */
256
+ function validateDerivedSummaryFieldDefinition(
257
+ field_definition,
258
+ refinement_context,
259
+ ) {
260
+ const evaluator_count =
261
+ Number(field_definition.count !== undefined) +
262
+ Number(field_definition.select !== undefined);
263
+
264
+ if (evaluator_count !== 1) {
265
+ refinement_context.addIssue({
266
+ code: 'custom',
267
+ message:
268
+ 'Derived summary fields must define exactly one of "count" or "select".',
269
+ });
270
+ }
271
+
272
+ if (
273
+ field_definition.count !== undefined &&
274
+ field_definition.default !== undefined
275
+ ) {
276
+ refinement_context.addIssue({
277
+ code: 'custom',
278
+ message: 'Derived summary count fields must not define "default".',
279
+ path: ['default'],
280
+ });
281
+ }
282
+
283
+ if (field_definition.select === undefined) {
284
+ return;
285
+ }
286
+
287
+ if (
288
+ Array.isArray(field_definition.select) &&
289
+ field_definition.select.length === 0
290
+ ) {
291
+ refinement_context.addIssue({
292
+ code: 'custom',
293
+ message: 'Derived summary "select" must contain at least one case.',
294
+ path: ['select'],
295
+ });
296
+ }
297
+
298
+ if (field_definition.default === undefined) {
299
+ refinement_context.addIssue({
300
+ code: 'custom',
301
+ message: 'Derived summary select fields must define "default".',
302
+ path: ['default'],
303
+ });
304
+ }
305
+ }
306
+
307
+ /**
308
+ * @param {{ fields: Array<{ name: string }> }} summary_definition
309
+ * @param {import('zod').RefinementCtx} refinement_context
310
+ */
311
+ function validateDerivedSummaryDefinition(
312
+ summary_definition,
313
+ refinement_context,
314
+ ) {
315
+ const seen_field_names = new Set();
316
+
317
+ for (const [
318
+ field_index,
319
+ field_definition,
320
+ ] of summary_definition.fields.entries()) {
321
+ if (!seen_field_names.has(field_definition.name)) {
322
+ seen_field_names.add(field_definition.name);
323
+ continue;
324
+ }
325
+
326
+ refinement_context.addIssue({
327
+ code: 'custom',
328
+ message: `Duplicate derived summary field "${field_definition.name}".`,
329
+ path: ['fields', field_index, 'name'],
330
+ });
331
+ }
332
+ }
333
+
334
+ /**
335
+ * @param {{ include: string[], queries: Record<string, { where: string }>, kinds?: unknown, mappings?: unknown, relations?: unknown }} repo_config
336
+ * @returns {PatramDiagnostic[]}
337
+ */
338
+ function validateGraphSchema(repo_config) {
339
+ if (
340
+ repo_config.kinds === undefined &&
341
+ repo_config.mappings === undefined &&
342
+ repo_config.relations === undefined
343
+ ) {
344
+ return [];
345
+ }
346
+
347
+ try {
348
+ parsePatramConfig({
349
+ kinds: repo_config.kinds ?? {},
350
+ mappings: repo_config.mappings ?? {},
351
+ relations: repo_config.relations ?? {},
352
+ });
353
+ } catch (error) {
354
+ if (error instanceof z.ZodError) {
355
+ return error.issues.map(createValidationDiagnostic);
356
+ }
357
+
358
+ throw error;
359
+ }
360
+
361
+ return [];
362
+ }
363
+
364
+ /**
365
+ * @param {PatramRepoConfig} repo_config
366
+ * @returns {PatramDiagnostic[]}
367
+ */
368
+ function validateDerivedSummaries(repo_config) {
369
+ if (!repo_config.derived_summaries) {
370
+ return [];
371
+ }
372
+
373
+ const graph_config = resolvePatramGraphConfig(repo_config);
374
+ const known_relation_names = new Set(Object.keys(graph_config.relations));
375
+ /** @type {PatramDiagnostic[]} */
376
+ const diagnostics = [];
377
+ const kind_coverage = new Map();
378
+
379
+ for (const [summary_name, summary_definition] of Object.entries(
380
+ repo_config.derived_summaries,
381
+ )) {
382
+ collectDuplicateKindDiagnostics(
383
+ diagnostics,
384
+ kind_coverage,
385
+ summary_definition.kinds,
386
+ summary_name,
387
+ );
388
+ collectDerivedSummaryFieldDiagnostics(
389
+ diagnostics,
390
+ known_relation_names,
391
+ summary_name,
392
+ summary_definition.fields,
393
+ );
394
+ }
395
+
396
+ return diagnostics;
397
+ }
398
+
399
+ /**
400
+ * @returns {PatramRepoConfig}
401
+ */
402
+ function createDefaultRepoConfig() {
403
+ return {
404
+ include: [...DEFAULT_INCLUDE_PATTERNS],
405
+ queries: {},
406
+ };
407
+ }
408
+
409
+ /**
410
+ * @param {{ derived_summaries?: unknown, include: string[], queries: Record<string, { where: string }>, kinds?: unknown, mappings?: unknown, relations?: unknown }} repo_config
411
+ * @returns {PatramRepoConfig}
412
+ */
413
+ function normalizeRepoConfig(repo_config) {
414
+ /** @type {PatramRepoConfig} */
415
+ const normalized_config = {
416
+ include: [...repo_config.include],
417
+ queries: { ...repo_config.queries },
418
+ };
419
+
420
+ if (
421
+ repo_config.derived_summaries !== undefined &&
422
+ repo_config.derived_summaries !== null
423
+ ) {
424
+ normalized_config.derived_summaries =
425
+ /** @type {PatramRepoConfig['derived_summaries']} */ (
426
+ repo_config.derived_summaries
427
+ );
428
+ }
429
+
430
+ if (repo_config.kinds !== undefined && repo_config.kinds !== null) {
431
+ normalized_config.kinds = /** @type {PatramRepoConfig['kinds']} */ (
432
+ repo_config.kinds
433
+ );
434
+ }
435
+
436
+ if (repo_config.mappings !== undefined && repo_config.mappings !== null) {
437
+ normalized_config.mappings = /** @type {PatramRepoConfig['mappings']} */ (
438
+ repo_config.mappings
439
+ );
440
+ }
441
+
442
+ if (repo_config.relations !== undefined && repo_config.relations !== null) {
443
+ normalized_config.relations = /** @type {PatramRepoConfig['relations']} */ (
444
+ repo_config.relations
445
+ );
446
+ }
447
+
448
+ return normalized_config;
449
+ }
450
+
451
+ /**
452
+ * @param {PatramDiagnostic[]} diagnostics
453
+ * @param {Map<string, string>} kind_coverage
454
+ * @param {string[]} kind_names
455
+ * @param {string} summary_name
456
+ */
457
+ function collectDuplicateKindDiagnostics(
458
+ diagnostics,
459
+ kind_coverage,
460
+ kind_names,
461
+ summary_name,
462
+ ) {
463
+ for (const kind_name of kind_names) {
464
+ const existing_summary_name = kind_coverage.get(kind_name);
465
+
466
+ if (!existing_summary_name) {
467
+ kind_coverage.set(kind_name, summary_name);
468
+ continue;
469
+ }
470
+
471
+ diagnostics.push(
472
+ createConfigDiagnostic(
473
+ `derived_summaries.${summary_name}.kinds`,
474
+ `Kind "${kind_name}" is already covered by derived summary "${existing_summary_name}".`,
475
+ ),
476
+ );
477
+ }
478
+ }
479
+
480
+ /**
481
+ * @param {PatramDiagnostic[]} diagnostics
482
+ * @param {Set<string>} known_relation_names
483
+ * @param {string} summary_name
484
+ * @param {import('./load-patram-config.types.ts').DerivedSummaryFieldConfig[]} field_definitions
485
+ */
486
+ function collectDerivedSummaryFieldDiagnostics(
487
+ diagnostics,
488
+ known_relation_names,
489
+ summary_name,
490
+ field_definitions,
491
+ ) {
492
+ for (const [field_index, field_definition] of field_definitions.entries()) {
493
+ if ('count' in field_definition) {
494
+ collectTraversalDiagnostic(
495
+ diagnostics,
496
+ field_definition.count.traversal,
497
+ known_relation_names,
498
+ `derived_summaries.${summary_name}.fields.${field_index}.count.traversal`,
499
+ );
500
+ collectWhereClauseDiagnostics(
501
+ diagnostics,
502
+ field_definition.count.where,
503
+ known_relation_names,
504
+ `derived_summaries.${summary_name}.fields.${field_index}.count.where`,
505
+ );
506
+ continue;
507
+ }
508
+
509
+ for (const [case_index, select_case] of field_definition.select.entries()) {
510
+ collectWhereClauseDiagnostics(
511
+ diagnostics,
512
+ select_case.when,
513
+ known_relation_names,
514
+ `derived_summaries.${summary_name}.fields.${field_index}.select.${case_index}.when`,
515
+ );
516
+ }
517
+ }
518
+ }
519
+
520
+ /**
521
+ * @param {PatramDiagnostic[]} diagnostics
522
+ * @param {string} traversal_text
523
+ * @param {Set<string>} known_relation_names
524
+ * @param {string} diagnostic_path
525
+ */
526
+ function collectTraversalDiagnostic(
527
+ diagnostics,
528
+ traversal_text,
529
+ known_relation_names,
530
+ diagnostic_path,
531
+ ) {
532
+ const traversal_match =
533
+ /^(?<direction>in|out):(?<relation_name>[a-zA-Z0-9_]+)$/du.exec(
534
+ traversal_text,
535
+ );
536
+
537
+ if (!traversal_match?.groups?.relation_name) {
538
+ diagnostics.push(
539
+ createConfigDiagnostic(
540
+ diagnostic_path,
541
+ 'Derived summary traversal must use "in:<relation>" or "out:<relation>".',
542
+ ),
543
+ );
544
+
545
+ return;
546
+ }
547
+
548
+ if (known_relation_names.has(traversal_match.groups.relation_name)) {
549
+ return;
550
+ }
551
+
552
+ diagnostics.push(
553
+ createConfigDiagnostic(
554
+ diagnostic_path,
555
+ `Unknown relation "${traversal_match.groups.relation_name}" in derived summary traversal.`,
556
+ ),
557
+ );
558
+ }
559
+
560
+ /**
561
+ * @param {PatramDiagnostic[]} diagnostics
562
+ * @param {string} where_clause
563
+ * @param {Set<string>} known_relation_names
564
+ * @param {string} diagnostic_path
565
+ */
566
+ function collectWhereClauseDiagnostics(
567
+ diagnostics,
568
+ where_clause,
569
+ known_relation_names,
570
+ diagnostic_path,
571
+ ) {
572
+ const parse_result = parseWhereClause(where_clause);
573
+
574
+ if (!parse_result.success) {
575
+ diagnostics.push(
576
+ createConfigDiagnostic(diagnostic_path, parse_result.diagnostic.message),
577
+ );
578
+
579
+ return;
580
+ }
581
+
582
+ for (const clause of parse_result.clauses) {
583
+ collectClauseRelationDiagnostics(
584
+ diagnostics,
585
+ clause.term,
586
+ known_relation_names,
587
+ diagnostic_path,
588
+ );
589
+ }
590
+ }
591
+
592
+ /**
593
+ * @param {PatramDiagnostic[]} diagnostics
594
+ * @param {import('./parse-where-clause.types.ts').ParsedTerm} term
595
+ * @param {Set<string>} known_relation_names
596
+ * @param {string} diagnostic_path
597
+ */
598
+ function collectClauseRelationDiagnostics(
599
+ diagnostics,
600
+ term,
601
+ known_relation_names,
602
+ diagnostic_path,
603
+ ) {
604
+ if (term.kind === 'aggregate') {
605
+ if (!known_relation_names.has(term.traversal.relation_name)) {
606
+ diagnostics.push(
607
+ createConfigDiagnostic(
608
+ diagnostic_path,
609
+ `Unknown relation "${term.traversal.relation_name}" in traversal clause.`,
610
+ ),
611
+ );
612
+ }
613
+
614
+ for (const nested_clause of term.clauses) {
615
+ collectClauseRelationDiagnostics(
616
+ diagnostics,
617
+ nested_clause.term,
618
+ known_relation_names,
619
+ diagnostic_path,
620
+ );
621
+ }
622
+
623
+ return;
624
+ }
625
+
626
+ if (term.kind !== 'relation' && term.kind !== 'relation_target') {
627
+ return;
628
+ }
629
+
630
+ if (known_relation_names.has(term.relation_name)) {
631
+ return;
632
+ }
633
+
634
+ diagnostics.push(
635
+ createConfigDiagnostic(
636
+ diagnostic_path,
637
+ `Unknown relation "${term.relation_name}" in relation clause.`,
638
+ ),
639
+ );
640
+ }
641
+
642
+ /**
643
+ * @param {string} issue_path
644
+ * @param {string} message
645
+ * @returns {PatramDiagnostic}
646
+ */
647
+ function createConfigDiagnostic(issue_path, message) {
648
+ return {
649
+ code: 'config.invalid',
650
+ column: 1,
651
+ level: 'error',
652
+ line: 1,
653
+ message: `Invalid config at "${issue_path}": ${message}`,
654
+ path: CONFIG_FILE_NAME,
655
+ };
656
+ }
657
+
171
658
  /**
172
659
  * @param {unknown} error
173
660
  * @returns {error is NodeJS.ErrnoException}
@@ -1,10 +1,50 @@
1
+ import type {
2
+ KindDefinition,
3
+ MappingDefinition,
4
+ RelationDefinition,
5
+ } from './patram-config.types.ts';
6
+
1
7
  export interface StoredQueryConfig {
2
8
  where: string;
3
9
  }
4
10
 
11
+ export type DerivedSummaryScalar = boolean | number | string | null;
12
+
13
+ export interface DerivedSummaryCountFieldConfig {
14
+ count: {
15
+ traversal: string;
16
+ where: string;
17
+ };
18
+ name: string;
19
+ }
20
+
21
+ export interface DerivedSummarySelectCaseConfig {
22
+ value: DerivedSummaryScalar;
23
+ when: string;
24
+ }
25
+
26
+ export interface DerivedSummarySelectFieldConfig {
27
+ default: DerivedSummaryScalar;
28
+ name: string;
29
+ select: DerivedSummarySelectCaseConfig[];
30
+ }
31
+
32
+ export type DerivedSummaryFieldConfig =
33
+ | DerivedSummaryCountFieldConfig
34
+ | DerivedSummarySelectFieldConfig;
35
+
36
+ export interface DerivedSummaryConfig {
37
+ fields: DerivedSummaryFieldConfig[];
38
+ kinds: string[];
39
+ }
40
+
5
41
  export interface PatramRepoConfig {
42
+ derived_summaries?: Record<string, DerivedSummaryConfig>;
6
43
  include: string[];
44
+ kinds?: Record<string, KindDefinition>;
45
+ mappings?: Record<string, MappingDefinition>;
7
46
  queries: Record<string, StoredQueryConfig>;
47
+ relations?: Record<string, RelationDefinition>;
8
48
  }
9
49
 
10
50
  export interface PatramDiagnostic {