@equinor/fusion-framework-cli-plugin-ai-index 3.0.5 → 3.0.7

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 (195) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/esm/bin/apply-metadata.js +13 -5
  3. package/dist/esm/bin/apply-metadata.js.map +1 -1
  4. package/dist/esm/bin/apply-schema.js +7 -1
  5. package/dist/esm/bin/apply-schema.js.map +1 -1
  6. package/dist/esm/bin/apply-schema.test.js +2 -1
  7. package/dist/esm/bin/apply-schema.test.js.map +1 -1
  8. package/dist/esm/bin/{delete-removed-files.js → create-delete-removed-files-stream.js} +12 -1
  9. package/dist/esm/bin/create-delete-removed-files-stream.js.map +1 -0
  10. package/dist/esm/bin/embed.js +260 -97
  11. package/dist/esm/bin/embed.js.map +1 -1
  12. package/dist/esm/bin/execute-pipeline.js +5 -0
  13. package/dist/esm/bin/execute-pipeline.js.map +1 -1
  14. package/dist/esm/bin/get-diff.js +6 -0
  15. package/dist/esm/bin/get-diff.js.map +1 -1
  16. package/dist/esm/{embeddings-command.options.js → command-options-schema.js} +1 -1
  17. package/dist/esm/command-options-schema.js.map +1 -0
  18. package/dist/esm/{embeddings-command.js → command.js} +4 -2
  19. package/dist/esm/command.js.map +1 -0
  20. package/dist/esm/{create-command.js → create-index-command.js} +13 -5
  21. package/dist/esm/create-index-command.js.map +1 -0
  22. package/dist/esm/{schema.js → define-index-schema.js} +1 -1
  23. package/dist/esm/define-index-schema.js.map +1 -0
  24. package/dist/esm/delete-command.js +11 -2
  25. package/dist/esm/delete-command.js.map +1 -1
  26. package/dist/esm/delete-index-command.js +4 -0
  27. package/dist/esm/delete-index-command.js.map +1 -1
  28. package/dist/esm/{delete-command.options.js → delete-options-schema.js} +1 -1
  29. package/dist/esm/delete-options-schema.js.map +1 -0
  30. package/dist/esm/embed-command.js +3 -4
  31. package/dist/esm/embed-command.js.map +1 -1
  32. package/dist/esm/index.js +3 -3
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/search-command.js +20 -0
  35. package/dist/esm/search-command.js.map +1 -1
  36. package/dist/esm/utils/git/{metadata.js → extract-git-metadata.js} +4 -2
  37. package/dist/esm/utils/git/extract-git-metadata.js.map +1 -0
  38. package/dist/esm/utils/git/get-changed-files.js +87 -0
  39. package/dist/esm/utils/git/get-changed-files.js.map +1 -0
  40. package/dist/esm/utils/git/{file-changes.js → get-file-status.js} +9 -95
  41. package/dist/esm/utils/git/get-file-status.js.map +1 -0
  42. package/dist/esm/utils/git/{status.js → get-git-status.js} +5 -2
  43. package/dist/esm/utils/git/get-git-status.js.map +1 -0
  44. package/dist/esm/utils/git/get-git.js +30 -0
  45. package/dist/esm/utils/git/get-git.js.map +1 -0
  46. package/dist/esm/utils/git/index.js +7 -4
  47. package/dist/esm/utils/git/index.js.map +1 -1
  48. package/dist/esm/utils/git/is-file-changed.js +19 -0
  49. package/dist/esm/utils/git/is-file-changed.js.map +1 -0
  50. package/dist/esm/utils/git/resolve-project-root.js +22 -0
  51. package/dist/esm/utils/git/resolve-project-root.js.map +1 -0
  52. package/dist/esm/utils/markdown/index.js +3 -1
  53. package/dist/esm/utils/markdown/index.js.map +1 -1
  54. package/dist/esm/utils/markdown/is-markdown-file.js +10 -0
  55. package/dist/esm/utils/markdown/is-markdown-file.js.map +1 -0
  56. package/dist/esm/utils/markdown/parse-markdown-file.js +29 -0
  57. package/dist/esm/utils/markdown/parse-markdown-file.js.map +1 -0
  58. package/dist/esm/utils/markdown/{parser.js → parse-markdown.js} +8 -37
  59. package/dist/esm/utils/markdown/parse-markdown.js.map +1 -0
  60. package/dist/esm/utils/{embedding-dimensions.js → resolve-embedding-dimensions.js} +3 -1
  61. package/dist/esm/utils/resolve-embedding-dimensions.js.map +1 -0
  62. package/dist/esm/utils/{package-resolver.js → resolve-package.js} +4 -1
  63. package/dist/esm/utils/resolve-package.js.map +1 -0
  64. package/dist/esm/utils/ts-doc/create-typescript-document.js +27 -0
  65. package/dist/esm/utils/ts-doc/create-typescript-document.js.map +1 -0
  66. package/dist/esm/utils/ts-doc/extract-document-from-class-node.js +86 -0
  67. package/dist/esm/utils/ts-doc/extract-document-from-class-node.js.map +1 -0
  68. package/dist/esm/utils/ts-doc/extract-document-from-node.js +82 -0
  69. package/dist/esm/utils/ts-doc/extract-document-from-node.js.map +1 -0
  70. package/dist/esm/utils/ts-doc/index.js +3 -1
  71. package/dist/esm/utils/ts-doc/index.js.map +1 -1
  72. package/dist/esm/utils/ts-doc/is-typescript-file.js +10 -0
  73. package/dist/esm/utils/ts-doc/is-typescript-file.js.map +1 -0
  74. package/dist/esm/utils/ts-doc/{constants.js → node-kinds.js} +1 -1
  75. package/dist/esm/utils/ts-doc/node-kinds.js.map +1 -0
  76. package/dist/esm/utils/ts-doc/parse-ts-doc-from-file-sync.js +23 -0
  77. package/dist/esm/utils/ts-doc/parse-ts-doc-from-file-sync.js.map +1 -0
  78. package/dist/esm/utils/ts-doc/parse-ts-doc-sync.js +18 -0
  79. package/dist/esm/utils/ts-doc/parse-ts-doc-sync.js.map +1 -0
  80. package/dist/esm/utils/ts-doc/process-source-file.js +22 -0
  81. package/dist/esm/utils/ts-doc/process-source-file.js.map +1 -0
  82. package/dist/esm/utils/zod-to-azure-fields.js +7 -0
  83. package/dist/esm/utils/zod-to-azure-fields.js.map +1 -1
  84. package/dist/esm/utils/zod-to-azure-fields.test.js +8 -7
  85. package/dist/esm/utils/zod-to-azure-fields.test.js.map +1 -1
  86. package/dist/esm/version.js +1 -1
  87. package/dist/tsconfig.tsbuildinfo +1 -1
  88. package/dist/types/bin/apply-schema.d.ts +1 -1
  89. package/dist/types/config.d.ts +1 -1
  90. package/dist/types/index.d.ts +1 -1
  91. package/dist/types/utils/git/get-changed-files.d.ts +13 -0
  92. package/dist/types/utils/git/get-file-status.d.ts +13 -0
  93. package/dist/types/utils/git/{git-client.d.ts → get-git.d.ts} +0 -10
  94. package/dist/types/utils/git/index.d.ts +7 -4
  95. package/dist/types/utils/git/is-file-changed.d.ts +12 -0
  96. package/dist/types/utils/git/resolve-project-root.d.ts +10 -0
  97. package/dist/types/utils/markdown/index.d.ts +3 -1
  98. package/dist/types/utils/markdown/is-markdown-file.d.ts +7 -0
  99. package/dist/types/utils/markdown/parse-markdown-file.d.ts +14 -0
  100. package/dist/types/utils/markdown/parse-markdown.d.ts +14 -0
  101. package/dist/types/utils/ts-doc/create-typescript-document.d.ts +12 -0
  102. package/dist/types/utils/ts-doc/extract-document-from-class-node.d.ts +15 -0
  103. package/dist/types/utils/ts-doc/extract-document-from-node.d.ts +20 -0
  104. package/dist/types/utils/ts-doc/index.d.ts +3 -1
  105. package/dist/types/utils/ts-doc/is-typescript-file.d.ts +7 -0
  106. package/dist/types/utils/ts-doc/parse-ts-doc-from-file-sync.d.ts +14 -0
  107. package/dist/types/utils/ts-doc/parse-ts-doc-sync.d.ts +12 -0
  108. package/dist/types/utils/ts-doc/process-source-file.d.ts +11 -0
  109. package/dist/types/version.d.ts +1 -1
  110. package/package.json +8 -8
  111. package/src/bin/apply-metadata.ts +69 -57
  112. package/src/bin/apply-schema.test.ts +2 -1
  113. package/src/bin/apply-schema.ts +6 -1
  114. package/src/bin/{delete-removed-files.ts → create-delete-removed-files-stream.ts} +12 -1
  115. package/src/bin/embed.ts +389 -145
  116. package/src/bin/execute-pipeline.ts +5 -0
  117. package/src/bin/get-diff.ts +7 -1
  118. package/src/bin/types.ts +1 -1
  119. package/src/{embeddings-command.ts → command.ts} +3 -1
  120. package/src/config.ts +1 -1
  121. package/src/{create-command.ts → create-index-command.ts} +15 -5
  122. package/src/delete-command.ts +11 -2
  123. package/src/delete-index-command.ts +4 -0
  124. package/src/embed-command.ts +3 -6
  125. package/src/index.ts +3 -3
  126. package/src/search-command.ts +20 -0
  127. package/src/utils/git/{metadata.ts → extract-git-metadata.ts} +3 -1
  128. package/src/utils/git/get-changed-files.ts +93 -0
  129. package/src/utils/git/{file-changes.ts → get-file-status.ts} +9 -103
  130. package/src/utils/git/{status.ts → get-git-status.ts} +4 -1
  131. package/src/utils/git/{git-client.ts → get-git.ts} +4 -22
  132. package/src/utils/git/index.ts +7 -4
  133. package/src/utils/git/is-file-changed.ts +21 -0
  134. package/src/utils/git/resolve-project-root.ts +22 -0
  135. package/src/utils/markdown/index.ts +3 -1
  136. package/src/utils/markdown/is-markdown-file.ts +9 -0
  137. package/src/utils/markdown/parse-markdown-file.ts +36 -0
  138. package/src/utils/markdown/{parser.ts → parse-markdown.ts} +16 -51
  139. package/src/utils/{embedding-dimensions.ts → resolve-embedding-dimensions.ts} +2 -0
  140. package/src/utils/{package-resolver.ts → resolve-package.ts} +3 -0
  141. package/src/utils/ts-doc/create-typescript-document.ts +36 -0
  142. package/src/utils/ts-doc/extract-document-from-class-node.ts +106 -0
  143. package/src/utils/ts-doc/extract-document-from-node.ts +120 -0
  144. package/src/utils/ts-doc/index.ts +3 -1
  145. package/src/utils/ts-doc/is-typescript-file.ts +9 -0
  146. package/src/utils/ts-doc/parse-ts-doc-from-file-sync.ts +32 -0
  147. package/src/utils/ts-doc/parse-ts-doc-sync.ts +23 -0
  148. package/src/utils/ts-doc/process-source-file.ts +30 -0
  149. package/src/utils/zod-to-azure-fields.test.ts +10 -7
  150. package/src/utils/zod-to-azure-fields.ts +7 -0
  151. package/src/version.ts +1 -1
  152. package/dist/esm/bin/delete-removed-files.js.map +0 -1
  153. package/dist/esm/bin/file-stream.js +0 -22
  154. package/dist/esm/bin/file-stream.js.map +0 -1
  155. package/dist/esm/create-command.js.map +0 -1
  156. package/dist/esm/delete-command.options.js.map +0 -1
  157. package/dist/esm/embeddings-command.js.map +0 -1
  158. package/dist/esm/embeddings-command.options.js.map +0 -1
  159. package/dist/esm/schema.js.map +0 -1
  160. package/dist/esm/utils/embedding-dimensions.js.map +0 -1
  161. package/dist/esm/utils/git/file-changes.js.map +0 -1
  162. package/dist/esm/utils/git/git-client.js +0 -48
  163. package/dist/esm/utils/git/git-client.js.map +0 -1
  164. package/dist/esm/utils/git/metadata.js.map +0 -1
  165. package/dist/esm/utils/git/status.js.map +0 -1
  166. package/dist/esm/utils/markdown/parser.js.map +0 -1
  167. package/dist/esm/utils/package-resolver.js.map +0 -1
  168. package/dist/esm/utils/ts-doc/constants.js.map +0 -1
  169. package/dist/esm/utils/ts-doc/extractors.js +0 -189
  170. package/dist/esm/utils/ts-doc/extractors.js.map +0 -1
  171. package/dist/esm/utils/ts-doc/parser.js +0 -46
  172. package/dist/esm/utils/ts-doc/parser.js.map +0 -1
  173. package/dist/types/bin/file-stream.d.ts +0 -1
  174. package/dist/types/utils/git/file-changes.d.ts +0 -36
  175. package/dist/types/utils/markdown/parser.d.ts +0 -34
  176. package/dist/types/utils/ts-doc/extractors.d.ts +0 -42
  177. package/dist/types/utils/ts-doc/parser.d.ts +0 -32
  178. package/src/bin/file-stream.ts +0 -34
  179. package/src/utils/ts-doc/extractors.ts +0 -260
  180. package/src/utils/ts-doc/parser.ts +0 -60
  181. /package/dist/types/bin/{delete-removed-files.d.ts → create-delete-removed-files-stream.d.ts} +0 -0
  182. /package/dist/types/{embeddings-command.options.d.ts → command-options-schema.d.ts} +0 -0
  183. /package/dist/types/{embeddings-command.d.ts → command.d.ts} +0 -0
  184. /package/dist/types/{create-command.d.ts → create-index-command.d.ts} +0 -0
  185. /package/dist/types/{schema.d.ts → define-index-schema.d.ts} +0 -0
  186. /package/dist/types/{delete-command.options.d.ts → delete-options-schema.d.ts} +0 -0
  187. /package/dist/types/utils/git/{metadata.d.ts → extract-git-metadata.d.ts} +0 -0
  188. /package/dist/types/utils/git/{status.d.ts → get-git-status.d.ts} +0 -0
  189. /package/dist/types/utils/{embedding-dimensions.d.ts → resolve-embedding-dimensions.d.ts} +0 -0
  190. /package/dist/types/utils/{package-resolver.d.ts → resolve-package.d.ts} +0 -0
  191. /package/dist/types/utils/ts-doc/{constants.d.ts → node-kinds.d.ts} +0 -0
  192. /package/src/{embeddings-command.options.ts → command-options-schema.ts} +0 -0
  193. /package/src/{schema.ts → define-index-schema.ts} +0 -0
  194. /package/src/{delete-command.options.ts → delete-options-schema.ts} +0 -0
  195. /package/src/utils/ts-doc/{constants.ts → node-kinds.ts} +0 -0
@@ -1,87 +1,6 @@
1
1
  import { join, relative } from 'node:path';
2
- import type { ChangedFile, FileChangeStatus, GitDiffOptions } from './types.js';
3
- import { resolveProjectRoot, getGit } from './git-client.js';
4
-
5
- /**
6
- * Returns a list of files changed between `baseRef` and HEAD.
7
- *
8
- * Parses the output of `git diff --name-status` to classify each file as
9
- * `'new'`, `'modified'`, or `'removed'`. Renames are expanded into a
10
- * `'removed'` entry for the old path and a `'new'` entry for the new path.
11
- *
12
- * @param options - Configuration controlling the diff reference and working directory.
13
- * @returns Array of changed files with their status.
14
- * @throws {Error} If the working directory is not inside a git repository.
15
- */
16
- export const getChangedFiles = async (options: GitDiffOptions): Promise<ChangedFile[]> => {
17
- const { diff, baseRef = 'HEAD~1', cwd = process.cwd() } = options;
18
-
19
- if (!diff) {
20
- return [];
21
- }
22
-
23
- const projectRoot = resolveProjectRoot(cwd);
24
- if (!projectRoot) {
25
- throw new Error('Not in a git repository. Cannot use --diff option.');
26
- }
27
-
28
- const { git } = getGit(cwd) ?? {};
29
- if (!git) {
30
- throw new Error('Failed to initialize git client');
31
- }
32
-
33
- try {
34
- // Get changes since baseRef with status (A=added, M=modified, D=deleted)
35
- try {
36
- const diffResult = await git.diff([`${baseRef}`, '--name-status']);
37
- const lines = diffResult.split('\n').filter((line) => line.trim() !== '');
38
-
39
- const changedFiles: ChangedFile[] = [];
40
-
41
- for (const line of lines) {
42
- // Match status and file path
43
- // Format: "A\tfile.ts" or "M\tfile.ts" or "D\tfile.ts"
44
- // Also handle renames: "R100\told.ts\tnew.ts"
45
- const renameMatch = line.match(/^R\d*\s+(.+?)\s+(.+)$/);
46
- if (renameMatch) {
47
- const [, oldFile, newFile] = renameMatch;
48
- // Add both the removed old file and the new file
49
- changedFiles.push({ filepath: `${projectRoot}/${oldFile}`, status: 'removed' });
50
- changedFiles.push({ filepath: `${projectRoot}/${newFile}`, status: 'new' });
51
- continue;
52
- }
53
-
54
- const match = line.match(/^([AMD])\s+(.+)$/);
55
- if (match) {
56
- const [, gitStatus, file] = match;
57
- const fullPath = `${projectRoot}/${file}`;
58
-
59
- let status: FileChangeStatus;
60
- if (gitStatus === 'A') {
61
- status = 'new';
62
- } else if (gitStatus === 'M') {
63
- status = 'modified';
64
- } else if (gitStatus === 'D') {
65
- status = 'removed';
66
- } else {
67
- // Skip unknown statuses (C=copied, etc.)
68
- continue;
69
- }
70
-
71
- changedFiles.push({ filepath: fullPath, status });
72
- }
73
- }
74
-
75
- return changedFiles;
76
- } catch {
77
- // Handle case where baseRef doesn't exist (e.g., first commit)
78
- console.warn(`⚠️ Warning: Git reference '${baseRef}' not found. Processing all files.`);
79
- return [];
80
- }
81
- } catch (error) {
82
- throw new Error(`Git diff failed: ${error instanceof Error ? error.message : String(error)}`);
83
- }
84
- };
2
+ import type { ChangedFile } from './types.js';
3
+ import { getGit } from './get-git.js';
85
4
 
86
5
  /**
87
6
  * Determines the git change status of a single file.
@@ -96,8 +15,8 @@ export const getChangedFiles = async (options: GitDiffOptions): Promise<ChangedF
96
15
  */
97
16
  export const getFileStatus = async (filePath: string): Promise<ChangedFile[]> => {
98
17
  const { git, gitRepoPath } = getGit(filePath) ?? {};
18
+ // Not in a git repository, assume new
99
19
  if (!git || !gitRepoPath) {
100
- // Not in a git repository, assume new
101
20
  return [{ filepath: filePath, status: 'new' }];
102
21
  }
103
22
 
@@ -112,8 +31,8 @@ export const getFileStatus = async (filePath: string): Promise<ChangedFile[]> =>
112
31
  .then(() => true)
113
32
  .catch(() => false);
114
33
 
34
+ // File is tracked at this path, it's modified
115
35
  if (isTracked) {
116
- // File is tracked at this path, it's modified
117
36
  return [{ filepath: filePath, status: 'modified' }];
118
37
  }
119
38
 
@@ -128,6 +47,7 @@ export const getFileStatus = async (filePath: string): Promise<ChangedFile[]> =>
128
47
  ]);
129
48
  const trimmed = fileStatusOutput.trim();
130
49
 
50
+ // Only inspect porcelain output when git actually reported something for this path
131
51
  if (trimmed.length > 0) {
132
52
  // If status shows ??, it's untracked (truly new)
133
53
  if (/^\?\?/.test(trimmed)) {
@@ -143,11 +63,14 @@ export const getFileStatus = async (filePath: string): Promise<ChangedFile[]> =>
143
63
  try {
144
64
  // Get full git status to check for renames (only if needed)
145
65
  const statusOutput = await git.raw(['status', '--porcelain']);
66
+ // Drop blank lines left by the trailing newline in git's output
146
67
  const lines = statusOutput.split('\n').filter((line) => line.trim() !== '');
147
68
 
69
+ // Scan every status line for a rename or copy that produced this file
148
70
  for (const line of lines) {
149
71
  // Check for rename format: "R100\told.ts\tnew.ts"
150
72
  const renameMatch = line.match(/^R\d+\s+(.+?)\s+(.+)$/);
73
+ // A rename line resolves this file only if it matches the new path
151
74
  if (renameMatch) {
152
75
  const [, oldPath, newPath] = renameMatch;
153
76
  const oldFullPath = join(gitRepoPath, oldPath);
@@ -164,6 +87,7 @@ export const getFileStatus = async (filePath: string): Promise<ChangedFile[]> =>
164
87
 
165
88
  // Check for copy format: "C100\told.ts\tnew.ts" (similar to rename)
166
89
  const copyMatch = line.match(/^C\d+\s+(.+?)\s+(.+)$/);
90
+ // A copy line resolves this file only if it matches the new path
167
91
  if (copyMatch) {
168
92
  const [, , newPath] = copyMatch;
169
93
  const newFullPath = join(gitRepoPath, newPath);
@@ -208,21 +132,3 @@ export const getFileStatus = async (filePath: string): Promise<ChangedFile[]> =>
208
132
  return [{ filepath: filePath, status: 'new' }];
209
133
  }
210
134
  };
211
-
212
- /**
213
- * Checks whether a file path appears in a list of changed files.
214
- *
215
- * When the changed-files list is empty (no diff filtering active), every
216
- * file is considered changed so that all files are processed.
217
- *
218
- * @param filePath - Absolute file path to look up.
219
- * @param changedFiles - Array of {@link ChangedFile} entries to search.
220
- * @returns `true` if the file has changed or if diff filtering is disabled.
221
- */
222
- export const isFileChanged = (filePath: string, changedFiles: ChangedFile[]): boolean => {
223
- if (changedFiles.length === 0) {
224
- return true; // If no diff filtering, process all files
225
- }
226
-
227
- return changedFiles.some((file) => file.filepath === filePath);
228
- };
@@ -1,4 +1,5 @@
1
- import { resolveProjectRoot, getGit } from './git-client.js';
1
+ import { resolveProjectRoot } from './resolve-project-root.js';
2
+ import { getGit } from './get-git.js';
2
3
 
3
4
  /**
4
5
  * Retrieves a summary of the current git working-tree status.
@@ -21,11 +22,13 @@ export const getGitStatus = async (
21
22
  unstagedFiles: number;
22
23
  }> => {
23
24
  const projectRoot = resolveProjectRoot(cwd);
25
+ // Status can only be reported when cwd is inside a git repository
24
26
  if (!projectRoot) {
25
27
  throw new Error('Not in a git repository');
26
28
  }
27
29
 
28
30
  const { git } = getGit(cwd) ?? {};
31
+ // Fail fast if the git client could not be created for this repository
29
32
  if (!git) {
30
33
  throw new Error('Failed to initialize git client');
31
34
  }
@@ -1,28 +1,8 @@
1
1
  import { simpleGit, type SimpleGit } from 'simple-git';
2
- import { findUpSync } from 'find-up';
3
- import { dirname, join } from 'node:path';
4
- import { existsSync } from 'node:fs';
5
2
 
6
- const gitCache = new Map<string, SimpleGit>();
3
+ import { resolveProjectRoot } from './resolve-project-root.js';
7
4
 
8
- /**
9
- * Resolves the git repository root for a given file path.
10
- *
11
- * Walks up the directory tree looking for a `.git` directory or file
12
- * (to support worktrees) and returns the enclosing directory.
13
- *
14
- * @param filePath - Absolute file or directory path to resolve from.
15
- * @returns Absolute path to the repository root, or `undefined` if not inside a git repo.
16
- */
17
- export const resolveProjectRoot = (filePath: string): string | undefined => {
18
- // if we are in the root of the git repository, return the root
19
- if (existsSync(join(filePath, '.git'))) {
20
- return filePath;
21
- }
22
- const gitRepoPath = findUpSync('.git', { cwd: dirname(filePath), type: 'both' });
23
- const projectRoot = gitRepoPath?.replace(/\.git$/, '');
24
- return projectRoot;
25
- };
5
+ const gitCache = new Map<string, SimpleGit>();
26
6
 
27
7
  /**
28
8
  * Returns a cached `SimpleGit` instance scoped to the repository that
@@ -39,7 +19,9 @@ export const getGit = (
39
19
  filePath: string,
40
20
  ): { git: SimpleGit | undefined; gitRepoPath: string } | undefined => {
41
21
  const gitRepoPath = resolveProjectRoot(filePath);
22
+ // Only build/cache a client when the file is inside a git repository
42
23
  if (gitRepoPath) {
24
+ // Reuse an existing client for this repo root instead of spawning a new one
43
25
  if (!gitCache.has(gitRepoPath)) {
44
26
  gitCache.set(gitRepoPath, simpleGit(gitRepoPath));
45
27
  }
@@ -7,13 +7,16 @@ export type {
7
7
  } from './types.js';
8
8
 
9
9
  // Re-export git client utilities
10
- export { resolveProjectRoot, getGit } from './git-client.js';
10
+ export { resolveProjectRoot } from './resolve-project-root.js';
11
+ export { getGit } from './get-git.js';
11
12
 
12
13
  // Re-export metadata functions
13
- export { extractGitMetadata } from './metadata.js';
14
+ export { extractGitMetadata } from './extract-git-metadata.js';
14
15
 
15
16
  // Re-export file change functions
16
- export { getChangedFiles, getFileStatus, isFileChanged } from './file-changes.js';
17
+ export { getChangedFiles } from './get-changed-files.js';
18
+ export { getFileStatus } from './get-file-status.js';
19
+ export { isFileChanged } from './is-file-changed.js';
17
20
 
18
21
  // Re-export status functions
19
- export { getGitStatus } from './status.js';
22
+ export { getGitStatus } from './get-git-status.js';
@@ -0,0 +1,21 @@
1
+ import type { ChangedFile } from './types.js';
2
+
3
+ /**
4
+ * Checks whether a file path appears in a list of changed files.
5
+ *
6
+ * When the changed-files list is empty (no diff filtering active), every
7
+ * file is considered changed so that all files are processed.
8
+ *
9
+ * @param filePath - Absolute file path to look up.
10
+ * @param changedFiles - Array of {@link ChangedFile} entries to search.
11
+ * @returns `true` if the file has changed or if diff filtering is disabled.
12
+ */
13
+ export const isFileChanged = (filePath: string, changedFiles: ChangedFile[]): boolean => {
14
+ // If no diff filtering, process all files
15
+ if (changedFiles.length === 0) {
16
+ return true;
17
+ }
18
+
19
+ // A file is considered changed when its path matches any entry in the list
20
+ return changedFiles.some((file) => file.filepath === filePath);
21
+ };
@@ -0,0 +1,22 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { findUpSync } from 'find-up';
4
+
5
+ /**
6
+ * Resolves the git repository root for a given file path.
7
+ *
8
+ * Walks up the directory tree looking for a `.git` directory or file
9
+ * (to support worktrees) and returns the enclosing directory.
10
+ *
11
+ * @param filePath - Absolute file or directory path to resolve from.
12
+ * @returns Absolute path to the repository root, or `undefined` if not inside a git repo.
13
+ */
14
+ export const resolveProjectRoot = (filePath: string): string | undefined => {
15
+ // if we are in the root of the git repository, return the root
16
+ if (existsSync(join(filePath, '.git'))) {
17
+ return filePath;
18
+ }
19
+ const gitRepoPath = findUpSync('.git', { cwd: dirname(filePath), type: 'both' });
20
+ const projectRoot = gitRepoPath?.replace(/\.git$/, '');
21
+ return projectRoot;
22
+ };
@@ -2,4 +2,6 @@
2
2
  export type { MarkdownMetadata, MarkdownDocument } from './types.js';
3
3
 
4
4
  // Re-export parser functions
5
- export { isMarkdownFile, parseMarkdown, parseMarkdownFile } from './parser.js';
5
+ export { isMarkdownFile } from './is-markdown-file.js';
6
+ export { parseMarkdown } from './parse-markdown.js';
7
+ export { parseMarkdownFile } from './parse-markdown-file.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Checks whether a file path has a Markdown (`.md`) or MDX (`.mdx`) extension.
3
+ *
4
+ * @param filePath - Absolute or relative file path.
5
+ * @returns `true` when the extension is `.md` or `.mdx`.
6
+ */
7
+ export const isMarkdownFile = (filePath: string): boolean => {
8
+ return filePath.endsWith('.md') || filePath.endsWith('.mdx');
9
+ };
@@ -0,0 +1,36 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { assert } from 'node:console';
3
+
4
+ import type { SourceFile } from '../types.js';
5
+ import type { MarkdownDocument } from './types.js';
6
+ import { isMarkdownFile } from './is-markdown-file.js';
7
+ import { parseMarkdown } from './parse-markdown.js';
8
+
9
+ /**
10
+ * Reads a Markdown or MDX file from disk and parses it into chunked documents.
11
+ *
12
+ * Delegates to {@link parseMarkdown} after reading the file content, then
13
+ * enriches each resulting document with the `rootPath` from the source file.
14
+ *
15
+ * @template T - Additional frontmatter attributes.
16
+ * @param file - Source file descriptor with path and optional project root.
17
+ * @returns Array of Markdown documents with root-path metadata.
18
+ * @throws {AssertionError} If the file does not have a `.md` or `.mdx` extension.
19
+ */
20
+ export const parseMarkdownFile = async <
21
+ T extends Record<string, unknown> = Record<string, unknown>,
22
+ >(
23
+ file: SourceFile,
24
+ ): Promise<MarkdownDocument<T>[]> => {
25
+ assert(isMarkdownFile(file.path), `File ${file.path} is not a markdown or MDX file`);
26
+ const content = readFileSync(file.path, 'utf8');
27
+ const result = await parseMarkdown<T>(content, file.relativePath ?? file.path);
28
+ // Enrich each parsed chunk with the file's project root for downstream path resolution
29
+ return result.map((document) => ({
30
+ ...document,
31
+ metadata: {
32
+ ...document.metadata,
33
+ rootPath: file.projectRoot,
34
+ },
35
+ }));
36
+ };
@@ -1,11 +1,7 @@
1
- import { readFileSync } from 'node:fs';
2
- import { assert } from 'node:console';
3
-
4
1
  import { default as grayMatter } from 'gray-matter';
5
2
 
6
3
  import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
7
4
 
8
- import type { SourceFile } from '../types.js';
9
5
  import type { MarkdownDocument, MarkdownMetadata } from './types.js';
10
6
  import { generateChunkId } from '../generate-chunk-id.js';
11
7
 
@@ -28,16 +24,6 @@ const markdownConfig = {
28
24
  keepSeparator: true,
29
25
  };
30
26
 
31
- /**
32
- * Checks whether a file path has a Markdown (`.md`) or MDX (`.mdx`) extension.
33
- *
34
- * @param filePath - Absolute or relative file path.
35
- * @returns `true` when the extension is `.md` or `.mdx`.
36
- */
37
- export const isMarkdownFile = (filePath: string): boolean => {
38
- return filePath.endsWith('.md') || filePath.endsWith('.mdx');
39
- };
40
-
41
27
  /**
42
28
  * Validates that a text chunk contains meaningful content.
43
29
  *
@@ -49,6 +35,7 @@ export const isMarkdownFile = (filePath: string): boolean => {
49
35
  */
50
36
  const isValidChunk = (chunk: string): boolean => {
51
37
  const trimmed = chunk.trim();
38
+ // Skip empty or whitespace-only chunks
52
39
  if (!trimmed) return false;
53
40
  // Skip chunks that are only code fence markers
54
41
  if (/^`{3,}[\w-]*$/.test(trimmed)) return false;
@@ -72,6 +59,7 @@ export const parseMarkdown = async <T extends Record<string, unknown> = Record<s
72
59
  source: string,
73
60
  ): Promise<MarkdownDocument<T>[]> => {
74
61
  const { content: markdownContent, data } = grayMatter(content);
62
+ // Prefix each frontmatter key with `md_` to namespace it in the document metadata
75
63
  const markdownAttributes = Object.entries(data).reduce(
76
64
  (acc, [key, value]) => {
77
65
  acc[`md_${key}`] = value;
@@ -86,48 +74,25 @@ export const parseMarkdown = async <T extends Record<string, unknown> = Record<s
86
74
 
87
75
  // Filter out empty chunks and chunks that are just code fence markers
88
76
  const validChunks = chunks.filter((chunk) => {
77
+ // Discard invalid chunks; keep everything else
89
78
  if (!isValidChunk(chunk)) {
90
79
  return false;
91
80
  }
92
81
  return true;
93
82
  });
94
83
 
95
- return validChunks.map(
96
- (chunk, _index): MarkdownDocument<T> => ({
97
- id: generateChunkId(source, _index),
98
- pageContent: chunk,
99
- metadata: {
100
- source,
101
- attributes: markdownAttributes as MarkdownMetadata<T>['attributes'],
102
- },
103
- }),
84
+ return (
85
+ validChunks
86
+ // Wrap each valid chunk into a vector-store-ready document
87
+ .map(
88
+ (chunk, _index): MarkdownDocument<T> => ({
89
+ id: generateChunkId(source, _index),
90
+ pageContent: chunk,
91
+ metadata: {
92
+ source,
93
+ attributes: markdownAttributes as MarkdownMetadata<T>['attributes'],
94
+ },
95
+ }),
96
+ )
104
97
  );
105
98
  };
106
-
107
- /**
108
- * Reads a Markdown or MDX file from disk and parses it into chunked documents.
109
- *
110
- * Delegates to {@link parseMarkdown} after reading the file content, then
111
- * enriches each resulting document with the `rootPath` from the source file.
112
- *
113
- * @template T - Additional frontmatter attributes.
114
- * @param file - Source file descriptor with path and optional project root.
115
- * @returns Array of Markdown documents with root-path metadata.
116
- * @throws {AssertionError} If the file does not have a `.md` or `.mdx` extension.
117
- */
118
- export const parseMarkdownFile = async <
119
- T extends Record<string, unknown> = Record<string, unknown>,
120
- >(
121
- file: SourceFile,
122
- ): Promise<MarkdownDocument<T>[]> => {
123
- assert(isMarkdownFile(file.path), `File ${file.path} is not a markdown or MDX file`);
124
- const content = readFileSync(file.path, 'utf8');
125
- const result = await parseMarkdown<T>(content, file.relativePath ?? file.path);
126
- return result.map((document) => ({
127
- ...document,
128
- metadata: {
129
- ...document.metadata,
130
- rootPath: file.projectRoot,
131
- },
132
- }));
133
- };
@@ -26,8 +26,10 @@ const KNOWN_MODEL_DIMENSIONS: ReadonlyMap<string, number> = new Map([
26
26
  */
27
27
  export function resolveEmbeddingDimensions(model: string, configDimensions?: number): number {
28
28
  const known = KNOWN_MODEL_DIMENSIONS.get(model);
29
+ // Known models resolve directly from the lookup map.
29
30
  if (known !== undefined) return known;
30
31
 
32
+ // Unknown models fall back to an explicit dimensions override from config.
31
33
  if (configDimensions !== undefined) return configDimensions;
32
34
 
33
35
  const knownModels = [...KNOWN_MODEL_DIMENSIONS.keys()].join(', ');
@@ -25,8 +25,10 @@ const packageMap = new Map<string, PackageJson>();
25
25
  export async function resolvePackage(filePath: string): Promise<PackageJson | undefined> {
26
26
  // Check cache: iterate through known package directories
27
27
  for (const packageRoot of packageMap.keys()) {
28
+ // A cached package root that is a prefix of filePath is the closest known package.
28
29
  if (filePath.startsWith(packageRoot)) {
29
30
  const packageJson = packageMap.get(packageRoot);
31
+ // Guard: satisfies TypeScript's strict Map#get return type despite the key check above.
30
32
  if (packageJson) {
31
33
  return packageJson;
32
34
  }
@@ -38,6 +40,7 @@ export async function resolvePackage(filePath: string): Promise<PackageJson | un
38
40
  const dirPath = dirname(filePath);
39
41
  const result = await readPackageUp({ cwd: dirPath, normalize: false });
40
42
 
43
+ // Cache the resolved package.json so future lookups under the same root are free.
41
44
  if (result) {
42
45
  // Cache using the package directory (where package.json is located)
43
46
  const packageDir = dirname(result.path);
@@ -0,0 +1,36 @@
1
+ import type { SourceFile as ProjectSourceFile } from 'ts-morph';
2
+
3
+ import { generateChunkId } from '../generate-chunk-id.js';
4
+ import type { TypescriptDocument, ParseTsDocOptions } from './types.js';
5
+
6
+ /**
7
+ * Creates a TypescriptDocument from the given parameters.
8
+ * @param sourceFile - The source file containing the node.
9
+ * @param pageContent - The content of the document.
10
+ * @param tsKind - The TypeScript node kind name.
11
+ * @param tsName - The name of the TypeScript node.
12
+ * @param options - Optional parsing configuration.
13
+ * @returns A TypeScript document with metadata.
14
+ */
15
+ export const createTypescriptDocument = (
16
+ sourceFile: ProjectSourceFile,
17
+ pageContent: string,
18
+ tsKind: string,
19
+ tsName: string,
20
+ options?: ParseTsDocOptions,
21
+ ): TypescriptDocument => {
22
+ const source = sourceFile.getFilePath().replace(/^\/+/, '');
23
+ return {
24
+ id: generateChunkId(source),
25
+ pageContent,
26
+ metadata: {
27
+ source,
28
+ ...(options?.projectRoot && { rootPath: options.projectRoot }),
29
+ attributes: {
30
+ type: 'tsdoc',
31
+ ts_kind: tsKind,
32
+ ts_name: tsName,
33
+ },
34
+ },
35
+ };
36
+ };
@@ -0,0 +1,106 @@
1
+ import { SyntaxKind, type SourceFile as ProjectSourceFile, type ClassDeclaration } from 'ts-morph';
2
+ import type { MethodDeclaration, PropertyDeclaration } from 'ts-morph';
3
+
4
+ import type { TypescriptDocument, ParseTsDocOptions } from './types.js';
5
+ import { createTypescriptDocument } from './create-typescript-document.js';
6
+
7
+ /**
8
+ * Extracts a vector-store document from a TypeScript class declaration.
9
+ *
10
+ * Collects the class-level TSDoc comment, constructor signature (if documented),
11
+ * and all public members with TSDoc into a single document whose `pageContent`
12
+ * mirrors a minimal class interface.
13
+ *
14
+ * @param classNode - The `ts-morph` {@link ClassDeclaration} node to inspect.
15
+ * @param sourceFile - The project source file that contains the class.
16
+ * @param _options - Optional parsing configuration.
17
+ * @returns A {@link TypescriptDocument}, or `null` when the class has no TSDoc.
18
+ */
19
+ export const extractDocumentFromClassNode = (
20
+ classNode: ClassDeclaration,
21
+ sourceFile: ProjectSourceFile,
22
+ _options?: ParseTsDocOptions,
23
+ ): TypescriptDocument | null => {
24
+ // Find TSDoc comment (/** ... */); only the block comment counts, not plain `//` or `/* */` comments
25
+ const docCommentRange = classNode
26
+ .getLeadingCommentRanges()
27
+ .find((range) => range.getText().startsWith('/**'));
28
+
29
+ // Skip undocumented classes — nothing to index
30
+ if (!docCommentRange) {
31
+ return null;
32
+ }
33
+
34
+ const docComment = docCommentRange.getText();
35
+ const className = classNode.getName() || 'AnonymousClass';
36
+
37
+ // Start with class TSDoc and declaration (without body)
38
+ const classSignature = `${classNode.getText({ includeJsDocComments: false }).split('{')[0].trim()} {`;
39
+ let pageContent = `${docComment}\n${classSignature}`;
40
+
41
+ // Add constructor with TSDoc if present
42
+ const constructorNode = classNode.getConstructors()[0]; // Get the first constructor (if any)
43
+ // Only surface the constructor signature when it has documentation to show
44
+ if (constructorNode) {
45
+ // Only the TSDoc block comment counts, not plain `//` or `/* */` comments
46
+ const constructorDocCommentRange = constructorNode
47
+ .getLeadingCommentRanges()
48
+ .find((range) => range.getText().startsWith('/**'));
49
+ // Skip undocumented constructors — nothing to add to the page content
50
+ if (constructorDocCommentRange) {
51
+ const constructorDocComment = constructorDocCommentRange.getText();
52
+ const constructorSignature = constructorNode
53
+ .getText({ includeJsDocComments: false })
54
+ .split('{')[0]
55
+ .trim();
56
+ pageContent += `\n ${constructorDocComment}\n ${constructorSignature}`;
57
+ }
58
+ }
59
+
60
+ // Add public methods and properties with TSDoc
61
+ const publicMembers = [
62
+ ...classNode
63
+ .getMethods()
64
+ // Only public methods form part of the class's documented surface
65
+ .filter((m) => m.hasModifier(SyntaxKind.PublicKeyword)),
66
+ ...classNode
67
+ .getProperties()
68
+ // Only public properties form part of the class's documented surface
69
+ .filter((p) => p.hasModifier(SyntaxKind.PublicKeyword)),
70
+ ];
71
+
72
+ // Append each documented public member's TSDoc and signature to the page content
73
+ publicMembers.forEach((member) => {
74
+ const memberDocCommentRange = member
75
+ .getLeadingCommentRanges()
76
+ // Only the TSDoc block comment counts, not plain `//` or `/* */` comments
77
+ .find((range) => range.getText().startsWith('/**'));
78
+ // Skip undocumented members — nothing to add to the page content
79
+ if (memberDocCommentRange) {
80
+ const memberDocComment = memberDocCommentRange.getText();
81
+ let memberSignature = '';
82
+ // Methods and properties render their signature differently
83
+ if (member.getKind() === SyntaxKind.MethodDeclaration) {
84
+ memberSignature = (member as MethodDeclaration)
85
+ .getText({ includeJsDocComments: false })
86
+ .split('{')[0]
87
+ .trim();
88
+ } else if (member.getKind() === SyntaxKind.PropertyDeclaration) {
89
+ memberSignature = (member as PropertyDeclaration)
90
+ .getText({ includeJsDocComments: false })
91
+ .trim();
92
+ }
93
+ pageContent += `\n ${memberDocComment}\n ${memberSignature}`;
94
+ }
95
+ });
96
+
97
+ pageContent += '\n}';
98
+
99
+ return createTypescriptDocument(
100
+ sourceFile,
101
+ pageContent,
102
+ classNode.getKindName(),
103
+ className,
104
+ _options,
105
+ );
106
+ };