patram 0.0.2 → 0.1.1

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 (51) hide show
  1. package/bin/patram.js +25 -147
  2. package/lib/build-graph-identity.js +238 -0
  3. package/lib/build-graph.js +143 -77
  4. package/lib/check-graph.js +23 -7
  5. package/lib/claim-helpers.js +55 -0
  6. package/lib/command-output.js +83 -0
  7. package/lib/layout-stored-queries.js +213 -0
  8. package/lib/list-queries.js +18 -0
  9. package/lib/list-source-files.js +50 -15
  10. package/lib/load-patram-config.js +106 -18
  11. package/lib/load-patram-config.types.ts +9 -0
  12. package/lib/load-project-graph.js +124 -0
  13. package/lib/output-view.types.ts +73 -0
  14. package/lib/parse-claims.js +38 -158
  15. package/lib/parse-claims.types.ts +7 -0
  16. package/lib/parse-cli-arguments-helpers.js +273 -0
  17. package/lib/parse-cli-arguments.js +114 -0
  18. package/lib/parse-cli-arguments.types.ts +24 -0
  19. package/lib/parse-cli-color-options.js +44 -0
  20. package/lib/parse-cli-query-pagination.js +49 -0
  21. package/lib/parse-jsdoc-blocks.js +184 -0
  22. package/lib/parse-jsdoc-claims.js +280 -0
  23. package/lib/parse-jsdoc-prose.js +111 -0
  24. package/lib/parse-markdown-claims.js +242 -0
  25. package/lib/parse-markdown-directives.js +136 -0
  26. package/lib/parse-where-clause.js +312 -0
  27. package/lib/patram-cli.js +337 -0
  28. package/lib/patram-config.js +3 -1
  29. package/lib/patram-config.types.ts +2 -1
  30. package/lib/query-graph.js +256 -0
  31. package/lib/render-check-output.js +315 -0
  32. package/lib/render-json-output.js +108 -0
  33. package/lib/render-output-view.js +193 -0
  34. package/lib/render-plain-output.js +237 -0
  35. package/lib/render-rich-output.js +293 -0
  36. package/lib/render-rich-source.js +1333 -0
  37. package/lib/resolve-check-target.js +190 -0
  38. package/lib/resolve-output-mode.js +60 -0
  39. package/lib/resolve-patram-graph-config.js +88 -0
  40. package/lib/resolve-where-clause.js +51 -0
  41. package/lib/show-document.js +311 -0
  42. package/lib/source-file-defaults.js +28 -0
  43. package/lib/write-paged-output.js +87 -0
  44. package/package.json +21 -10
  45. package/bin/patram.test.js +0 -184
  46. package/lib/build-graph.test.js +0 -141
  47. package/lib/check-graph.test.js +0 -103
  48. package/lib/list-source-files.test.js +0 -101
  49. package/lib/load-patram-config.test.js +0 -211
  50. package/lib/parse-claims.test.js +0 -113
  51. package/lib/patram-config.test.js +0 -147
@@ -1,10 +1,19 @@
1
+ import type {
2
+ KindDefinition,
3
+ MappingDefinition,
4
+ RelationDefinition,
5
+ } from './patram-config.types.ts';
6
+
1
7
  export interface StoredQueryConfig {
2
8
  where: string;
3
9
  }
4
10
 
5
11
  export interface PatramRepoConfig {
6
12
  include: string[];
13
+ kinds?: Record<string, KindDefinition>;
14
+ mappings?: Record<string, MappingDefinition>;
7
15
  queries: Record<string, StoredQueryConfig>;
16
+ relations?: Record<string, RelationDefinition>;
8
17
  }
9
18
 
10
19
  export interface PatramDiagnostic {
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @import { BuildGraphResult } from './build-graph.types.ts';
3
+ * @import { PatramClaim } from './parse-claims.types.ts';
4
+ * @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
5
+ */
6
+
7
+ import { readFile } from 'node:fs/promises';
8
+ import { resolve } from 'node:path';
9
+
10
+ import { buildGraph } from './build-graph.js';
11
+ import { listSourceFiles } from './list-source-files.js';
12
+ import { loadPatramConfig } from './load-patram-config.js';
13
+ import { parseSourceFile } from './parse-claims.js';
14
+ import { resolvePatramGraphConfig } from './resolve-patram-graph-config.js';
15
+
16
+ /**
17
+ * Project graph loading pipeline.
18
+ *
19
+ * Loads config, scans source files, parses claims, and materializes the graph
20
+ * used by CLI commands.
21
+ *
22
+ * Kind: graph
23
+ * Status: active
24
+ * Uses Term: ../docs/reference/terms/claim.md
25
+ * Uses Term: ../docs/reference/terms/graph.md
26
+ * Uses Term: ../docs/reference/terms/mapping.md
27
+ * Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
28
+ * Decided by: ../docs/decisions/dogfood-query-graph-v0.md
29
+ * @patram
30
+ * @see {@link ./parse-claims.js}
31
+ * @see {@link ./build-graph.js}
32
+ */
33
+
34
+ /**
35
+ * Load config, source files, claims, and the materialized graph for one
36
+ * project directory.
37
+ *
38
+ * @param {string} project_directory
39
+ * @returns {Promise<{ config: PatramRepoConfig, diagnostics: PatramDiagnostic[], graph: BuildGraphResult, source_file_paths: string[] }>}
40
+ */
41
+ export async function loadProjectGraph(project_directory) {
42
+ const load_result = await loadPatramConfig(project_directory);
43
+
44
+ if (load_result.diagnostics.length > 0) {
45
+ return {
46
+ config: {
47
+ include: [],
48
+ queries: {},
49
+ },
50
+ diagnostics: load_result.diagnostics,
51
+ graph: {
52
+ edges: [],
53
+ nodes: {},
54
+ },
55
+ source_file_paths: [],
56
+ };
57
+ }
58
+
59
+ const repo_config = load_result.config;
60
+
61
+ if (!repo_config) {
62
+ throw new Error('Expected a valid Patram repo config.');
63
+ }
64
+
65
+ const source_file_paths = await listSourceFiles(
66
+ repo_config.include,
67
+ project_directory,
68
+ );
69
+ const collect_result = await collectClaims(
70
+ source_file_paths,
71
+ project_directory,
72
+ );
73
+ const graph_config = resolvePatramGraphConfig(repo_config);
74
+
75
+ if (collect_result.diagnostics.length > 0) {
76
+ return {
77
+ config: repo_config,
78
+ diagnostics: collect_result.diagnostics,
79
+ graph: {
80
+ edges: [],
81
+ nodes: {},
82
+ },
83
+ source_file_paths,
84
+ };
85
+ }
86
+
87
+ return {
88
+ config: repo_config,
89
+ diagnostics: [],
90
+ graph: buildGraph(graph_config, collect_result.claims),
91
+ source_file_paths,
92
+ };
93
+ }
94
+
95
+ /**
96
+ * @param {string[]} source_file_paths
97
+ * @param {string} project_directory
98
+ * @returns {Promise<{ claims: PatramClaim[], diagnostics: PatramDiagnostic[] }>}
99
+ */
100
+ async function collectClaims(source_file_paths, project_directory) {
101
+ /** @type {PatramClaim[]} */
102
+ const claims = [];
103
+ /** @type {PatramDiagnostic[]} */
104
+ const diagnostics = [];
105
+
106
+ for (const source_file_path of source_file_paths) {
107
+ const source_text = await readFile(
108
+ resolve(project_directory, source_file_path),
109
+ 'utf8',
110
+ );
111
+ const parse_result = parseSourceFile({
112
+ path: source_file_path,
113
+ source: source_text,
114
+ });
115
+
116
+ claims.push(...parse_result.claims);
117
+ diagnostics.push(...parse_result.diagnostics);
118
+ }
119
+
120
+ return {
121
+ claims,
122
+ diagnostics,
123
+ };
124
+ }
@@ -0,0 +1,73 @@
1
+ import type { GraphNode } from './build-graph.types.ts';
2
+
3
+ export interface OutputViewSummary {
4
+ count: number;
5
+ kind: 'resolved_link_list' | 'result_list' | 'stored_query_list';
6
+ }
7
+
8
+ export interface QueryOutputViewSummary extends OutputViewSummary {
9
+ kind: 'result_list';
10
+ limit: number;
11
+ offset: number;
12
+ total_count: number;
13
+ }
14
+
15
+ export interface OutputNodeItem {
16
+ id: string;
17
+ kind: 'node';
18
+ node_kind: GraphNode['kind'];
19
+ path: string;
20
+ status?: string;
21
+ title: string;
22
+ }
23
+
24
+ export interface OutputStoredQueryItem {
25
+ kind: 'stored_query';
26
+ name: string;
27
+ where: string;
28
+ }
29
+
30
+ export interface OutputResolvedLinkTarget {
31
+ kind?: string;
32
+ path: string;
33
+ status?: string;
34
+ title: string;
35
+ }
36
+
37
+ export interface OutputResolvedLinkItem {
38
+ kind: 'resolved_link';
39
+ label: string;
40
+ reference: number;
41
+ target: OutputResolvedLinkTarget;
42
+ }
43
+
44
+ export interface QueryOutputView {
45
+ command: 'query';
46
+ hints: string[];
47
+ items: OutputNodeItem[];
48
+ summary: QueryOutputViewSummary;
49
+ }
50
+
51
+ export interface QueriesOutputView {
52
+ command: 'queries';
53
+ hints: string[];
54
+ items: OutputStoredQueryItem[];
55
+ summary: OutputViewSummary;
56
+ }
57
+
58
+ export interface ShowOutputView {
59
+ command: 'show';
60
+ hints: string[];
61
+ items: OutputResolvedLinkItem[];
62
+ path: string;
63
+ rendered_source: string;
64
+ source: string;
65
+ summary: OutputViewSummary;
66
+ }
67
+
68
+ export type OutputView = QueryOutputView | QueriesOutputView | ShowOutputView;
69
+
70
+ export interface ResolvedOutputMode {
71
+ color_enabled: boolean;
72
+ renderer_name: 'json' | 'plain' | 'rich';
73
+ }
@@ -1,178 +1,58 @@
1
+ /** @import * as $k$$l$parse$j$claims$k$types$k$ts from './parse-claims.types.ts'; */
1
2
  /**
2
- * @import { PatramClaim, ParseClaimsInput, PatramClaimFields } from './parse-claims.types.ts';
3
+ * @import { ParseClaimsInput, ParseSourceFileResult } from './parse-claims.types.ts';
3
4
  */
4
5
 
5
- const MARKDOWN_EXTENSIONS = new Set(['.md', '.markdown']);
6
- const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/dgu;
7
- const DIRECTIVE_PATTERN = /^([A-Z][A-Za-z ]+):\s+(.+)$/du;
8
- const HEADING_PATTERN = /^#\s+(.+)$/du;
6
+ import { getFileExtension } from './claim-helpers.js';
7
+ import { parseJsdocClaims } from './parse-jsdoc-claims.js';
8
+ import { parseMarkdownClaims } from './parse-markdown-claims.js';
9
+ import { MARKDOWN_SOURCE_FILE_EXTENSIONS } from './source-file-defaults.js';
9
10
 
10
11
  /**
11
- * Parse a file into neutral Patram claims.
12
+ * Source claim dispatch.
12
13
  *
13
- * @param {ParseClaimsInput} parse_input
14
- * @returns {PatramClaim[]}
14
+ * Routes each source file to markdown or JSDoc claim parsing and keeps claim
15
+ * extraction on one entrypoint.
16
+ *
17
+ * Kind: parse
18
+ * Status: active
19
+ * Uses Term: ../docs/reference/terms/claim.md
20
+ * Uses Term: ../docs/reference/terms/document.md
21
+ * Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
22
+ * Decided by: ../docs/decisions/jsdoc-metadata-directive-syntax.md
23
+ * Implements: ../docs/tasks/v0/parse-claims.md
24
+ * @patram
25
+ * @see {@link ./parse-markdown-claims.js}
26
+ * @see {@link ./parse-jsdoc-claims.js}
15
27
  */
16
- export function parseClaims(parse_input) {
17
- const file_extension = getFileExtension(parse_input.path);
18
-
19
- if (MARKDOWN_EXTENSIONS.has(file_extension)) {
20
- return parseMarkdownClaims(parse_input);
21
- }
22
28
 
23
- return [];
24
- }
29
+ const MARKDOWN_EXTENSIONS = new Set(MARKDOWN_SOURCE_FILE_EXTENSIONS);
25
30
 
26
31
  /**
32
+ * Parse one source file into claims and diagnostics.
33
+ *
27
34
  * @param {ParseClaimsInput} parse_input
28
- * @returns {PatramClaim[]}
29
- */
30
- function parseMarkdownClaims(parse_input) {
31
- const lines = parse_input.source.split('\n');
32
-
33
- /** @type {PatramClaim[]} */
34
- const claims = [];
35
- const title_value = getMarkdownTitle(lines);
36
-
37
- if (title_value) {
38
- claims.push(
39
- createClaim(parse_input.path, claims.length + 1, 'document.title', {
40
- value: title_value,
41
- }),
42
- );
43
- }
44
-
45
- for (const [line_index, line] of lines.entries()) {
46
- const line_number = line_index + 1;
47
-
48
- collectMarkdownLinkClaims(parse_input.path, line, line_number, claims);
49
- collectDirectiveClaims(parse_input.path, line, line_number, claims);
50
- }
51
-
52
- return claims;
53
- }
54
-
55
- /**
56
- * @param {string} file_path
57
- * @param {string} line
58
- * @param {number} line_number
59
- * @param {PatramClaim[]} claims
60
- */
61
- function collectMarkdownLinkClaims(file_path, line, line_number, claims) {
62
- for (const link_match of line.matchAll(MARKDOWN_LINK_PATTERN)) {
63
- const link_text = link_match[1];
64
- const target_value = link_match[2];
65
- const column_number =
66
- link_match.index === undefined ? 1 : link_match.index + 1;
67
-
68
- claims.push(
69
- createClaim(file_path, claims.length + 1, 'markdown.link', {
70
- origin: {
71
- column: column_number,
72
- line: line_number,
73
- path: file_path,
74
- },
75
- value: {
76
- target: target_value,
77
- text: link_text,
78
- },
79
- }),
80
- );
81
- }
82
- }
83
-
84
- /**
85
- * @param {string} file_path
86
- * @param {string} line
87
- * @param {number} line_number
88
- * @param {PatramClaim[]} claims
89
- */
90
- function collectDirectiveClaims(file_path, line, line_number, claims) {
91
- const directive_match = line.match(DIRECTIVE_PATTERN);
92
-
93
- if (!directive_match) {
94
- return;
95
- }
96
-
97
- const directive_name = normalizeDirectiveName(directive_match[1]);
98
- const directive_value = directive_match[2].trim();
99
-
100
- claims.push(
101
- createClaim(file_path, claims.length + 1, 'directive', {
102
- name: directive_name,
103
- origin: {
104
- column: 1,
105
- line: line_number,
106
- path: file_path,
107
- },
108
- parser: 'markdown',
109
- value: directive_value,
110
- }),
111
- );
112
- }
113
-
114
- /**
115
- * @param {string[]} lines
116
- * @returns {string | null}
35
+ * @returns {ParseSourceFileResult}
117
36
  */
118
- function getMarkdownTitle(lines) {
119
- const first_line = lines[0].trim();
120
-
121
- if (first_line.length === 0) {
122
- return null;
123
- }
124
-
125
- const heading_match = first_line.match(HEADING_PATTERN);
37
+ export function parseSourceFile(parse_input) {
38
+ const file_extension = getFileExtension(parse_input.path);
126
39
 
127
- if (heading_match) {
128
- return heading_match[1].trim();
40
+ if (MARKDOWN_EXTENSIONS.has(file_extension)) {
41
+ return {
42
+ claims: parseMarkdownClaims(parse_input),
43
+ diagnostics: [],
44
+ };
129
45
  }
130
46
 
131
- return first_line;
132
- }
133
-
134
- /**
135
- * @param {string} file_path
136
- * @param {number} claim_number
137
- * @param {string} claim_type
138
- * @param {PatramClaimFields} claim_fields
139
- * @returns {PatramClaim}
140
- */
141
- function createClaim(file_path, claim_number, claim_type, claim_fields) {
142
- const document_id = `doc:${file_path}`;
143
- const origin = claim_fields.origin ?? {
144
- column: 1,
145
- line: 1,
146
- path: file_path,
147
- };
148
-
149
- return {
150
- ...claim_fields,
151
- document_id,
152
- id: `claim:${document_id}:${claim_number}`,
153
- origin,
154
- type: claim_type,
155
- };
156
- }
157
-
158
- /**
159
- * @param {string} directive_label
160
- * @returns {string}
161
- */
162
- function normalizeDirectiveName(directive_label) {
163
- return directive_label.trim().toLowerCase().replaceAll(/\s+/dgu, '_');
47
+ return parseJsdocClaims(parse_input);
164
48
  }
165
49
 
166
50
  /**
167
- * @param {string} file_path
168
- * @returns {string}
51
+ * Parse a file into neutral Patram claims.
52
+ *
53
+ * @param {ParseClaimsInput} parse_input
54
+ * @returns {$k$$l$parse$j$claims$k$types$k$ts.PatramClaim[]}
169
55
  */
170
- function getFileExtension(file_path) {
171
- const last_dot_index = file_path.lastIndexOf('.');
172
-
173
- if (last_dot_index < 0) {
174
- return '';
175
- }
176
-
177
- return file_path.slice(last_dot_index);
56
+ export function parseClaims(parse_input) {
57
+ return parseSourceFile(parse_input).claims;
178
58
  }
@@ -1,3 +1,5 @@
1
+ import type { PatramDiagnostic } from './load-patram-config.types.ts';
2
+
1
3
  export interface ParseClaimsInput {
2
4
  path: string;
3
5
  source: string;
@@ -25,3 +27,8 @@ export type PatramClaimFields = Omit<
25
27
  > & {
26
28
  origin?: ClaimOrigin;
27
29
  };
30
+
31
+ export interface ParseSourceFileResult {
32
+ claims: PatramClaim[];
33
+ diagnostics: PatramDiagnostic[];
34
+ }