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.
- package/lib/build-graph-identity.js +86 -99
- package/lib/build-graph.js +536 -31
- package/lib/build-graph.types.ts +6 -2
- package/lib/check-directive-metadata.js +534 -0
- package/lib/check-directive-value.js +291 -0
- package/lib/check-graph.js +23 -5
- package/lib/cli-help-metadata.js +56 -16
- package/lib/command-output.js +16 -1
- package/lib/derived-summary.js +10 -8
- package/lib/directive-diagnostics.js +38 -0
- package/lib/directive-type-rules.js +133 -0
- package/lib/discover-fields.js +435 -0
- package/lib/discover-fields.types.ts +52 -0
- package/lib/document-node-identity.js +317 -0
- package/lib/format-node-header.js +9 -7
- package/lib/format-output-metadata.js +15 -23
- package/lib/layout-stored-queries.js +124 -85
- package/lib/load-patram-config.js +433 -96
- package/lib/load-patram-config.types.ts +98 -3
- package/lib/load-project-graph.js +4 -1
- package/lib/output-view.types.ts +14 -6
- package/lib/parse-cli-arguments.types.ts +1 -1
- package/lib/parse-where-clause.js +344 -107
- package/lib/parse-where-clause.types.ts +25 -8
- package/lib/patram-cli.js +68 -4
- package/lib/patram-config.js +31 -31
- package/lib/patram-config.types.ts +10 -4
- package/lib/query-graph.js +269 -40
- package/lib/query-inspection.js +440 -60
- package/lib/render-field-discovery.js +184 -0
- package/lib/render-json-output.js +21 -22
- package/lib/render-output-view.js +301 -34
- package/lib/render-plain-output.js +1 -1
- package/lib/render-rich-output.js +1 -1
- package/lib/render-rich-source.js +245 -14
- package/lib/resolve-patram-graph-config.js +15 -9
- package/lib/show-document.js +66 -9
- 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
|
+
*/
|
package/lib/check-graph.js
CHANGED
|
@@ -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 (
|
|
127
|
+
if (!target_path) {
|
|
110
128
|
return;
|
|
111
129
|
}
|
|
112
130
|
|
|
113
|
-
if (existing_file_path_set.has(
|
|
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 "${
|
|
139
|
+
`Document link target "${target_path}" was not found.`,
|
|
122
140
|
),
|
|
123
141
|
);
|
|
124
142
|
}
|
package/lib/cli-help-metadata.js
CHANGED
|
@@ -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 "
|
|
124
|
-
'patram query --where "count(in:decided_by,
|
|
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 "
|
|
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> |
|
|
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
|
-
'
|
|
255
|
-
'
|
|
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,
|
|
261
|
-
'none(in:tracked_in,
|
|
262
|
-
'count(in:decided_by,
|
|
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,
|
|
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,
|
|
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
|
};
|
package/lib/command-output.js
CHANGED
|
@@ -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 === '
|
|
79
|
+
(parsed_command.command_name === 'fields' ||
|
|
80
|
+
parsed_command.command_name === 'query' ||
|
|
66
81
|
parsed_command.command_name === 'show')
|
|
67
82
|
);
|
|
68
83
|
}
|
package/lib/derived-summary.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
|
260
|
+
function createSummaryByClass(derived_summaries) {
|
|
259
261
|
/** @type {Map<string, { definition: DerivedSummaryConfig, name: string }>} */
|
|
260
|
-
const
|
|
262
|
+
const summary_by_class = new Map();
|
|
261
263
|
|
|
262
264
|
if (!derived_summaries) {
|
|
263
|
-
return
|
|
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
|
|
270
|
-
|
|
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
|
|
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
|
+
}
|