patram 0.6.2 → 0.8.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.d.ts +40 -0
- package/lib/build-graph.d.ts +11 -0
- package/lib/build-graph.types.d.ts +24 -0
- package/lib/claim-helpers.d.ts +20 -0
- package/lib/cli-help-metadata.js +48 -9
- package/lib/command-output.js +1 -0
- package/lib/document-node-identity.d.ts +47 -0
- package/lib/inspect-reverse-references.js +184 -0
- package/lib/layout-incoming-references.js +105 -0
- package/lib/layout-incoming-summary-lines.js +21 -0
- package/lib/list-source-files.d.ts +29 -0
- package/lib/load-patram-config.d.ts +384 -0
- package/lib/load-patram-config.types.d.ts +45 -0
- package/lib/load-project-graph.d.ts +35 -0
- package/lib/output-view.types.d.ts +88 -0
- package/lib/output-view.types.ts +19 -2
- package/lib/overlay-graph.d.ts +43 -0
- package/lib/overlay-graph.js +191 -0
- package/lib/parse-claims.d.ts +40 -0
- package/lib/parse-claims.types.d.ts +31 -0
- package/lib/parse-cli-arguments-helpers.js +11 -4
- package/lib/parse-cli-arguments.types.ts +8 -2
- package/lib/parse-jsdoc-blocks.d.ts +15 -0
- package/lib/parse-jsdoc-claims.d.ts +9 -0
- package/lib/parse-jsdoc-prose.d.ts +28 -0
- package/lib/parse-markdown-claims.d.ts +14 -0
- package/lib/parse-markdown-directives.d.ts +34 -0
- package/lib/parse-where-clause.d.ts +75 -0
- package/lib/parse-where-clause.js +157 -37
- package/lib/parse-where-clause.types.d.ts +63 -0
- package/lib/parse-yaml-claims.d.ts +38 -0
- package/lib/patram-cli.js +66 -1
- package/lib/patram-config.d.ts +106 -0
- package/lib/patram-config.types.d.ts +14 -0
- package/lib/patram.d.ts +73 -0
- package/lib/patram.js +3 -0
- package/lib/query-graph.d.ts +68 -0
- package/lib/query-graph.js +27 -24
- package/lib/query-inspection.d.ts +86 -0
- package/lib/render-cli-help.js +1 -1
- package/lib/render-json-output.js +91 -62
- package/lib/render-output-view.js +58 -3
- package/lib/render-plain-output.js +46 -3
- package/lib/render-rich-output.js +50 -5
- package/lib/resolve-patram-graph-config.d.ts +9 -0
- package/lib/reverse-reference-test-helpers.js +76 -0
- package/lib/show-document.js +44 -6
- package/lib/source-file-defaults.d.ts +5 -0
- package/lib/tagged-fenced-block-error.d.ts +10 -0
- package/lib/tagged-fenced-block-markdown.d.ts +47 -0
- package/lib/tagged-fenced-block-metadata.d.ts +26 -0
- package/lib/tagged-fenced-block-parser.d.ts +61 -0
- package/lib/tagged-fenced-blocks.d.ts +39 -0
- package/lib/tagged-fenced-blocks.types.d.ts +32 -0
- package/package.json +13 -2
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the node key for one mapped claim.
|
|
3
|
+
*
|
|
4
|
+
* @param {{ field: string, key?: 'path' | 'value', class: string }} node_mapping
|
|
5
|
+
* @param {PatramClaim} claim
|
|
6
|
+
* @param {Map<string, string>} document_entity_keys
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function resolveNodeKey(node_mapping: {
|
|
10
|
+
field: string;
|
|
11
|
+
key?: "path" | "value";
|
|
12
|
+
class: string;
|
|
13
|
+
}, claim: PatramClaim, document_entity_keys: Map<string, string>): string;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve one edge target key and canonical path.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} target_class
|
|
18
|
+
* @param {'path' | 'value'} target_type
|
|
19
|
+
* @param {PatramClaim} claim
|
|
20
|
+
* @param {Map<string, string>} document_entity_keys
|
|
21
|
+
* @param {Map<string, DocumentNodeReference>} document_node_references
|
|
22
|
+
* @param {Set<string>} document_paths
|
|
23
|
+
* @returns {{ class_name: string, key: string, path?: string }}
|
|
24
|
+
*/
|
|
25
|
+
export function resolveTargetReference(target_class: string, target_type: "path" | "value", claim: PatramClaim, document_entity_keys: Map<string, string>, document_node_references: Map<string, DocumentNodeReference>, document_paths: Set<string>): {
|
|
26
|
+
class_name: string;
|
|
27
|
+
key: string;
|
|
28
|
+
path?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Attach one canonical path to a graph node.
|
|
32
|
+
*
|
|
33
|
+
* @param {GraphNode} graph_node
|
|
34
|
+
* @param {string | undefined} source_path
|
|
35
|
+
*/
|
|
36
|
+
export function setCanonicalPath(graph_node: GraphNode, source_path: string | undefined): void;
|
|
37
|
+
import type { PatramClaim } from './parse-claims.types.ts';
|
|
38
|
+
import type { DocumentNodeReference } from './document-node-identity.js';
|
|
39
|
+
import type { GraphNode } from './build-graph.types.ts';
|
|
40
|
+
export { collectDocumentEntityKeys, collectDocumentNodeReferences, normalizeRepoRelativePath, resolveDocumentNodeId } from "./document-node-identity.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a Patram graph from semantic config and parsed claims.
|
|
3
|
+
*
|
|
4
|
+
* @param {PatramConfig} patram_config
|
|
5
|
+
* @param {PatramClaim[]} claims
|
|
6
|
+
* @returns {BuildGraphResult}
|
|
7
|
+
*/
|
|
8
|
+
export function buildGraph(patram_config: PatramConfig, claims: PatramClaim[]): BuildGraphResult;
|
|
9
|
+
import type { PatramConfig } from './patram-config.types.ts';
|
|
10
|
+
import type { PatramClaim } from './parse-claims.types.ts';
|
|
11
|
+
import type { BuildGraphResult } from './build-graph.types.ts';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ClaimOrigin } from './parse-claims.types.ts';
|
|
2
|
+
export interface GraphNode {
|
|
3
|
+
$class?: string;
|
|
4
|
+
$id?: string;
|
|
5
|
+
$path?: string;
|
|
6
|
+
id: string;
|
|
7
|
+
kind?: string;
|
|
8
|
+
key?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
[field: string]: string | string[] | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface GraphEdge {
|
|
14
|
+
from: string;
|
|
15
|
+
id: string;
|
|
16
|
+
origin: ClaimOrigin;
|
|
17
|
+
relation: string;
|
|
18
|
+
to: string;
|
|
19
|
+
}
|
|
20
|
+
export interface BuildGraphResult {
|
|
21
|
+
document_node_ids?: Record<string, string>;
|
|
22
|
+
edges: GraphEdge[];
|
|
23
|
+
nodes: Record<string, GraphNode>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} target_value
|
|
3
|
+
* @returns {boolean}
|
|
4
|
+
*/
|
|
5
|
+
export function isPathLikeTarget(target_value: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} file_path
|
|
8
|
+
* @param {number} claim_number
|
|
9
|
+
* @param {string} claim_type
|
|
10
|
+
* @param {PatramClaimFields} claim_fields
|
|
11
|
+
* @returns {PatramClaim}
|
|
12
|
+
*/
|
|
13
|
+
export function createClaim(file_path: string, claim_number: number, claim_type: string, claim_fields: PatramClaimFields): PatramClaim;
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} file_path
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function getFileExtension(file_path: string): string;
|
|
19
|
+
import type { PatramClaimFields } from './parse-claims.types.ts';
|
|
20
|
+
import type { PatramClaim } from './parse-claims.types.ts';
|
package/lib/cli-help-metadata.js
CHANGED
|
@@ -50,6 +50,7 @@ export const COMMAND_NAMES = /** @type {const} */ ([
|
|
|
50
50
|
'fields',
|
|
51
51
|
'query',
|
|
52
52
|
'queries',
|
|
53
|
+
'refs',
|
|
53
54
|
'show',
|
|
54
55
|
]);
|
|
55
56
|
|
|
@@ -146,12 +147,12 @@ const COMMAND_DEFINITIONS = {
|
|
|
146
147
|
]),
|
|
147
148
|
examples: [
|
|
148
149
|
'patram query active-plans',
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
"patram query --where 'tracked_in=doc:docs/plans/v0/worktracking-agent-guidance.md'",
|
|
151
|
+
"patram query --where 'status not in [done, dropped, superseded]'",
|
|
152
|
+
"patram query --where '$class=plan and none(in:tracked_in, $class=decision)'",
|
|
153
|
+
"patram query --where 'count(in:decided_by, $class=task) = 0'",
|
|
153
154
|
'patram query ready-tasks --explain',
|
|
154
|
-
|
|
155
|
+
"patram query --where '$class=decision and status=accepted and count(in:decided_by, $class=task) = 0' --lint",
|
|
155
156
|
'patram query active-plans --limit 10 --offset 20',
|
|
156
157
|
],
|
|
157
158
|
extra_positionals_message:
|
|
@@ -161,12 +162,12 @@ const COMMAND_DEFINITIONS = {
|
|
|
161
162
|
min_positionals: 0,
|
|
162
163
|
missing_argument_examples: [
|
|
163
164
|
'patram query active-plans',
|
|
164
|
-
|
|
165
|
+
"patram query --where 'tracked_in=doc:docs/plans/v0/worktracking-agent-guidance.md'",
|
|
165
166
|
],
|
|
166
|
-
missing_argument_label:
|
|
167
|
+
missing_argument_label: "<name> or --where '<clause>'",
|
|
167
168
|
missing_usage_lines: [
|
|
168
169
|
'patram query <name> [options]',
|
|
169
|
-
|
|
170
|
+
"patram query --where '<clause>' [options]",
|
|
170
171
|
],
|
|
171
172
|
option_column_width: 19,
|
|
172
173
|
options: [
|
|
@@ -214,7 +215,7 @@ const COMMAND_DEFINITIONS = {
|
|
|
214
215
|
],
|
|
215
216
|
usage_lines: [
|
|
216
217
|
'patram query <name> [options]',
|
|
217
|
-
|
|
218
|
+
"patram query --where '<clause>' [options]",
|
|
218
219
|
],
|
|
219
220
|
},
|
|
220
221
|
queries: {
|
|
@@ -243,6 +244,44 @@ const COMMAND_DEFINITIONS = {
|
|
|
243
244
|
summary: 'List the stored queries defined in the project configuration.',
|
|
244
245
|
usage_lines: ['patram queries [options]'],
|
|
245
246
|
},
|
|
247
|
+
refs: {
|
|
248
|
+
allowed_option_names: new Set(['where']),
|
|
249
|
+
examples: [
|
|
250
|
+
'patram refs docs/decisions/query-language.md',
|
|
251
|
+
"patram refs docs/decisions/query-language.md --where '$class=document'",
|
|
252
|
+
'patram refs docs/decisions/query-language.md --json',
|
|
253
|
+
],
|
|
254
|
+
extra_positionals_message: 'Refs accepts exactly one file path.',
|
|
255
|
+
help_topics: ['query-language'],
|
|
256
|
+
max_positionals: 1,
|
|
257
|
+
min_positionals: 1,
|
|
258
|
+
missing_argument_examples: [
|
|
259
|
+
'patram refs docs/decisions/query-language.md',
|
|
260
|
+
"patram refs docs/patram.md --where '$class=document'",
|
|
261
|
+
],
|
|
262
|
+
missing_argument_label: '<file>',
|
|
263
|
+
missing_usage_lines: ['patram refs <file>'],
|
|
264
|
+
option_column_width: 19,
|
|
265
|
+
options: [
|
|
266
|
+
{
|
|
267
|
+
description: 'Filter incoming source nodes with a where clause',
|
|
268
|
+
label: '--where <clause>',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
description: 'Print plain text output',
|
|
272
|
+
label: '--plain',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
description: 'Print JSON output',
|
|
276
|
+
label: '--json',
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
related: ['show', 'query'],
|
|
280
|
+
root_summary: 'Inspect incoming graph references for one file',
|
|
281
|
+
summary:
|
|
282
|
+
'Inspect incoming graph references for one file, grouped by relation.',
|
|
283
|
+
usage_lines: ['patram refs <file> [options]'],
|
|
284
|
+
},
|
|
246
285
|
show: {
|
|
247
286
|
allowed_option_names: new Set(),
|
|
248
287
|
examples: ['patram show docs/patram.md', 'patram show lib/patram-cli.js'],
|
package/lib/command-output.js
CHANGED
|
@@ -78,6 +78,7 @@ export function shouldPageCommandOutput(parsed_command, output_stream) {
|
|
|
78
78
|
output_stream.isTTY === true &&
|
|
79
79
|
(parsed_command.command_name === 'fields' ||
|
|
80
80
|
parsed_command.command_name === 'query' ||
|
|
81
|
+
parsed_command.command_name === 'refs' ||
|
|
81
82
|
parsed_command.command_name === 'show')
|
|
82
83
|
);
|
|
83
84
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* class_name: string,
|
|
4
|
+
* id: string,
|
|
5
|
+
* key: string,
|
|
6
|
+
* path: string,
|
|
7
|
+
* }} DocumentNodeReference
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Collect semantic entity keys defined by canonical documents.
|
|
11
|
+
*
|
|
12
|
+
* @param {Record<string, MappingDefinition>} mappings
|
|
13
|
+
* @param {PatramClaim[]} claims
|
|
14
|
+
* @returns {Map<string, string>}
|
|
15
|
+
*/
|
|
16
|
+
export function collectDocumentEntityKeys(mappings: Record<string, MappingDefinition>, claims: PatramClaim[]): Map<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* Collect canonical graph identities for document-backed source paths.
|
|
19
|
+
*
|
|
20
|
+
* @param {Record<string, MappingDefinition>} mappings
|
|
21
|
+
* @param {PatramClaim[]} claims
|
|
22
|
+
* @returns {Map<string, DocumentNodeReference>}
|
|
23
|
+
*/
|
|
24
|
+
export function collectDocumentNodeReferences(mappings: Record<string, MappingDefinition>, claims: PatramClaim[]): Map<string, DocumentNodeReference>;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the canonical node id for a source document path.
|
|
27
|
+
*
|
|
28
|
+
* @param {Record<string, string> | undefined} document_node_ids
|
|
29
|
+
* @param {string} document_path
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function resolveDocumentNodeId(document_node_ids: Record<string, string> | undefined, document_path: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Normalize one repo-relative source path.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} source_path
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export function normalizeRepoRelativePath(source_path: string): string;
|
|
40
|
+
export type DocumentNodeReference = {
|
|
41
|
+
class_name: string;
|
|
42
|
+
id: string;
|
|
43
|
+
key: string;
|
|
44
|
+
path: string;
|
|
45
|
+
};
|
|
46
|
+
import type { MappingDefinition } from './patram-config.types.ts';
|
|
47
|
+
import type { PatramClaim } from './parse-claims.types.ts';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { BuildGraphResult, GraphNode } from './build-graph.types.ts';
|
|
3
|
+
* @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
normalizeRepoRelativePath,
|
|
8
|
+
resolveDocumentNodeId,
|
|
9
|
+
} from './document-node-identity.js';
|
|
10
|
+
import { queryGraph } from './query-graph.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inspect incoming graph edges for one canonical target file.
|
|
14
|
+
*
|
|
15
|
+
* @param {BuildGraphResult} graph
|
|
16
|
+
* @param {string} target_file_path
|
|
17
|
+
* @param {PatramRepoConfig | undefined} repo_config
|
|
18
|
+
* @param {string | undefined} where_clause
|
|
19
|
+
* @returns {{
|
|
20
|
+
* diagnostics: PatramDiagnostic[],
|
|
21
|
+
* incoming: Record<string, GraphNode[]>,
|
|
22
|
+
* node: GraphNode,
|
|
23
|
+
* }}
|
|
24
|
+
*/
|
|
25
|
+
export function inspectReverseReferences(
|
|
26
|
+
graph,
|
|
27
|
+
target_file_path,
|
|
28
|
+
repo_config,
|
|
29
|
+
where_clause,
|
|
30
|
+
) {
|
|
31
|
+
const normalized_target_path = normalizeRepoRelativePath(target_file_path);
|
|
32
|
+
const target_node_id = resolveDocumentNodeId(
|
|
33
|
+
graph.document_node_ids,
|
|
34
|
+
normalized_target_path,
|
|
35
|
+
);
|
|
36
|
+
const target_node =
|
|
37
|
+
graph.nodes[target_node_id] ??
|
|
38
|
+
createFallbackTargetNode(target_node_id, normalized_target_path);
|
|
39
|
+
const allowed_source_node_ids = where_clause
|
|
40
|
+
? resolveAllowedSourceNodeIds(graph, where_clause, repo_config)
|
|
41
|
+
: null;
|
|
42
|
+
|
|
43
|
+
if (allowed_source_node_ids && 'diagnostics' in allowed_source_node_ids) {
|
|
44
|
+
return {
|
|
45
|
+
diagnostics: allowed_source_node_ids.diagnostics,
|
|
46
|
+
incoming: {},
|
|
47
|
+
node: target_node,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
diagnostics: [],
|
|
53
|
+
incoming: collectIncomingGroups(
|
|
54
|
+
graph,
|
|
55
|
+
target_node.id,
|
|
56
|
+
allowed_source_node_ids,
|
|
57
|
+
),
|
|
58
|
+
node: target_node,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {BuildGraphResult} graph
|
|
64
|
+
* @param {string} target_node_id
|
|
65
|
+
* @param {Set<string> | null} allowed_source_node_ids
|
|
66
|
+
* @returns {Record<string, GraphNode[]>}
|
|
67
|
+
*/
|
|
68
|
+
function collectIncomingGroups(graph, target_node_id, allowed_source_node_ids) {
|
|
69
|
+
/** @type {Map<string, Map<string, GraphNode>>} */
|
|
70
|
+
const grouped_incoming = new Map();
|
|
71
|
+
|
|
72
|
+
for (const graph_edge of graph.edges) {
|
|
73
|
+
if (graph_edge.to !== target_node_id) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
allowed_source_node_ids &&
|
|
79
|
+
!allowed_source_node_ids.has(graph_edge.from)
|
|
80
|
+
) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const source_node = graph.nodes[graph_edge.from];
|
|
85
|
+
|
|
86
|
+
if (!source_node) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let relation_sources = grouped_incoming.get(graph_edge.relation);
|
|
91
|
+
|
|
92
|
+
if (!relation_sources) {
|
|
93
|
+
relation_sources = new Map();
|
|
94
|
+
grouped_incoming.set(graph_edge.relation, relation_sources);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
relation_sources.set(source_node.id, source_node);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return formatIncomingGroups(grouped_incoming);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {BuildGraphResult} graph
|
|
105
|
+
* @param {string} where_clause
|
|
106
|
+
* @param {PatramRepoConfig | undefined} repo_config
|
|
107
|
+
* @returns {{ diagnostics: PatramDiagnostic[] } | Set<string>}
|
|
108
|
+
*/
|
|
109
|
+
function resolveAllowedSourceNodeIds(graph, where_clause, repo_config) {
|
|
110
|
+
const query_result = queryGraph(
|
|
111
|
+
graph,
|
|
112
|
+
where_clause,
|
|
113
|
+
repo_config ?? {
|
|
114
|
+
fields: {},
|
|
115
|
+
include: [],
|
|
116
|
+
queries: {},
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (query_result.diagnostics.length > 0) {
|
|
121
|
+
return {
|
|
122
|
+
diagnostics: query_result.diagnostics,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return new Set(query_result.nodes.map((graph_node) => graph_node.id));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {Map<string, Map<string, GraphNode>>} grouped_incoming
|
|
131
|
+
* @returns {Record<string, GraphNode[]>}
|
|
132
|
+
*/
|
|
133
|
+
function formatIncomingGroups(grouped_incoming) {
|
|
134
|
+
/** @type {Record<string, GraphNode[]>} */
|
|
135
|
+
const incoming = {};
|
|
136
|
+
|
|
137
|
+
for (const relation_name of [...grouped_incoming.keys()].sort(
|
|
138
|
+
compareStrings,
|
|
139
|
+
)) {
|
|
140
|
+
const relation_sources = /** @type {Map<string, GraphNode>} */ (
|
|
141
|
+
grouped_incoming.get(relation_name)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
incoming[relation_name] = [...relation_sources.values()].sort(
|
|
145
|
+
compareGraphNodes,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return incoming;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {string} node_id
|
|
154
|
+
* @param {string} target_path
|
|
155
|
+
* @returns {GraphNode}
|
|
156
|
+
*/
|
|
157
|
+
function createFallbackTargetNode(node_id, target_path) {
|
|
158
|
+
return {
|
|
159
|
+
$class: 'document',
|
|
160
|
+
$id: node_id,
|
|
161
|
+
$path: target_path,
|
|
162
|
+
id: node_id,
|
|
163
|
+
path: target_path,
|
|
164
|
+
title: target_path,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {GraphNode} left_node
|
|
170
|
+
* @param {GraphNode} right_node
|
|
171
|
+
* @returns {number}
|
|
172
|
+
*/
|
|
173
|
+
function compareGraphNodes(left_node, right_node) {
|
|
174
|
+
return compareStrings(left_node.id, right_node.id);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {string} left_value
|
|
179
|
+
* @param {string} right_value
|
|
180
|
+
* @returns {number}
|
|
181
|
+
*/
|
|
182
|
+
function compareStrings(left_value, right_value) {
|
|
183
|
+
return left_value.localeCompare(right_value, 'en');
|
|
184
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { OutputNodeItem } from './output-view.types.ts';
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { formatOutputNodeMetadataRows } from './format-output-metadata.js';
|
|
6
|
+
import { formatNodeHeader } from './format-node-header.js';
|
|
7
|
+
import { formatOutputItemBlock } from './format-output-item-block.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Layout grouped incoming references as plain text lines.
|
|
11
|
+
*
|
|
12
|
+
* @param {Record<string, OutputNodeItem[]>} incoming
|
|
13
|
+
* @param {{
|
|
14
|
+
* format_node_header?: (output_item: OutputNodeItem) => string,
|
|
15
|
+
* format_relation_header?: (relation_name: string, relation_count: number) => string,
|
|
16
|
+
* }} layout_options
|
|
17
|
+
* @returns {string[]}
|
|
18
|
+
*/
|
|
19
|
+
export function layoutIncomingReferenceLines(incoming, layout_options = {}) {
|
|
20
|
+
const format_node_header =
|
|
21
|
+
layout_options.format_node_header ?? defaultNodeHeaderFormatter;
|
|
22
|
+
const format_relation_header =
|
|
23
|
+
layout_options.format_relation_header ?? defaultRelationHeaderFormatter;
|
|
24
|
+
/** @type {string[]} */
|
|
25
|
+
const output_lines = [];
|
|
26
|
+
|
|
27
|
+
for (const relation_name of Object.keys(incoming)) {
|
|
28
|
+
if (output_lines.length > 0) {
|
|
29
|
+
output_lines.push('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const relation_sources = incoming[relation_name];
|
|
33
|
+
|
|
34
|
+
output_lines.push(
|
|
35
|
+
format_relation_header(relation_name, relation_sources.length),
|
|
36
|
+
);
|
|
37
|
+
output_lines.push(
|
|
38
|
+
...layoutIncomingRelationSources(relation_sources, format_node_header),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return output_lines;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {OutputNodeItem} output_item
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function defaultNodeHeaderFormatter(output_item) {
|
|
50
|
+
return formatNodeHeader(output_item);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} relation_name
|
|
55
|
+
* @param {number} relation_count
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function defaultRelationHeaderFormatter(relation_name, relation_count) {
|
|
59
|
+
return `${relation_name} (${relation_count})`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {OutputNodeItem[]} relation_sources
|
|
64
|
+
* @param {(output_item: OutputNodeItem) => string} format_node_header
|
|
65
|
+
* @returns {string[]}
|
|
66
|
+
*/
|
|
67
|
+
function layoutIncomingRelationSources(relation_sources, format_node_header) {
|
|
68
|
+
/** @type {string[]} */
|
|
69
|
+
const output_lines = [];
|
|
70
|
+
|
|
71
|
+
for (const [item_index, output_item] of relation_sources.entries()) {
|
|
72
|
+
if (item_index > 0) {
|
|
73
|
+
output_lines.push('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
output_lines.push(
|
|
77
|
+
...indentIncomingNodeBlock(
|
|
78
|
+
formatIncomingNodeBlock(output_item, format_node_header),
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return output_lines;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {OutputNodeItem} output_item
|
|
88
|
+
* @param {(output_item: OutputNodeItem) => string} format_node_header
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function formatIncomingNodeBlock(output_item, format_node_header) {
|
|
92
|
+
return formatOutputItemBlock({
|
|
93
|
+
header: format_node_header(output_item),
|
|
94
|
+
metadata_rows: formatOutputNodeMetadataRows(output_item),
|
|
95
|
+
title: output_item.title,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {string} block
|
|
101
|
+
* @returns {string[]}
|
|
102
|
+
*/
|
|
103
|
+
function indentIncomingNodeBlock(block) {
|
|
104
|
+
return block.split('\n').map((line) => (line.length > 0 ? ` ${line}` : ''));
|
|
105
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout compact incoming-summary lines for `show`.
|
|
3
|
+
*
|
|
4
|
+
* @param {Record<string, number>} incoming_summary
|
|
5
|
+
* @returns {string[]}
|
|
6
|
+
*/
|
|
7
|
+
export function layoutIncomingSummaryLines(incoming_summary) {
|
|
8
|
+
const relation_names = Object.keys(incoming_summary);
|
|
9
|
+
const output_lines = ['incoming refs:'];
|
|
10
|
+
|
|
11
|
+
if (relation_names.length === 0) {
|
|
12
|
+
output_lines.push(' none');
|
|
13
|
+
return output_lines;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const relation_name of relation_names) {
|
|
17
|
+
output_lines.push(` ${relation_name}: ${incoming_summary[relation_name]}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return output_lines;
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source file scanning.
|
|
3
|
+
*
|
|
4
|
+
* Expands include globs into stable repo-relative file lists for indexing and
|
|
5
|
+
* broken-link validation.
|
|
6
|
+
*
|
|
7
|
+
* Kind: scan
|
|
8
|
+
* Status: active
|
|
9
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
10
|
+
* Decided by: ../docs/decisions/source-scan.md
|
|
11
|
+
* @patram
|
|
12
|
+
* @see {@link ./load-project-graph.js}
|
|
13
|
+
* @see {@link ../docs/decisions/source-scan.md}
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* List source files matched by Patram include globs.
|
|
17
|
+
*
|
|
18
|
+
* @param {string[]} include_patterns
|
|
19
|
+
* @param {string} [project_directory]
|
|
20
|
+
* @returns {Promise<string[]>}
|
|
21
|
+
*/
|
|
22
|
+
export function listSourceFiles(include_patterns: string[], project_directory?: string): Promise<string[]>;
|
|
23
|
+
/**
|
|
24
|
+
* List repo files available for broken-link validation.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} [project_directory]
|
|
27
|
+
* @returns {Promise<string[]>}
|
|
28
|
+
*/
|
|
29
|
+
export function listRepoFiles(project_directory?: string): Promise<string[]>;
|