@aigne/doc-smith 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 (44) hide show
  1. package/README.md +87 -0
  2. package/agents/batch-docs-detail-generator.yaml +14 -0
  3. package/agents/batch-translate.yaml +44 -0
  4. package/agents/check-detail-generated.mjs +128 -0
  5. package/agents/check-detail-result.mjs +141 -0
  6. package/agents/check-structure-planning-result.yaml +30 -0
  7. package/agents/check-structure-planning.mjs +54 -0
  8. package/agents/content-detail-generator.yaml +50 -0
  9. package/agents/detail-generator-and-translate.yaml +88 -0
  10. package/agents/detail-regenerator.yaml +107 -0
  11. package/agents/docs-generator.yaml +93 -0
  12. package/agents/format-structure-plan.mjs +23 -0
  13. package/agents/input-generator.mjs +142 -0
  14. package/agents/load-sources.mjs +329 -0
  15. package/agents/publish-docs.mjs +212 -0
  16. package/agents/reflective-structure-planner.yaml +10 -0
  17. package/agents/save-docs.mjs +153 -0
  18. package/agents/save-output.mjs +25 -0
  19. package/agents/save-single-doc.mjs +18 -0
  20. package/agents/schema/structure-plan-result.yaml +32 -0
  21. package/agents/schema/structure-plan.yaml +26 -0
  22. package/agents/structure-planning.yaml +49 -0
  23. package/agents/transform-detail-datasources.mjs +14 -0
  24. package/agents/translate.yaml +28 -0
  25. package/aigne.yaml +28 -0
  26. package/biome.json +51 -0
  27. package/docs-mcp/aigne.yaml +8 -0
  28. package/docs-mcp/get-docs-detail.mjs +42 -0
  29. package/docs-mcp/get-docs-structure.mjs +11 -0
  30. package/package.json +33 -0
  31. package/prompts/check-structure-planning-result.md +82 -0
  32. package/prompts/content-detail-generator.md +99 -0
  33. package/prompts/document/detail-example.md +441 -0
  34. package/prompts/document/detail-generator.md +95 -0
  35. package/prompts/document/structure-example.md +98 -0
  36. package/prompts/document/structure-getting-started.md +10 -0
  37. package/prompts/document/structure-planning.md +17 -0
  38. package/prompts/structure-planning.md +108 -0
  39. package/prompts/translator.md +69 -0
  40. package/tests/README.md +93 -0
  41. package/tests/check-detail-result.test.mjs +103 -0
  42. package/tests/load-sources.test.mjs +642 -0
  43. package/tests/test-save-docs.mjs +132 -0
  44. package/utils/utils.mjs +86 -0
@@ -0,0 +1,107 @@
1
+ type: team
2
+ name: regenerator
3
+ description: 生成文档详情并翻译
4
+ skills:
5
+ - ./load-sources.mjs
6
+ - type: transform
7
+ jsonata: |
8
+ $merge([
9
+ $,
10
+ {
11
+ 'structurePlan': originalStructurePlan,
12
+ 'structurePlanResult': $map(originalStructurePlan, function($item) {
13
+ $merge([
14
+ $item,
15
+ {
16
+ 'translates': [$map(translateLanguages, function($lang) { {"language": $lang} })]
17
+ }
18
+ ])
19
+ })
20
+ }
21
+ ])
22
+ - type: transform
23
+ jsonata: |
24
+ (
25
+ $array := structurePlanResult;
26
+ $path := currentPath;
27
+ $foundItem := $array[path=$path][0];
28
+ $merge([
29
+ $foundItem,
30
+ { "originalStructurePlan": originalStructurePlan }
31
+
32
+ ])
33
+ )
34
+ - ./format-structure-plan.mjs
35
+ - ./detail-generator-and-translate.yaml
36
+ input_schema:
37
+ type: object
38
+ properties:
39
+ nodeName:
40
+ type: string
41
+ description: 节点名称
42
+ default: 部分
43
+ locale:
44
+ type: string
45
+ description: 语言
46
+ targetAudience:
47
+ type: string
48
+ description: 目标受众
49
+ glossary:
50
+ type: string
51
+ description: 术语表
52
+ currentPath:
53
+ type: string
54
+ description: 当前路径
55
+ feedback:
56
+ type: string
57
+ description: 反馈
58
+ sources:
59
+ type: array
60
+ items:
61
+ type: string
62
+ description: 源码路径
63
+ sourcesPath:
64
+ type: array
65
+ items:
66
+ type: string
67
+ description: 源码路径
68
+ default:
69
+ - ./
70
+ includePatterns:
71
+ type: array
72
+ items:
73
+ type: string
74
+ description: 包含的文件名
75
+ excludePatterns:
76
+ type: array
77
+ items:
78
+ type: string
79
+ description: 排除的文件名
80
+ outputDir:
81
+ type: string
82
+ description: 输出目录
83
+ default: ./aigne-docs/output
84
+ docsDir:
85
+ type: string
86
+ description: 文档目录
87
+ default: ./aigne-docs/docs
88
+ additionalInformation:
89
+ type: string
90
+ description: 额外补充信息
91
+ labels:
92
+ type: array
93
+ items:
94
+ type: string
95
+ description: 标签
96
+ output_schema:
97
+ type: object
98
+ properties:
99
+ title:
100
+ type: string
101
+ description:
102
+ type: string
103
+ path:
104
+ type: string
105
+ content:
106
+ type: string
107
+ mode: sequential
@@ -0,0 +1,93 @@
1
+ type: team
2
+ name: generator
3
+ description: 文档生成
4
+ skills:
5
+ - ./load-sources.mjs
6
+ - ./check-structure-planning.mjs
7
+ - type: transform
8
+ jsonata: |
9
+ $merge([$, {
10
+ "saveKey": "structurePlan",
11
+ "savePath": outputDir,
12
+ "fileName": "structure-plan.json"
13
+ }])
14
+ - ./save-output.mjs
15
+ - type: transform
16
+ jsonata: |
17
+ $merge([
18
+ $,
19
+ {
20
+ 'structurePlanResult': $map(structurePlan, function($item) {
21
+ $merge([
22
+ $item,
23
+ {
24
+ 'translates': [$map(translateLanguages, function($lang) { {"language": $lang} })]
25
+ }
26
+ ])
27
+ })
28
+ }
29
+ ])
30
+ - ./format-structure-plan.mjs
31
+ - ./batch-docs-detail-generator.yaml
32
+ - ./save-docs.mjs
33
+
34
+ input_schema:
35
+ type: object
36
+ properties:
37
+ nodeName:
38
+ type: string
39
+ description: 节点名称
40
+ default: Section
41
+ rules:
42
+ type: string
43
+ description: 用户的结构规划的要求和规则
44
+ locale:
45
+ type: string
46
+ description: 用户语言,如 zh、en
47
+ sources:
48
+ type: array
49
+ items:
50
+ type: string
51
+ description: 用户生成文档的源码文件
52
+ sourcesPath:
53
+ type: array
54
+ items:
55
+ type: string
56
+ description: 源码路径
57
+ default:
58
+ - ./
59
+ docsDir:
60
+ type: string
61
+ description: 文档保存目录
62
+ default: ./aigne-docs/docs
63
+ outputDir:
64
+ type: string
65
+ description: 输出目录
66
+ default: ./aigne-docs/output
67
+ translateLanguages:
68
+ type: array
69
+ items:
70
+ type: string
71
+ description: 翻译语言,如 zh、en
72
+ glossary:
73
+ type: string
74
+ description: 术语表
75
+ additionalInformation:
76
+ type: string
77
+ description: 额外补充信息
78
+ docsType:
79
+ type: string
80
+ description: 文档类型,支持:general、getting-started、reference、faq
81
+ default: general
82
+ structurePlanFeedback:
83
+ type: string
84
+ description: 结构规划反馈
85
+ labels:
86
+ type: array
87
+ items:
88
+ type: string
89
+ description: 标签
90
+ required:
91
+ - rules
92
+ - docsDir
93
+ mode: sequential
@@ -0,0 +1,23 @@
1
+ import { stringify } from "yaml";
2
+
3
+ export default async function formatStructurePlan({ structurePlan }) {
4
+ // Extract required fields from each item in structurePlan
5
+ const formattedData = structurePlan.map((item) => ({
6
+ title: item.title,
7
+ path: item.path,
8
+ parentId: item.parentId,
9
+ description: item.description,
10
+ }));
11
+
12
+ // Convert to YAML string
13
+ const yamlString = stringify(formattedData, {
14
+ indent: 2,
15
+ lineWidth: 120,
16
+ minContentWidth: 20,
17
+ });
18
+
19
+ return {
20
+ structurePlanYaml: yamlString,
21
+ structurePlan,
22
+ };
23
+ }
@@ -0,0 +1,142 @@
1
+ import { writeFile, mkdir } from "node:fs/promises";
2
+ import { join, dirname } from "node:path";
3
+
4
+ /**
5
+ * Guide users through multi-turn dialogue to collect information and generate YAML configuration
6
+ * @param {Object} params
7
+ * @param {string} params.outputPath - Output file path
8
+ * @param {string} params.fileName - File name
9
+ * @returns {Promise<Object>}
10
+ */
11
+ export default async function init(
12
+ { outputPath = "./doc-smith", fileName = "input.yaml" },
13
+ options
14
+ ) {
15
+ console.log("Welcome to AIGNE Doc Smith!");
16
+ console.log(
17
+ "I will help you generate a configuration file through several questions.\n"
18
+ );
19
+
20
+ // Collect user information
21
+ const input = {};
22
+
23
+ // 1. Document generation rules
24
+ console.log("=== Document Generation Rules ===");
25
+ const rulesInput = await options.prompts.input({
26
+ message: "Please describe the document generation rules and requirements:",
27
+ });
28
+ input.rules = rulesInput.trim();
29
+
30
+ // 2. Target audience
31
+ console.log("\n=== Target Audience ===");
32
+ const targetAudienceInput = await options.prompts.input({
33
+ message:
34
+ "What is the target audience? (e.g., developers, users, press Enter to skip):",
35
+ });
36
+ input.targetAudience = targetAudienceInput.trim() || "";
37
+
38
+ // 3. Language settings
39
+ console.log("\n=== Language Settings ===");
40
+ const localeInput = await options.prompts.input({
41
+ message: "Primary language (e.g., en, zh, press Enter to skip):",
42
+ });
43
+ input.locale = localeInput.trim() || "";
44
+
45
+ // 4. Translation languages
46
+ console.log("\n=== Translation Settings ===");
47
+ const translateInput = await options.prompts.input({
48
+ message:
49
+ "Translation language list (comma-separated, e.g., zh,en, press Enter to skip):",
50
+ });
51
+ input.translateLanguages = translateInput.trim()
52
+ ? translateInput.split(",").map((lang) => lang.trim())
53
+ : [];
54
+
55
+ // Generate YAML content
56
+ const yamlContent = generateYAML(input, outputPath);
57
+
58
+ // Save file
59
+ try {
60
+ const filePath = join(outputPath, fileName);
61
+ const dirPath = dirname(filePath);
62
+
63
+ // Create directory if it doesn't exist
64
+ await mkdir(dirPath, { recursive: true });
65
+
66
+ await writeFile(filePath, yamlContent, "utf8");
67
+ console.log(`\n✅ Configuration file saved to: ${filePath}`);
68
+
69
+ return {
70
+ inputGeneratorStatus: true,
71
+ inputGeneratorPath: filePath,
72
+ inputGeneratorContent: yamlContent,
73
+ };
74
+ } catch (error) {
75
+ console.error(`❌ Failed to save configuration file: ${error.message}`);
76
+ return {
77
+ inputGeneratorStatus: false,
78
+ inputGeneratorError: error.message,
79
+ };
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Generate YAML configuration content
85
+ * @param {Object} input - Input object
86
+ * @param {string} outputPath - Output path for directory configuration
87
+ * @returns {string} YAML string
88
+ */
89
+ function generateYAML(input, outputPath) {
90
+ let yaml = "";
91
+
92
+ // Add rules
93
+ if (input.rules) {
94
+ yaml += `rules: |\n`;
95
+ yaml += ` ${input.rules.split("\n").join("\n ")}\n\n`;
96
+ }
97
+
98
+ // Add target audience
99
+ if (input.targetAudience && input.targetAudience.trim()) {
100
+ yaml += `targetAudience: ${input.targetAudience}\n`;
101
+ } else {
102
+ yaml += `# targetAudience: developers # Target audience for the documentation (e.g., developers, users)\n`;
103
+ }
104
+
105
+ // Add language settings
106
+ if (input.locale && input.locale.trim()) {
107
+ yaml += `locale: ${input.locale}\n`;
108
+ } else {
109
+ yaml += `# locale: en # Primary language for the documentation (e.g., en, zh)\n`;
110
+ }
111
+
112
+ // Add translation languages
113
+ if (
114
+ input.translateLanguages &&
115
+ input.translateLanguages.length > 0 &&
116
+ input.translateLanguages.some((lang) => lang.trim())
117
+ ) {
118
+ yaml += `translateLanguages:\n`;
119
+ input.translateLanguages.forEach((lang) => {
120
+ if (lang.trim()) {
121
+ yaml += ` - ${lang}\n`;
122
+ }
123
+ });
124
+ } else {
125
+ yaml += `# translateLanguages: # List of languages to translate the documentation to\n`;
126
+ yaml += `# - zh # Example: Chinese translation\n`;
127
+ yaml += `# - en # Example: English translation\n`;
128
+ }
129
+
130
+ // Add default directory and source path configurations
131
+ yaml += `docsDir: ${outputPath}/docs # Directory to save generated documentation\n`;
132
+ yaml += `outputDir: ${outputPath}/output # Directory to save output files\n`;
133
+ yaml += `sourcesPath: # Source code paths to analyze\n`;
134
+ yaml += ` - ./ # Current directory\n`;
135
+
136
+ return yaml;
137
+ }
138
+
139
+ // Execute the function if this file is run directly
140
+ if (import.meta.url === `file://${process.argv[1]}`) {
141
+ inputGenerator({}).catch(console.error);
142
+ }
@@ -0,0 +1,329 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { glob } from "glob";
4
+
5
+ // Default file patterns for inclusion and exclusion
6
+ const DEFAULT_INCLUDE_PATTERNS = [
7
+ "*.py",
8
+ "*.js",
9
+ "*.jsx",
10
+ "*.ts",
11
+ "*.tsx",
12
+ "*.go",
13
+ "*.java",
14
+ "*.pyi",
15
+ "*.pyx",
16
+ "*.c",
17
+ "*.cc",
18
+ "*.cpp",
19
+ "*.h",
20
+ "*.md",
21
+ "*.rst",
22
+ "*.json",
23
+ "*Dockerfile",
24
+ "*Makefile",
25
+ "*.yaml",
26
+ "*.yml",
27
+ ];
28
+
29
+ const DEFAULT_EXCLUDE_PATTERNS = [
30
+ "aigne-docs/**",
31
+ "doc-smith/**",
32
+ "assets/**",
33
+ "data/**",
34
+ "images/**",
35
+ "public/**",
36
+ "static/**",
37
+ "**/vendor/**",
38
+ "temp/**",
39
+ "**/*docs/**",
40
+ "**/*doc/**",
41
+ "**/*venv/**",
42
+ "*.venv/**",
43
+ "*test*",
44
+ "**/*test/**",
45
+ "**/*tests/**",
46
+ "**/*examples/**",
47
+ "v1/**",
48
+ "**/*dist/**",
49
+ "**/*build/**",
50
+ "**/*experimental/**",
51
+ "**/*deprecated/**",
52
+ "**/*misc/**",
53
+ "**/*legacy/**",
54
+ ".git/**",
55
+ ".github/**",
56
+ ".next/**",
57
+ ".vscode/**",
58
+ "**/*obj/**",
59
+ "**/*bin/**",
60
+ "**/*node_modules/**",
61
+ "*.log",
62
+ ];
63
+
64
+ /**
65
+ * Load .gitignore patterns from a directory
66
+ * @param {string} dir - Directory path
67
+ * @returns {object|null} Ignore instance or null if no .gitignore found
68
+ */
69
+ async function loadGitignore(dir) {
70
+ const gitignorePath = path.join(dir, ".gitignore");
71
+ try {
72
+ await access(gitignorePath);
73
+ const gitignoreContent = await readFile(gitignorePath, "utf8");
74
+ // Create ignore patterns from .gitignore content
75
+ const ignorePatterns = gitignoreContent
76
+ .split("\n")
77
+ .map((line) => line.trim())
78
+ .filter((line) => line && !line.startsWith("#"))
79
+ .map((line) => line.replace(/^\//, "")); // Remove leading slash
80
+
81
+ return ignorePatterns.length > 0 ? ignorePatterns : null;
82
+ } catch {
83
+ // .gitignore file doesn't exist
84
+ return null;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get files using glob patterns
90
+ * @param {string} dir - Directory to search
91
+ * @param {string[]} includePatterns - Include patterns
92
+ * @param {string[]} excludePatterns - Exclude patterns
93
+ * @param {string[]} gitignorePatterns - .gitignore patterns
94
+ * @returns {Promise<string[]>} Array of file paths
95
+ */
96
+ async function getFilesWithGlob(
97
+ dir,
98
+ includePatterns,
99
+ excludePatterns,
100
+ gitignorePatterns
101
+ ) {
102
+ // Prepare all ignore patterns
103
+ const allIgnorePatterns = [];
104
+
105
+ if (excludePatterns) {
106
+ allIgnorePatterns.push(...excludePatterns);
107
+ }
108
+
109
+ if (gitignorePatterns) {
110
+ allIgnorePatterns.push(...gitignorePatterns);
111
+ }
112
+
113
+ // Add default exclusions if not already present
114
+ const defaultExclusions = ["node_modules/**", "test/**", "temp/**"];
115
+ for (const exclusion of defaultExclusions) {
116
+ if (!allIgnorePatterns.includes(exclusion)) {
117
+ allIgnorePatterns.push(exclusion);
118
+ }
119
+ }
120
+
121
+ // Convert patterns to be relative to the directory
122
+ const patterns = includePatterns.map((pattern) => {
123
+ // If pattern doesn't start with / or **, make it relative to dir
124
+ if (!pattern.startsWith("/") && !pattern.startsWith("**")) {
125
+ return `**/${pattern}`; // Use ** to search recursively
126
+ }
127
+ return pattern;
128
+ });
129
+
130
+ try {
131
+ const files = await glob(patterns, {
132
+ cwd: dir,
133
+ ignore: allIgnorePatterns.length > 0 ? allIgnorePatterns : undefined,
134
+ absolute: true,
135
+ nodir: true, // Only return files, not directories
136
+ dot: false, // Don't include dot files by default
137
+ gitignore: true, // Enable .gitignore support
138
+ });
139
+
140
+ return files;
141
+ } catch (error) {
142
+ console.warn(
143
+ `Warning: Error during glob search in ${dir}: ${error.message}`
144
+ );
145
+ return [];
146
+ }
147
+ }
148
+
149
+ export default async function loadSources({
150
+ sources = [],
151
+ sourcesPath = [],
152
+ includePatterns,
153
+ excludePatterns,
154
+ outputDir,
155
+ docsDir,
156
+ currentPath,
157
+ useDefaultPatterns = true,
158
+ } = {}) {
159
+ let files = Array.isArray(sources) ? [...sources] : [];
160
+
161
+ if (sourcesPath) {
162
+ const paths = Array.isArray(sourcesPath) ? sourcesPath : [sourcesPath];
163
+ let allFiles = [];
164
+
165
+ for (const dir of paths) {
166
+ try {
167
+ // Load .gitignore for this directory
168
+ const gitignorePatterns = await loadGitignore(dir);
169
+
170
+ // Prepare patterns
171
+ let finalIncludePatterns = null;
172
+ let finalExcludePatterns = null;
173
+
174
+ if (useDefaultPatterns) {
175
+ // Merge with default patterns
176
+ const userInclude = includePatterns
177
+ ? Array.isArray(includePatterns)
178
+ ? includePatterns
179
+ : [includePatterns]
180
+ : [];
181
+ const userExclude = excludePatterns
182
+ ? Array.isArray(excludePatterns)
183
+ ? excludePatterns
184
+ : [excludePatterns]
185
+ : [];
186
+
187
+ finalIncludePatterns = [...DEFAULT_INCLUDE_PATTERNS, ...userInclude];
188
+ finalExcludePatterns = [...DEFAULT_EXCLUDE_PATTERNS, ...userExclude];
189
+ } else {
190
+ // Use only user patterns
191
+ if (includePatterns) {
192
+ finalIncludePatterns = Array.isArray(includePatterns)
193
+ ? includePatterns
194
+ : [includePatterns];
195
+ }
196
+ if (excludePatterns) {
197
+ finalExcludePatterns = Array.isArray(excludePatterns)
198
+ ? excludePatterns
199
+ : [excludePatterns];
200
+ }
201
+ }
202
+
203
+ // Get files using glob
204
+ const filesInDir = await getFilesWithGlob(
205
+ dir,
206
+ finalIncludePatterns,
207
+ finalExcludePatterns,
208
+ gitignorePatterns
209
+ );
210
+ allFiles = allFiles.concat(filesInDir);
211
+ } catch (err) {
212
+ if (err.code !== "ENOENT") throw err;
213
+ }
214
+ }
215
+
216
+ files = files.concat(allFiles);
217
+ }
218
+
219
+ files = [...new Set(files)];
220
+ let allSources = "";
221
+ const sourceFiles = await Promise.all(
222
+ files.map(async (file) => {
223
+ const content = await readFile(file, "utf8");
224
+ allSources += `// sourceId: ${file}\n${content}\n`;
225
+ return {
226
+ sourceId: file,
227
+ content,
228
+ };
229
+ })
230
+ );
231
+
232
+ // Get the last structure plan result
233
+ let originalStructurePlan;
234
+ const structurePlanPath = path.join(outputDir, "structure-plan.json");
235
+ try {
236
+ await access(structurePlanPath);
237
+ const structurePlanResult = await readFile(structurePlanPath, "utf8");
238
+ if (structurePlanResult) {
239
+ try {
240
+ originalStructurePlan = JSON.parse(structurePlanResult);
241
+ } catch (err) {
242
+ console.error(`Failed to parse structure-plan.json: ${err.message}`);
243
+ }
244
+ }
245
+ } catch {
246
+ // The file does not exist, originalStructurePlan remains undefined
247
+ }
248
+
249
+ // Get the last output result of the specified path
250
+ let content;
251
+ if (currentPath) {
252
+ const flatName = currentPath.replace(/^\//, "").replace(/\//g, "-");
253
+ const fileFullName = `${flatName}.md`;
254
+ const filePath = path.join(docsDir, fileFullName);
255
+ try {
256
+ await access(filePath);
257
+ content = await readFile(filePath, "utf8");
258
+ } catch {
259
+ // The file does not exist, content remains undefined
260
+ }
261
+ }
262
+
263
+ return {
264
+ datasourcesList: sourceFiles,
265
+ datasources: allSources,
266
+ content,
267
+ originalStructurePlan,
268
+ files,
269
+ };
270
+ }
271
+
272
+ loadSources.input_schema = {
273
+ type: "object",
274
+ properties: {
275
+ sources: {
276
+ type: "array",
277
+ items: { type: "string" },
278
+ description: "Array of paths to the sources files",
279
+ },
280
+ sourcesPath: {
281
+ anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
282
+ description: "Directory or directories to recursively read files from",
283
+ },
284
+ includePatterns: {
285
+ anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
286
+ description:
287
+ "Glob patterns to filter files by path or filename. If not set, include all.",
288
+ },
289
+ excludePatterns: {
290
+ anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
291
+ description:
292
+ "Glob patterns to exclude files by path or filename. If not set, exclude none.",
293
+ },
294
+ useDefaultPatterns: {
295
+ type: "boolean",
296
+ description:
297
+ "Whether to use default include/exclude patterns. Defaults to true.",
298
+ },
299
+ currentPath: {
300
+ type: "string",
301
+ description: "The current path of the document",
302
+ },
303
+ },
304
+ required: [],
305
+ };
306
+
307
+ loadSources.output_schema = {
308
+ type: "object",
309
+ properties: {
310
+ datasources: {
311
+ type: "string",
312
+ },
313
+ datasourcesList: {
314
+ type: "array",
315
+ items: {
316
+ type: "object",
317
+ properties: {
318
+ sourceId: { type: "string" },
319
+ content: { type: "string" },
320
+ },
321
+ },
322
+ },
323
+ files: {
324
+ type: "array",
325
+ items: { type: "string" },
326
+ description: "Array of file paths that were loaded",
327
+ },
328
+ },
329
+ };