patram 0.10.0 → 0.12.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/bin/patram.js +4 -4
- package/lib/cli/arguments.types.d.ts +1 -0
- package/lib/cli/commands/check.js +27 -15
- package/lib/cli/commands/fields.js +0 -4
- package/lib/cli/commands/queries.js +179 -1
- package/lib/cli/commands/query.js +1 -8
- package/lib/cli/commands/refs.js +3 -10
- package/lib/cli/commands/show.js +1 -8
- package/lib/cli/help-metadata.js +106 -111
- package/lib/cli/main.js +10 -10
- package/lib/cli/parse-arguments-helpers.js +416 -66
- package/lib/cli/parse-arguments.js +4 -4
- package/lib/cli/render-help.js +10 -4
- package/lib/config/defaults.js +33 -25
- package/lib/config/load-patram-config.d.ts +19 -33
- package/lib/config/load-patram-config.js +18 -121
- package/lib/config/load-patram-config.types.d.ts +3 -40
- package/lib/config/manage-stored-queries-helpers.d.ts +69 -0
- package/lib/config/manage-stored-queries-helpers.js +320 -0
- package/lib/config/manage-stored-queries-jsonc.d.ts +31 -0
- package/lib/config/manage-stored-queries-jsonc.js +95 -0
- package/lib/config/manage-stored-queries.d.ts +77 -0
- package/lib/config/manage-stored-queries.js +300 -0
- package/lib/config/patram-config.d.ts +34 -34
- package/lib/config/patram-config.js +3 -3
- package/lib/config/patram-config.types.d.ts +5 -11
- package/lib/config/resolve-patram-graph-config.d.ts +5 -1
- package/lib/config/resolve-patram-graph-config.js +3 -119
- package/lib/config/schema.d.ts +158 -269
- package/lib/config/schema.js +72 -210
- package/lib/config/validate-patram-config-value.d.ts +13 -0
- package/lib/config/validate-patram-config-value.js +94 -0
- package/lib/config/validation.d.ts +2 -12
- package/lib/config/validation.js +125 -483
- package/lib/find-close-match.d.ts +4 -1
- package/lib/graph/build-graph-identity.d.ts +1 -32
- package/lib/graph/build-graph-identity.js +5 -269
- package/lib/graph/build-graph.d.ts +13 -4
- package/lib/graph/build-graph.js +347 -488
- package/lib/graph/build-graph.types.d.ts +8 -9
- package/lib/graph/check-directive-metadata-helpers.d.ts +30 -0
- package/lib/graph/check-directive-metadata-helpers.js +126 -0
- package/lib/graph/check-directive-metadata.d.ts +8 -9
- package/lib/graph/check-directive-metadata.js +70 -561
- package/lib/graph/check-directive-path-target.d.ts +6 -13
- package/lib/graph/check-directive-path-target.js +26 -57
- package/lib/graph/check-directive-value.d.ts +1 -5
- package/lib/graph/check-directive-value.js +40 -180
- package/lib/graph/check-graph.d.ts +5 -5
- package/lib/graph/check-graph.js +8 -6
- package/lib/graph/document-node-identity.d.ts +23 -7
- package/lib/graph/document-node-identity.js +417 -160
- package/lib/graph/graph-node.d.ts +42 -0
- package/lib/graph/graph-node.js +83 -0
- package/lib/graph/inspect-reverse-references.js +16 -11
- package/lib/graph/load-project-graph.d.ts +7 -7
- package/lib/graph/load-project-graph.js +7 -7
- package/lib/graph/parse-where-clause.types.d.ts +3 -2
- package/lib/graph/query/cypher-reader.d.ts +59 -0
- package/lib/graph/query/cypher-reader.js +151 -0
- package/lib/graph/query/cypher-support.d.ts +79 -0
- package/lib/graph/query/cypher-support.js +213 -0
- package/lib/graph/query/cypher-tokenize.d.ts +13 -0
- package/lib/graph/query/cypher-tokenize.js +225 -0
- package/lib/graph/query/cypher.types.d.ts +43 -0
- package/lib/graph/query/execute.d.ts +7 -7
- package/lib/graph/query/execute.js +71 -33
- package/lib/graph/query/inspect.js +58 -24
- package/lib/graph/query/parse-cypher-patterns.d.ts +27 -0
- package/lib/graph/query/parse-cypher-patterns.js +382 -0
- package/lib/graph/query/parse-cypher.d.ts +7 -0
- package/lib/graph/query/parse-cypher.js +580 -0
- package/lib/graph/query/parse-query.d.ts +13 -0
- package/lib/graph/query/parse-query.js +97 -0
- package/lib/graph/query/resolve.d.ts +6 -0
- package/lib/graph/query/resolve.js +81 -24
- package/lib/output/command-output.js +12 -5
- package/lib/output/compact-layout.js +221 -0
- package/lib/output/format-output-item-block.js +31 -1
- package/lib/output/format-output-metadata.js +16 -29
- package/lib/output/format-stored-query-block.js +95 -0
- package/lib/output/layout-incoming-references.js +101 -19
- package/lib/output/layout-stored-queries.js +23 -330
- package/lib/output/list-queries.js +1 -1
- package/lib/output/render-field-discovery.js +11 -2
- package/lib/output/render-output-view.js +9 -5
- package/lib/output/renderers/json.js +5 -26
- package/lib/output/renderers/plain.js +155 -35
- package/lib/output/renderers/rich.js +250 -36
- package/lib/output/resolve-check-target.js +120 -11
- package/lib/output/resolved-link-layout.js +43 -0
- package/lib/output/rich-source/render.js +193 -35
- package/lib/output/show-document.js +25 -18
- package/lib/output/view-model/index.js +124 -103
- package/lib/parse/jsdoc/parse-jsdoc-blocks.js +1 -1
- package/lib/parse/jsdoc/parse-jsdoc-claims.js +12 -6
- package/lib/parse/markdown/parse-markdown-claims.js +99 -62
- package/lib/parse/markdown/parse-markdown-directives.d.ts +10 -6
- package/lib/parse/markdown/parse-markdown-directives.js +104 -18
- package/lib/parse/markdown/parse-markdown-prose.d.ts +27 -0
- package/lib/parse/markdown/parse-markdown-prose.js +243 -0
- package/lib/parse/parse-claims.d.ts +2 -6
- package/lib/parse/parse-claims.js +11 -53
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.d.ts +4 -4
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.js +4 -4
- package/lib/parse/yaml/parse-yaml-claims.js +4 -4
- package/lib/patram.d.ts +9 -3
- package/lib/patram.js +1 -1
- package/lib/scan/discover-fields.js +194 -55
- package/lib/scan/list-source-files.d.ts +4 -4
- package/lib/scan/list-source-files.js +4 -4
- package/package.json +2 -1
- package/lib/directive-validation-test-helpers.js +0 -87
- package/lib/graph/query/parse.d.ts +0 -75
- package/lib/graph/query/parse.js +0 -1064
- package/lib/output/derived-summary.js +0 -280
- package/lib/output/format-derived-summary-row.js +0 -9
|
@@ -11,16 +11,12 @@ export function parseSourceFile(parse_input: ParseClaimsInput, parse_options?: {
|
|
|
11
11
|
/**
|
|
12
12
|
* Build parser options from repo config.
|
|
13
13
|
*
|
|
14
|
-
* @param {{ fields?: Record<string, {
|
|
14
|
+
* @param {{ fields?: Record<string, { many?: boolean }> } | undefined} repo_config
|
|
15
15
|
* @returns {{ multi_value_directive_names: Set<string> }}
|
|
16
16
|
*/
|
|
17
17
|
export function createParseOptions(repo_config: {
|
|
18
18
|
fields?: Record<string, {
|
|
19
|
-
|
|
20
|
-
}>;
|
|
21
|
-
mappings?: Record<string, {
|
|
22
|
-
emit?: unknown;
|
|
23
|
-
node?: unknown;
|
|
19
|
+
many?: boolean;
|
|
24
20
|
}>;
|
|
25
21
|
} | undefined): {
|
|
26
22
|
multi_value_directive_names: Set<string>;
|
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
* Routes each source file to markdown or JSDoc claim parsing and keeps claim
|
|
21
21
|
* extraction on one entrypoint.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
23
|
+
* kind: parse
|
|
24
|
+
* status: active
|
|
25
|
+
* uses_term: ../../docs/reference/terms/claim.md
|
|
26
|
+
* uses_term: ../../docs/reference/terms/document.md
|
|
27
|
+
* tracked_in: ../../docs/plans/v0/source-anchor-dogfooding.md
|
|
28
|
+
* decided_by: ../../docs/decisions/jsdoc-metadata-directive-syntax.md
|
|
29
|
+
* implements: ../../docs/tasks/v0/parse-claims.md
|
|
30
30
|
* @patram
|
|
31
31
|
* @see {@link ./markdown/parse-markdown-claims.js}
|
|
32
32
|
* @see {@link ./jsdoc/parse-jsdoc-claims.js}
|
|
@@ -59,7 +59,7 @@ export function parseSourceFile(parse_input, parse_options) {
|
|
|
59
59
|
/**
|
|
60
60
|
* Build parser options from repo config.
|
|
61
61
|
*
|
|
62
|
-
* @param {{ fields?: Record<string, {
|
|
62
|
+
* @param {{ fields?: Record<string, { many?: boolean }> } | undefined} repo_config
|
|
63
63
|
* @returns {{ multi_value_directive_names: Set<string> }}
|
|
64
64
|
*/
|
|
65
65
|
export function createParseOptions(repo_config) {
|
|
@@ -69,7 +69,7 @@ export function createParseOptions(repo_config) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* @param {{ fields?: Record<string, {
|
|
72
|
+
* @param {{ fields?: Record<string, { many?: boolean }> } | undefined} repo_config
|
|
73
73
|
* @returns {Set<string>}
|
|
74
74
|
*/
|
|
75
75
|
function collectMultiValueDirectiveNames(repo_config) {
|
|
@@ -77,60 +77,18 @@ function collectMultiValueDirectiveNames(repo_config) {
|
|
|
77
77
|
const multi_value_directive_names = new Set();
|
|
78
78
|
|
|
79
79
|
collectMultipleFieldNames(repo_config?.fields, multi_value_directive_names);
|
|
80
|
-
collectEmitOnlyDirectiveNames(
|
|
81
|
-
repo_config?.mappings,
|
|
82
|
-
multi_value_directive_names,
|
|
83
|
-
);
|
|
84
80
|
|
|
85
81
|
return multi_value_directive_names;
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
/**
|
|
89
|
-
* @param {Record<string, {
|
|
85
|
+
* @param {Record<string, { many?: boolean }> | undefined} fields
|
|
90
86
|
* @param {Set<string>} multi_value_directive_names
|
|
91
87
|
*/
|
|
92
88
|
function collectMultipleFieldNames(fields, multi_value_directive_names) {
|
|
93
89
|
for (const [field_name, field_definition] of Object.entries(fields ?? {})) {
|
|
94
|
-
if (field_definition.
|
|
90
|
+
if (field_definition.many === true) {
|
|
95
91
|
multi_value_directive_names.add(field_name);
|
|
96
92
|
}
|
|
97
93
|
}
|
|
98
94
|
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @param {Record<string, { emit?: unknown, node?: unknown }> | undefined} mappings
|
|
102
|
-
* @param {Set<string>} multi_value_directive_names
|
|
103
|
-
*/
|
|
104
|
-
function collectEmitOnlyDirectiveNames(mappings, multi_value_directive_names) {
|
|
105
|
-
for (const [mapping_name, mapping_definition] of Object.entries(
|
|
106
|
-
mappings ?? {},
|
|
107
|
-
)) {
|
|
108
|
-
const directive_name = resolveEmitOnlyDirectiveName(
|
|
109
|
-
mapping_name,
|
|
110
|
-
mapping_definition,
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
if (directive_name) {
|
|
114
|
-
multi_value_directive_names.add(directive_name);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* @param {string} mapping_name
|
|
121
|
-
* @param {{ emit?: unknown, node?: unknown }} mapping_definition
|
|
122
|
-
* @returns {string | null}
|
|
123
|
-
*/
|
|
124
|
-
function resolveEmitOnlyDirectiveName(mapping_name, mapping_definition) {
|
|
125
|
-
const directive_match = mapping_name.match(/^[^.]+\.directive\.(.+)$/du);
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
!directive_match ||
|
|
129
|
-
mapping_definition.emit === undefined ||
|
|
130
|
-
mapping_definition.node !== undefined
|
|
131
|
-
) {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return directive_match[1];
|
|
136
|
-
}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Loads or extracts one markdown file worth of tagged fenced blocks and
|
|
5
5
|
* provides exact-match selection helpers.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* kind: parse
|
|
8
|
+
* status: active
|
|
9
|
+
* tracked_in: ../../../docs/plans/v0/tagged-fenced-block-extraction.md
|
|
10
|
+
* decided_by: ../../../docs/decisions/tagged-fenced-block-extraction.md
|
|
11
11
|
* @patram
|
|
12
12
|
* @see {@link ../../../docs/decisions/tagged-fenced-block-extraction.md}
|
|
13
13
|
*/
|
|
@@ -18,10 +18,10 @@ import { extractTaggedFencedBlocksFromSource } from './tagged-fenced-block-parse
|
|
|
18
18
|
* Loads or extracts one markdown file worth of tagged fenced blocks and
|
|
19
19
|
* provides exact-match selection helpers.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* kind: parse
|
|
22
|
+
* status: active
|
|
23
|
+
* tracked_in: ../../../docs/plans/v0/tagged-fenced-block-extraction.md
|
|
24
|
+
* decided_by: ../../../docs/decisions/tagged-fenced-block-extraction.md
|
|
25
25
|
* @patram
|
|
26
26
|
* @see {@link ../../../docs/decisions/tagged-fenced-block-extraction.md}
|
|
27
27
|
*/
|
|
@@ -16,10 +16,10 @@ import { YAML_SOURCE_FILE_EXTENSIONS } from '../../config/source-file-defaults.j
|
|
|
16
16
|
* Parses standalone YAML metadata files and front matter with one projection
|
|
17
17
|
* model for top-level scalar directives.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* kind: parse
|
|
20
|
+
* status: active
|
|
21
|
+
* tracked_in: ../../../docs/plans/v0/yaml-source-and-front-matter.md
|
|
22
|
+
* decided_by: ../../../docs/decisions/yaml-source-and-front-matter.md
|
|
23
23
|
* @patram
|
|
24
24
|
* @see {@link ../parse-claims.js}
|
|
25
25
|
* @see {@link ../markdown/parse-markdown-directives.js}
|
package/lib/patram.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export {
|
|
|
5
5
|
selectTaggedBlocks,
|
|
6
6
|
} from './parse/tagged-fenced/tagged-fenced-blocks.js';
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export { parseQueryExpression } from './graph/query/parse-query.js';
|
|
9
9
|
export { getQuerySemanticDiagnostics } from './graph/query/inspect.js';
|
|
10
10
|
export { loadProjectGraph } from './graph/load-project-graph.js';
|
|
11
11
|
export { queryGraph } from './graph/query/execute.js';
|
|
@@ -48,8 +48,8 @@ export type PatramParsedTerm =
|
|
|
48
48
|
import('./graph/parse-where-clause.types.d.ts').ParsedTerm;
|
|
49
49
|
export type PatramParsedExpression =
|
|
50
50
|
import('./graph/parse-where-clause.types.d.ts').ParsedExpression;
|
|
51
|
-
export type
|
|
52
|
-
import('./graph/parse-where-clause.types.d.ts').
|
|
51
|
+
export type PatramParseQueryResult =
|
|
52
|
+
import('./graph/parse-where-clause.types.d.ts').ParseQueryResult;
|
|
53
53
|
export type PatramQuerySource =
|
|
54
54
|
| {
|
|
55
55
|
kind: 'ad_hoc';
|
|
@@ -59,6 +59,12 @@ export type PatramQuerySource =
|
|
|
59
59
|
name: string;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
export interface PatramQueryGraphOptions {
|
|
63
|
+
bindings?: Record<string, string>;
|
|
64
|
+
limit?: number;
|
|
65
|
+
offset?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
export interface PatramProjectGraphResult {
|
|
63
69
|
claims: import('./parse/parse-claims.types.d.ts').PatramClaim[];
|
|
64
70
|
config: import('./config/load-patram-config.types.d.ts').PatramRepoConfig;
|
package/lib/patram.js
CHANGED
|
@@ -5,7 +5,7 @@ export {
|
|
|
5
5
|
selectTaggedBlocks,
|
|
6
6
|
} from './parse/tagged-fenced/tagged-fenced-blocks.js';
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export { parseQueryExpression } from './graph/query/parse-query.js';
|
|
9
9
|
export { getQuerySemanticDiagnostics } from './graph/query/inspect.js';
|
|
10
10
|
export { loadProjectGraph } from './graph/load-project-graph.js';
|
|
11
11
|
export { queryGraph } from './graph/query/execute.js';
|
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @import { ClaimOrigin, PatramClaim } from '../parse/parse-claims.types.ts';
|
|
4
4
|
* @import {
|
|
5
|
-
* DiscoveredFieldMultiplicity,
|
|
6
5
|
* DiscoveredFieldTypeName,
|
|
7
|
-
* FieldDiscoveryClassUsage,
|
|
8
6
|
* FieldDiscoveryEvidenceReference,
|
|
9
7
|
* FieldDiscoveryMultiplicitySuggestion,
|
|
8
|
+
* FieldDiscoveryOnUsage,
|
|
10
9
|
* FieldDiscoveryResult,
|
|
11
10
|
* FieldDiscoverySuggestion,
|
|
11
|
+
* FieldDiscoveryTargetSuggestion,
|
|
12
12
|
* FieldDiscoveryTypeSuggestion,
|
|
13
13
|
* } from './discover-fields.types.ts';
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { readFile } from 'node:fs/promises';
|
|
17
17
|
import process from 'node:process';
|
|
18
|
-
import { resolve } from 'node:path';
|
|
18
|
+
import { posix, resolve } from 'node:path';
|
|
19
19
|
|
|
20
20
|
import { DEFAULT_INCLUDE_PATTERNS } from '../config/source-file-defaults.js';
|
|
21
21
|
import { listSourceFiles } from './list-source-files.js';
|
|
@@ -26,21 +26,8 @@ import {
|
|
|
26
26
|
} from '../parse/markdown/parse-markdown-directives.js';
|
|
27
27
|
import { isPathLikeTarget } from '../parse/claim-helpers.js';
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* Field discovery from source claims.
|
|
31
|
-
*
|
|
32
|
-
* Scans the repository source files directly, infers likely metadata fields,
|
|
33
|
-
* and reports advisory suggestions without requiring repo config to load.
|
|
34
|
-
*
|
|
35
|
-
* Kind: discovery
|
|
36
|
-
* Status: active
|
|
37
|
-
* Tracked in: ../../docs/plans/v1/field-model-redesign.md
|
|
38
|
-
* Decided by: ../../docs/decisions/field-discovery-workflow.md
|
|
39
|
-
* @patram
|
|
40
|
-
* @see {@link ../output/render-field-discovery.js}
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
29
|
const TYPE_NAME_ORDER = /** @type {const} */ ([
|
|
30
|
+
'ref',
|
|
44
31
|
'integer',
|
|
45
32
|
'date_time',
|
|
46
33
|
'date',
|
|
@@ -57,7 +44,11 @@ const ENUM_PATTERN = /^[a-z0-9_][a-z0-9_-]*$/du;
|
|
|
57
44
|
const PATH_PATTERN = /^[a-z0-9_.-]+\.[a-z0-9]+$/du;
|
|
58
45
|
|
|
59
46
|
/**
|
|
60
|
-
* @typedef {
|
|
47
|
+
* @typedef {FieldDiscoveryOnUsage & { confidence: number }} InferredFieldOnUsage
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {FieldDiscoveryTargetSuggestion & { confidence: number }} InferredFieldTargetSuggestion
|
|
61
52
|
*/
|
|
62
53
|
|
|
63
54
|
/**
|
|
@@ -98,20 +89,10 @@ export async function discoverFields(
|
|
|
98
89
|
);
|
|
99
90
|
/** @type {FieldObservation[]} */
|
|
100
91
|
const field_observations = parse_results.flatMap((parse_result) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
claim.type === 'directive' &&
|
|
107
|
-
claim.name === 'kind' &&
|
|
108
|
-
typeof claim.value === 'string' &&
|
|
109
|
-
claim.value.length > 0
|
|
110
|
-
) {
|
|
111
|
-
document_classes.add(claim.value);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
92
|
+
const document_types = inferDocumentTypes(
|
|
93
|
+
parse_result.path,
|
|
94
|
+
parse_result.claims,
|
|
95
|
+
);
|
|
115
96
|
const allowed_markdown_lines = collectAllowedMarkdownDirectiveLines(
|
|
116
97
|
parse_result.path,
|
|
117
98
|
parse_result.source_text,
|
|
@@ -132,11 +113,11 @@ export async function discoverFields(
|
|
|
132
113
|
|
|
133
114
|
return [
|
|
134
115
|
{
|
|
135
|
-
class_names: new Set(document_classes),
|
|
136
116
|
document_id: claim.document_id,
|
|
137
117
|
name: claim.name,
|
|
138
118
|
normalized_value: normalizeDiscoveryValue(claim.value),
|
|
139
119
|
origin: claim.origin,
|
|
120
|
+
type_names: new Set(document_types),
|
|
140
121
|
value: claim.value,
|
|
141
122
|
},
|
|
142
123
|
];
|
|
@@ -188,20 +169,28 @@ export async function discoverFields(
|
|
|
188
169
|
* @returns {FieldDiscoverySuggestion}
|
|
189
170
|
*/
|
|
190
171
|
function buildFieldSuggestion(field_bucket) {
|
|
191
|
-
const
|
|
172
|
+
const target_result = inferFieldTarget(field_bucket.observations);
|
|
173
|
+
const type_result = inferFieldType(field_bucket.observations, target_result);
|
|
192
174
|
const multiplicity_result = inferFieldMultiplicity(field_bucket.observations);
|
|
193
|
-
const
|
|
175
|
+
const on_result = inferFieldOn(field_bucket.observations);
|
|
194
176
|
const evidence_references = buildEvidenceReferences(
|
|
195
177
|
field_bucket.observations,
|
|
196
178
|
);
|
|
197
179
|
const conflicting_evidence = buildEvidenceReferences(
|
|
198
|
-
field_bucket.observations.filter(
|
|
199
|
-
(
|
|
180
|
+
field_bucket.observations.filter((field_observation) => {
|
|
181
|
+
if (type_result.name === 'ref') {
|
|
182
|
+
return (
|
|
183
|
+
scoreFieldValue(field_observation.normalized_value, 'path') === 0
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return (
|
|
200
188
|
scoreFieldValue(
|
|
201
189
|
field_observation.normalized_value,
|
|
202
190
|
type_result.name,
|
|
203
|
-
) === 0
|
|
204
|
-
|
|
191
|
+
) === 0
|
|
192
|
+
);
|
|
193
|
+
}),
|
|
205
194
|
);
|
|
206
195
|
|
|
207
196
|
return {
|
|
@@ -209,16 +198,17 @@ function buildFieldSuggestion(field_bucket) {
|
|
|
209
198
|
Math.round(
|
|
210
199
|
((type_result.confidence +
|
|
211
200
|
multiplicity_result.confidence +
|
|
212
|
-
|
|
201
|
+
on_result.confidence) /
|
|
213
202
|
3) *
|
|
214
203
|
100,
|
|
215
204
|
) / 100,
|
|
216
205
|
conflicting_evidence,
|
|
217
206
|
evidence_references,
|
|
218
|
-
likely_class_usage: {
|
|
219
|
-
classes: class_usage_result.classes,
|
|
220
|
-
},
|
|
221
207
|
likely_multiplicity: multiplicity_result,
|
|
208
|
+
likely_on: {
|
|
209
|
+
types: on_result.types,
|
|
210
|
+
},
|
|
211
|
+
likely_to: type_result.name === 'ref' ? target_result : undefined,
|
|
222
212
|
likely_type: type_result,
|
|
223
213
|
name: field_bucket.name,
|
|
224
214
|
};
|
|
@@ -288,29 +278,29 @@ function inferFieldMultiplicity(observations) {
|
|
|
288
278
|
|
|
289
279
|
/**
|
|
290
280
|
* @param {FieldObservation[]} observations
|
|
291
|
-
* @returns {
|
|
281
|
+
* @returns {InferredFieldOnUsage}
|
|
292
282
|
*/
|
|
293
|
-
function
|
|
283
|
+
function inferFieldOn(observations) {
|
|
294
284
|
/** @type {Map<string, number>} */
|
|
295
|
-
const
|
|
285
|
+
const type_counts = new Map();
|
|
296
286
|
let documented_observation_count = 0;
|
|
297
287
|
|
|
298
288
|
for (const observation of observations) {
|
|
299
|
-
if (observation.
|
|
289
|
+
if (observation.type_names.size === 0) {
|
|
300
290
|
continue;
|
|
301
291
|
}
|
|
302
292
|
|
|
303
293
|
documented_observation_count += 1;
|
|
304
294
|
|
|
305
|
-
for (const
|
|
306
|
-
|
|
295
|
+
for (const type_name of observation.type_names) {
|
|
296
|
+
type_counts.set(type_name, (type_counts.get(type_name) ?? 0) + 1);
|
|
307
297
|
}
|
|
308
298
|
}
|
|
309
299
|
|
|
310
|
-
if (
|
|
300
|
+
if (type_counts.size === 0) {
|
|
311
301
|
return {
|
|
312
302
|
confidence: 0.2,
|
|
313
|
-
|
|
303
|
+
types: ['document'],
|
|
314
304
|
};
|
|
315
305
|
}
|
|
316
306
|
|
|
@@ -319,17 +309,25 @@ function inferFieldClassUsage(observations) {
|
|
|
319
309
|
Math.round(
|
|
320
310
|
(documented_observation_count / Math.max(observations.length, 1)) * 100,
|
|
321
311
|
) / 100,
|
|
322
|
-
|
|
323
|
-
|
|
312
|
+
types: [...type_counts.keys()].sort((left_type, right_type) =>
|
|
313
|
+
left_type.localeCompare(right_type, 'en'),
|
|
324
314
|
),
|
|
325
315
|
};
|
|
326
316
|
}
|
|
327
317
|
|
|
328
318
|
/**
|
|
329
319
|
* @param {FieldObservation[]} observations
|
|
320
|
+
* @param {InferredFieldTargetSuggestion | undefined} target_result
|
|
330
321
|
* @returns {FieldDiscoveryTypeSuggestion}
|
|
331
322
|
*/
|
|
332
|
-
function inferFieldType(observations) {
|
|
323
|
+
function inferFieldType(observations, target_result) {
|
|
324
|
+
if (target_result && scoreFieldType(observations, 'path') >= 0.9) {
|
|
325
|
+
return {
|
|
326
|
+
confidence: target_result.confidence,
|
|
327
|
+
name: 'ref',
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
333
331
|
/** @type {FieldDiscoveryTypeSuggestion[]} */
|
|
334
332
|
const type_candidates = TYPE_NAME_ORDER.map((type_name) => ({
|
|
335
333
|
confidence: scoreFieldType(observations, type_name),
|
|
@@ -350,6 +348,38 @@ function inferFieldType(observations) {
|
|
|
350
348
|
return type_candidates[0];
|
|
351
349
|
}
|
|
352
350
|
|
|
351
|
+
/**
|
|
352
|
+
* @param {FieldObservation[]} observations
|
|
353
|
+
* @returns {InferredFieldTargetSuggestion | undefined}
|
|
354
|
+
*/
|
|
355
|
+
function inferFieldTarget(observations) {
|
|
356
|
+
/** @type {Map<string, number>} */
|
|
357
|
+
const target_counts = new Map();
|
|
358
|
+
let path_observation_count = 0;
|
|
359
|
+
|
|
360
|
+
for (const observation of observations) {
|
|
361
|
+
const target_type = inferTargetTypeFromObservation(observation);
|
|
362
|
+
|
|
363
|
+
if (!target_type) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
path_observation_count += 1;
|
|
368
|
+
target_counts.set(target_type, (target_counts.get(target_type) ?? 0) + 1);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (path_observation_count === 0 || target_counts.size !== 1) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const [type_name, count] = [...target_counts.entries()][0];
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
confidence: Math.round((count / path_observation_count) * 100) / 100,
|
|
379
|
+
type: type_name,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
353
383
|
/**
|
|
354
384
|
* @param {FieldDiscoveryEvidenceReference} left_reference
|
|
355
385
|
* @param {FieldDiscoveryEvidenceReference} right_reference
|
|
@@ -434,16 +464,17 @@ const FIELD_TYPE_SCORERS = {
|
|
|
434
464
|
value.includes(']')
|
|
435
465
|
? 0.8
|
|
436
466
|
: 1,
|
|
467
|
+
ref: () => 0,
|
|
437
468
|
string: () => 0.5,
|
|
438
469
|
};
|
|
439
470
|
|
|
440
471
|
/**
|
|
441
472
|
* @typedef {{
|
|
442
|
-
* class_names: Set<string>,
|
|
443
473
|
* document_id: string,
|
|
444
474
|
* name: string,
|
|
445
475
|
* normalized_value: string,
|
|
446
476
|
* origin: ClaimOrigin,
|
|
477
|
+
* type_names: Set<string>,
|
|
447
478
|
* value: string,
|
|
448
479
|
* }} FieldObservation
|
|
449
480
|
*/
|
|
@@ -551,6 +582,114 @@ function normalizeDiscoveryValue(value) {
|
|
|
551
582
|
return trimmed_value;
|
|
552
583
|
}
|
|
553
584
|
|
|
585
|
+
/**
|
|
586
|
+
* @param {string} source_path
|
|
587
|
+
* @param {PatramClaim[]} claims
|
|
588
|
+
* @returns {string[]}
|
|
589
|
+
*/
|
|
590
|
+
function inferDocumentTypes(source_path, claims) {
|
|
591
|
+
/** @type {Set<string>} */
|
|
592
|
+
const document_types = new Set();
|
|
593
|
+
const path_type = inferPathBackedType(source_path);
|
|
594
|
+
|
|
595
|
+
if (path_type) {
|
|
596
|
+
document_types.add(path_type);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
for (const claim of claims) {
|
|
600
|
+
if (
|
|
601
|
+
claim.type === 'directive' &&
|
|
602
|
+
(claim.name === 'command' || claim.name === 'term') &&
|
|
603
|
+
typeof claim.value === 'string' &&
|
|
604
|
+
claim.value.length > 0
|
|
605
|
+
) {
|
|
606
|
+
document_types.add(claim.name);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return [...document_types].sort((left_type, right_type) =>
|
|
611
|
+
left_type.localeCompare(right_type, 'en'),
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* @param {string} source_path
|
|
617
|
+
* @returns {string | null}
|
|
618
|
+
*/
|
|
619
|
+
function inferPathBackedType(source_path) {
|
|
620
|
+
if (source_path.startsWith('docs/conventions/')) {
|
|
621
|
+
return 'convention';
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (source_path.startsWith('docs/decisions/')) {
|
|
625
|
+
return 'decision';
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (source_path.startsWith('docs/plans/')) {
|
|
629
|
+
return 'plan';
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (source_path.startsWith('docs/research/')) {
|
|
633
|
+
return 'idea';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (source_path.startsWith('docs/roadmap/')) {
|
|
637
|
+
return 'roadmap';
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (source_path.startsWith('docs/tasks/')) {
|
|
641
|
+
return 'task';
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* @param {FieldObservation} observation
|
|
649
|
+
* @returns {string | null}
|
|
650
|
+
*/
|
|
651
|
+
function inferTargetTypeFromObservation(observation) {
|
|
652
|
+
const value = resolveDiscoveryTargetPath(
|
|
653
|
+
observation.origin.path,
|
|
654
|
+
observation.normalized_value,
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
if (
|
|
658
|
+
!value.includes('/') &&
|
|
659
|
+
!PATH_PATTERN.test(value) &&
|
|
660
|
+
!value.startsWith('docs/') &&
|
|
661
|
+
!value.startsWith('lib/') &&
|
|
662
|
+
!value.startsWith('test/')
|
|
663
|
+
) {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (value.startsWith('docs/reference/commands/')) {
|
|
668
|
+
return 'command';
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (value.startsWith('docs/reference/terms/')) {
|
|
672
|
+
return 'term';
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return inferPathBackedType(value) ?? 'document';
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @param {string} source_path
|
|
680
|
+
* @param {string} target_value
|
|
681
|
+
* @returns {string}
|
|
682
|
+
*/
|
|
683
|
+
function resolveDiscoveryTargetPath(source_path, target_value) {
|
|
684
|
+
if (target_value.startsWith('./') || target_value.startsWith('../')) {
|
|
685
|
+
const parent_directory = posix.dirname(source_path);
|
|
686
|
+
|
|
687
|
+
return posix.normalize(posix.join(parent_directory, target_value));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return target_value;
|
|
691
|
+
}
|
|
692
|
+
|
|
554
693
|
/**
|
|
555
694
|
* @param {string} field_name
|
|
556
695
|
* @returns {boolean}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Expands include globs into stable repo-relative file lists for indexing and
|
|
5
5
|
* broken-link validation.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
11
|
* @patram
|
|
12
12
|
* @see {@link ../graph/load-project-graph.js}
|
|
13
13
|
* @see {@link ../../docs/decisions/source-scan.md}
|
|
@@ -7,10 +7,10 @@ import { listMatchingFiles } from './list-repo-files.js';
|
|
|
7
7
|
* Expands include globs into stable repo-relative file lists for indexing and
|
|
8
8
|
* broken-link validation.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* kind: scan
|
|
11
|
+
* status: active
|
|
12
|
+
* tracked_in: ../../docs/plans/v0/source-anchor-dogfooding.md
|
|
13
|
+
* decided_by: ../../docs/decisions/source-scan.md
|
|
14
14
|
* @patram
|
|
15
15
|
* @see {@link ../graph/load-project-graph.js}
|
|
16
16
|
* @see {@link ../../docs/decisions/source-scan.md}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/patram.js",
|
|
6
6
|
"types": "./lib/patram.d.ts",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"ansis": "^4.2.0",
|
|
68
68
|
"beautiful-mermaid": "^1.1.3",
|
|
69
69
|
"globby": "^16.1.1",
|
|
70
|
+
"jsonc-parser": "^3.3.1",
|
|
70
71
|
"md4x": "^0.0.25",
|
|
71
72
|
"shiki": "^4.0.2",
|
|
72
73
|
"string-width": "^8.2.0",
|