agentic-knowledge-mcp 0.0.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 (77) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +530 -0
  3. package/package.json +94 -0
  4. package/packages/cli/dist/cli.d.ts +5 -0
  5. package/packages/cli/dist/cli.js +21 -0
  6. package/packages/cli/dist/commands/create.d.ts +5 -0
  7. package/packages/cli/dist/commands/create.js +90 -0
  8. package/packages/cli/dist/commands/init.d.ts +5 -0
  9. package/packages/cli/dist/commands/init.js +182 -0
  10. package/packages/cli/dist/commands/refresh.d.ts +5 -0
  11. package/packages/cli/dist/commands/refresh.js +322 -0
  12. package/packages/cli/dist/commands/status.d.ts +5 -0
  13. package/packages/cli/dist/commands/status.js +268 -0
  14. package/packages/cli/dist/index.d.ts +6 -0
  15. package/packages/cli/dist/index.js +6 -0
  16. package/packages/cli/package.json +57 -0
  17. package/packages/content-loader/dist/__tests__/debug-filtering.d.ts +1 -0
  18. package/packages/content-loader/dist/__tests__/debug-filtering.js +17 -0
  19. package/packages/content-loader/dist/__tests__/test-filtering.d.ts +1 -0
  20. package/packages/content-loader/dist/__tests__/test-filtering.js +19 -0
  21. package/packages/content-loader/dist/content/api-documentation-loader.d.ts +26 -0
  22. package/packages/content-loader/dist/content/api-documentation-loader.js +45 -0
  23. package/packages/content-loader/dist/content/content-processor.d.ts +44 -0
  24. package/packages/content-loader/dist/content/content-processor.js +86 -0
  25. package/packages/content-loader/dist/content/documentation-site-loader.d.ts +26 -0
  26. package/packages/content-loader/dist/content/documentation-site-loader.js +45 -0
  27. package/packages/content-loader/dist/content/git-repo-loader.d.ts +79 -0
  28. package/packages/content-loader/dist/content/git-repo-loader.js +368 -0
  29. package/packages/content-loader/dist/content/index.d.ts +9 -0
  30. package/packages/content-loader/dist/content/index.js +9 -0
  31. package/packages/content-loader/dist/content/loader.d.ts +47 -0
  32. package/packages/content-loader/dist/content/loader.js +8 -0
  33. package/packages/content-loader/dist/content/metadata-manager.d.ts +65 -0
  34. package/packages/content-loader/dist/content/metadata-manager.js +160 -0
  35. package/packages/content-loader/dist/index.d.ts +5 -0
  36. package/packages/content-loader/dist/index.js +5 -0
  37. package/packages/content-loader/dist/types.d.ts +127 -0
  38. package/packages/content-loader/dist/types.js +48 -0
  39. package/packages/content-loader/package.json +50 -0
  40. package/packages/core/dist/config/discovery.d.ts +15 -0
  41. package/packages/core/dist/config/discovery.js +65 -0
  42. package/packages/core/dist/config/loader.d.ts +22 -0
  43. package/packages/core/dist/config/loader.js +236 -0
  44. package/packages/core/dist/config/manager.d.ts +55 -0
  45. package/packages/core/dist/config/manager.js +180 -0
  46. package/packages/core/dist/content/api-documentation-loader.d.ts +26 -0
  47. package/packages/core/dist/content/api-documentation-loader.js +45 -0
  48. package/packages/core/dist/content/content-processor.d.ts +44 -0
  49. package/packages/core/dist/content/content-processor.js +81 -0
  50. package/packages/core/dist/content/documentation-site-loader.d.ts +26 -0
  51. package/packages/core/dist/content/documentation-site-loader.js +45 -0
  52. package/packages/core/dist/content/git-repo-loader.d.ts +54 -0
  53. package/packages/core/dist/content/git-repo-loader.js +264 -0
  54. package/packages/core/dist/content/index.d.ts +9 -0
  55. package/packages/core/dist/content/index.js +9 -0
  56. package/packages/core/dist/content/loader.d.ts +50 -0
  57. package/packages/core/dist/content/loader.js +7 -0
  58. package/packages/core/dist/content/metadata-manager.d.ts +65 -0
  59. package/packages/core/dist/content/metadata-manager.js +160 -0
  60. package/packages/core/dist/index.d.ts +12 -0
  61. package/packages/core/dist/index.js +30 -0
  62. package/packages/core/dist/paths/calculator.d.ts +46 -0
  63. package/packages/core/dist/paths/calculator.js +166 -0
  64. package/packages/core/dist/templates/processor.d.ts +40 -0
  65. package/packages/core/dist/templates/processor.js +111 -0
  66. package/packages/core/dist/types.d.ts +129 -0
  67. package/packages/core/dist/types.js +79 -0
  68. package/packages/core/package.json +50 -0
  69. package/packages/mcp-server/dist/bin.d.ts +5 -0
  70. package/packages/mcp-server/dist/bin.js +10 -0
  71. package/packages/mcp-server/dist/cli.d.ts +7 -0
  72. package/packages/mcp-server/dist/cli.js +17 -0
  73. package/packages/mcp-server/dist/index.d.ts +8 -0
  74. package/packages/mcp-server/dist/index.js +9 -0
  75. package/packages/mcp-server/dist/server.d.ts +35 -0
  76. package/packages/mcp-server/dist/server.js +244 -0
  77. package/packages/mcp-server/package.json +54 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Documentation site content loader (STUB - not implemented yet)
3
+ */
4
+ import { ContentLoader } from "./loader.js";
5
+ import { WebSourceType, KnowledgeError, ErrorType } from "../types.js";
6
+ /**
7
+ * Content loader for documentation websites (STUB IMPLEMENTATION)
8
+ */
9
+ export class DocumentationSiteLoader extends ContentLoader {
10
+ /**
11
+ * Check if this loader can handle the given web source type
12
+ */
13
+ canHandle(webSource) {
14
+ return webSource.type === WebSourceType.DOCUMENTATION_SITE;
15
+ }
16
+ /**
17
+ * Validate the web source configuration
18
+ */
19
+ validateConfig(webSource) {
20
+ if (!webSource.url) {
21
+ return "Documentation site URL is required";
22
+ }
23
+ return true;
24
+ }
25
+ /**
26
+ * Load content from a documentation site (NOT IMPLEMENTED)
27
+ */
28
+ async load(webSource, targetPath) {
29
+ throw new KnowledgeError(
30
+ ErrorType.NOT_IMPLEMENTED,
31
+ "Documentation site loading is not yet implemented. Use git_repo type for repositories with documentation.",
32
+ { webSource: webSource.url, targetPath },
33
+ );
34
+ }
35
+ /**
36
+ * Get content identifier (NOT IMPLEMENTED)
37
+ */
38
+ async getContentId(webSource) {
39
+ throw new KnowledgeError(
40
+ ErrorType.NOT_IMPLEMENTED,
41
+ "Documentation site content ID generation is not yet implemented.",
42
+ { webSource: webSource.url },
43
+ );
44
+ }
45
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Git repository content loader
3
+ */
4
+ import { ContentLoader, type LoadResult } from "./loader.js";
5
+ import { WebSourceConfig } from "../types.js";
6
+ /**
7
+ * Content loader for Git repositories (GitHub, GitLab, any Git repo)
8
+ */
9
+ export declare class GitRepoLoader extends ContentLoader {
10
+ /**
11
+ * Check if this loader can handle the given web source type
12
+ */
13
+ canHandle(webSource: WebSourceConfig): boolean;
14
+ /**
15
+ * Validate the web source configuration
16
+ */
17
+ validateConfig(webSource: WebSourceConfig): true | string;
18
+ /**
19
+ * Load content from a Git repository
20
+ */
21
+ load(webSource: WebSourceConfig, targetPath: string): Promise<LoadResult>;
22
+ /**
23
+ * Get content identifier for change detection
24
+ */
25
+ getContentId(webSource: WebSourceConfig): Promise<string>;
26
+ /**
27
+ * Validate if URL is a valid Git repository URL
28
+ */
29
+ private isValidGitUrl;
30
+ /**
31
+ * Create a temporary directory for cloning
32
+ */
33
+ private createTempDirectory;
34
+ /**
35
+ * Clone the Git repository
36
+ */
37
+ private cloneRepository;
38
+ /**
39
+ * Extract content from cloned repository to target directory
40
+ */
41
+ private extractContent;
42
+ /**
43
+ * Copy directory recursively
44
+ */
45
+ private copyDirectory;
46
+ /**
47
+ * Generate content hash for change detection
48
+ */
49
+ private generateContentHash;
50
+ /**
51
+ * Clean up temporary directory
52
+ */
53
+ private cleanupTempDirectory;
54
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Git repository content loader
3
+ */
4
+ import { promises as fs } from "node:fs";
5
+ import * as path from "node:path";
6
+ import { execSync } from "node:child_process";
7
+ import { ContentLoader } from "./loader.js";
8
+ import { WebSourceType, KnowledgeError, ErrorType } from "../types.js";
9
+ import * as crypto from "node:crypto";
10
+ /**
11
+ * Content loader for Git repositories (GitHub, GitLab, any Git repo)
12
+ */
13
+ export class GitRepoLoader extends ContentLoader {
14
+ /**
15
+ * Check if this loader can handle the given web source type
16
+ */
17
+ canHandle(webSource) {
18
+ return webSource.type === WebSourceType.GIT_REPO;
19
+ }
20
+ /**
21
+ * Validate the web source configuration
22
+ */
23
+ validateConfig(webSource) {
24
+ if (!webSource.url) {
25
+ return "Git repository URL is required";
26
+ }
27
+ // Basic URL validation for Git repos
28
+ if (!this.isValidGitUrl(webSource.url)) {
29
+ return "Invalid Git repository URL";
30
+ }
31
+ return true;
32
+ }
33
+ /**
34
+ * Load content from a Git repository
35
+ */
36
+ async load(webSource, targetPath) {
37
+ try {
38
+ const options = webSource.options;
39
+ const tempDir = await this.createTempDirectory();
40
+ try {
41
+ // Clone the repository
42
+ await this.cloneRepository(webSource.url, tempDir, options);
43
+ // Extract specified paths or all content
44
+ const extractedFiles = await this.extractContent(
45
+ tempDir,
46
+ targetPath,
47
+ options?.paths,
48
+ );
49
+ // Generate content hash
50
+ const contentHash = await this.generateContentHash(
51
+ targetPath,
52
+ extractedFiles,
53
+ );
54
+ return {
55
+ success: true,
56
+ files: extractedFiles,
57
+ contentHash,
58
+ };
59
+ } finally {
60
+ // Clean up temp directory
61
+ await this.cleanupTempDirectory(tempDir);
62
+ }
63
+ } catch (error) {
64
+ const errorMessage =
65
+ error instanceof Error ? error.message : String(error);
66
+ return {
67
+ success: false,
68
+ files: [],
69
+ contentHash: "",
70
+ error: `Git repository loading failed: ${errorMessage}`,
71
+ };
72
+ }
73
+ }
74
+ /**
75
+ * Get content identifier for change detection
76
+ */
77
+ async getContentId(webSource) {
78
+ try {
79
+ const options = webSource.options;
80
+ const branch = options?.branch || "HEAD";
81
+ // Get the latest commit hash from the remote repository
82
+ const command = `git ls-remote ${webSource.url} ${branch}`;
83
+ const output = execSync(command, { encoding: "utf8", timeout: 30000 });
84
+ const commitHash = output.trim().split("\t")[0];
85
+ // Combine with URL and paths for a unique identifier
86
+ const paths = options?.paths ? options.paths.sort().join(",") : "all";
87
+ return crypto
88
+ .createHash("sha256")
89
+ .update(`${webSource.url}:${commitHash}:${paths}`)
90
+ .digest("hex");
91
+ } catch (error) {
92
+ // Fallback to URL-based hash if remote access fails
93
+ const options = webSource.options;
94
+ const paths = options?.paths ? options.paths.sort().join(",") : "all";
95
+ return crypto
96
+ .createHash("sha256")
97
+ .update(`${webSource.url}:${paths}`)
98
+ .digest("hex");
99
+ }
100
+ }
101
+ /**
102
+ * Validate if URL is a valid Git repository URL
103
+ */
104
+ isValidGitUrl(url) {
105
+ const gitUrlPatterns = [
106
+ /^https:\/\/github\.com\/[\w\-._]+\/[\w\-._]+(?:\.git)?$/,
107
+ /^https:\/\/gitlab\.com\/[\w\-._\/]+(?:\.git)?$/,
108
+ /^https:\/\/[\w\-._]+\/[\w\-._\/]+\.git$/,
109
+ /^git@[\w\-._]+:[\w\-._\/]+\.git$/,
110
+ ];
111
+ return gitUrlPatterns.some((pattern) => pattern.test(url));
112
+ }
113
+ /**
114
+ * Create a temporary directory for cloning
115
+ */
116
+ async createTempDirectory() {
117
+ const tempDir = path.join(
118
+ process.cwd(),
119
+ ".tmp",
120
+ `git-clone-${Date.now()}-${Math.random().toString(36).slice(2)}`,
121
+ );
122
+ await fs.mkdir(tempDir, { recursive: true });
123
+ return tempDir;
124
+ }
125
+ /**
126
+ * Clone the Git repository
127
+ */
128
+ async cloneRepository(url, targetDir, options) {
129
+ const branch = options?.branch || "main";
130
+ const depth = "--depth 1"; // Shallow clone for efficiency
131
+ let gitCommand = `git clone ${depth} --branch ${branch} ${url} ${targetDir}`;
132
+ // Add authentication if token is provided
133
+ if (options?.token) {
134
+ // For HTTPS URLs, inject token
135
+ if (url.startsWith("https://")) {
136
+ const urlWithToken = url.replace(
137
+ "https://",
138
+ `https://${options.token}@`,
139
+ );
140
+ gitCommand = `git clone ${depth} --branch ${branch} ${urlWithToken} ${targetDir}`;
141
+ }
142
+ }
143
+ try {
144
+ execSync(gitCommand, {
145
+ stdio: "pipe",
146
+ timeout: 120000, // 2 minutes timeout
147
+ cwd: process.cwd(),
148
+ });
149
+ } catch (error) {
150
+ // Try with master branch if main fails
151
+ if (branch === "main") {
152
+ const masterCommand = gitCommand.replace(
153
+ "--branch main",
154
+ "--branch master",
155
+ );
156
+ try {
157
+ execSync(masterCommand, {
158
+ stdio: "pipe",
159
+ timeout: 120000,
160
+ cwd: process.cwd(),
161
+ });
162
+ return;
163
+ } catch (masterError) {
164
+ // If both fail, throw the original error
165
+ }
166
+ }
167
+ throw new KnowledgeError(
168
+ ErrorType.GIT_REPO_ERROR,
169
+ `Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`,
170
+ { url, branch, command: gitCommand },
171
+ );
172
+ }
173
+ }
174
+ /**
175
+ * Extract content from cloned repository to target directory
176
+ */
177
+ async extractContent(sourceDir, targetDir, paths) {
178
+ await fs.mkdir(targetDir, { recursive: true });
179
+ const extractedFiles = [];
180
+ if (paths && paths.length > 0) {
181
+ // Extract only specified paths
182
+ for (const relPath of paths) {
183
+ const sourcePath = path.join(sourceDir, relPath);
184
+ const targetPath = path.join(targetDir, relPath);
185
+ try {
186
+ const stats = await fs.stat(sourcePath);
187
+ if (stats.isDirectory()) {
188
+ await this.copyDirectory(sourcePath, targetPath, extractedFiles);
189
+ } else if (stats.isFile()) {
190
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
191
+ await fs.copyFile(sourcePath, targetPath);
192
+ extractedFiles.push(relPath);
193
+ }
194
+ } catch (error) {
195
+ // Skip files that don't exist or can't be accessed
196
+ console.warn(
197
+ `Warning: Could not extract ${relPath}: ${error instanceof Error ? error.message : String(error)}`,
198
+ );
199
+ }
200
+ }
201
+ } else {
202
+ // Extract all content (excluding .git directory)
203
+ await this.copyDirectory(sourceDir, targetDir, extractedFiles, [".git"]);
204
+ }
205
+ return extractedFiles;
206
+ }
207
+ /**
208
+ * Copy directory recursively
209
+ */
210
+ async copyDirectory(source, target, fileList, excludeDirs = []) {
211
+ await fs.mkdir(target, { recursive: true });
212
+ const items = await fs.readdir(source);
213
+ for (const item of items) {
214
+ if (excludeDirs.includes(item)) {
215
+ continue;
216
+ }
217
+ const sourcePath = path.join(source, item);
218
+ const targetPath = path.join(target, item);
219
+ const stats = await fs.stat(sourcePath);
220
+ if (stats.isDirectory()) {
221
+ await this.copyDirectory(sourcePath, targetPath, fileList, excludeDirs);
222
+ } else {
223
+ await fs.copyFile(sourcePath, targetPath);
224
+ // Calculate relative path from the initial target directory
225
+ const relativePath = path.relative(target, targetPath);
226
+ fileList.push(relativePath);
227
+ }
228
+ }
229
+ }
230
+ /**
231
+ * Generate content hash for change detection
232
+ */
233
+ async generateContentHash(targetDir, files) {
234
+ const hash = crypto.createHash("sha256");
235
+ // Sort files for consistent hashing
236
+ const sortedFiles = files.slice().sort();
237
+ for (const file of sortedFiles) {
238
+ const filePath = path.join(targetDir, file);
239
+ try {
240
+ const content = await fs.readFile(filePath);
241
+ hash.update(file); // Include filename
242
+ hash.update(content); // Include content
243
+ } catch (error) {
244
+ // Skip files that can't be read
245
+ console.warn(
246
+ `Warning: Could not hash ${file}: ${error instanceof Error ? error.message : String(error)}`,
247
+ );
248
+ }
249
+ }
250
+ return hash.digest("hex");
251
+ }
252
+ /**
253
+ * Clean up temporary directory
254
+ */
255
+ async cleanupTempDirectory(tempDir) {
256
+ try {
257
+ await fs.rm(tempDir, { recursive: true, force: true });
258
+ } catch (error) {
259
+ console.warn(
260
+ `Warning: Could not clean up temp directory ${tempDir}: ${error instanceof Error ? error.message : String(error)}`,
261
+ );
262
+ }
263
+ }
264
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Content loading and processing exports
3
+ */
4
+ export { ContentLoader } from "./loader.js";
5
+ export { GitRepoLoader } from "./git-repo-loader.js";
6
+ export { DocumentationSiteLoader } from "./documentation-site-loader.js";
7
+ export { ApiDocumentationLoader } from "./api-documentation-loader.js";
8
+ export { ContentProcessor } from "./content-processor.js";
9
+ export { MetadataManager } from "./metadata-manager.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Content loading and processing exports
3
+ */
4
+ export { ContentLoader } from "./loader.js";
5
+ export { GitRepoLoader } from "./git-repo-loader.js";
6
+ export { DocumentationSiteLoader } from "./documentation-site-loader.js";
7
+ export { ApiDocumentationLoader } from "./api-documentation-loader.js";
8
+ export { ContentProcessor } from "./content-processor.js";
9
+ export { MetadataManager } from "./metadata-manager.js";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Abstract base class for content loaders
3
+ */
4
+ import type { WebSourceConfig } from "../types.js";
5
+ /**
6
+ * Result of a content loading operation
7
+ */
8
+ export interface LoadResult {
9
+ /** Whether the operation was successful */
10
+ success: boolean;
11
+ /** Files that were created or updated */
12
+ files: string[];
13
+ /** Hash of the loaded content for change detection */
14
+ contentHash: string;
15
+ /** Error message if success is false */
16
+ error?: string;
17
+ }
18
+ /**
19
+ * Abstract base class for loading content from different web sources
20
+ */
21
+ export declare abstract class ContentLoader {
22
+ /**
23
+ * Load content from a web source to the specified target directory
24
+ * @param webSource - Configuration for the web source
25
+ * @param targetPath - Local directory to store the content
26
+ * @returns Promise with load result
27
+ */
28
+ abstract load(
29
+ webSource: WebSourceConfig,
30
+ targetPath: string,
31
+ ): Promise<LoadResult>;
32
+ /**
33
+ * Check if this loader can handle the given web source type
34
+ * @param webSource - Web source configuration
35
+ * @returns True if this loader can handle the source type
36
+ */
37
+ abstract canHandle(webSource: WebSourceConfig): boolean;
38
+ /**
39
+ * Validate the web source configuration for this loader
40
+ * @param webSource - Web source configuration
41
+ * @returns True if configuration is valid, error message if not
42
+ */
43
+ abstract validateConfig(webSource: WebSourceConfig): true | string;
44
+ /**
45
+ * Get a unique identifier for the content (used for change detection)
46
+ * @param webSource - Web source configuration
47
+ * @returns Promise with content identifier
48
+ */
49
+ abstract getContentId(webSource: WebSourceConfig): Promise<string>;
50
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Abstract base class for content loaders
3
+ */
4
+ /**
5
+ * Abstract base class for loading content from different web sources
6
+ */
7
+ export class ContentLoader {}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Metadata manager for tracking web source downloads
3
+ */
4
+ import { DocsetMetadata, WebSourceMetadata } from "../types.js";
5
+ /**
6
+ * Manager for docset metadata files
7
+ */
8
+ export declare class MetadataManager {
9
+ /**
10
+ * Load metadata from a docset directory
11
+ * @param docsetPath - Path to the docset directory
12
+ * @returns Metadata object or null if not found
13
+ */
14
+ loadMetadata(docsetPath: string): Promise<DocsetMetadata | null>;
15
+ /**
16
+ * Save metadata to a docset directory
17
+ * @param docsetPath - Path to the docset directory
18
+ * @param metadata - Metadata to save
19
+ */
20
+ saveMetadata(docsetPath: string, metadata: DocsetMetadata): Promise<void>;
21
+ /**
22
+ * Create initial metadata for a docset
23
+ * @param docsetId - ID of the docset
24
+ * @returns Initial metadata structure
25
+ */
26
+ createInitialMetadata(docsetId: string): DocsetMetadata;
27
+ /**
28
+ * Update metadata for a specific web source
29
+ * @param metadata - Current metadata
30
+ * @param sourceUrl - URL of the web source
31
+ * @param sourceMetadata - New metadata for the source
32
+ * @returns Updated metadata
33
+ */
34
+ updateSourceMetadata(
35
+ metadata: DocsetMetadata,
36
+ sourceUrl: string,
37
+ sourceMetadata: Partial<WebSourceMetadata>,
38
+ ): DocsetMetadata;
39
+ /**
40
+ * Get metadata for a specific web source
41
+ * @param metadata - Metadata to search
42
+ * @param sourceUrl - URL of the web source
43
+ * @returns Source metadata or null if not found
44
+ */
45
+ getSourceMetadata(
46
+ metadata: DocsetMetadata,
47
+ sourceUrl: string,
48
+ ): WebSourceMetadata | null;
49
+ /**
50
+ * Check if metadata file exists
51
+ * @param docsetPath - Path to the docset directory
52
+ * @returns True if metadata file exists
53
+ */
54
+ metadataExists(docsetPath: string): Promise<boolean>;
55
+ /**
56
+ * Remove metadata for a specific web source
57
+ * @param metadata - Current metadata
58
+ * @param sourceUrl - URL of the web source to remove
59
+ * @returns Updated metadata
60
+ */
61
+ removeSourceMetadata(
62
+ metadata: DocsetMetadata,
63
+ sourceUrl: string,
64
+ ): DocsetMetadata;
65
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Metadata manager for tracking web source downloads
3
+ */
4
+ import { promises as fs } from "node:fs";
5
+ import * as path from "node:path";
6
+ import { METADATA_FILENAME } from "../types.js";
7
+ /**
8
+ * Manager for docset metadata files
9
+ */
10
+ export class MetadataManager {
11
+ /**
12
+ * Load metadata from a docset directory
13
+ * @param docsetPath - Path to the docset directory
14
+ * @returns Metadata object or null if not found
15
+ */
16
+ async loadMetadata(docsetPath) {
17
+ const metadataPath = path.join(docsetPath, METADATA_FILENAME);
18
+ try {
19
+ const content = await fs.readFile(metadataPath, "utf-8");
20
+ return JSON.parse(content);
21
+ } catch (error) {
22
+ if (error.code === "ENOENT") {
23
+ return null; // File doesn't exist
24
+ }
25
+ throw error; // Other errors should be propagated
26
+ }
27
+ }
28
+ /**
29
+ * Save metadata to a docset directory
30
+ * @param docsetPath - Path to the docset directory
31
+ * @param metadata - Metadata to save
32
+ */
33
+ async saveMetadata(docsetPath, metadata) {
34
+ await fs.mkdir(docsetPath, { recursive: true });
35
+ const metadataPath = path.join(docsetPath, METADATA_FILENAME);
36
+ const content = JSON.stringify(metadata, null, 2);
37
+ await fs.writeFile(metadataPath, content, "utf-8");
38
+ }
39
+ /**
40
+ * Create initial metadata for a docset
41
+ * @param docsetId - ID of the docset
42
+ * @returns Initial metadata structure
43
+ */
44
+ createInitialMetadata(docsetId) {
45
+ return {
46
+ version: "1.0",
47
+ docset_id: docsetId,
48
+ sources: [],
49
+ last_refresh: new Date().toISOString(),
50
+ };
51
+ }
52
+ /**
53
+ * Update metadata for a specific web source
54
+ * @param metadata - Current metadata
55
+ * @param sourceUrl - URL of the web source
56
+ * @param sourceMetadata - New metadata for the source
57
+ * @returns Updated metadata
58
+ */
59
+ updateSourceMetadata(metadata, sourceUrl, sourceMetadata) {
60
+ const updatedMetadata = { ...metadata };
61
+ updatedMetadata.last_refresh = new Date().toISOString();
62
+ // Find existing source or create new one
63
+ const existingIndex = updatedMetadata.sources.findIndex(
64
+ (s) => s.url === sourceUrl,
65
+ );
66
+ if (existingIndex >= 0) {
67
+ // Update existing source - TypeScript knows it exists here
68
+ const existing = updatedMetadata.sources[existingIndex];
69
+ updatedMetadata.sources[existingIndex] = {
70
+ url: existing.url,
71
+ type: existing.type,
72
+ status: sourceMetadata.status ?? existing.status,
73
+ };
74
+ // Update optional fields if provided, preserve existing if not
75
+ const updated = updatedMetadata.sources[existingIndex];
76
+ if (sourceMetadata.last_fetched !== undefined) {
77
+ updated.last_fetched = sourceMetadata.last_fetched;
78
+ } else if (existing.last_fetched !== undefined) {
79
+ updated.last_fetched = existing.last_fetched;
80
+ }
81
+ if (sourceMetadata.content_hash !== undefined) {
82
+ updated.content_hash = sourceMetadata.content_hash;
83
+ } else if (existing.content_hash !== undefined) {
84
+ updated.content_hash = existing.content_hash;
85
+ }
86
+ if (sourceMetadata.files_created !== undefined) {
87
+ updated.files_created = sourceMetadata.files_created;
88
+ } else if (existing.files_created !== undefined) {
89
+ updated.files_created = existing.files_created;
90
+ }
91
+ if (sourceMetadata.error_message !== undefined) {
92
+ updated.error_message = sourceMetadata.error_message;
93
+ } else if (existing.error_message !== undefined) {
94
+ updated.error_message = existing.error_message;
95
+ }
96
+ } else {
97
+ // Add new source - ensure required fields are present
98
+ if (!sourceMetadata.type) {
99
+ throw new Error("Source type is required for new metadata entries");
100
+ }
101
+ const newSource = {
102
+ url: sourceUrl,
103
+ type: sourceMetadata.type,
104
+ status: sourceMetadata.status || "pending",
105
+ };
106
+ // Add optional fields if provided
107
+ if (sourceMetadata.last_fetched !== undefined) {
108
+ newSource.last_fetched = sourceMetadata.last_fetched;
109
+ }
110
+ if (sourceMetadata.content_hash !== undefined) {
111
+ newSource.content_hash = sourceMetadata.content_hash;
112
+ }
113
+ if (sourceMetadata.files_created !== undefined) {
114
+ newSource.files_created = sourceMetadata.files_created;
115
+ }
116
+ if (sourceMetadata.error_message !== undefined) {
117
+ newSource.error_message = sourceMetadata.error_message;
118
+ }
119
+ updatedMetadata.sources.push(newSource);
120
+ }
121
+ return updatedMetadata;
122
+ }
123
+ /**
124
+ * Get metadata for a specific web source
125
+ * @param metadata - Metadata to search
126
+ * @param sourceUrl - URL of the web source
127
+ * @returns Source metadata or null if not found
128
+ */
129
+ getSourceMetadata(metadata, sourceUrl) {
130
+ return metadata.sources.find((s) => s.url === sourceUrl) || null;
131
+ }
132
+ /**
133
+ * Check if metadata file exists
134
+ * @param docsetPath - Path to the docset directory
135
+ * @returns True if metadata file exists
136
+ */
137
+ async metadataExists(docsetPath) {
138
+ const metadataPath = path.join(docsetPath, METADATA_FILENAME);
139
+ try {
140
+ await fs.access(metadataPath);
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+ /**
147
+ * Remove metadata for a specific web source
148
+ * @param metadata - Current metadata
149
+ * @param sourceUrl - URL of the web source to remove
150
+ * @returns Updated metadata
151
+ */
152
+ removeSourceMetadata(metadata, sourceUrl) {
153
+ const updatedMetadata = { ...metadata };
154
+ updatedMetadata.last_refresh = new Date().toISOString();
155
+ updatedMetadata.sources = updatedMetadata.sources.filter(
156
+ (s) => s.url !== sourceUrl,
157
+ );
158
+ return updatedMetadata;
159
+ }
160
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @codemcp/knowledge-core
3
+ *
4
+ * Core functionality for the agentic knowledge guidance system.
5
+ * Provides configuration discovery, path calculation, and template processing.
6
+ */
7
+ export * from "./types.js";
8
+ export { findConfigPath, findConfigPathSync } from "./config/discovery.js";
9
+ export { loadConfig, loadConfigSync, validateConfig } from "./config/loader.js";
10
+ export { ConfigManager } from "./config/manager.js";
11
+ export { calculateLocalPath, formatPath, validatePath, validatePathSync, getRelativePath, ensureKnowledgeGitignore, ensureKnowledgeGitignoreSync, } from "./paths/calculator.js";
12
+ export { processTemplate, getEffectiveTemplate, validateTemplate, extractVariables, createTemplateContext, } from "./templates/processor.js";