patram 0.2.0 → 0.4.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 (38) hide show
  1. package/lib/build-graph-identity.js +86 -99
  2. package/lib/build-graph.js +536 -31
  3. package/lib/build-graph.types.ts +6 -2
  4. package/lib/check-directive-metadata.js +534 -0
  5. package/lib/check-directive-value.js +291 -0
  6. package/lib/check-graph.js +23 -5
  7. package/lib/cli-help-metadata.js +56 -16
  8. package/lib/command-output.js +16 -1
  9. package/lib/derived-summary.js +10 -8
  10. package/lib/directive-diagnostics.js +38 -0
  11. package/lib/directive-type-rules.js +133 -0
  12. package/lib/discover-fields.js +435 -0
  13. package/lib/discover-fields.types.ts +52 -0
  14. package/lib/document-node-identity.js +317 -0
  15. package/lib/format-node-header.js +9 -7
  16. package/lib/format-output-metadata.js +15 -23
  17. package/lib/layout-stored-queries.js +124 -85
  18. package/lib/load-patram-config.js +433 -96
  19. package/lib/load-patram-config.types.ts +98 -3
  20. package/lib/load-project-graph.js +4 -1
  21. package/lib/output-view.types.ts +14 -6
  22. package/lib/parse-cli-arguments.types.ts +1 -1
  23. package/lib/parse-where-clause.js +344 -107
  24. package/lib/parse-where-clause.types.ts +25 -8
  25. package/lib/patram-cli.js +68 -4
  26. package/lib/patram-config.js +31 -31
  27. package/lib/patram-config.types.ts +10 -4
  28. package/lib/query-graph.js +269 -40
  29. package/lib/query-inspection.js +440 -60
  30. package/lib/render-field-discovery.js +184 -0
  31. package/lib/render-json-output.js +21 -22
  32. package/lib/render-output-view.js +301 -34
  33. package/lib/render-plain-output.js +1 -1
  34. package/lib/render-rich-output.js +1 -1
  35. package/lib/render-rich-source.js +245 -14
  36. package/lib/resolve-patram-graph-config.js +15 -9
  37. package/lib/show-document.js +66 -9
  38. package/package.json +5 -5
@@ -0,0 +1,291 @@
1
+ /**
2
+ * @import { DirectiveTypeConfig, MetadataDirectiveRuleConfig, PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
3
+ * @import { PatramClaim } from './parse-claims.types.ts';
4
+ * @import { MappingDefinition } from './patram-config.types.ts';
5
+ */
6
+
7
+ import { resolveTargetReference } from './build-graph-identity.js';
8
+ import { createOriginDiagnostic } from './directive-diagnostics.js';
9
+ import {
10
+ formatQuotedList,
11
+ getInvalidTypeMessage,
12
+ isDirectiveValueValid,
13
+ } from './directive-type-rules.js';
14
+
15
+ /**
16
+ * Check one directive claim value against typed validation rules.
17
+ *
18
+ * @param {PatramClaim} claim
19
+ * @param {string} directive_name
20
+ * @param {Record<string, MappingDefinition>} mappings
21
+ * @param {PatramRepoConfig} repo_config
22
+ * @param {MetadataDirectiveRuleConfig | undefined} _directive_rule
23
+ * @param {Map<string, string>} document_entity_keys
24
+ * @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
25
+ * @param {Set<string>} document_paths
26
+ * @returns {PatramDiagnostic[]}
27
+ */
28
+ export function checkDirectiveValue(
29
+ claim,
30
+ directive_name,
31
+ mappings,
32
+ repo_config,
33
+ _directive_rule,
34
+ document_entity_keys,
35
+ document_node_references,
36
+ document_paths,
37
+ ) {
38
+ const mapping_definition = resolveDirectiveMapping(mappings, claim);
39
+ const validation_field_name = getDirectiveValidationFieldName(
40
+ directive_name,
41
+ mapping_definition,
42
+ );
43
+
44
+ if (!validation_field_name || typeof claim.value !== 'string') {
45
+ return [];
46
+ }
47
+
48
+ if (validation_field_name === '$class') {
49
+ return checkClassValue(claim, directive_name, repo_config);
50
+ }
51
+
52
+ if (isStructuralDirectiveField(validation_field_name)) {
53
+ return [];
54
+ }
55
+
56
+ const type_definition = repo_config.fields?.[validation_field_name];
57
+
58
+ if (!type_definition) {
59
+ return [];
60
+ }
61
+
62
+ if (type_definition.type === 'enum') {
63
+ return checkEnumValue(claim, directive_name, type_definition.values);
64
+ }
65
+
66
+ const type_diagnostic = createInvalidTypeDiagnostic(
67
+ claim,
68
+ directive_name,
69
+ type_definition,
70
+ claim.value,
71
+ );
72
+
73
+ if (type_diagnostic) {
74
+ return [type_diagnostic];
75
+ }
76
+
77
+ return createPathClassDiagnostics(
78
+ claim,
79
+ directive_name,
80
+ mappings,
81
+ repo_config,
82
+ type_definition,
83
+ document_entity_keys,
84
+ document_node_references,
85
+ document_paths,
86
+ );
87
+ }
88
+
89
+ /**
90
+ * @param {PatramClaim} claim
91
+ * @param {string} directive_name
92
+ * @param {PatramRepoConfig} repo_config
93
+ * @returns {PatramDiagnostic[]}
94
+ */
95
+ function checkClassValue(claim, directive_name, repo_config) {
96
+ if (
97
+ typeof claim.value !== 'string' ||
98
+ repo_config.classes?.[claim.value] !== undefined
99
+ ) {
100
+ return [];
101
+ }
102
+
103
+ return [
104
+ createOriginDiagnostic(
105
+ claim,
106
+ 'directive.invalid_enum',
107
+ `Directive "${directive_name}" must reference a configured class.`,
108
+ ),
109
+ ];
110
+ }
111
+
112
+ /**
113
+ * @param {PatramClaim} claim
114
+ * @param {string} directive_name
115
+ * @param {string[]} allowed_values
116
+ * @returns {PatramDiagnostic[]}
117
+ */
118
+ function checkEnumValue(claim, directive_name, allowed_values) {
119
+ if (typeof claim.value !== 'string' || allowed_values.includes(claim.value)) {
120
+ return [];
121
+ }
122
+
123
+ return [
124
+ createOriginDiagnostic(
125
+ claim,
126
+ 'directive.invalid_enum',
127
+ `Directive "${directive_name}" must be one of ${formatQuotedList(allowed_values)}.`,
128
+ ),
129
+ ];
130
+ }
131
+
132
+ /**
133
+ * @param {PatramClaim} claim
134
+ * @param {string} directive_name
135
+ * @param {Exclude<DirectiveTypeConfig, { type: 'enum' }>} type_definition
136
+ * @param {string} directive_value
137
+ * @returns {PatramDiagnostic | null}
138
+ */
139
+ function createInvalidTypeDiagnostic(
140
+ claim,
141
+ directive_name,
142
+ type_definition,
143
+ directive_value,
144
+ ) {
145
+ if (isDirectiveValueValid(type_definition, directive_value)) {
146
+ return null;
147
+ }
148
+
149
+ return createOriginDiagnostic(
150
+ claim,
151
+ 'directive.invalid_type',
152
+ getInvalidTypeMessage(directive_name, type_definition.type),
153
+ );
154
+ }
155
+
156
+ /**
157
+ * @param {PatramClaim} claim
158
+ * @param {string} directive_name
159
+ * @param {Record<string, MappingDefinition>} mappings
160
+ * @param {PatramRepoConfig} repo_config
161
+ * @param {Exclude<DirectiveTypeConfig, { type: 'enum' }>} type_definition
162
+ * @param {Map<string, string>} document_entity_keys
163
+ * @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
164
+ * @param {Set<string>} document_paths
165
+ * @returns {PatramDiagnostic[]}
166
+ */
167
+ function createPathClassDiagnostics(
168
+ claim,
169
+ directive_name,
170
+ mappings,
171
+ repo_config,
172
+ type_definition,
173
+ document_entity_keys,
174
+ document_node_references,
175
+ document_paths,
176
+ ) {
177
+ if (
178
+ type_definition.type !== 'path' ||
179
+ type_definition.path_class === undefined ||
180
+ isDirectivePathInClass(
181
+ mappings,
182
+ claim,
183
+ type_definition.path_class,
184
+ document_entity_keys,
185
+ document_node_references,
186
+ document_paths,
187
+ repo_config,
188
+ )
189
+ ) {
190
+ return [];
191
+ }
192
+
193
+ return [
194
+ createOriginDiagnostic(
195
+ claim,
196
+ 'directive.invalid_path_class',
197
+ `Directive "${directive_name}" must point to path class "${type_definition.path_class}".`,
198
+ ),
199
+ ];
200
+ }
201
+
202
+ /**
203
+ * @param {Record<string, MappingDefinition>} mappings
204
+ * @param {PatramClaim} claim
205
+ * @param {string} path_class_name
206
+ * @param {Map<string, string>} document_entity_keys
207
+ * @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
208
+ * @param {Set<string>} document_paths
209
+ * @param {PatramRepoConfig} repo_config
210
+ * @returns {boolean}
211
+ */
212
+ function isDirectivePathInClass(
213
+ mappings,
214
+ claim,
215
+ path_class_name,
216
+ document_entity_keys,
217
+ document_node_references,
218
+ document_paths,
219
+ repo_config,
220
+ ) {
221
+ const path_class_definition = repo_config.path_classes?.[path_class_name];
222
+
223
+ if (!path_class_definition) {
224
+ return true;
225
+ }
226
+
227
+ const mapping_definition = resolveDirectiveMapping(mappings, claim);
228
+ const target_kind = mapping_definition?.emit?.target_class ?? 'document';
229
+ const resolved_target = resolveTargetReference(
230
+ target_kind,
231
+ 'path',
232
+ claim,
233
+ document_entity_keys,
234
+ document_node_references,
235
+ document_paths,
236
+ );
237
+
238
+ if (!resolved_target.path) {
239
+ return false;
240
+ }
241
+
242
+ return path_class_definition.prefixes.some((prefix) =>
243
+ resolved_target.path?.startsWith(prefix),
244
+ );
245
+ }
246
+
247
+ /**
248
+ * @param {Record<string, MappingDefinition>} mappings
249
+ * @param {PatramClaim} claim
250
+ * @returns {MappingDefinition | null}
251
+ */
252
+ function resolveDirectiveMapping(mappings, claim) {
253
+ if (!claim.name || !claim.parser) {
254
+ return null;
255
+ }
256
+
257
+ return mappings[`${claim.parser}.directive.${claim.name}`] ?? null;
258
+ }
259
+
260
+ /**
261
+ * @param {string} directive_name
262
+ * @param {MappingDefinition | null} mapping_definition
263
+ * @returns {string}
264
+ */
265
+ function getDirectiveValidationFieldName(directive_name, mapping_definition) {
266
+ if (mapping_definition?.node?.field) {
267
+ return mapping_definition.node.field;
268
+ }
269
+
270
+ return directive_name;
271
+ }
272
+
273
+ /**
274
+ * @param {string} field_name
275
+ * @returns {boolean}
276
+ */
277
+ function isStructuralDirectiveField(field_name) {
278
+ return (
279
+ field_name === '$class' ||
280
+ field_name === '$id' ||
281
+ field_name === '$path' ||
282
+ field_name === 'title'
283
+ );
284
+ }
285
+
286
+ /**
287
+ * @param {PatramClaim} claim
288
+ * @param {string} code
289
+ * @param {string} message
290
+ * @returns {PatramDiagnostic}
291
+ */
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * @import { BuildGraphResult, GraphEdge, GraphNode } from './build-graph.types.ts';
3
- * @import { PatramDiagnostic } from './load-patram-config.types.ts';
3
+ * @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
4
+ * @import { PatramClaim } from './parse-claims.types.ts';
4
5
  */
5
6
 
7
+ import { checkDirectiveMetadata } from './check-directive-metadata.js';
8
+
6
9
  /**
7
10
  * Graph validation.
8
11
  *
@@ -24,9 +27,11 @@
24
27
  *
25
28
  * @param {BuildGraphResult} graph
26
29
  * @param {string[]} existing_file_paths
30
+ * @param {PatramRepoConfig} [repo_config]
31
+ * @param {PatramClaim[]} [claims]
27
32
  * @returns {PatramDiagnostic[]}
28
33
  */
29
- export function checkGraph(graph, existing_file_paths) {
34
+ export function checkGraph(graph, existing_file_paths, repo_config, claims) {
30
35
  /** @type {PatramDiagnostic[]} */
31
36
  const diagnostics = [];
32
37
  const existing_file_path_set = new Set(existing_file_paths);
@@ -54,6 +59,17 @@ export function checkGraph(graph, existing_file_paths) {
54
59
  );
55
60
  }
56
61
 
62
+ if (repo_config && claims) {
63
+ diagnostics.push(
64
+ ...checkDirectiveMetadata(
65
+ graph,
66
+ repo_config,
67
+ claims,
68
+ existing_file_paths,
69
+ ),
70
+ );
71
+ }
72
+
57
73
  return diagnostics;
58
74
  }
59
75
 
@@ -102,15 +118,17 @@ function collectBrokenLinkDiagnostics(
102
118
  target_node,
103
119
  existing_file_path_set,
104
120
  ) {
121
+ const target_path = target_node.$path ?? target_node.path;
122
+
105
123
  if (graph_edge.relation !== 'links_to') {
106
124
  return;
107
125
  }
108
126
 
109
- if (target_node.kind !== 'document' || !target_node.path) {
127
+ if (!target_path) {
110
128
  return;
111
129
  }
112
130
 
113
- if (existing_file_path_set.has(target_node.path)) {
131
+ if (existing_file_path_set.has(target_path)) {
114
132
  return;
115
133
  }
116
134
 
@@ -118,7 +136,7 @@ function collectBrokenLinkDiagnostics(
118
136
  createDiagnostic(
119
137
  graph_edge,
120
138
  'graph.link_broken',
121
- `Document link target "${target_node.path}" was not found.`,
139
+ `Document link target "${target_path}" was not found.`,
122
140
  ),
123
141
  );
124
142
  }
@@ -47,6 +47,7 @@
47
47
 
48
48
  export const COMMAND_NAMES = /** @type {const} */ ([
49
49
  'check',
50
+ 'fields',
50
51
  'query',
51
52
  'queries',
52
53
  'show',
@@ -108,6 +109,33 @@ const COMMAND_DEFINITIONS = {
108
109
  'Validate a project, directory, or file and report graph diagnostics.',
109
110
  usage_lines: ['patram check [path] [options]'],
110
111
  },
112
+ fields: {
113
+ allowed_option_names: new Set(),
114
+ examples: ['patram fields', 'patram fields --json'],
115
+ extra_positionals_message: 'Fields does not accept positional arguments.',
116
+ help_topics: [],
117
+ max_positionals: 0,
118
+ min_positionals: 0,
119
+ missing_argument_examples: [],
120
+ missing_argument_label: null,
121
+ missing_usage_lines: ['patram fields'],
122
+ option_column_width: 10,
123
+ options: [
124
+ {
125
+ description: 'Print plain text output',
126
+ label: '--plain',
127
+ },
128
+ {
129
+ description: 'Print JSON output',
130
+ label: '--json',
131
+ },
132
+ ],
133
+ related: ['query', 'check'],
134
+ root_summary: 'Discover likely field schema from source claims',
135
+ summary:
136
+ 'Discover likely metadata fields, multiplicity, and class usage from source claims.',
137
+ usage_lines: ['patram fields [options]'],
138
+ },
111
139
  query: {
112
140
  allowed_option_names: new Set([
113
141
  'explain',
@@ -120,10 +148,10 @@ const COMMAND_DEFINITIONS = {
120
148
  'patram query active-plans',
121
149
  'patram query --where "tracked_in=doc:docs/plans/v0/worktracking-agent-guidance.md"',
122
150
  'patram query --where "status not in [done, dropped, superseded]"',
123
- 'patram query --where "kind=plan and none(in:tracked_in, kind=decision)"',
124
- 'patram query --where "count(in:decided_by, kind=task) = 0"',
151
+ 'patram query --where "$class=plan and none(in:tracked_in, $class=decision)"',
152
+ 'patram query --where "count(in:decided_by, $class=task) = 0"',
125
153
  'patram query ready-tasks --explain',
126
- 'patram query --where "kind=decision and status=accepted and count(in:decided_by, kind=task) = 0" --lint',
154
+ 'patram query --where "$class=decision and status=accepted and count(in:decided_by, $class=task) = 0" --lint',
127
155
  'patram query active-plans --limit 10 --offset 20',
128
156
  ],
129
157
  extra_positionals_message:
@@ -176,8 +204,8 @@ const COMMAND_DEFINITIONS = {
176
204
  summary:
177
205
  'Run a stored query or an ad hoc where clause against graph nodes.',
178
206
  syntax_lines: [
179
- 'id=<value> | kind=<value> | path=<value> | status=<value>',
180
- 'id^=<prefix> | path^=<prefix> | title~<text>',
207
+ '$id=<value> | $class=<value> | $path=<value> | status=<value>',
208
+ '$id^=<prefix> | $path^=<prefix> | title~<text>',
181
209
  '<field> in [<value>, ...] | <field> not in [<value>, ...]',
182
210
  '<relation>:* | <relation>=<target-id>',
183
211
  'any(<traversal>, <term> and <term>)',
@@ -251,15 +279,17 @@ const COMMAND_DEFINITIONS = {
251
279
  const HELP_TOPIC_DEFINITIONS = {
252
280
  'query-language': {
253
281
  examples: [
254
- 'kind=decision and status=accepted',
255
- 'path^=docs/plans/',
282
+ '$class=decision and status=accepted',
283
+ '$class=task or status=done',
284
+ '($class=task or status=blocked) and title~Show',
285
+ '$path^=docs/plans/',
256
286
  'title~query',
257
287
  'tracked_in=doc:docs/plans/v0/worktracking-agent-guidance.md',
258
288
  'implements_command=command:query',
259
289
  'status not in [done, dropped, superseded]',
260
- 'any(in:tracked_in, kind=task and status in [pending, ready, in_progress, blocked])',
261
- 'none(in:tracked_in, kind=decision)',
262
- 'count(in:decided_by, kind=task) = 0',
290
+ 'any(in:tracked_in, $class=task and status in [pending, ready, in_progress, blocked])',
291
+ 'none(in:tracked_in, $class=decision)',
292
+ 'count(in:decided_by, $class=task) = 0',
263
293
  'not uses_term=term:graph',
264
294
  ],
265
295
  lead: 'Query language filters graph nodes with field, relation, traversal, and aggregate terms.',
@@ -269,7 +299,7 @@ const HELP_TOPIC_DEFINITIONS = {
269
299
  label: '=',
270
300
  },
271
301
  {
272
- description: 'Prefix match for id and path',
302
+ description: 'Prefix match for structural id and path',
273
303
  label: '^=',
274
304
  },
275
305
  {
@@ -292,6 +322,14 @@ const HELP_TOPIC_DEFINITIONS = {
292
322
  description: 'Combine terms',
293
323
  label: 'and',
294
324
  },
325
+ {
326
+ description: 'Match either side',
327
+ label: 'or',
328
+ },
329
+ {
330
+ description: 'Group boolean expressions',
331
+ label: '( )',
332
+ },
295
333
  {
296
334
  description: 'Count comparisons',
297
335
  label: '!= < > >= <=',
@@ -316,15 +354,15 @@ const HELP_TOPIC_DEFINITIONS = {
316
354
  },
317
355
  ],
318
356
  terms: [
319
- 'Exact match: id, kind, path, status',
320
- 'Prefix match: id, path',
357
+ 'Exact match: $id, $class, $path, status',
358
+ 'Prefix match: $id, $path',
321
359
  'Contains text: title',
322
- 'Set membership: id, kind, path, status, title',
360
+ 'Set membership: $id, $class, $path, status, title',
323
361
  ],
324
362
  usage_lines: [
325
363
  '<field>=<value>',
326
- 'id^=<prefix>',
327
- 'path^=<prefix>',
364
+ '$id^=<prefix>',
365
+ '$path^=<prefix>',
328
366
  'title~<text>',
329
367
  '<field> in [<value>, ...]',
330
368
  '<field> not in [<value>, ...]',
@@ -335,6 +373,8 @@ const HELP_TOPIC_DEFINITIONS = {
335
373
  'count(<traversal>, <term> and <term>) <comparison> <number>',
336
374
  'not <term>',
337
375
  '<term> and <term>',
376
+ '<term> or <term>',
377
+ '(<expression>)',
338
378
  ],
339
379
  },
340
380
  };
@@ -45,6 +45,20 @@ export async function writeCommandOutput(
45
45
  parsed_command,
46
46
  );
47
47
 
48
+ await writeRenderedCommandOutput(io_context, parsed_command, rendered_output);
49
+ }
50
+
51
+ /**
52
+ * @param {{ stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
53
+ * @param {ParsedCliArguments} parsed_command
54
+ * @param {string} rendered_output
55
+ * @returns {Promise<void>}
56
+ */
57
+ export async function writeRenderedCommandOutput(
58
+ io_context,
59
+ parsed_command,
60
+ rendered_output,
61
+ ) {
48
62
  if (shouldPageCommandOutput(parsed_command, io_context.stdout)) {
49
63
  await writeInteractiveOutput(io_context, rendered_output);
50
64
 
@@ -62,7 +76,8 @@ export async function writeCommandOutput(
62
76
  export function shouldPageCommandOutput(parsed_command, output_stream) {
63
77
  return (
64
78
  output_stream.isTTY === true &&
65
- (parsed_command.command_name === 'query' ||
79
+ (parsed_command.command_name === 'fields' ||
80
+ parsed_command.command_name === 'query' ||
66
81
  parsed_command.command_name === 'show')
67
82
  );
68
83
  }
@@ -34,13 +34,15 @@ import { queryGraph } from './query-graph.js';
34
34
  * @returns {DerivedSummaryEvaluator}
35
35
  */
36
36
  export function createDerivedSummaryEvaluator(repo_config, graph) {
37
- const summary_by_kind = createSummaryByKind(repo_config.derived_summaries);
37
+ const summary_by_class = createSummaryByClass(repo_config.derived_summaries);
38
38
  /** @type {Map<string, Set<string>>} */
39
39
  const matching_node_id_cache = new Map();
40
40
 
41
41
  return {
42
42
  evaluate(graph_node) {
43
- const configured_summary = summary_by_kind.get(graph_node.kind);
43
+ const configured_summary = summary_by_class.get(
44
+ graph_node.$class ?? 'document',
45
+ );
44
46
 
45
47
  if (!configured_summary) {
46
48
  return null;
@@ -255,24 +257,24 @@ function parseTraversal(traversal_text) {
255
257
  * @param {PatramRepoConfig['derived_summaries']} derived_summaries
256
258
  * @returns {Map<string, { definition: DerivedSummaryConfig, name: string }>}
257
259
  */
258
- function createSummaryByKind(derived_summaries) {
260
+ function createSummaryByClass(derived_summaries) {
259
261
  /** @type {Map<string, { definition: DerivedSummaryConfig, name: string }>} */
260
- const summary_by_kind = new Map();
262
+ const summary_by_class = new Map();
261
263
 
262
264
  if (!derived_summaries) {
263
- return summary_by_kind;
265
+ return summary_by_class;
264
266
  }
265
267
 
266
268
  for (const [summary_name, summary_definition] of Object.entries(
267
269
  derived_summaries,
268
270
  )) {
269
- for (const kind_name of summary_definition.kinds) {
270
- summary_by_kind.set(kind_name, {
271
+ for (const class_name of summary_definition.classes) {
272
+ summary_by_class.set(class_name, {
271
273
  definition: summary_definition,
272
274
  name: summary_name,
273
275
  });
274
276
  }
275
277
  }
276
278
 
277
- return summary_by_kind;
279
+ return summary_by_class;
278
280
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @import { PatramDiagnostic } from './load-patram-config.types.ts';
3
+ * @import { PatramClaim } from './parse-claims.types.ts';
4
+ */
5
+
6
+ /**
7
+ * @param {PatramClaim} claim
8
+ * @param {string} code
9
+ * @param {string} message
10
+ * @returns {PatramDiagnostic}
11
+ */
12
+ export function createOriginDiagnostic(claim, code, message) {
13
+ return {
14
+ code,
15
+ column: claim.origin.column,
16
+ level: 'error',
17
+ line: claim.origin.line,
18
+ message,
19
+ path: claim.origin.path,
20
+ };
21
+ }
22
+
23
+ /**
24
+ * @param {string} document_path
25
+ * @param {string} code
26
+ * @param {string} message
27
+ * @returns {PatramDiagnostic}
28
+ */
29
+ export function createDocumentDiagnostic(document_path, code, message) {
30
+ return {
31
+ code,
32
+ column: 1,
33
+ level: 'error',
34
+ line: 1,
35
+ message,
36
+ path: document_path,
37
+ };
38
+ }