@equinor/fusion-framework-cli-plugin-ai-index 1.0.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/LICENSE +21 -0
  3. package/README.md +114 -0
  4. package/dist/esm/bin/apply-metadata.js +63 -0
  5. package/dist/esm/bin/apply-metadata.js.map +1 -0
  6. package/dist/esm/bin/delete-removed-files.js +36 -0
  7. package/dist/esm/bin/delete-removed-files.js.map +1 -0
  8. package/dist/esm/bin/embed.js +196 -0
  9. package/dist/esm/bin/embed.js.map +1 -0
  10. package/dist/esm/bin/execute-pipeline.js +40 -0
  11. package/dist/esm/bin/execute-pipeline.js.map +1 -0
  12. package/dist/esm/bin/file-stream.js +22 -0
  13. package/dist/esm/bin/file-stream.js.map +1 -0
  14. package/dist/esm/bin/get-diff.js +29 -0
  15. package/dist/esm/bin/get-diff.js.map +1 -0
  16. package/dist/esm/bin/index.js +2 -0
  17. package/dist/esm/bin/index.js.map +1 -0
  18. package/dist/esm/bin/types.js +2 -0
  19. package/dist/esm/bin/types.js.map +1 -0
  20. package/dist/esm/command.js +82 -0
  21. package/dist/esm/command.js.map +1 -0
  22. package/dist/esm/command.options.js +48 -0
  23. package/dist/esm/command.options.js.map +1 -0
  24. package/dist/esm/config.js +2 -0
  25. package/dist/esm/config.js.map +1 -0
  26. package/dist/esm/index.js +13 -0
  27. package/dist/esm/index.js.map +1 -0
  28. package/dist/esm/utils/generate-chunk-id.js +18 -0
  29. package/dist/esm/utils/generate-chunk-id.js.map +1 -0
  30. package/dist/esm/utils/git/file-changes.js +196 -0
  31. package/dist/esm/utils/git/file-changes.js.map +1 -0
  32. package/dist/esm/utils/git/git-client.js +39 -0
  33. package/dist/esm/utils/git/git-client.js.map +1 -0
  34. package/dist/esm/utils/git/index.js +9 -0
  35. package/dist/esm/utils/git/index.js.map +1 -0
  36. package/dist/esm/utils/git/metadata.js +41 -0
  37. package/dist/esm/utils/git/metadata.js.map +1 -0
  38. package/dist/esm/utils/git/status.js +34 -0
  39. package/dist/esm/utils/git/status.js.map +1 -0
  40. package/dist/esm/utils/git/types.js +2 -0
  41. package/dist/esm/utils/git/types.js.map +1 -0
  42. package/dist/esm/utils/markdown/index.js +3 -0
  43. package/dist/esm/utils/markdown/index.js.map +1 -0
  44. package/dist/esm/utils/markdown/parser.js +72 -0
  45. package/dist/esm/utils/markdown/parser.js.map +1 -0
  46. package/dist/esm/utils/markdown/types.js +2 -0
  47. package/dist/esm/utils/markdown/types.js.map +1 -0
  48. package/dist/esm/utils/package-resolver.js +40 -0
  49. package/dist/esm/utils/package-resolver.js.map +1 -0
  50. package/dist/esm/utils/ts-doc/constants.js +13 -0
  51. package/dist/esm/utils/ts-doc/constants.js.map +1 -0
  52. package/dist/esm/utils/ts-doc/extractors.js +175 -0
  53. package/dist/esm/utils/ts-doc/extractors.js.map +1 -0
  54. package/dist/esm/utils/ts-doc/index.js +3 -0
  55. package/dist/esm/utils/ts-doc/index.js.map +1 -0
  56. package/dist/esm/utils/ts-doc/parser.js +37 -0
  57. package/dist/esm/utils/ts-doc/parser.js.map +1 -0
  58. package/dist/esm/utils/ts-doc/types.js +2 -0
  59. package/dist/esm/utils/ts-doc/types.js.map +1 -0
  60. package/dist/esm/utils/types.js +2 -0
  61. package/dist/esm/utils/types.js.map +1 -0
  62. package/dist/esm/version.js +3 -0
  63. package/dist/esm/version.js.map +1 -0
  64. package/dist/tsconfig.tsbuildinfo +1 -0
  65. package/dist/types/bin/apply-metadata.d.ts +1 -0
  66. package/dist/types/bin/delete-removed-files.d.ts +1 -0
  67. package/dist/types/bin/embed.d.ts +1 -0
  68. package/dist/types/bin/execute-pipeline.d.ts +1 -0
  69. package/dist/types/bin/file-stream.d.ts +1 -0
  70. package/dist/types/bin/get-diff.d.ts +1 -0
  71. package/dist/types/bin/index.d.ts +1 -0
  72. package/dist/types/bin/types.d.ts +1 -0
  73. package/dist/types/command.d.ts +2 -0
  74. package/dist/types/command.options.d.ts +62 -0
  75. package/dist/types/config.d.ts +33 -0
  76. package/dist/types/index.d.ts +8 -0
  77. package/dist/types/utils/generate-chunk-id.d.ts +8 -0
  78. package/dist/types/utils/git/file-changes.d.ts +21 -0
  79. package/dist/types/utils/git/git-client.d.ts +17 -0
  80. package/dist/types/utils/git/index.d.ts +5 -0
  81. package/dist/types/utils/git/metadata.d.ts +7 -0
  82. package/dist/types/utils/git/status.d.ts +12 -0
  83. package/dist/types/utils/git/types.d.ts +33 -0
  84. package/dist/types/utils/markdown/index.d.ts +2 -0
  85. package/dist/types/utils/markdown/parser.d.ts +21 -0
  86. package/dist/types/utils/markdown/types.d.ts +11 -0
  87. package/dist/types/utils/package-resolver.d.ts +14 -0
  88. package/dist/types/utils/ts-doc/constants.d.ts +5 -0
  89. package/dist/types/utils/ts-doc/extractors.d.ts +28 -0
  90. package/dist/types/utils/ts-doc/index.d.ts +2 -0
  91. package/dist/types/utils/ts-doc/parser.d.ts +23 -0
  92. package/dist/types/utils/ts-doc/types.d.ts +20 -0
  93. package/dist/types/utils/types.d.ts +17 -0
  94. package/dist/types/version.d.ts +1 -0
  95. package/package.json +72 -0
  96. package/src/bin/apply-metadata.ts +77 -0
  97. package/src/bin/delete-removed-files.ts +49 -0
  98. package/src/bin/embed.ts +262 -0
  99. package/src/bin/execute-pipeline.ts +48 -0
  100. package/src/bin/file-stream.ts +34 -0
  101. package/src/bin/get-diff.ts +33 -0
  102. package/src/bin/index.ts +1 -0
  103. package/src/bin/types.ts +48 -0
  104. package/src/command.options.ts +58 -0
  105. package/src/command.ts +100 -0
  106. package/src/config.ts +39 -0
  107. package/src/index.ts +19 -0
  108. package/src/utils/generate-chunk-id.ts +17 -0
  109. package/src/utils/git/file-changes.ts +213 -0
  110. package/src/utils/git/git-client.ts +43 -0
  111. package/src/utils/git/index.ts +19 -0
  112. package/src/utils/git/metadata.ts +47 -0
  113. package/src/utils/git/status.ts +48 -0
  114. package/src/utils/git/types.ts +36 -0
  115. package/src/utils/markdown/index.ts +5 -0
  116. package/src/utils/markdown/parser.ts +92 -0
  117. package/src/utils/markdown/types.ts +20 -0
  118. package/src/utils/package-resolver.ts +44 -0
  119. package/src/utils/ts-doc/constants.ts +13 -0
  120. package/src/utils/ts-doc/extractors.ts +246 -0
  121. package/src/utils/ts-doc/index.ts +5 -0
  122. package/src/utils/ts-doc/parser.ts +51 -0
  123. package/src/utils/ts-doc/types.ts +26 -0
  124. package/src/utils/types.ts +18 -0
  125. package/src/version.ts +2 -0
  126. package/tsconfig.json +27 -0
  127. package/vitest.config.ts +14 -0
@@ -0,0 +1,82 @@
1
+ import { createCommand, createOption } from 'commander';
2
+ import { loadFusionAIConfig, setupFramework } from '@equinor/fusion-framework-cli-plugin-ai-base';
3
+ import { withOptions as withAiOptions } from '@equinor/fusion-framework-cli-plugin-ai-base/command-options';
4
+ import { embed } from './bin/embed.js';
5
+ import { CommandOptionsSchema } from './command.options.js';
6
+ /**
7
+ * CLI command: `ai embeddings`
8
+ *
9
+ * Document embedding utilities for Large Language Model processing.
10
+ *
11
+ * Features:
12
+ * - Markdown/MDX document chunking with frontmatter extraction
13
+ * - TypeScript/TSX TSDoc extraction and chunking
14
+ * - Glob pattern support for file collection
15
+ * - Git diff-based processing for workflow integration
16
+ * - Dry-run mode for testing without actual processing
17
+ * - Configurable file patterns via fusion-ai.config.ts
18
+ *
19
+ * Usage:
20
+ * $ ffc ai embeddings [options] [glob-patterns...]
21
+ *
22
+ * Arguments:
23
+ * glob-patterns Glob patterns to match files (optional when using --diff)
24
+ * Defaults to patterns from fusion-ai.config.ts if not provided
25
+ *
26
+ * Options:
27
+ * --dry-run Show what would be processed without actually doing it
28
+ * --config <config> Path to a config file (default: fusion-ai.config)
29
+ * --diff Process only changed files (workflow mode)
30
+ * --base-ref <ref> Git reference to compare against (default: HEAD~1)
31
+ * --clean Delete all existing documents from the vector store before processing
32
+ *
33
+ * AI Options (required):
34
+ * --openai-api-key <key> Azure OpenAI API key (or AZURE_OPENAI_API_KEY env var)
35
+ * --openai-api-version <version> Azure OpenAI API version (default: 2024-02-15-preview)
36
+ * --openai-instance <name> Azure OpenAI instance name (or AZURE_OPENAI_INSTANCE_NAME env var)
37
+ * --openai-embedding-deployment <name> Azure OpenAI embedding deployment name (or AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME env var)
38
+ * --azure-search-endpoint <url> Azure Search endpoint URL (or AZURE_SEARCH_ENDPOINT env var)
39
+ * --azure-search-api-key <key> Azure Search API key (or AZURE_SEARCH_API_KEY env var)
40
+ * --azure-search-index-name <name> Azure Search index name (or AZURE_SEARCH_INDEX_NAME env var)
41
+ *
42
+ * Examples:
43
+ * $ ffc ai embeddings --dry-run ./src
44
+ * $ ffc ai embeddings "*.ts" "*.md" "*.mdx"
45
+ * $ ffc ai embeddings --diff
46
+ * $ ffc ai embeddings --diff --base-ref origin/main
47
+ * $ ffc ai embeddings --clean "*.ts"
48
+ */
49
+ const _command = createCommand('embeddings')
50
+ .description('Document embedding utilities for Large Language Model processing')
51
+ .addOption(createOption('--dry-run', 'Show what would be processed without actually doing it').default(false))
52
+ .addOption(createOption('--config <config>', 'Path to a config file').default('fusion-ai.config'))
53
+ .addOption(createOption('--diff', 'Process only changed files (workflow mode)').default(false))
54
+ .addOption(createOption('--base-ref <ref>', 'Git reference to compare against').default('HEAD~1'))
55
+ .addOption(createOption('--clean', 'Delete all existing documents from the vector store before processing').default(false))
56
+ .argument('[glob-patterns...]', 'Glob patterns to match files (optional when using --diff)')
57
+ .action(async (patterns, commandOptions) => {
58
+ const options = await CommandOptionsSchema.parseAsync(commandOptions);
59
+ // Load configuration
60
+ const config = await loadFusionAIConfig(options.config, {
61
+ baseDir: process.cwd(),
62
+ });
63
+ // CLI args take precedence over config patterns
64
+ const indexConfig = config.index ?? {};
65
+ const allowedFilePatterns = indexConfig.patterns ?? ['**/*.ts', '**/*.md', '**/*.mdx'];
66
+ const filePatterns = patterns.length ? patterns : allowedFilePatterns;
67
+ // Initialize framework
68
+ const framework = await setupFramework(options);
69
+ // Execute embeddings bin with framework and options
70
+ await embed({
71
+ framework,
72
+ options,
73
+ config,
74
+ filePatterns,
75
+ });
76
+ });
77
+ export const command = withAiOptions(_command, {
78
+ includeEmbedding: true,
79
+ includeSearch: true,
80
+ });
81
+ export default command;
82
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,8CAA8C,CAAC;AAClG,OAAO,EAAE,WAAW,IAAI,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAE5G,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAuB,MAAM,sBAAsB,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,QAAQ,GAAG,aAAa,CAAC,YAAY,CAAC;KACzC,WAAW,CAAC,kEAAkE,CAAC;KAC/E,SAAS,CACR,YAAY,CAAC,WAAW,EAAE,wDAAwD,CAAC,CAAC,OAAO,CACzF,KAAK,CACN,CACF;KACA,SAAS,CAAC,YAAY,CAAC,mBAAmB,EAAE,uBAAuB,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;KACjG,SAAS,CAAC,YAAY,CAAC,QAAQ,EAAE,4CAA4C,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;KAC9F,SAAS,CAAC,YAAY,CAAC,kBAAkB,EAAE,kCAAkC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;KACjG,SAAS,CACR,YAAY,CACV,SAAS,EACT,uEAAuE,CACxE,CAAC,OAAO,CAAC,KAAK,CAAC,CACjB;KACA,QAAQ,CAAC,oBAAoB,EAAE,2DAA2D,CAAC;KAC3F,MAAM,CAAC,KAAK,EAAE,QAAkB,EAAE,cAA8B,EAAE,EAAE;IACnE,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IAEtE,qBAAqB;IACrB,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAA0B,OAAO,CAAC,MAAM,EAAE;QAC/E,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE;KACvB,CAAC,CAAC;IAEH,gDAAgD;IAChD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IACvC,MAAM,mBAAmB,GAAG,WAAW,CAAC,QAAQ,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IACvF,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC;IAEtE,uBAAuB;IACvB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IAEhD,oDAAoD;IACpD,MAAM,KAAK,CAAC;QACV,SAAS;QACT,OAAO;QACP,MAAM;QACN,YAAY;KACb,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,EAAE;IAC7C,gBAAgB,EAAE,IAAI;IACtB,aAAa,EAAE,IAAI;CACpB,CAAC,CAAC;AAEH,eAAe,OAAO,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ import { AiOptionsSchema } from '@equinor/fusion-framework-cli-plugin-ai-base/command-options';
3
+ /**
4
+ * Zod schema for validating command options for the embeddings command.
5
+ *
6
+ * This schema extends the base AI options schema with embeddings-specific options,
7
+ * ensuring type safety and runtime validation of command arguments.
8
+ *
9
+ * Note: Some optional AI options become required for the embeddings command
10
+ * (openaiEmbeddingDeployment, azureSearchEndpoint, azureSearchApiKey, azureSearchIndexName)
11
+ * because the command uses withAiOptions with includeEmbedding and includeSearch set to true.
12
+ */
13
+ export const CommandOptionsSchema = AiOptionsSchema.extend({
14
+ // Override optional AI options to make them required for embeddings command
15
+ openaiEmbeddingDeployment: z
16
+ .string({ message: 'Embedding deployment name is required for embeddings command.' })
17
+ .min(1, 'Embedding deployment name must be a non-empty string.')
18
+ .describe('Azure OpenAI embedding deployment name'),
19
+ azureSearchEndpoint: z
20
+ .string({ message: 'Azure Search endpoint is required for embeddings command.' })
21
+ .url('Azure Search endpoint must be a valid URL.')
22
+ .min(1, 'Azure Search endpoint must be a non-empty string.')
23
+ .describe('Azure Search endpoint URL'),
24
+ azureSearchApiKey: z
25
+ .string({ message: 'Azure Search API key is required for embeddings command.' })
26
+ .min(1, 'Azure Search API key must be a non-empty string.')
27
+ .describe('Azure Search API key'),
28
+ azureSearchIndexName: z
29
+ .string({ message: 'Azure Search index name is required for embeddings command.' })
30
+ .min(1, 'Azure Search index name must be a non-empty string.')
31
+ .describe('Azure Search index name'),
32
+ // Embeddings-specific options
33
+ dryRun: z
34
+ .boolean({ message: 'dryRun must be a boolean value.' })
35
+ .describe('Show what would be processed without actually doing it'),
36
+ config: z
37
+ .string({ message: 'Config file path is required and must be a non-empty string.' })
38
+ .min(1, 'Config file path must be a non-empty string.')
39
+ .describe('Path to a config file'),
40
+ diff: z
41
+ .boolean({ message: 'diff must be a boolean value.' })
42
+ .describe('Process only changed files (workflow mode)'),
43
+ baseRef: z.string().min(1).optional().describe('Git reference to compare against'),
44
+ clean: z
45
+ .boolean({ message: 'clean must be a boolean value.' })
46
+ .describe('Delete all existing documents from the vector store before processing'),
47
+ }).describe('Command options for the embeddings command');
48
+ //# sourceMappingURL=command.options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.options.js","sourceRoot":"","sources":["../../src/command.options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAC;AAE/F;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC,MAAM,CAAC;IACzD,4EAA4E;IAC5E,yBAAyB,EAAE,CAAC;SACzB,MAAM,CAAC,EAAE,OAAO,EAAE,+DAA+D,EAAE,CAAC;SACpF,GAAG,CAAC,CAAC,EAAE,uDAAuD,CAAC;SAC/D,QAAQ,CAAC,wCAAwC,CAAC;IACrD,mBAAmB,EAAE,CAAC;SACnB,MAAM,CAAC,EAAE,OAAO,EAAE,2DAA2D,EAAE,CAAC;SAChF,GAAG,CAAC,4CAA4C,CAAC;SACjD,GAAG,CAAC,CAAC,EAAE,mDAAmD,CAAC;SAC3D,QAAQ,CAAC,2BAA2B,CAAC;IACxC,iBAAiB,EAAE,CAAC;SACjB,MAAM,CAAC,EAAE,OAAO,EAAE,0DAA0D,EAAE,CAAC;SAC/E,GAAG,CAAC,CAAC,EAAE,kDAAkD,CAAC;SAC1D,QAAQ,CAAC,sBAAsB,CAAC;IACnC,oBAAoB,EAAE,CAAC;SACpB,MAAM,CAAC,EAAE,OAAO,EAAE,6DAA6D,EAAE,CAAC;SAClF,GAAG,CAAC,CAAC,EAAE,qDAAqD,CAAC;SAC7D,QAAQ,CAAC,yBAAyB,CAAC;IAEtC,8BAA8B;IAC9B,MAAM,EAAE,CAAC;SACN,OAAO,CAAC,EAAE,OAAO,EAAE,iCAAiC,EAAE,CAAC;SACvD,QAAQ,CAAC,wDAAwD,CAAC;IACrE,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,EAAE,OAAO,EAAE,8DAA8D,EAAE,CAAC;SACnF,GAAG,CAAC,CAAC,EAAE,8CAA8C,CAAC;SACtD,QAAQ,CAAC,uBAAuB,CAAC;IACpC,IAAI,EAAE,CAAC;SACJ,OAAO,CAAC,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;SACrD,QAAQ,CAAC,4CAA4C,CAAC;IACzD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;IAClF,KAAK,EAAE,CAAC;SACL,OAAO,CAAC,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC;SACtD,QAAQ,CAAC,uEAAuE,CAAC;CACrF,CAAC,CAAC,QAAQ,CAAC,4CAA4C,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import { registerAiPlugin as registerAiPluginBase } from '@equinor/fusion-framework-cli-plugin-ai-base';
2
+ import { command as embeddingsCommand } from './command.js';
3
+ /**
4
+ * Registers the AI index plugin command with the CLI program
5
+ * @param program - The Commander program instance to register commands with
6
+ */
7
+ export function registerAiPlugin(program) {
8
+ registerAiPluginBase(program, embeddingsCommand);
9
+ }
10
+ export default registerAiPlugin;
11
+ // Re-export config utilities for convenience
12
+ export { configureFusionAI, } from '@equinor/fusion-framework-cli-plugin-ai-base';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AACxG,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAE5D;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;AACnD,CAAC;AAED,eAAe,gBAAgB,CAAC;AAEhC,6CAA6C;AAC7C,OAAO,EACL,iBAAiB,GAElB,MAAM,8CAA8C,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Generates a unique identifier for a document chunk based on file path
3
+ * Creates a deterministic, URL-safe hash from the file path for validation and checks
4
+ * @param filePath - The file path to generate an ID from
5
+ * @param chunkIndex - Optional chunk index to append for multi-chunk documents
6
+ * @returns A base64-encoded hash of the file path, optionally suffixed with chunk index
7
+ */
8
+ export const generateChunkId = (filePath, chunkIndex) => {
9
+ // Convert file path to base64 and remove non-alphanumeric characters
10
+ // This creates a stable, URL-safe identifier from the file path
11
+ // The deterministic nature allows for validation and duplicate detection
12
+ const pathHash = Buffer.from(filePath)
13
+ .toString('base64')
14
+ .replace(/[^a-zA-Z0-9]/g, '');
15
+ // Append chunk index if provided to distinguish multiple chunks from the same file
16
+ return chunkIndex ? `${pathHash}-${chunkIndex}` : pathHash;
17
+ };
18
+ //# sourceMappingURL=generate-chunk-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-chunk-id.js","sourceRoot":"","sources":["../../../src/utils/generate-chunk-id.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAgB,EAAE,UAAmB,EAAU,EAAE;IAC/E,qEAAqE;IACrE,gEAAgE;IAChE,yEAAyE;IACzE,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;SACnC,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IAChC,mFAAmF;IACnF,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7D,CAAC,CAAC"}
@@ -0,0 +1,196 @@
1
+ import { join, relative } from 'node:path';
2
+ import { resolveProjectRoot, getGit } from './git-client.js';
3
+ /**
4
+ * Get list of changed files using git diff with status
5
+ * @param options - Git diff configuration options
6
+ * @returns Array of changed files with their status
7
+ */
8
+ export const getChangedFiles = async (options) => {
9
+ const { diff, baseRef = 'HEAD~1', cwd = process.cwd() } = options;
10
+ if (!diff) {
11
+ return [];
12
+ }
13
+ const projectRoot = resolveProjectRoot(cwd);
14
+ if (!projectRoot) {
15
+ throw new Error('Not in a git repository. Cannot use --diff option.');
16
+ }
17
+ const { git } = getGit(cwd) ?? {};
18
+ if (!git) {
19
+ throw new Error('Failed to initialize git client');
20
+ }
21
+ try {
22
+ // Get changes since baseRef with status (A=added, M=modified, D=deleted)
23
+ try {
24
+ const diffResult = await git.diff([`${baseRef}`, '--name-status']);
25
+ const lines = diffResult.split('\n').filter((line) => line.trim() !== '');
26
+ const changedFiles = [];
27
+ for (const line of lines) {
28
+ // Match status and file path
29
+ // Format: "A\tfile.ts" or "M\tfile.ts" or "D\tfile.ts"
30
+ // Also handle renames: "R100\told.ts\tnew.ts"
31
+ const renameMatch = line.match(/^R\d*\s+(.+?)\s+(.+)$/);
32
+ if (renameMatch) {
33
+ const [, oldFile, newFile] = renameMatch;
34
+ // Add both the removed old file and the new file
35
+ changedFiles.push({ filepath: `${projectRoot}/${oldFile}`, status: 'removed' });
36
+ changedFiles.push({ filepath: `${projectRoot}/${newFile}`, status: 'new' });
37
+ continue;
38
+ }
39
+ const match = line.match(/^([AMD])\s+(.+)$/);
40
+ if (match) {
41
+ const [, gitStatus, file] = match;
42
+ const fullPath = `${projectRoot}/${file}`;
43
+ let status;
44
+ if (gitStatus === 'A') {
45
+ status = 'new';
46
+ }
47
+ else if (gitStatus === 'M') {
48
+ status = 'modified';
49
+ }
50
+ else if (gitStatus === 'D') {
51
+ status = 'removed';
52
+ }
53
+ else {
54
+ // Skip unknown statuses (C=copied, etc.)
55
+ continue;
56
+ }
57
+ changedFiles.push({ filepath: fullPath, status });
58
+ }
59
+ }
60
+ return changedFiles;
61
+ }
62
+ catch {
63
+ // Handle case where baseRef doesn't exist (e.g., first commit)
64
+ console.warn(`⚠️ Warning: Git reference '${baseRef}' not found. Processing all files.`);
65
+ return [];
66
+ }
67
+ }
68
+ catch (error) {
69
+ throw new Error(`Git diff failed: ${error instanceof Error ? error.message : String(error)}`);
70
+ }
71
+ };
72
+ /**
73
+ * Determine the git status of a file, including handling renames
74
+ * Returns an array of ChangedFile objects - if the file was renamed, returns both old and new paths
75
+ * @param filePath - Absolute file path to check
76
+ * @returns Promise resolving to array of changed files (1 or 2 items if renamed)
77
+ */
78
+ export const getFileStatus = async (filePath) => {
79
+ const { git, gitRepoPath } = getGit(filePath) ?? {};
80
+ if (!git || !gitRepoPath) {
81
+ // Not in a git repository, assume new
82
+ return [{ filepath: filePath, status: 'new' }];
83
+ }
84
+ const gitFilePath = relative(gitRepoPath, filePath);
85
+ // Normalize path separators for git commands (git uses forward slashes on all platforms)
86
+ const normalizedGitFilePath = gitFilePath.replace(/\\/g, '/');
87
+ try {
88
+ // First check if file is tracked in git at the current path
89
+ const isTracked = await git
90
+ .raw(['ls-files', '--error-unmatch', normalizedGitFilePath])
91
+ .then(() => true)
92
+ .catch(() => false);
93
+ if (isTracked) {
94
+ // File is tracked at this path, it's modified
95
+ return [{ filepath: filePath, status: 'modified' }];
96
+ }
97
+ // File is not tracked - quickly check if it's explicitly untracked
98
+ // This is much faster than checking full status or history
99
+ try {
100
+ const fileStatusOutput = await git.raw([
101
+ 'status',
102
+ '--porcelain',
103
+ '--',
104
+ normalizedGitFilePath,
105
+ ]);
106
+ const trimmed = fileStatusOutput.trim();
107
+ if (trimmed.length > 0) {
108
+ // If status shows ??, it's untracked (truly new)
109
+ if (/^\?\?/.test(trimmed)) {
110
+ return [{ filepath: filePath, status: 'new' }];
111
+ }
112
+ }
113
+ }
114
+ catch {
115
+ // If status check fails, continue to rename/history checks
116
+ }
117
+ // File is not tracked and not explicitly untracked - check if it's a rename
118
+ // Only do expensive checks if we haven't determined status yet
119
+ try {
120
+ // Get full git status to check for renames (only if needed)
121
+ const statusOutput = await git.raw(['status', '--porcelain']);
122
+ const lines = statusOutput.split('\n').filter((line) => line.trim() !== '');
123
+ for (const line of lines) {
124
+ // Check for rename format: "R100\told.ts\tnew.ts"
125
+ const renameMatch = line.match(/^R\d+\s+(.+?)\s+(.+)$/);
126
+ if (renameMatch) {
127
+ const [, oldPath, newPath] = renameMatch;
128
+ const oldFullPath = join(gitRepoPath, oldPath);
129
+ const newFullPath = join(gitRepoPath, newPath);
130
+ // Check if the current file is the new path in a rename
131
+ if (newFullPath === filePath) {
132
+ return [
133
+ { filepath: oldFullPath, status: 'removed' },
134
+ { filepath: newFullPath, status: 'new' },
135
+ ];
136
+ }
137
+ }
138
+ // Check for copy format: "C100\told.ts\tnew.ts" (similar to rename)
139
+ const copyMatch = line.match(/^C\d+\s+(.+?)\s+(.+)$/);
140
+ if (copyMatch) {
141
+ const [, , newPath] = copyMatch;
142
+ const newFullPath = join(gitRepoPath, newPath);
143
+ // For copies, the old file still exists, so only return the new one
144
+ if (newFullPath === filePath) {
145
+ return [{ filepath: newFullPath, status: 'new' }];
146
+ }
147
+ }
148
+ }
149
+ }
150
+ catch {
151
+ // If status check fails, continue to history check
152
+ }
153
+ // Last resort: check if file content exists in git history (very slow, only if needed)
154
+ // Use --follow to track renames, limit to 1 commit for performance
155
+ try {
156
+ const hasHistory = await git
157
+ .raw([
158
+ 'log',
159
+ '--all',
160
+ '--full-history',
161
+ '--follow',
162
+ '--oneline',
163
+ '-1',
164
+ '--',
165
+ normalizedGitFilePath,
166
+ ])
167
+ .then((output) => output.trim().length > 0)
168
+ .catch(() => false);
169
+ // If file has history but isn't tracked, it might have been moved
170
+ // For now, treat as 'new' at the new location
171
+ // Note: We can't easily find the old path without more complex git operations
172
+ return [{ filepath: filePath, status: hasHistory ? 'modified' : 'new' }];
173
+ }
174
+ catch {
175
+ // If we can't determine, default to 'new'
176
+ return [{ filepath: filePath, status: 'new' }];
177
+ }
178
+ }
179
+ catch {
180
+ // If we can't determine status, default to 'new'
181
+ return [{ filepath: filePath, status: 'new' }];
182
+ }
183
+ };
184
+ /**
185
+ * Check if a file path matches any of the changed files
186
+ * @param filePath - File path to check
187
+ * @param changedFiles - Array of changed file objects
188
+ * @returns True if file has changed
189
+ */
190
+ export const isFileChanged = (filePath, changedFiles) => {
191
+ if (changedFiles.length === 0) {
192
+ return true; // If no diff filtering, process all files
193
+ }
194
+ return changedFiles.some((file) => file.filepath === filePath);
195
+ };
196
+ //# sourceMappingURL=file-changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-changes.js","sourceRoot":"","sources":["../../../../src/utils/git/file-changes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { simpleGit } from 'simple-git';
2
+ import { findUpSync } from 'find-up';
3
+ import { dirname, join } from 'node:path';
4
+ import { existsSync } from 'node:fs';
5
+ const gitCache = new Map();
6
+ /**
7
+ * Resolve the project root (git repository root) for a given file path
8
+ * @param filePath - File path to resolve from
9
+ * @returns Project root path or undefined if not in a git repository
10
+ */
11
+ export const resolveProjectRoot = (filePath) => {
12
+ // if we are in the root of the git repository, return the root
13
+ if (existsSync(join(filePath, '.git'))) {
14
+ return filePath;
15
+ }
16
+ const gitRepoPath = findUpSync('.git', { cwd: dirname(filePath), type: 'both' });
17
+ const projectRoot = gitRepoPath?.replace(/\.git$/, '');
18
+ return projectRoot;
19
+ };
20
+ /**
21
+ * Get or create a SimpleGit instance for a given file path
22
+ * Uses caching to avoid creating multiple instances for the same repository
23
+ * @param filePath - File path to get git instance for
24
+ * @returns Git instance and repository path, or undefined if not in a git repository
25
+ */
26
+ export const getGit = (filePath) => {
27
+ const gitRepoPath = resolveProjectRoot(filePath);
28
+ if (gitRepoPath) {
29
+ if (!gitCache.has(gitRepoPath)) {
30
+ gitCache.set(gitRepoPath, simpleGit(gitRepoPath));
31
+ }
32
+ return {
33
+ git: gitCache.get(gitRepoPath),
34
+ gitRepoPath,
35
+ };
36
+ }
37
+ return undefined;
38
+ };
39
+ //# sourceMappingURL=git-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-client.js","sourceRoot":"","sources":["../../../../src/utils/git/git-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;AAE9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAsB,EAAE;IACzE,+DAA+D;IAC/D,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACvC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACjF,MAAM,WAAW,GAAG,WAAW,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACvD,OAAO,WAAW,CAAC;AACrB,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,QAAgB,EACiD,EAAE;IACnE,MAAM,WAAW,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,WAAW,EAAE,CAAC;QAChB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;QACpD,CAAC;QACD,OAAO;YACL,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC;YAC9B,WAAW;SACZ,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ // Re-export git client utilities
2
+ export { resolveProjectRoot, getGit } from './git-client.js';
3
+ // Re-export metadata functions
4
+ export { extractGitMetadata } from './metadata.js';
5
+ // Re-export file change functions
6
+ export { getChangedFiles, getFileStatus, isFileChanged } from './file-changes.js';
7
+ // Re-export status functions
8
+ export { getGitStatus } from './status.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/git/index.ts"],"names":[],"mappings":"AAQA,iCAAiC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE7D,+BAA+B;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,kCAAkC;AAClC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElF,6BAA6B;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { relative } from 'node:path';
2
+ import { getGit } from './git-client.js';
3
+ /**
4
+ * Generate a GitHub permalink for a file
5
+ * @param gitRemoteUrl - Git remote URL
6
+ * @param filePath - Relative file path from repository root
7
+ * @param slug - Git reference (branch/tag/commit), defaults to 'main'
8
+ * @returns GitHub permalink URL or undefined if not a GitHub repository
9
+ * @internal
10
+ */
11
+ const generateGithubPermalink = (gitRemoteUrl, filePath, slug) => {
12
+ const githubMatch = gitRemoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
13
+ if (githubMatch) {
14
+ const [, owner, repo] = githubMatch;
15
+ return `https://github.com/${owner}/${repo}/blob/${slug ?? 'main'}/${filePath}`;
16
+ }
17
+ return undefined;
18
+ };
19
+ /**
20
+ * Extract git metadata for a file
21
+ * @param filePath - Absolute file path
22
+ * @returns Git metadata or undefined if not in a git repository
23
+ */
24
+ export const extractGitMetadata = async (filePath) => {
25
+ const { git, gitRepoPath: gitRepoRoot } = getGit(filePath) ?? {};
26
+ if (!git || !gitRepoRoot) {
27
+ return undefined;
28
+ }
29
+ const gitFilePath = relative(gitRepoRoot, filePath);
30
+ const { latest } = await git.log({ file: gitFilePath, maxCount: 1 });
31
+ const gitRemoteUrl = await git
32
+ .getConfig('remote.origin.url')
33
+ .then(({ value }) => value ?? undefined);
34
+ const git_link = gitRemoteUrl ? generateGithubPermalink(gitRemoteUrl, gitFilePath) : undefined;
35
+ return {
36
+ git_link,
37
+ git_commit_hash: latest?.hash,
38
+ git_commit_date: latest?.date,
39
+ };
40
+ };
41
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../../src/utils/git/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;;;;;;GAOG;AACH,MAAM,uBAAuB,GAAG,CAC9B,YAAoB,EACpB,QAAgB,EAChB,IAAa,EACO,EAAE;IACtB,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACtF,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,WAAW,CAAC;QACpC,OAAO,sBAAsB,KAAK,IAAI,IAAI,SAAS,IAAI,IAAI,MAAM,IAAI,QAAQ,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,QAAgB,EAAoC,EAAE;IAC7F,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjE,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACpD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IACrE,MAAM,YAAY,GAAG,MAAM,GAAG;SAC3B,SAAS,CAAC,mBAAmB,CAAC;SAC9B,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,uBAAuB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/F,OAAO;QACL,QAAQ;QACR,eAAe,EAAE,MAAM,EAAE,IAAI;QAC7B,eAAe,EAAE,MAAM,EAAE,IAAI;KAC9B,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { resolveProjectRoot, getGit } from './git-client.js';
2
+ /**
3
+ * Get git status information for debugging
4
+ * @param cwd - Working directory
5
+ * @returns Git status information
6
+ */
7
+ export const getGitStatus = async (cwd = process.cwd()) => {
8
+ const projectRoot = resolveProjectRoot(cwd);
9
+ if (!projectRoot) {
10
+ throw new Error('Not in a git repository');
11
+ }
12
+ const { git } = getGit(cwd) ?? {};
13
+ if (!git) {
14
+ throw new Error('Failed to initialize git client');
15
+ }
16
+ try {
17
+ const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
18
+ const commit = await git.revparse(['--short', 'HEAD']);
19
+ const statusResult = await git.status();
20
+ const stagedFiles = statusResult.staged.length;
21
+ const unstagedFiles = statusResult.modified.length + statusResult.deleted.length + statusResult.not_added.length;
22
+ return {
23
+ branch: branch.trim(),
24
+ commit: commit.trim(),
25
+ hasChanges: stagedFiles > 0 || unstagedFiles > 0,
26
+ stagedFiles,
27
+ unstagedFiles,
28
+ };
29
+ }
30
+ catch (error) {
31
+ throw new Error(`Failed to get git status: ${error instanceof Error ? error.message : String(error)}`);
32
+ }
33
+ };
34
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../src/utils/git/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE7D;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,MAAc,OAAO,CAAC,GAAG,EAAE,EAO1B,EAAE;IACH,MAAM,WAAW,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;QAEvD,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC;QAC/C,MAAM,aAAa,GACjB,YAAY,CAAC,QAAQ,CAAC,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC;QAE7F,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;YACrB,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;YACrB,UAAU,EAAE,WAAW,GAAG,CAAC,IAAI,aAAa,GAAG,CAAC;YAChD,WAAW;YACX,aAAa;SACd,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,6BAA6B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtF,CAAC;IACJ,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/utils/git/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ // Re-export parser functions
2
+ export { isMarkdownFile, parseMarkdown, parseMarkdownFile } from './parser.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/markdown/index.ts"],"names":[],"mappings":"AAGA,6BAA6B;AAC7B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { assert } from 'node:console';
3
+ import { default as grayMatter } from 'gray-matter';
4
+ import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
5
+ import { generateChunkId } from '../generate-chunk-id.js';
6
+ const markdownConfig = {
7
+ chunkSize: 3000,
8
+ chunkOverlap: 300,
9
+ keepSeparator: true,
10
+ separators: [
11
+ '\n# ',
12
+ '\n## ',
13
+ '\n### ',
14
+ '\n#### ',
15
+ '\n##### ',
16
+ '\n###### ',
17
+ '\n```',
18
+ '\n---\n',
19
+ '\n\n',
20
+ ],
21
+ };
22
+ /**
23
+ * Check if a file is a markdown or MDX file
24
+ * @param filePath - File path to check
25
+ * @returns True if file has .md or .mdx extension
26
+ */
27
+ export const isMarkdownFile = (filePath) => {
28
+ return filePath.endsWith('.md') || filePath.endsWith('.mdx');
29
+ };
30
+ /**
31
+ * Parse markdown or MDX content into document chunks
32
+ * @param content - Markdown or MDX content string
33
+ * @param source - Source file path
34
+ * @returns Array of markdown documents
35
+ */
36
+ export const parseMarkdown = async (content, source) => {
37
+ const { content: markdownContent, data } = grayMatter(content);
38
+ const markdownAttributes = Object.entries(data).reduce((acc, [key, value]) => {
39
+ acc[`md_${key}`] = value;
40
+ return acc;
41
+ }, {
42
+ type: 'markdown',
43
+ });
44
+ const textSplitter = new RecursiveCharacterTextSplitter(markdownConfig);
45
+ const chunks = await textSplitter.splitText(markdownContent);
46
+ return chunks.map((chunk, _index) => ({
47
+ id: generateChunkId(source, _index),
48
+ pageContent: chunk,
49
+ metadata: {
50
+ source,
51
+ attributes: markdownAttributes,
52
+ },
53
+ }));
54
+ };
55
+ /**
56
+ * Parse a markdown or MDX file into document chunks
57
+ * @param file - Source file object
58
+ * @returns Array of markdown documents with root path metadata
59
+ */
60
+ export const parseMarkdownFile = async (file) => {
61
+ assert(isMarkdownFile(file.path), `File ${file.path} is not a markdown or MDX file`);
62
+ const content = readFileSync(file.path, 'utf8');
63
+ const result = await parseMarkdown(content, file.relativePath ?? file.path);
64
+ return result.map((document) => ({
65
+ ...document,
66
+ metadata: {
67
+ ...document.metadata,
68
+ rootPath: file.projectRoot,
69
+ },
70
+ }));
71
+ };
72
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../../src/utils/markdown/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,cAAc,GAAG;IACrB,SAAS,EAAE,IAAI;IACf,YAAY,EAAE,GAAG;IACjB,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE;QACV,MAAM;QACN,OAAO;QACP,QAAQ;QACR,SAAS;QACT,UAAU;QACV,WAAW;QACX,OAAO;QACP,SAAS;QACT,MAAM;KACP;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAgB,EAAW,EAAE;IAC1D,OAAO,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC/D,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,OAAe,EACf,MAAc,EACkB,EAAE;IAClC,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/D,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CACpD,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACpB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC;QACzB,OAAO,GAAG,CAAC;IACb,CAAC,EACD;QACE,IAAI,EAAE,UAAU;KACU,CAC7B,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,8BAA8B,CAAC,cAAc,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAC7D,OAAO,MAAM,CAAC,GAAG,CACf,CAAC,KAAK,EAAE,MAAM,EAAuB,EAAE,CAAC,CAAC;QACvC,EAAE,EAAE,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC;QACnC,WAAW,EAAE,KAAK;QAClB,QAAQ,EAAE;YACR,MAAM;YACN,UAAU,EAAE,kBAAuD;SACpE;KACF,CAAC,CACH,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAGpC,IAAgB,EACgB,EAAE;IAClC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,QAAQ,IAAI,CAAC,IAAI,gCAAgC,CAAC,CAAC;IACrF,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAI,OAAO,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC/B,GAAG,QAAQ;QACX,QAAQ,EAAE;YACR,GAAG,QAAQ,CAAC,QAAQ;YACpB,QAAQ,EAAE,IAAI,CAAC,WAAW;SAC3B;KACF,CAAC,CAAC,CAAC;AACN,CAAC,CAAC"}