@aigne/doc-smith 0.0.1 → 0.1.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.
@@ -0,0 +1,49 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ release-please:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: googleapis/release-please-action@v4
17
+ id: release
18
+ with:
19
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
20
+ release-type: node
21
+
22
+ - name: Checkout
23
+ uses: actions/checkout@v4
24
+ if: ${{ steps.release.outputs.release_created }}
25
+ with:
26
+ fetch-depth: 2
27
+
28
+ - uses: pnpm/action-setup@v3
29
+ if: ${{ steps.release.outputs.release_created }}
30
+ with:
31
+ version: 10
32
+
33
+ - name: Setup node
34
+ uses: actions/setup-node@v4
35
+ if: ${{ steps.release.outputs.release_created }}
36
+ with:
37
+ node-version: 23
38
+ cache: pnpm
39
+
40
+ - name: Install dependencies
41
+ run: |
42
+ pnpm install
43
+ if: ${{ steps.release.outputs.release_created }}
44
+
45
+ - name: Publish to NPM
46
+ if: ${{ steps.release.outputs.release_created }}
47
+ run: |
48
+ npm config set '//registry.npmjs.org/:_authToken' "${{ secrets.NPM_TOKEN }}"
49
+ pnpm publish --access public --no-git-checks
package/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.0.2...v0.1.0) (2025-07-31)
4
+
5
+
6
+ ### Features
7
+
8
+ * add entry agents for mcp server and cli ([fa85d65](https://github.com/AIGNE-io/aigne-doc-smith/commit/fa85d651e8dc723e2b97150fc2258b115c6c5bb0))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * polish command param ([#3](https://github.com/AIGNE-io/aigne-doc-smith/issues/3)) ([003f6b8](https://github.com/AIGNE-io/aigne-doc-smith/commit/003f6b8ae2c9e1af55ba1841458fa8567a0eb2f0))
14
+ * polish docs mcp agent ([8654cd4](https://github.com/AIGNE-io/aigne-doc-smith/commit/8654cd4ea38034f3af0244f56b27acf66ba704e1))
15
+
16
+ ## 0.0.2 (2025-07-30)
17
+
18
+
19
+ ### Features
20
+
21
+ * add auto create board ([3ff06ad](https://github.com/AIGNE-io/aigne-doc-smith/commit/3ff06ad0241e208b09bcf828c52c2c5051c67ef8))
22
+ * add docs-mcp ([a7508a1](https://github.com/AIGNE-io/aigne-doc-smith/commit/a7508a13abb2222968b1bc9c14948427af509f97))
23
+ * add input generator agent ([20c01bb](https://github.com/AIGNE-io/aigne-doc-smith/commit/20c01bbca6d6f9414695071fc907bd7cf43d7f62))
24
+ * add publish docs ([41bb126](https://github.com/AIGNE-io/aigne-doc-smith/commit/41bb126caeb1c3c242c7a2be27abb114aeab9953))
25
+ * add support docs labels ([4522c07](https://github.com/AIGNE-io/aigne-doc-smith/commit/4522c07b1ceb05664a1f5b5fb4df06feee536eba))
26
+ * detail add review ([8f1aa4f](https://github.com/AIGNE-io/aigne-doc-smith/commit/8f1aa4f22e2d2e590d7aa37288c2e1ee7ea48f07))
27
+ * init commit ([dafc40e](https://github.com/AIGNE-io/aigne-doc-smith/commit/dafc40e94f3c407e50b2c46ecb46237f23a15cf7))
28
+ * structure plan add review ([b56e83e](https://github.com/AIGNE-io/aigne-doc-smith/commit/b56e83e558f509302b422205f30e9b2adb42d452))
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * polish agent name ([25875a0](https://github.com/AIGNE-io/aigne-doc-smith/commit/25875a0688ebbca71f6c25bf4bd5246361f3dd2d))
34
+ * polish agent param and description ([290eb24](https://github.com/AIGNE-io/aigne-doc-smith/commit/290eb240ce986b0f1f406bf42824ce1235df11e5))
35
+ * polish code ([34f9a24](https://github.com/AIGNE-io/aigne-doc-smith/commit/34f9a24fc3748b4177cad2b5330fe6b3ccd99175))
36
+ * polish code ([0343486](https://github.com/AIGNE-io/aigne-doc-smith/commit/0343486aa086bbe2ced8de849de6a4a42567719c))
37
+ * polish code ([7b7dfb9](https://github.com/AIGNE-io/aigne-doc-smith/commit/7b7dfb925b3aa55956ef7a99ededc749fb6a42d7))
38
+ * polish code ([4fa4694](https://github.com/AIGNE-io/aigne-doc-smith/commit/4fa4694dbbd5880d501883a7cf3c0d3494509fb4))
39
+ * polish code ([74fee51](https://github.com/AIGNE-io/aigne-doc-smith/commit/74fee51ad6337af8811a35f2a4334b67ec109439))
40
+ * polish code ([7fa1675](https://github.com/AIGNE-io/aigne-doc-smith/commit/7fa1675b2cab6144d1fb9d4388130209c6cfa0bc))
41
+ * polish docs review ([70374ab](https://github.com/AIGNE-io/aigne-doc-smith/commit/70374abed74946eafa7b0f87331c2e496fa61592))
42
+ * polish input generator agent ([ae908bb](https://github.com/AIGNE-io/aigne-doc-smith/commit/ae908bbc0cb98b9b196e8b08f23149e5693e0abe))
43
+ * polish structure plan ([3a0a196](https://github.com/AIGNE-io/aigne-doc-smith/commit/3a0a196a97196ba445c4709d3466ff355917ac53))
44
+ * save docs remove useless docs ([bec5ba3](https://github.com/AIGNE-io/aigne-doc-smith/commit/bec5ba3afd462c990a0aa813bbe38ce9a61363ee))
45
+
46
+
47
+ ### Miscellaneous Chores
48
+
49
+ * release 0.0.2 ([73bf26a](https://github.com/AIGNE-io/aigne-doc-smith/commit/73bf26a5c55fa4726d866cff64bd48d1ca37a3b3))
package/README.md CHANGED
@@ -61,17 +61,40 @@ Contributions are welcome! Please feel free to submit a pull request or open an
61
61
  npx --no doc-smith run --entry-agent init
62
62
 
63
63
  # 生成命令
64
- npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent generator --model gemini:gemini-2.5-pro
64
+ npx --no doc-smith run --entry-agent generator --model gemini:gemini-2.5-flash
65
65
 
66
66
  # 重新生成单篇
67
- npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent regenerator --input-currentPath /overview --input-feedback "输出只能使用 markdown 格式,不能使用 html 标签" --model gemini:gemini-2.5-pro
67
+ npx --no doc-smith run --entry-agent regenerator --input-path bitnet-getting-started
68
68
 
69
69
  # 结构规划优化
70
- npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent generator --input-structurePlanFeedback "删除 Contributing 下的子节点,只保留主节点,相关信息在主节点中显示" --model gemini:gemini-2.5-pro
70
+ npx --no doc-smith run --entry-agent generator --input-feedback "补充节点的 sourceIds,确保所有节点 sourceIds 都有值" --model gemini:gemini-2.5-pro
71
71
 
72
72
 
73
73
  # 发布文档
74
- npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent publish
74
+ npx --no doc-smith run --entry-agent publish
75
+
76
+
77
+ ```
78
+
79
+ 使用 `aigne doc` 运行
80
+
81
+ ```shell
82
+ # 初始化
83
+ aigne doc init
84
+
85
+ # 生成文档
86
+ aigne doc generate
87
+
88
+ # 优化结构规划
89
+ aigne doc generate --feedback "删除 About 文档"
90
+
91
+ # 优化单篇文档
92
+ # 可使用 structure-plan.json 中的 path ,或 Discuss Kit 中访问的 path
93
+ aigne doc update --doc-path /faq --feedback "添加更多的 FAQ"
94
+
95
+ # 发布文档
96
+ aigne doc publish
97
+
75
98
 
76
99
  ```
77
100
 
@@ -11,7 +11,7 @@ input_schema:
11
11
  originalStructurePlan:
12
12
  $ref: ./schema/structure-plan.yaml
13
13
  description: 上一轮生成的结构规划,用于对比。如果不存在,则检查默认通过。
14
- structurePlanFeedback:
14
+ feedback:
15
15
  type: string
16
16
  description: 用户针对上一轮结果提供的反馈。
17
17
  required:
@@ -9,11 +9,11 @@ import { OpenAIChatModel } from "@aigne/openai";
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
10
 
11
11
  export default async function checkStructurePlanning(
12
- { originalStructurePlan, structurePlanFeedback, ...rest },
12
+ { originalStructurePlan, feedback, ...rest },
13
13
  options
14
14
  ) {
15
15
  // If originalStructurePlan exists, return directly
16
- if (originalStructurePlan && !structurePlanFeedback) {
16
+ if (originalStructurePlan && !feedback) {
17
17
  return {
18
18
  structurePlan: originalStructurePlan,
19
19
  };
@@ -39,14 +39,14 @@ export default async function checkStructurePlanning(
39
39
  const panningAgent = aigne.agents["reflective-structure-planner"];
40
40
 
41
41
  const result = await options.context.invoke(panningAgent, {
42
- structurePlanFeedback,
42
+ feedback: feedback || "",
43
43
  originalStructurePlan,
44
44
  ...rest,
45
45
  });
46
46
 
47
47
  return {
48
48
  ...result,
49
- structurePlanFeedback,
49
+ feedback: "", // clear feedback
50
50
  originalStructurePlan: originalStructurePlan
51
51
  ? originalStructurePlan
52
52
  : JSON.parse(JSON.stringify(result.structurePlan || [])),
@@ -35,7 +35,7 @@ input_schema:
35
35
  glossary:
36
36
  type: string
37
37
  description: 术语表
38
- currentPath:
38
+ doc-path:
39
39
  type: string
40
40
  description: 当前路径
41
41
  feedback:
@@ -1,7 +1,8 @@
1
1
  type: team
2
- name: regenerator
3
- description: 生成文档详情并翻译
2
+ name: update
3
+ description: Optimize and regenerate individual document content and translations
4
4
  skills:
5
+ - ./load-config.mjs
5
6
  - ./load-sources.mjs
6
7
  - type: transform
7
8
  jsonata: |
@@ -19,80 +20,78 @@ skills:
19
20
  })
20
21
  }
21
22
  ])
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
- )
23
+ - ./find-item-by-path.mjs
34
24
  - ./format-structure-plan.mjs
35
25
  - ./detail-generator-and-translate.yaml
36
26
  input_schema:
37
27
  type: object
38
28
  properties:
39
- nodeName:
40
- type: string
41
- description: 节点名称
42
- default: 部分
43
- locale:
44
- type: string
45
- description: 语言
46
- targetAudience:
29
+ config:
47
30
  type: string
48
- description: 目标受众
31
+ description: Path to the config file
32
+ default: ./doc-smith/config.yaml
33
+ # nodeName:
34
+ # type: string
35
+ # description: Name of the section to generate documentation for
36
+ # default: Section
37
+ # locale:
38
+ # type: string
39
+ # description: Primary language for documentation (e.g., zh, en)
40
+ # targetAudience:
41
+ # type: string
42
+ # description: Target audience for the documentation
49
43
  glossary:
50
44
  type: string
51
- description: 术语表
52
- currentPath:
45
+ description: Glossary of terms for consistent terminology
46
+ doc-path:
53
47
  type: string
54
- description: 当前路径
48
+ description: Document path to regenerate
55
49
  feedback:
56
50
  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: 标签
51
+ description: Feedback for content improvement
52
+ # sources:
53
+ # type: array
54
+ # items:
55
+ # type: string
56
+ # description: Source code files for documentation generation
57
+ # sourcesPath:
58
+ # type: array
59
+ # description: Source code paths
60
+ # items:
61
+ # type: string
62
+ # default:
63
+ # - ./
64
+ # includePatterns:
65
+ # type: array
66
+ # description: File patterns to include
67
+ # items:
68
+ # type: string
69
+ # excludePatterns:
70
+ # type: array
71
+ # description: File patterns to exclude
72
+ # items:
73
+ # type: string
74
+ # outputDir:
75
+ # type: string
76
+ # description: Output directory for intermediate files
77
+ # default: ./doc-smith/output
78
+ # docsDir:
79
+ # type: string
80
+ # description: Directory to save generated documentation
81
+ # default: ./doc-smith/docs
82
+ # additionalInformation:
83
+ # type: string
84
+ # description: Additional context or information for documentation
85
+ # translateLanguages:
86
+ # type: array
87
+ # items:
88
+ # type: string
89
+ # description: Target languages for translation (e.g., zh, en)
90
+ # labels:
91
+ # type: array
92
+ # items:
93
+ # type: string
94
+ # description: Tags or labels for categorization
96
95
  output_schema:
97
96
  type: object
98
97
  properties:
@@ -1,7 +1,8 @@
1
1
  type: team
2
- name: generator
3
- description: 文档生成
2
+ name: generate
3
+ description: Automatically generates comprehensive project documentation
4
4
  skills:
5
+ - ./load-config.mjs
5
6
  - ./load-sources.mjs
6
7
  - ./check-structure-planning.mjs
7
8
  - type: transform
@@ -34,60 +35,73 @@ skills:
34
35
  input_schema:
35
36
  type: object
36
37
  properties:
37
- nodeName:
38
+ config:
38
39
  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
40
+ description: Path to the config file
41
+ default: ./doc-smith/config.yaml
42
+ # nodeName:
43
+ # type: string
44
+ # description: Name of the section to generate documentation for
45
+ # default: Section
46
+ # rules:
47
+ # type: string
48
+ # description: Documentation generation requirements and rules
49
+ # locale:
50
+ # type: string
51
+ # description: Primary language for documentation (e.g., zh, en)
52
+ # sources:
53
+ # type: array
54
+ # items:
55
+ # type: string
56
+ # description: Source code files for documentation generation
57
+ # sourcesPath:
58
+ # type: array
59
+ # description: Source code paths
60
+ # items:
61
+ # type: string
62
+ # default:
63
+ # - ./
64
+ # includePatterns:
65
+ # type: array
66
+ # description: File patterns to include
67
+ # items:
68
+ # type: string
69
+ # excludePatterns:
70
+ # type: array
71
+ # description: File patterns to exclude
72
+ # items:
73
+ # type: string
74
+ # docsDir:
75
+ # type: string
76
+ # description: Directory to save generated documentation
77
+ # default: ./doc-smith/docs
78
+ # outputDir:
79
+ # type: string
80
+ # description: Output directory for intermediate files
81
+ # default: ./doc-smith/output
82
+ # translateLanguages:
83
+ # type: array
84
+ # items:
85
+ # type: string
86
+ # description: Target languages for translation (e.g., zh, en)
72
87
  glossary:
73
88
  type: string
74
- description: 术语表
75
- additionalInformation:
76
- type: string
77
- description: 额外补充信息
78
- docsType:
79
- type: string
80
- description: 文档类型,支持:generalgetting-startedreferencefaq
81
- default: general
82
- structurePlanFeedback:
89
+ description: Glossary of terms for consistent terminology
90
+ # additionalInformation:
91
+ # type: string
92
+ # description: Additional context or information for documentation
93
+ # docsType:
94
+ # type: string
95
+ # description: Type of documentation (general, getting-started, reference, faq)
96
+ # default: general
97
+ feedback:
83
98
  type: string
84
- description: 结构规划反馈
85
- labels:
86
- type: array
87
- items:
88
- type: string
89
- description: 标签
99
+ description: Feedback for structure planning adjustments
100
+ # labels:
101
+ # type: array
102
+ # items:
103
+ # type: string
104
+ # description: Tags or labels for categorization
90
105
  required:
91
- - rules
92
- - docsDir
106
+ - config
93
107
  mode: sequential
@@ -0,0 +1,39 @@
1
+ export default async function findItemByPath({
2
+ "doc-path": docPath,
3
+ structurePlanResult,
4
+ boardId,
5
+ }) {
6
+ let foundItem = null;
7
+
8
+ // First try direct path matching
9
+ foundItem = structurePlanResult.find((item) => item.path === docPath);
10
+
11
+ // If not found and boardId is provided, try boardId-flattenedPath format matching
12
+ if (!foundItem && boardId) {
13
+ // Check if path starts with boardId followed by a dash
14
+ if (docPath.startsWith(`${boardId}-`)) {
15
+ // Extract the flattened path part after boardId-
16
+ const flattenedPath = docPath.substring(boardId.length + 1);
17
+
18
+ // Find item by comparing flattened paths
19
+ foundItem = structurePlanResult.find((item) => {
20
+ // Convert item.path to flattened format (replace / with -)
21
+ const itemFlattenedPath = item.path
22
+ .replace(/^\//, "")
23
+ .replace(/\//g, "-");
24
+ return itemFlattenedPath === flattenedPath;
25
+ });
26
+ }
27
+ }
28
+
29
+ if (!foundItem) {
30
+ throw new Error(
31
+ `Item with path "${docPath}" not found in structurePlanResult`
32
+ );
33
+ }
34
+
35
+ // Merge the found item with originalStructurePlan
36
+ return {
37
+ ...foundItem,
38
+ };
39
+ }
@@ -9,7 +9,7 @@ import { join, dirname } from "node:path";
9
9
  * @returns {Promise<Object>}
10
10
  */
11
11
  export default async function init(
12
- { outputPath = "./doc-smith", fileName = "input.yaml" },
12
+ { outputPath = "./doc-smith", fileName = "config.yaml" },
13
13
  options
14
14
  ) {
15
15
  console.log("Welcome to AIGNE Doc Smith!");
@@ -31,16 +31,16 @@ export default async function init(
31
31
  console.log("\n=== Target Audience ===");
32
32
  const targetAudienceInput = await options.prompts.input({
33
33
  message:
34
- "What is the target audience? (e.g., developers, users, press Enter to skip):",
34
+ "What is the target audience? (e.g., developers, users, press Enter for default 'developers'):",
35
35
  });
36
- input.targetAudience = targetAudienceInput.trim() || "";
36
+ input.targetAudience = targetAudienceInput.trim() || "developers";
37
37
 
38
38
  // 3. Language settings
39
39
  console.log("\n=== Language Settings ===");
40
40
  const localeInput = await options.prompts.input({
41
- message: "Primary language (e.g., en, zh, press Enter to skip):",
41
+ message: "Primary language (e.g., en, zh, press Enter for default 'en'):",
42
42
  });
43
- input.locale = localeInput.trim() || "";
43
+ input.locale = localeInput.trim() || "en";
44
44
 
45
45
  // 4. Translation languages
46
46
  console.log("\n=== Translation Settings ===");
@@ -89,25 +89,19 @@ export default async function init(
89
89
  function generateYAML(input, outputPath) {
90
90
  let yaml = "";
91
91
 
92
- // Add rules
93
- if (input.rules) {
94
- yaml += `rules: |\n`;
92
+ // Add rules (required field)
93
+ yaml += `rules: |\n`;
94
+ if (input.rules && input.rules.trim()) {
95
95
  yaml += ` ${input.rules.split("\n").join("\n ")}\n\n`;
96
+ } else {
97
+ yaml += ` \n\n`;
96
98
  }
97
99
 
98
100
  // 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
- }
101
+ yaml += `targetAudience: ${input.targetAudience}\n`;
104
102
 
105
103
  // 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
- }
104
+ yaml += `locale: ${input.locale}\n`;
111
105
 
112
106
  // Add translation languages
113
107
  if (
@@ -136,7 +130,5 @@ function generateYAML(input, outputPath) {
136
130
  return yaml;
137
131
  }
138
132
 
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
- }
133
+ init.description =
134
+ "Generate a configuration file for the documentation generation process";
@@ -0,0 +1,43 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs/promises";
3
+ import { parse } from "yaml";
4
+
5
+ export default async function loadConfig({ config }) {
6
+ const configPath = path.join(process.cwd(), config);
7
+
8
+ try {
9
+ // Check if config file exists
10
+ await fs.access(configPath);
11
+ } catch (error) {
12
+ console.log(`Config file not found: ${configPath}`);
13
+ console.log("Please run 'aigne doc init' to create the config file.");
14
+ throw new Error(`Config file not found: ${configPath}`);
15
+ }
16
+
17
+ try {
18
+ // Read and parse YAML file
19
+ const configContent = await fs.readFile(configPath, "utf-8");
20
+ const parsedConfig = parse(configContent);
21
+ return {
22
+ nodeName: "Section",
23
+ locale: "en",
24
+ sourcesPath: ["./"],
25
+ docDir: "./doc-smith/docs",
26
+ outputDir: "./doc-smith/output",
27
+ ...parsedConfig,
28
+ };
29
+ } catch (error) {
30
+ console.error(`Error parsing config file: ${error.message}`);
31
+ throw new Error(`Failed to parse config file: ${error.message}`);
32
+ }
33
+ }
34
+
35
+ loadConfig.input_schema = {
36
+ type: "object",
37
+ properties: {
38
+ config: {
39
+ type: "string",
40
+ default: "./doc-smith/config.yaml",
41
+ },
42
+ },
43
+ };
@@ -44,8 +44,9 @@ const DEFAULT_EXCLUDE_PATTERNS = [
44
44
  "**/*test/**",
45
45
  "**/*tests/**",
46
46
  "**/*examples/**",
47
+ "**/playgrounds/**",
47
48
  "v1/**",
48
- "**/*dist/**",
49
+ "**/dist/**",
49
50
  "**/*build/**",
50
51
  "**/*experimental/**",
51
52
  "**/*deprecated/**",
@@ -59,6 +60,7 @@ const DEFAULT_EXCLUDE_PATTERNS = [
59
60
  "**/*bin/**",
60
61
  "**/*node_modules/**",
61
62
  "*.log",
63
+ "**/*test.*",
62
64
  ];
63
65
 
64
66
  /**
@@ -153,7 +155,8 @@ export default async function loadSources({
153
155
  excludePatterns,
154
156
  outputDir,
155
157
  docsDir,
156
- currentPath,
158
+ "doc-path": docPath,
159
+ boardId,
157
160
  useDefaultPatterns = true,
158
161
  } = {}) {
159
162
  let files = Array.isArray(sources) ? [...sources] : [];
@@ -248,15 +251,32 @@ export default async function loadSources({
248
251
 
249
252
  // Get the last output result of the specified path
250
253
  let content;
251
- if (currentPath) {
252
- const flatName = currentPath.replace(/^\//, "").replace(/\//g, "-");
253
- const fileFullName = `${flatName}.md`;
254
- const filePath = path.join(docsDir, fileFullName);
254
+ if (docPath) {
255
+ let fileFullName;
256
+
257
+ // First try direct path matching (original format)
258
+ const flatName = docPath.replace(/^\//, "").replace(/\//g, "-");
259
+ fileFullName = `${flatName}.md`;
260
+ let filePath = path.join(docsDir, fileFullName);
261
+
255
262
  try {
256
263
  await access(filePath);
257
264
  content = await readFile(filePath, "utf8");
258
265
  } catch {
259
- // The file does not exist, content remains undefined
266
+ // If not found and boardId is provided, try boardId-flattenedPath format
267
+ if (boardId && docPath.startsWith(`${boardId}-`)) {
268
+ // Extract the flattened path part after boardId-
269
+ const flattenedPath = docPath.substring(boardId.length + 1);
270
+ fileFullName = `${flattenedPath}.md`;
271
+ filePath = path.join(docsDir, fileFullName);
272
+
273
+ try {
274
+ await access(filePath);
275
+ content = await readFile(filePath, "utf8");
276
+ } catch {
277
+ // The file does not exist, content remains undefined
278
+ }
279
+ }
260
280
  }
261
281
  }
262
282
 
@@ -296,9 +316,13 @@ loadSources.input_schema = {
296
316
  description:
297
317
  "Whether to use default include/exclude patterns. Defaults to true.",
298
318
  },
299
- currentPath: {
319
+ "doc-path": {
320
+ type: "string",
321
+ description: "The document path to load content for",
322
+ },
323
+ boardId: {
300
324
  type: "string",
301
- description: "The current path of the document",
325
+ description: "The board ID for boardId-flattenedPath format matching",
302
326
  },
303
327
  },
304
328
  required: [],
@@ -117,7 +117,7 @@ async function getAccessToken(appUrl) {
117
117
  }
118
118
 
119
119
  /**
120
- * Save boardId to input.yaml file if it was auto-created
120
+ * Save boardId to config.yaml file if it was auto-created
121
121
  * @param {string} boardId - The original boardId (may be empty)
122
122
  * @param {string} newBoardId - The boardId returned from publishDocsFn
123
123
  */
@@ -130,7 +130,7 @@ async function saveBoardIdToInput(boardId, newBoardId) {
130
130
  mkdirSync(docSmithDir, { recursive: true });
131
131
  }
132
132
 
133
- const inputFilePath = join(docSmithDir, "input.yaml");
133
+ const inputFilePath = join(docSmithDir, "config.yaml");
134
134
  let fileContent = "";
135
135
 
136
136
  // Read existing file content if it exists
@@ -156,12 +156,12 @@ async function saveBoardIdToInput(boardId, newBoardId) {
156
156
  await writeFile(inputFilePath, fileContent);
157
157
  console.log(`Board ID saved to: ${inputFilePath}`);
158
158
  } catch (error) {
159
- console.warn("Failed to save board ID to input.yaml:", error.message);
159
+ console.warn("Failed to save board ID to config.yaml:", error.message);
160
160
  }
161
161
  }
162
162
  }
163
163
 
164
- export default async function publish({ docsDir, appUrl, boardId }) {
164
+ export default async function publishDocs({ docsDir, appUrl, boardId }) {
165
165
  const accessToken = await getAccessToken(appUrl);
166
166
 
167
167
  process.env.DOC_ROOT_DIR = docsDir;
@@ -180,7 +180,7 @@ export default async function publish({ docsDir, appUrl, boardId }) {
180
180
  autoCreateBoard: !boardId,
181
181
  });
182
182
 
183
- // Save boardId to input.yaml if it was auto-created
183
+ // Save boardId to config.yaml if it was auto-created
184
184
  await saveBoardIdToInput(boardId, newBoardId);
185
185
 
186
186
  return {
@@ -190,7 +190,7 @@ export default async function publish({ docsDir, appUrl, boardId }) {
190
190
  };
191
191
  }
192
192
 
193
- publish.input_schema = {
193
+ publishDocs.input_schema = {
194
194
  type: "object",
195
195
  properties: {
196
196
  docsDir: {
@@ -210,3 +210,5 @@ publish.input_schema = {
210
210
  },
211
211
  },
212
212
  };
213
+
214
+ publishDocs.description = "Publish the documentation to Discuss Kit";
@@ -12,6 +12,7 @@ export default async function saveDocs({
12
12
  structurePlanResult: structurePlan,
13
13
  docsDir,
14
14
  translateLanguages = [],
15
+ locale,
15
16
  }) {
16
17
  const results = [];
17
18
 
@@ -30,7 +31,8 @@ export default async function saveDocs({
30
31
  const cleanupResults = await cleanupInvalidFiles(
31
32
  structurePlan,
32
33
  docsDir,
33
- translateLanguages
34
+ translateLanguages,
35
+ locale
34
36
  );
35
37
  results.push(...cleanupResults);
36
38
  } catch (err) {
@@ -40,14 +42,31 @@ export default async function saveDocs({
40
42
  return { saveDocsResult: results };
41
43
  }
42
44
 
45
+ /**
46
+ * Generate filename based on flatName and language
47
+ * @param {string} flatName - Flattened path name
48
+ * @param {string} language - Language code
49
+ * @returns {string} - Generated filename
50
+ */
51
+ function generateFileName(flatName, language) {
52
+ const isEnglish = language === "en";
53
+ return isEnglish ? `${flatName}.md` : `${flatName}.${language}.md`;
54
+ }
55
+
43
56
  /**
44
57
  * Clean up .md files that are no longer in the structure plan
45
58
  * @param {Array<{path: string, title: string}>} structurePlan
46
59
  * @param {string} docsDir
47
60
  * @param {Array<string>} translateLanguages
61
+ * @param {string} locale - Main language locale (e.g., 'en', 'zh', 'fr')
48
62
  * @returns {Promise<Array<{ path: string, success: boolean, error?: string }>>}
49
63
  */
50
- async function cleanupInvalidFiles(structurePlan, docsDir, translateLanguages) {
64
+ async function cleanupInvalidFiles(
65
+ structurePlan,
66
+ docsDir,
67
+ translateLanguages,
68
+ locale
69
+ ) {
51
70
  const results = [];
52
71
 
53
72
  try {
@@ -61,12 +80,14 @@ async function cleanupInvalidFiles(structurePlan, docsDir, translateLanguages) {
61
80
  // Add main document files
62
81
  for (const { path } of structurePlan) {
63
82
  const flatName = path.replace(/^\//, "").replace(/\//g, "-");
64
- const fileName = `${flatName}.md`;
65
- expectedFiles.add(fileName);
83
+
84
+ // Main language file
85
+ const mainFileName = generateFileName(flatName, locale);
86
+ expectedFiles.add(mainFileName);
66
87
 
67
88
  // Add translation files for each language
68
89
  for (const lang of translateLanguages) {
69
- const translateFileName = `${flatName}.${lang}.md`;
90
+ const translateFileName = generateFileName(flatName, lang);
70
91
  expectedFiles.add(translateFileName);
71
92
  }
72
93
  }
@@ -6,6 +6,7 @@ export default async function saveSingleDoc({
6
6
  docsDir,
7
7
  translates,
8
8
  labels,
9
+ locale,
9
10
  }) {
10
11
  const results = await saveDocWithTranslations({
11
12
  path,
@@ -13,6 +14,7 @@ export default async function saveSingleDoc({
13
14
  docsDir,
14
15
  translates,
15
16
  labels,
17
+ locale,
16
18
  });
17
19
  return { saveSingleDocResult: results };
18
20
  }
@@ -23,7 +23,7 @@ input_schema:
23
23
  glossary:
24
24
  type: string
25
25
  description: 术语表
26
- structurePlanFeedback:
26
+ feedback:
27
27
  type: string
28
28
  description: 结构规划的反馈
29
29
  docsType:
@@ -0,0 +1,13 @@
1
+ type: team
2
+ name: publish
3
+ description: Publish the documentation to Discuss Kit
4
+ skills:
5
+ - load-config.mjs
6
+ - publish-docs.mjs
7
+ input_schema:
8
+ type: object
9
+ properties:
10
+ config:
11
+ type: string
12
+ description: Path to the config file
13
+ default: ./doc-smith/config.yaml
package/aigne.yaml CHANGED
@@ -6,7 +6,6 @@ chat_model:
6
6
  # name: gemini-2.5-flash-preview-05-20
7
7
  temperature: 0.8
8
8
  agents:
9
- - ./agents/docs-generator.yaml
10
9
  - ./agents/structure-planning.yaml
11
10
  - ./agents/batch-docs-detail-generator.yaml
12
11
  - ./agents/load-sources.mjs
@@ -23,6 +22,22 @@ agents:
23
22
  - ./agents/reflective-structure-planner.yaml
24
23
  - ./agents/check-structure-planning-result.yaml
25
24
  - ./agents/input-generator.mjs
25
+ - ./agents/docs-generator.yaml
26
26
  - ./agents/detail-regenerator.yaml
27
27
  - ./agents/publish-docs.mjs
28
28
  - ./agents/format-structure-plan.mjs
29
+ - ./agents/load-config.mjs
30
+ - ./agents/team-publish-docs.yaml
31
+ - ./agents/find-item-by-path.mjs
32
+ mcp_server:
33
+ agents:
34
+ - ./agents/input-generator.mjs
35
+ - ./agents/docs-generator.yaml
36
+ - ./agents/detail-regenerator.yaml
37
+ - ./agents/publish-docs.mjs
38
+ cli:
39
+ agents:
40
+ - ./agents/input-generator.mjs
41
+ - ./agents/docs-generator.yaml
42
+ - ./agents/detail-regenerator.yaml
43
+ - ./agents/publish-docs.mjs
File without changes
@@ -1,8 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
- const docsDir =
5
- "/Users/lban/arcblock/code/arcblock-sdk-docs/packages/aigne-framework/docs/";
4
+ const docsDir = path.join(process.cwd(), "doc-smith", "docs");
6
5
 
7
6
  export default async function getDocDetail({ path: docPath }) {
8
7
  try {
@@ -39,4 +38,4 @@ getDocDetail.input_schema = {
39
38
  },
40
39
  };
41
40
 
42
- getDocDetail.description = "Get AIGNE Framework single docs detail";
41
+ getDocDetail.description = "Get single docs detail";
@@ -1,11 +1,16 @@
1
1
  import fs from "node:fs/promises";
2
+ import path from "node:path";
2
3
 
3
- const structureDir =
4
- "/Users/lban/arcblock/code/arcblock-sdk-docs/packages/aigne-framework/output/structure-plan.json";
4
+ const structureDir = path.join(
5
+ process.cwd(),
6
+ "doc-smith",
7
+ "output",
8
+ "structure-plan.json"
9
+ );
5
10
 
6
11
  export default async function getDocsStructure() {
7
12
  const structure = await fs.readFile(structureDir, "utf-8");
8
13
  return { structure };
9
14
  }
10
15
 
11
- getDocsStructure.description = "Get AIGNE Framework docs structure";
16
+ getDocsStructure.description = "Get docs structure";
package/package.json CHANGED
@@ -1,18 +1,11 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "main": "index.js",
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1",
11
- "lint": "biome check && pnpm -r run lint",
12
- "update:deps": "npx -y taze major -r -w -f -n '/@abtnode|@aigne|@arcblock|@blocklet|@did-connect|@did-pay|@did-space|@nft-store|@nft-studio|@ocap/' && pnpm install && pnpm run deduplicate",
13
- "deduplicate": "pnpm dedupe",
14
- "lint:fix": "biome check --write && pnpm -r run lint"
15
- },
16
9
  "type": "module",
17
10
  "bin": "aigne.yaml",
18
11
  "keywords": [],
@@ -20,14 +13,21 @@
20
13
  "license": "MIT",
21
14
  "dependencies": {
22
15
  "@aigne/anthropic": "^0.10.2",
23
- "@aigne/cli": "^1.26.0",
16
+ "@aigne/cli": "^1.27.0",
24
17
  "@aigne/core": "^1.39.0",
25
18
  "@aigne/gemini": "^0.8.6",
26
19
  "@aigne/openai": "^0.10.6",
27
- "@aigne/publish-docs": "^0.4.0",
20
+ "@aigne/publish-docs": "^0.5.0",
28
21
  "glob": "^11.0.3",
29
22
  "open": "^10.2.0",
30
23
  "ufo": "^1.6.1",
31
24
  "yaml": "^2.8.0"
25
+ },
26
+ "scripts": {
27
+ "test": "echo \"Error: no test specified\" && exit 1",
28
+ "lint": "biome check && pnpm -r run lint",
29
+ "update:deps": "npx -y taze major -r -w -f -n '/@abtnode|@aigne|@arcblock|@blocklet|@did-connect|@did-pay|@did-space|@nft-store|@nft-studio|@ocap/' && pnpm install && pnpm run deduplicate",
30
+ "deduplicate": "pnpm dedupe",
31
+ "lint:fix": "biome check --write && pnpm -r run lint"
32
32
  }
33
- }
33
+ }
@@ -13,16 +13,19 @@
13
13
  {{ structurePlan }}
14
14
  ```
15
15
 
16
- - **用户反馈 (structurePlanFeedback)**:
16
+ - **用户反馈**:
17
17
  ```
18
- {{ structurePlanFeedback }}
18
+ {{ feedback }}
19
+
20
+ 根据最新的 Data Sources 按需要更新节点的 sourceIds ,更新至状态。
19
21
  ```
20
22
  </context>
21
23
 
22
24
  <goal>
23
25
  你的主要目标是验证两条关键规则:
24
26
  1. **反馈的实现**:新的结构规划**必须**正确地实现用户反馈中要求的所有变更。
25
- 2. **无关节点的稳定性**:没有在用户反馈中被提及的节点**绝不能**被修改,特别是它们的 `path` 属性。`path` 是关联现有内容的关键标识符,其稳定性至关重要。
27
+ 2. **无关节点的稳定性**:没有在用户反馈中被提及的节点 ** path 属性不能被修改 **。`path` 是关联现有内容的关键标识符,其稳定性至关重要。
28
+ 3. **数据有效性**: 所有 {{ nodeName }} 都有关联数据源,sourceIds 中都有值。
26
29
  </goal>
27
30
 
28
31
  <rules>
@@ -34,12 +37,12 @@
34
37
  这是主要场景。你必须执行详细的比较。
35
38
 
36
39
  **分步分析**:
37
- 1. **分析反馈**:仔细阅读并理解 `<context>` 中 `structurePlanFeedback` 提供的每一项变更要求。明确哪些节点是需要被修改、添加或删除的目标。
40
+ 1. **分析反馈**:仔细阅读并理解 `<context>` 中 用户反馈 提出的每一项变更要求。明确哪些节点是需要被修改、添加或删除的目标。
38
41
  2. **验证反馈的实现**:对比 `<context>` 中的 `structurePlan` 和 `originalStructurePlan`,确认所要求的变更是否已执行。例如,如果反馈是“移除‘示例’部分”,你必须检查该部分在 `structurePlan` 中是否已不存在。
39
42
  3. **验证无关节点的稳定性**:这是最关键的检查。遍历 `structurePlan` 中的所有节点。对于每一个在 `originalStructurePlan` 中也存在、但并未在反馈中被提及的节点:
40
43
  * **至关重要**:其 `path` 属性**必须**与 `originalStructurePlan` 中的完全相同。
41
44
  * 理想情况下,其他属性(如 `title`)也应保持稳定,除非这些变更是由某个被要求的变更直接导致的。
42
- * **`sourcesIds` 的变更是允许的**,可以根据最新的 DataSources 变更依赖的数据源
45
+ 4. **`sourcesIds` 是允许变更的**,每次结构规划可以根据最新的 DataSources 变更依赖的数据源,sourceIds 不能为空。
43
46
  </rules>
44
47
 
45
48
  <output>
@@ -71,6 +74,15 @@
71
74
  "reason": "The new structure plan modified unrelated nodes, which is not allowed. [Please provide specific details, e.g.: 'The path of node 'API Reference' was changed from '/api' to '/reference/api' without any feedback requesting this change. This is a critical error.']"
72
75
  }
73
76
  ```
77
+
78
+ * ** 如果数据无效 **:
79
+ ```json
80
+ {
81
+ "isValid": false,
82
+ "reason": "The structure plan contains nodes without associated data sources. Each node must have at least one source file linked through sourcesIds."
83
+ }
84
+ ```
85
+
74
86
  * **如果是首次运行**:
75
87
 
76
88
  ```json
@@ -64,8 +64,10 @@ parentId: {{parentId}}
64
64
  - 结合当前{{nodeName}}的标题、描述,合理规划{{nodeName}}内容结构,内容要丰富、有条理、有吸引力。
65
65
  - 内容风格需要匹配目标受众
66
66
  - 明确区分与 structurePlan 其他{{nodeName}}的内容,避免重复,突出本{{nodeName}}的独特价值。
67
+ {% if enforceInfoCompleteness %}
67
68
  - 如果 DataSources 相关信息不足,直接返回错误信息,提示用户补充内容,要确保页面内容足够丰富,你可以放心的向用户提出补充信息的要求。
68
69
  - 只展示有价值、能吸引用户的信息,如信息不足,提示用户补充信息
70
+ {% endif %}
69
71
  - 输出为完整的信息,包含{{nodeName}}计划展示的全部信息。
70
72
  - 确保每个{{nodeName}}的详情中,都包含一个 markdown 的一级标题,展示当前{{nodeName}}的标题:{{title}}
71
73
  - markdown 输出内容正常换行、添加空行,让内容容易阅读
@@ -28,7 +28,9 @@
28
28
  </last_structure_plan>
29
29
 
30
30
  <structure_plan_feedback>
31
- {{structurePlanFeedback}}
31
+ {{ feedback }}
32
+
33
+ 根据最新的 Data Sources 按需要更新节点的 sourceIds ,更新至状态。
32
34
  </structure_plan_feedback>
33
35
 
34
36
  <review_structure_plan>
package/utils/utils.mjs CHANGED
@@ -34,6 +34,7 @@ export function processContent({ content }) {
34
34
  * @param {string} params.path - Relative path (without extension)
35
35
  * @param {string} params.content - Main document content
36
36
  * @param {string} params.docsDir - Root directory
37
+ * @param {string} params.locale - Main content language (e.g., 'en', 'zh', 'fr')
37
38
  * @param {Array<{language: string, translation: string}>} [params.translates] - Translation content
38
39
  * @param {Array<string>} [params.labels] - Document labels for front matter
39
40
  * @returns {Promise<Array<{ path: string, success: boolean, error?: string }>>}
@@ -42,6 +43,7 @@ export async function saveDocWithTranslations({
42
43
  path: docPath,
43
44
  content,
44
45
  docsDir,
46
+ locale,
45
47
  translates = [],
46
48
  labels,
47
49
  }) {
@@ -49,10 +51,18 @@ export async function saveDocWithTranslations({
49
51
  try {
50
52
  // Flatten path: remove leading /, replace all / with -
51
53
  const flatName = docPath.replace(/^\//, "").replace(/\//g, "-");
52
- const fileFullName = `${flatName}.md`;
53
- const filePath = path.join(docsDir, fileFullName);
54
54
  await fs.mkdir(docsDir, { recursive: true });
55
55
 
56
+ // Helper function to generate filename based on language
57
+ const getFileName = (language) => {
58
+ const isEnglish = language === "en";
59
+ return isEnglish ? `${flatName}.md` : `${flatName}.${language}.md`;
60
+ };
61
+
62
+ // Save main content with appropriate filename based on locale
63
+ const mainFileName = getFileName(locale);
64
+ const mainFilePath = path.join(docsDir, mainFileName);
65
+
56
66
  // Add labels front matter if labels are provided
57
67
  let finalContent = processContent({ content });
58
68
  if (labels && labels.length > 0) {
@@ -60,11 +70,12 @@ export async function saveDocWithTranslations({
60
70
  finalContent = frontMatter + finalContent;
61
71
  }
62
72
 
63
- await fs.writeFile(filePath, finalContent, "utf8");
64
- results.push({ path: filePath, success: true });
73
+ await fs.writeFile(mainFilePath, finalContent, "utf8");
74
+ results.push({ path: mainFilePath, success: true });
65
75
 
76
+ // Process all translations
66
77
  for (const translate of translates) {
67
- const translateFileName = `${flatName}.${translate.language}.md`;
78
+ const translateFileName = getFileName(translate.language);
68
79
  const translatePath = path.join(docsDir, translateFileName);
69
80
 
70
81
  // Add labels front matter to translation content if labels are provided