@aigne/doc-smith 0.5.1 → 0.7.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 (42) hide show
  1. package/.github/workflows/ci.yml +46 -0
  2. package/.github/workflows/reviewer.yml +2 -1
  3. package/CHANGELOG.md +17 -0
  4. package/agents/chat.yaml +30 -0
  5. package/agents/check-detail-result.mjs +2 -1
  6. package/agents/check-detail.mjs +1 -0
  7. package/agents/check-structure-plan.mjs +1 -1
  8. package/agents/docs-fs.yaml +25 -0
  9. package/agents/exit.mjs +6 -0
  10. package/agents/feedback-refiner.yaml +5 -1
  11. package/agents/find-items-by-paths.mjs +10 -4
  12. package/agents/fs.mjs +60 -0
  13. package/agents/input-generator.mjs +159 -90
  14. package/agents/load-config.mjs +0 -5
  15. package/agents/load-sources.mjs +119 -12
  16. package/agents/publish-docs.mjs +28 -11
  17. package/agents/retranslate.yaml +1 -1
  18. package/agents/team-publish-docs.yaml +2 -2
  19. package/aigne.yaml +1 -0
  20. package/package.json +13 -10
  21. package/prompts/content-detail-generator.md +12 -4
  22. package/prompts/document/custom-components.md +80 -0
  23. package/prompts/document/d2-chart/diy-examples.md +44 -0
  24. package/prompts/document/d2-chart/official-examples.md +708 -0
  25. package/prompts/document/d2-chart/rules.md +48 -0
  26. package/prompts/document/detail-generator.md +13 -15
  27. package/prompts/document/structure-planning.md +1 -3
  28. package/prompts/feedback-refiner.md +81 -60
  29. package/prompts/structure-planning.md +20 -3
  30. package/tests/check-detail-result.test.mjs +50 -2
  31. package/tests/conflict-resolution.test.mjs +237 -0
  32. package/tests/input-generator.test.mjs +940 -0
  33. package/tests/load-sources.test.mjs +627 -3
  34. package/tests/preferences-utils.test.mjs +94 -0
  35. package/tests/save-value-to-config.test.mjs +182 -5
  36. package/tests/utils.test.mjs +49 -0
  37. package/utils/auth-utils.mjs +1 -1
  38. package/utils/conflict-detector.mjs +72 -1
  39. package/utils/constants.mjs +139 -126
  40. package/utils/kroki-utils.mjs +162 -0
  41. package/utils/markdown-checker.mjs +175 -67
  42. package/utils/utils.mjs +97 -29
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ env:
4
+ NODE_OPTIONS: "--max_old_space_size=6144"
5
+
6
+ on:
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ lint_and_test:
17
+ runs-on: ubuntu-latest
18
+
19
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
20
+
21
+ steps:
22
+ - name: Checkout repo
23
+ uses: actions/checkout@v4
24
+
25
+ - uses: pnpm/action-setup@v3
26
+ with:
27
+ version: 10
28
+
29
+ - name: Setup node
30
+ uses: actions/setup-node@v4
31
+ with:
32
+ node-version: 23
33
+ cache: pnpm
34
+
35
+ - name: Setup bun
36
+ uses: oven-sh/setup-bun@v2
37
+
38
+ - name: Install dependencies
39
+ run: |
40
+ pnpm install
41
+
42
+ - name: Lint
43
+ run: pnpm lint
44
+
45
+ - name: Test
46
+ run: pnpm test:coverage
@@ -21,7 +21,7 @@ jobs:
21
21
  review:
22
22
  runs-on: ubuntu-latest
23
23
  steps:
24
- - uses: aigne-io/aigne-codesmith@v0.1.0
24
+ - uses: aigne-io/aigne-code-smith@latest
25
25
  env:
26
26
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27
27
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -32,6 +32,7 @@ jobs:
32
32
  disable_review: false
33
33
  review_simple_changes: false
34
34
  review_comment_lgtm: false
35
+ suggest_pr_title: true
35
36
  path_filters: |
36
37
  !docs/*
37
38
  !**/*.md
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.6.0...v0.7.0) (2025-08-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * add chat mode support ([#60](https://github.com/AIGNE-io/aigne-doc-smith/issues/60)) ([9b2ce50](https://github.com/AIGNE-io/aigne-doc-smith/commit/9b2ce50014d1894a4e41bf043e699fdc4f1d74b7))
9
+ * support custom components and more robust config handling ([#70](https://github.com/AIGNE-io/aigne-doc-smith/issues/70)) ([727ab42](https://github.com/AIGNE-io/aigne-doc-smith/commit/727ab429b00f924ef605530b35d2a12b4be77e3a))
10
+ * support d2 chart in doc generate and publish workflow ([#69](https://github.com/AIGNE-io/aigne-doc-smith/issues/69)) ([bf95889](https://github.com/AIGNE-io/aigne-doc-smith/commit/bf958891516973636c4847b084c6fe75d1ea124b))
11
+ * support multi purpose doc planning and generating ([#68](https://github.com/AIGNE-io/aigne-doc-smith/issues/68)) ([44152c5](https://github.com/AIGNE-io/aigne-doc-smith/commit/44152c53b7e8f82e3af1245a1affd77b9817486d))
12
+
13
+ ## [0.6.0](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.5.1...v0.6.0) (2025-08-27)
14
+
15
+
16
+ ### Features
17
+
18
+ * complete support for media processing before publish ([#63](https://github.com/AIGNE-io/aigne-doc-smith/issues/63)) ([5257ca1](https://github.com/AIGNE-io/aigne-doc-smith/commit/5257ca1756f47487b65a1813949e547b6fc51aca))
19
+
3
20
  ## [0.5.1](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.5.0...v0.5.1) (2025-08-26)
4
21
 
5
22
 
@@ -0,0 +1,30 @@
1
+ type: ai
2
+ name: chat
3
+ description: Start interactive document generation assistant
4
+ instructions: |
5
+ You are a professional document generation assistant that helps users create, modify, and manage documentation through interactive chat. Your primary role is to understand user requirements and intelligently call upon various specialized skills to complete documentation tasks efficiently.
6
+
7
+ Core Capabilities:
8
+ - Generate comprehensive documentation from user inputs and specifications
9
+ - Regenerate and refine document details based on feedback
10
+ - Translate and localize documentation content
11
+ - Publish and manage team documentation workflows
12
+ - Provide interactive guidance throughout the document creation process
13
+
14
+ Interaction Guidelines:
15
+ - Engage users in a professional yet friendly manner
16
+ - Ask clarifying questions to understand specific documentation needs
17
+ - Suggest appropriate skills and workflows based on user requests
18
+ - Provide clear explanations of available capabilities and processes
19
+ - Maintain context throughout multi-step documentation tasks
20
+ - Offer proactive suggestions for improving document quality and structure
21
+ input_key: message
22
+ memory: true
23
+ skills:
24
+ - ./input-generator.mjs
25
+ - ./docs-generator.yaml
26
+ - ./detail-regenerator.yaml
27
+ - ./team-publish-docs.yaml
28
+ - ./retranslate.yaml
29
+ - ./docs-fs.yaml
30
+ - ./exit.mjs
@@ -1,6 +1,6 @@
1
1
  import { checkMarkdown } from "../utils/markdown-checker.mjs";
2
2
 
3
- export default async function checkDetailResult({ structurePlan, reviewContent }) {
3
+ export default async function checkDetailResult({ structurePlan, reviewContent, docsDir }) {
4
4
  let isApproved = true;
5
5
  const detailFeedback = [];
6
6
 
@@ -24,6 +24,7 @@ export default async function checkDetailResult({ structurePlan, reviewContent }
24
24
  try {
25
25
  const markdownErrors = await checkMarkdown(reviewContent, "result", {
26
26
  allowedLinks,
27
+ baseDir: docsDir,
27
28
  });
28
29
 
29
30
  if (markdownErrors.length > 0) {
@@ -85,6 +85,7 @@ export default async function checkDetail(
85
85
  const validationResult = await checkDetailResult({
86
86
  structurePlan,
87
87
  reviewContent: fileContent,
88
+ docsDir,
88
89
  });
89
90
 
90
91
  if (!validationResult.isApproved) {
@@ -63,7 +63,7 @@ export default async function checkStructurePlan(
63
63
  1. 对于新增的内容,可以根据需要新增节点,或补充到原有节点展示
64
64
  2. 谨慎删除节点,除非节点关联 sourceIds 都被删除了
65
65
  3. 不能修改原有节点的 path
66
- 4. 根据最新的 Data Sources 按需要更新节点的 sourceIds,如没有大的变化,可以不更新。
66
+ 4. 根据最新的 Data Sources 可以按需要更新节点的 sourceIds
67
67
  `;
68
68
  }
69
69
  }
@@ -0,0 +1,25 @@
1
+ type: team
2
+ name: docs_fs_actor
3
+ description: File system operations for documentation management
4
+ skills:
5
+ - ./load-config.mjs
6
+ - url: ./fs.mjs
7
+ default_input:
8
+ rootDir:
9
+ $get: docsDir
10
+ input_schema:
11
+ type: object
12
+ properties:
13
+ action:
14
+ type: "string"
15
+ enum: ["read_file", "write_file", "delete_file", "list_directory"]
16
+ description:
17
+ "The file system action to perform, available actions are: read_file, write_file, delete_file, list_directory"
18
+ path:
19
+ type: "string"
20
+ description: "The path to the file or directory to operate on"
21
+ content:
22
+ type: "string"
23
+ description: "The content to write to the file, required for write_file action"
24
+ required: ["action", "path"]
25
+ task_render_mode: hide
@@ -0,0 +1,6 @@
1
+ export default async function exit() {
2
+ process.exit(0);
3
+ }
4
+
5
+ exit.description =
6
+ "Exit the chat session. Equivalent to saying goodbye, quit, exit, close, or see you. Must print a bye message before calling this tool, as it will exit the process immediately.";
@@ -41,8 +41,12 @@ output_schema:
41
41
  limitToInputPaths:
42
42
  type: boolean
43
43
  description: Whether to limit to the "paths specified in current input" when used subsequently
44
+ reason:
45
+ type: string
46
+ description: Explanation of why the save decision was made and how the rule and scope were derived
44
47
  required:
45
48
  - rule
46
49
  - scope
47
50
  - save
48
- - limitToInputPaths
51
+ - limitToInputPaths
52
+ - reason
@@ -26,10 +26,16 @@ export default async function selectedDocs(
26
26
  // Let user select multiple files
27
27
  selectedFiles = await options.prompts.checkbox({
28
28
  message: getActionText(isTranslate, "Select documents to {action}:"),
29
- choices: mainLanguageFiles.map((file) => ({
30
- name: file,
31
- value: file,
32
- })),
29
+ source: (term) => {
30
+ const choices = mainLanguageFiles.map((file) => ({
31
+ name: file,
32
+ value: file,
33
+ }));
34
+
35
+ if (!term) return choices;
36
+
37
+ return choices.filter((choice) => choice.name.toLowerCase().includes(term.toLowerCase()));
38
+ },
33
39
  validate: (answer) => {
34
40
  if (answer.length === 0) {
35
41
  return "Please select at least one document";
package/agents/fs.mjs ADDED
@@ -0,0 +1,60 @@
1
+ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+
4
+ export default async function fs({ rootDir, action, path, content }) {
5
+ if (!rootDir) throw new Error("Root directory is not specified");
6
+
7
+ path = join(rootDir, path);
8
+
9
+ switch (action) {
10
+ case "read_file":
11
+ return {
12
+ status: "ok",
13
+ path,
14
+ content: await readFile(path, "utf-8"),
15
+ };
16
+ case "write_file": {
17
+ await mkdir(dirname(path), { recursive: true });
18
+ await writeFile(path, content || "");
19
+ return {
20
+ status: "ok",
21
+ path,
22
+ content,
23
+ };
24
+ }
25
+ case "delete_file":
26
+ await rm(path, { recursive: true, force: true });
27
+ return {
28
+ status: "ok",
29
+ path,
30
+ };
31
+ case "list_directory":
32
+ return {
33
+ status: "ok",
34
+ entries: await readdir(path, { withFileTypes: true }).then((list) =>
35
+ list.map((entry) => ({
36
+ path: join(entry.parentPath, entry.name),
37
+ isDirectory: entry.isDirectory(),
38
+ })),
39
+ ),
40
+ };
41
+ }
42
+ }
43
+
44
+ fs.input_schema = {
45
+ type: "object",
46
+ properties: {
47
+ action: {
48
+ type: "string",
49
+ enum: ["read_file", "write_file", "delete_file", "list_directory"],
50
+ description:
51
+ "The file system action to perform, available actions are: read_file, write_file, delete_file, list_directory",
52
+ },
53
+ path: { type: "string", description: "The path to the file or directory to operate on" },
54
+ content: {
55
+ type: "string",
56
+ description: "The content to write to the file, required for write_file action",
57
+ },
58
+ },
59
+ required: ["action", "path"],
60
+ };
@@ -1,6 +1,7 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import chalk from "chalk";
4
+ import { stringify as yamlStringify } from "yaml";
4
5
  import { getFilteredOptions, validateSelection } from "../utils/conflict-detector.mjs";
5
6
  import {
6
7
  DEPTH_RECOMMENDATION_LOGIC,
@@ -15,6 +16,7 @@ import {
15
16
  detectSystemLanguage,
16
17
  getAvailablePaths,
17
18
  getProjectInfo,
19
+ isGlobPattern,
18
20
  validatePath,
19
21
  } from "../utils/utils.mjs";
20
22
 
@@ -34,7 +36,9 @@ export default async function init(
34
36
  ) {
35
37
  if (skipIfExists) {
36
38
  const filePath = join(outputPath, fileName);
37
- if (await readFile(filePath, "utf8").catch(() => null)) {
39
+ const configContent = await readFile(filePath, "utf8").catch(() => null);
40
+ // Only skip if file exists AND has non-empty content
41
+ if (configContent && configContent.trim() !== "") {
38
42
  return {};
39
43
  }
40
44
  }
@@ -228,17 +232,18 @@ export default async function init(
228
232
  // 8. Source code paths
229
233
  console.log("\n🔍 [8/8]: Source Code Paths");
230
234
  console.log("Enter paths to analyze for documentation (e.g., ./src, ./lib)");
235
+ console.log("💡 You can also enter glob patterns (e.g., src/**/*.js, **/*.md)");
231
236
  console.log("💡 If no paths are configured, './' will be used as default");
232
237
 
233
238
  const sourcePaths = [];
234
239
  while (true) {
235
240
  const selectedPath = await options.prompts.search({
236
- message: "Path:",
241
+ message: "Path or glob pattern:",
237
242
  source: async (input) => {
238
243
  if (!input || input.trim() === "") {
239
244
  return [
240
245
  {
241
- name: "Press Enter to finish",
246
+ name: _PRESS_ENTER_TO_FINISH,
242
247
  value: "",
243
248
  description: "",
244
249
  },
@@ -247,35 +252,71 @@ export default async function init(
247
252
 
248
253
  const searchTerm = input.trim();
249
254
 
255
+ // Check if input looks like a glob pattern
256
+ const isGlobPatternResult = isGlobPattern(searchTerm);
257
+
258
+ if (isGlobPatternResult) {
259
+ // If it looks like a glob pattern, allow direct input
260
+ return [
261
+ {
262
+ name: `Use glob pattern: ${searchTerm}`,
263
+ value: searchTerm,
264
+ description: "Glob pattern for file matching",
265
+ },
266
+ ];
267
+ }
268
+
250
269
  // Search for matching files and folders in current directory
251
270
  const availablePaths = getAvailablePaths(searchTerm);
252
271
 
253
- return [...availablePaths];
272
+ // Also add option to use as glob pattern
273
+ const options = [...availablePaths];
274
+ if (searchTerm.length > 0) {
275
+ options.unshift({
276
+ name: `Use as glob pattern: ${searchTerm}`,
277
+ value: searchTerm,
278
+ description: "Treat input as glob pattern",
279
+ });
280
+ }
281
+
282
+ return options;
254
283
  },
255
284
  });
256
285
 
257
286
  // Check if user chose to exit
258
- if (!selectedPath || selectedPath.trim() === "" || selectedPath === "Press Enter to finish") {
287
+ if (!selectedPath || selectedPath.trim() === "" || selectedPath === _PRESS_ENTER_TO_FINISH) {
259
288
  break;
260
289
  }
261
290
 
262
291
  const trimmedPath = selectedPath.trim();
263
292
 
264
- // Use validatePath to check if path is valid
265
- const validation = validatePath(trimmedPath);
293
+ // Check if it's a glob pattern
294
+ const isGlobPatternResult = isGlobPattern(trimmedPath);
266
295
 
267
- if (!validation.isValid) {
268
- console.log(`⚠️ ${validation.error}`);
269
- continue;
270
- }
296
+ if (isGlobPatternResult) {
297
+ // For glob patterns, just add them without validation
298
+ if (sourcePaths.includes(trimmedPath)) {
299
+ console.log(`⚠️ Pattern already exists: ${trimmedPath}`);
300
+ continue;
301
+ }
302
+ sourcePaths.push(trimmedPath);
303
+ } else {
304
+ // Use validatePath to check if path is valid for regular paths
305
+ const validation = validatePath(trimmedPath);
306
+
307
+ if (!validation.isValid) {
308
+ console.log(`⚠️ ${validation.error}`);
309
+ continue;
310
+ }
271
311
 
272
- // Avoid duplicate paths
273
- if (sourcePaths.includes(trimmedPath)) {
274
- console.log(`⚠️ Path already exists: ${trimmedPath}`);
275
- continue;
276
- }
312
+ // Avoid duplicate paths
313
+ if (sourcePaths.includes(trimmedPath)) {
314
+ console.log(`⚠️ Path already exists: ${trimmedPath}`);
315
+ continue;
316
+ }
277
317
 
278
- sourcePaths.push(trimmedPath);
318
+ sourcePaths.push(trimmedPath);
319
+ }
279
320
  }
280
321
 
281
322
  // If no paths entered, use default
@@ -324,114 +365,142 @@ export default async function init(
324
365
  * @param {Object} input - Input object
325
366
  * @returns {string} YAML string
326
367
  */
327
- function generateYAML(input) {
328
- let yaml = "";
368
+ export function generateYAML(input) {
369
+ // Create the main configuration object that will be safely serialized
370
+ const config = {
371
+ // Project information (safely handled by yaml library)
372
+ projectName: input.projectName || "",
373
+ projectDesc: input.projectDesc || "",
374
+ projectLogo: input.projectLogo || "",
375
+
376
+ // Documentation configuration
377
+ documentPurpose: input.documentPurpose || [],
378
+ targetAudienceTypes: input.targetAudienceTypes || [],
379
+ readerKnowledgeLevel: input.readerKnowledgeLevel || "",
380
+ documentationDepth: input.documentationDepth || "",
381
+
382
+ // Custom rules and target audience (empty for user to fill)
383
+ rules: "",
384
+ targetAudience: "",
385
+
386
+ // Language settings
387
+ locale: input.locale || "en",
388
+ translateLanguages: input.translateLanguages?.filter((lang) => lang.trim()) || [],
389
+
390
+ // Paths
391
+ docsDir: input.docsDir || "./aigne/doc-smith/docs",
392
+ sourcesPath: input.sourcesPath || [],
393
+ };
394
+
395
+ // Generate comments and structure
396
+ let yaml = "# Project information for documentation publishing\n";
329
397
 
330
- // Add project information at the beginning
331
- yaml += `# Project information for documentation publishing\n`;
332
- yaml += `projectName: ${input.projectName || ""}\n`;
333
- yaml += `projectDesc: ${input.projectDesc || ""}\n`;
334
- yaml += `projectLogo: ${input.projectLogo || ""}\n`;
335
- yaml += `\n`;
398
+ // Serialize the project info section safely
399
+ const projectSection = yamlStringify({
400
+ projectName: config.projectName,
401
+ projectDesc: config.projectDesc,
402
+ projectLogo: config.projectLogo,
403
+ }).trim();
336
404
 
337
- // Add documentation configuration choices with all available options
338
- yaml += `# =============================================================================\n`;
339
- yaml += `# Documentation Configuration\n`;
340
- yaml += `# =============================================================================\n\n`;
405
+ yaml += `${projectSection}\n\n`;
406
+
407
+ // Add documentation configuration with comments
408
+ yaml += "# =============================================================================\n";
409
+ yaml += "# Documentation Configuration\n";
410
+ yaml += "# =============================================================================\n\n";
341
411
 
342
412
  // Document Purpose with all available options
343
- yaml += `# Purpose: What's the main outcome you want readers to achieve?\n`;
344
- yaml += `# Available options (uncomment and modify as needed):\n`;
413
+ yaml += "# Purpose: What's the main outcome you want readers to achieve?\n";
414
+ yaml += "# Available options (uncomment and modify as needed):\n";
345
415
  Object.entries(DOCUMENT_STYLES).forEach(([key, style]) => {
346
416
  if (key !== "custom") {
347
417
  yaml += `# ${key.padEnd(16)} - ${style.name}: ${style.description}\n`;
348
418
  }
349
419
  });
350
- yaml += `documentPurpose:\n`;
351
- if (input.documentPurpose && input.documentPurpose.length > 0) {
352
- input.documentPurpose.forEach((purpose) => {
353
- yaml += ` - ${purpose}\n`;
354
- });
355
- }
356
- yaml += `\n`;
420
+
421
+ // Safely serialize documentPurpose
422
+ const documentPurposeSection = yamlStringify({ documentPurpose: config.documentPurpose }).trim();
423
+ yaml += `${documentPurposeSection.replace(/^documentPurpose:/, "documentPurpose:")}\n\n`;
357
424
 
358
425
  // Target Audience Types with all available options
359
- yaml += `# Target Audience: Who will be reading this most often?\n`;
360
- yaml += `# Available options (uncomment and modify as needed):\n`;
426
+ yaml += "# Target Audience: Who will be reading this most often?\n";
427
+ yaml += "# Available options (uncomment and modify as needed):\n";
361
428
  Object.entries(TARGET_AUDIENCES).forEach(([key, audience]) => {
362
429
  if (key !== "custom") {
363
430
  yaml += `# ${key.padEnd(16)} - ${audience.name}: ${audience.description}\n`;
364
431
  }
365
432
  });
366
- yaml += `targetAudienceTypes:\n`;
367
- if (input.targetAudienceTypes && input.targetAudienceTypes.length > 0) {
368
- input.targetAudienceTypes.forEach((audience) => {
369
- yaml += ` - ${audience}\n`;
370
- });
371
- }
372
- yaml += `\n`;
433
+
434
+ // Safely serialize targetAudienceTypes
435
+ const targetAudienceTypesSection = yamlStringify({
436
+ targetAudienceTypes: config.targetAudienceTypes,
437
+ }).trim();
438
+ yaml += `${targetAudienceTypesSection.replace(/^targetAudienceTypes:/, "targetAudienceTypes:")}\n\n`;
373
439
 
374
440
  // Reader Knowledge Level with all available options
375
- yaml += `# Reader Knowledge Level: What do readers typically know when they arrive?\n`;
376
- yaml += `# Available options (uncomment and modify as needed):\n`;
441
+ yaml += "# Reader Knowledge Level: What do readers typically know when they arrive?\n";
442
+ yaml += "# Available options (uncomment and modify as needed):\n";
377
443
  Object.entries(READER_KNOWLEDGE_LEVELS).forEach(([key, level]) => {
378
444
  yaml += `# ${key.padEnd(20)} - ${level.name}: ${level.description}\n`;
379
445
  });
380
- yaml += `readerKnowledgeLevel: ${input.readerKnowledgeLevel || ""}\n`;
381
- yaml += `\n`;
446
+
447
+ // Safely serialize readerKnowledgeLevel
448
+ const readerKnowledgeLevelSection = yamlStringify({
449
+ readerKnowledgeLevel: config.readerKnowledgeLevel,
450
+ }).trim();
451
+ yaml += `${readerKnowledgeLevelSection.replace(/^readerKnowledgeLevel:/, "readerKnowledgeLevel:")}\n\n`;
382
452
 
383
453
  // Documentation Depth with all available options
384
- yaml += `# Documentation Depth: How comprehensive should the documentation be?\n`;
385
- yaml += `# Available options (uncomment and modify as needed):\n`;
454
+ yaml += "# Documentation Depth: How comprehensive should the documentation be?\n";
455
+ yaml += "# Available options (uncomment and modify as needed):\n";
386
456
  Object.entries(DOCUMENTATION_DEPTH).forEach(([key, depth]) => {
387
457
  yaml += `# ${key.padEnd(18)} - ${depth.name}: ${depth.description}\n`;
388
458
  });
389
- yaml += `documentationDepth: ${input.documentationDepth || ""}\n`;
390
- yaml += `\n`;
459
+
460
+ // Safely serialize documentationDepth
461
+ const documentationDepthSection = yamlStringify({
462
+ documentationDepth: config.documentationDepth,
463
+ }).trim();
464
+ yaml += `${documentationDepthSection.replace(/^documentationDepth:/, "documentationDepth:")}\n\n`;
391
465
 
392
466
  // Custom Documentation Rules and Requirements
393
- yaml += `# Custom Rules: Define specific documentation generation rules and requirements\n`;
394
- yaml += `rules: |\n`;
395
- yaml += ` \n\n`;
467
+ yaml += "# Custom Rules: Define specific documentation generation rules and requirements\n";
468
+ const rulesSection = yamlStringify({ rules: config.rules }).trim();
469
+ // Use literal style for multiline strings
470
+ yaml += `${rulesSection.replace(/rules: ''/, "rules: |\n ")}\n\n`;
396
471
 
397
472
  // Target Audience Description
398
- yaml += `# Target Audience: Describe your specific target audience and their characteristics\n`;
399
- yaml += `targetAudience: |\n`;
400
- yaml += ` \n\n`;
473
+ yaml += "# Target Audience: Describe your specific target audience and their characteristics\n";
474
+ const targetAudienceSection = yamlStringify({ targetAudience: config.targetAudience }).trim();
475
+ // Use literal style for multiline strings
476
+ yaml += `${targetAudienceSection.replace(/targetAudience: ''/, "targetAudience: |\n ")}\n\n`;
401
477
 
402
478
  // Glossary Configuration
403
- yaml += `# Glossary: Define project-specific terms and definitions\n`;
404
- yaml += `# glossary: "@glossary.md" # Path to markdown file containing glossary definitions\n`;
405
- yaml += `\n`;
406
-
407
- // Add language settings
408
- yaml += `locale: ${input.locale}\n`;
409
-
410
- // Add translation languages
411
- if (
412
- input.translateLanguages &&
413
- input.translateLanguages.length > 0 &&
414
- input.translateLanguages.some((lang) => lang.trim())
415
- ) {
416
- yaml += `translateLanguages:\n`;
417
- input.translateLanguages.forEach((lang) => {
418
- if (lang.trim()) {
419
- yaml += ` - ${lang}\n`;
420
- }
421
- });
479
+ yaml += "# Glossary: Define project-specific terms and definitions\n";
480
+ yaml += '# glossary: "@glossary.md" # Path to markdown file containing glossary definitions\n\n';
481
+
482
+ // Language settings - safely serialize
483
+ const localeSection = yamlStringify({ locale: config.locale }).trim();
484
+ yaml += `${localeSection.replace(/^locale:/, "locale:")}\n`;
485
+
486
+ // Translation languages
487
+ if (config.translateLanguages.length > 0) {
488
+ const translateLanguagesSection = yamlStringify({
489
+ translateLanguages: config.translateLanguages,
490
+ }).trim();
491
+ yaml += `${translateLanguagesSection.replace(/^translateLanguages:/, "translateLanguages:")}\n`;
422
492
  } else {
423
- yaml += `# translateLanguages: # List of languages to translate the documentation to\n`;
424
- yaml += `# - zh # Example: Chinese translation\n`;
425
- yaml += `# - en # Example: English translation\n`;
493
+ yaml += "# translateLanguages: # List of languages to translate the documentation to\n";
494
+ yaml += "# - zh # Example: Chinese translation\n";
495
+ yaml += "# - en # Example: English translation\n";
426
496
  }
427
497
 
428
- // Add directory and source path configurations
429
- yaml += `docsDir: ${input.docsDir} # Directory to save generated documentation\n`;
430
- // yaml += `outputDir: ${outputPath}/output # Directory to save output files\n`;
431
- yaml += `sourcesPath: # Source code paths to analyze\n`;
432
- input.sourcesPath.forEach((path) => {
433
- yaml += ` - ${path}\n`;
434
- });
498
+ // Directory and source path configurations - safely serialize
499
+ const docsDirSection = yamlStringify({ docsDir: config.docsDir }).trim();
500
+ yaml += `${docsDirSection.replace(/^docsDir:/, "docsDir:")} # Directory to save generated documentation\n`;
501
+
502
+ const sourcesPathSection = yamlStringify({ sourcesPath: config.sourcesPath }).trim();
503
+ yaml += `${sourcesPathSection.replace(/^sourcesPath:/, "sourcesPath: # Source code paths to analyze")}\n`;
435
504
 
436
505
  return yaml;
437
506
  }
@@ -31,11 +31,6 @@ export default async function loadConfig({ config, appUrl }) {
31
31
  const processedConfig = processConfigFields(parsedConfig);
32
32
 
33
33
  return {
34
- nodeName: "Section",
35
- locale: "en",
36
- sourcesPath: ["./"],
37
- docsDir: "./.aigne/doc-smith/docs",
38
- outputDir: "./.aigne/doc-smith/output",
39
34
  lastGitHead: parsedConfig.lastGitHead || "",
40
35
  ...parsedConfig,
41
36
  ...processedConfig,