@aigne/doc-smith 0.6.0 → 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 (39) hide show
  1. package/.github/workflows/ci.yml +46 -0
  2. package/.github/workflows/reviewer.yml +2 -1
  3. package/CHANGELOG.md +10 -0
  4. package/agents/chat.yaml +30 -0
  5. package/agents/check-structure-plan.mjs +1 -1
  6. package/agents/docs-fs.yaml +25 -0
  7. package/agents/exit.mjs +6 -0
  8. package/agents/feedback-refiner.yaml +5 -1
  9. package/agents/find-items-by-paths.mjs +10 -4
  10. package/agents/fs.mjs +60 -0
  11. package/agents/input-generator.mjs +159 -90
  12. package/agents/load-config.mjs +0 -5
  13. package/agents/load-sources.mjs +61 -8
  14. package/agents/publish-docs.mjs +27 -12
  15. package/agents/retranslate.yaml +1 -1
  16. package/agents/team-publish-docs.yaml +2 -2
  17. package/aigne.yaml +1 -0
  18. package/package.json +13 -10
  19. package/prompts/content-detail-generator.md +7 -3
  20. package/prompts/document/custom-components.md +80 -0
  21. package/prompts/document/d2-chart/diy-examples.md +44 -0
  22. package/prompts/document/d2-chart/official-examples.md +708 -0
  23. package/prompts/document/d2-chart/rules.md +48 -0
  24. package/prompts/document/detail-generator.md +12 -15
  25. package/prompts/document/structure-planning.md +1 -3
  26. package/prompts/feedback-refiner.md +81 -60
  27. package/prompts/structure-planning.md +20 -3
  28. package/tests/check-detail-result.test.mjs +3 -4
  29. package/tests/conflict-resolution.test.mjs +237 -0
  30. package/tests/input-generator.test.mjs +940 -0
  31. package/tests/load-sources.test.mjs +627 -3
  32. package/tests/preferences-utils.test.mjs +94 -0
  33. package/tests/save-value-to-config.test.mjs +182 -5
  34. package/tests/utils.test.mjs +49 -0
  35. package/utils/conflict-detector.mjs +72 -1
  36. package/utils/constants.mjs +125 -124
  37. package/utils/kroki-utils.mjs +162 -0
  38. package/utils/markdown-checker.mjs +98 -70
  39. package/utils/utils.mjs +96 -28
@@ -2,7 +2,11 @@ import { access, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_INCLUDE_PATTERNS } from "../utils/constants.mjs";
4
4
  import { getFilesWithGlob, loadGitignore } from "../utils/file-utils.mjs";
5
- import { getCurrentGitHead, getModifiedFilesBetweenCommits } from "../utils/utils.mjs";
5
+ import {
6
+ getCurrentGitHead,
7
+ getModifiedFilesBetweenCommits,
8
+ isGlobPattern,
9
+ } from "../utils/utils.mjs";
6
10
 
7
11
  export default async function loadSources({
8
12
  sources = [],
@@ -24,7 +28,12 @@ export default async function loadSources({
24
28
 
25
29
  for (const dir of paths) {
26
30
  try {
27
- // Check if the path is a file or directory
31
+ if (typeof dir !== "string") {
32
+ console.warn(`Invalid source path: ${dir}`);
33
+ continue;
34
+ }
35
+
36
+ // First try to access as a file or directory
28
37
  const stats = await stat(dir);
29
38
 
30
39
  if (stats.isFile()) {
@@ -78,7 +87,31 @@ export default async function loadSources({
78
87
  allFiles = allFiles.concat(filesInDir);
79
88
  }
80
89
  } catch (err) {
81
- if (err.code !== "ENOENT") throw err;
90
+ if (err.code === "ENOENT") {
91
+ // Path doesn't exist as file or directory, try as glob pattern
92
+ try {
93
+ // Check if it looks like a glob pattern
94
+ const isGlobPatternResult = isGlobPattern(dir);
95
+
96
+ if (isGlobPatternResult) {
97
+ // Use glob to find matching files from current working directory
98
+ const { glob } = await import("glob");
99
+ const matchedFiles = await glob(dir, {
100
+ absolute: true,
101
+ nodir: true, // Only files, not directories
102
+ dot: false, // Don't include hidden files
103
+ });
104
+
105
+ if (matchedFiles.length > 0) {
106
+ allFiles = allFiles.concat(matchedFiles);
107
+ }
108
+ }
109
+ } catch (globErr) {
110
+ console.warn(`Failed to process glob pattern "${dir}": ${globErr.message}`);
111
+ }
112
+ } else {
113
+ throw err;
114
+ }
82
115
  }
83
116
  }
84
117
 
@@ -206,11 +239,31 @@ export default async function loadSources({
206
239
  let assetsContent = "# Available Media Assets for Documentation\n\n";
207
240
 
208
241
  if (mediaFiles.length > 0) {
209
- const mediaMarkdown = mediaFiles
210
- .map((file) => `![${file.description}](${file.path})`)
211
- .join("\n\n");
212
-
213
- assetsContent += mediaMarkdown;
242
+ // Helper function to determine file type from extension
243
+ const getFileType = (filePath) => {
244
+ const ext = path.extname(filePath).toLowerCase();
245
+ const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"];
246
+ const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
247
+
248
+ if (imageExts.includes(ext)) return "image";
249
+ if (videoExts.includes(ext)) return "video";
250
+ return "media";
251
+ };
252
+
253
+ const mediaYaml = mediaFiles.map((file) => ({
254
+ name: file.name,
255
+ path: file.path,
256
+ type: getFileType(file.path),
257
+ }));
258
+
259
+ assetsContent += "```yaml\n";
260
+ assetsContent += "assets:\n";
261
+ mediaYaml.forEach((asset) => {
262
+ assetsContent += ` - name: "${asset.name}"\n`;
263
+ assetsContent += ` path: "${asset.path}"\n`;
264
+ assetsContent += ` type: "${asset.type}"\n`;
265
+ });
266
+ assetsContent += "```\n";
214
267
  }
215
268
 
216
269
  // Count words and lines in allSources
@@ -1,16 +1,33 @@
1
1
  import { basename, join } from "node:path";
2
2
  import { publishDocs as publishDocsFn } from "@aigne/publish-docs";
3
3
  import chalk from "chalk";
4
+ import fs from "fs-extra";
5
+
4
6
  import { getAccessToken } from "../utils/auth-utils.mjs";
5
- import { DISCUSS_KIT_STORE_URL } from "../utils/constants.mjs";
7
+ import { DISCUSS_KIT_STORE_URL, TMP_DIR, TMP_DOCS_DIR } from "../utils/constants.mjs";
8
+ import { beforePublishHook, ensureTmpDir } from "../utils/kroki-utils.mjs";
6
9
  import { getGithubRepoUrl, loadConfigFromFile, saveValueToConfig } from "../utils/utils.mjs";
7
10
 
8
11
  const DEFAULT_APP_URL = "https://docsmith.aigne.io";
9
12
 
10
13
  export default async function publishDocs(
11
- { docsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
14
+ { docsDir: rawDocsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
12
15
  options,
13
16
  ) {
17
+ // move work dir to tmp-dir
18
+ await ensureTmpDir();
19
+
20
+ const docsDir = join(".aigne", "doc-smith", TMP_DIR, TMP_DOCS_DIR);
21
+ await fs.rm(docsDir, { recursive: true, force: true });
22
+ await fs.mkdir(docsDir, {
23
+ recursive: true,
24
+ });
25
+ await fs.cp(rawDocsDir, docsDir, { recursive: true });
26
+
27
+ // ----------------- trigger beforePublishHook -----------------------------
28
+ await beforePublishHook({ docsDir });
29
+
30
+ // ----------------- main publish process flow -----------------------------
14
31
  // Check if DOC_DISCUSS_KIT_URL is set in environment variables
15
32
  const envAppUrl = process.env.DOC_DISCUSS_KIT_URL;
16
33
  const useEnvAppUrl = !!envAppUrl;
@@ -87,6 +104,8 @@ export default async function publishDocs(
87
104
  ].filter((lang, index, arr) => arr.indexOf(lang) === index), // Remove duplicates
88
105
  };
89
106
 
107
+ let message;
108
+
90
109
  try {
91
110
  const { success, boardId: newBoardId } = await publishDocsFn({
92
111
  sidebarPath,
@@ -98,7 +117,7 @@ export default async function publishDocs(
98
117
  boardName: projectInfo.name,
99
118
  boardDesc: projectInfo.description,
100
119
  boardCover: projectInfo.icon,
101
- mediaFolder: docsDir,
120
+ mediaFolder: rawDocsDir,
102
121
  cacheFilePath: join(".aigne", "doc-smith", "upload-cache.yaml"),
103
122
  boardMeta,
104
123
  });
@@ -114,18 +133,14 @@ export default async function publishDocs(
114
133
  if (boardId !== newBoardId) {
115
134
  await saveValueToConfig("boardId", newBoardId);
116
135
  }
117
- const message = `✅ Documentation Published Successfully!`;
118
- return {
119
- message,
120
- };
136
+ message = `✅ Documentation Published Successfully!`;
121
137
  }
122
-
123
- return {};
124
138
  } catch (error) {
125
- return {
126
- message: `❌ Failed to publish docs: ${error.message}`,
127
- };
139
+ message = `❌ Failed to publish docs: ${error.message}`;
128
140
  }
141
+ // clean up tmp work dir
142
+ await fs.rm(docsDir, { recursive: true, force: true });
143
+ return message ? { message } : {};
129
144
  }
130
145
 
131
146
  publishDocs.input_schema = {
@@ -58,7 +58,7 @@ input_schema:
58
58
  type: array
59
59
  items:
60
60
  type: string
61
- description: Languages to translate to
61
+ description: "Languages to translate to, available languages are: en, zh, zh-TW, ja, fr, de, es, it, ru, ko, pt, ar"
62
62
  feedback:
63
63
  type: string
64
64
  description: Feedback for translation improvement
@@ -13,6 +13,6 @@ skills:
13
13
  input_schema:
14
14
  type: object
15
15
  properties:
16
- appUrl:
16
+ appUrl:
17
17
  type: string
18
- description: target website URL where the documentation will be published
18
+ description: target website URL where the documentation will be published (optional - if not provided, will prompt for interactive input)
package/aigne.yaml CHANGED
@@ -41,6 +41,7 @@ agents:
41
41
  - ./agents/feedback-refiner.yaml
42
42
  - ./agents/manage-prefs.mjs
43
43
  cli:
44
+ chat: ./agents/chat.yaml
44
45
  agents:
45
46
  - ./agents/input-generator.mjs
46
47
  - ./agents/docs-generator.yaml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -12,19 +12,21 @@
12
12
  "author": "Arcblock <blocklet@arcblock.io> https://github.com/blocklet",
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "@aigne/aigne-hub": "^0.6.10",
16
- "@aigne/anthropic": "^0.11.10",
17
- "@aigne/cli": "^1.41.1",
18
- "@aigne/core": "^1.55.1",
19
- "@aigne/gemini": "^0.9.10",
20
- "@aigne/openai": "^0.12.4",
21
- "@aigne/publish-docs": "^0.6.0",
15
+ "@aigne/aigne-hub": "^0.8.1",
16
+ "@aigne/anthropic": "^0.11.12",
17
+ "@aigne/cli": "^1.42.0",
18
+ "@aigne/core": "^1.57.0",
19
+ "@aigne/gemini": "^0.11.1",
20
+ "@aigne/openai": "^0.13.2",
21
+ "@aigne/publish-docs": "^0.8.0",
22
22
  "chalk": "^5.5.0",
23
23
  "dompurify": "^3.2.6",
24
+ "fs-extra": "^11.3.1",
24
25
  "glob": "^11.0.3",
25
26
  "jsdom": "^26.1.0",
26
27
  "mermaid": "^11.9.0",
27
28
  "open": "^10.2.0",
29
+ "p-map": "^7.0.3",
28
30
  "remark-gfm": "^4.0.1",
29
31
  "remark-lint": "^10.0.1",
30
32
  "remark-parse": "^11.0.0",
@@ -40,10 +42,11 @@
40
42
  },
41
43
  "scripts": {
42
44
  "test": "bun test",
45
+ "test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text",
43
46
  "test:watch": "bun test --watch",
44
- "lint": "biome check && pnpm -r run lint",
47
+ "lint": "biome check",
45
48
  "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",
46
49
  "deduplicate": "pnpm dedupe",
47
- "lint:fix": "biome check --write && pnpm -r run lint"
50
+ "lint:fix": "biome check --write"
48
51
  }
49
52
  }
@@ -101,14 +101,18 @@ parentId: {{parentId}}
101
101
 
102
102
  </media_rules>
103
103
 
104
- {% include "../prompts/document/detail-generator.md" %}
104
+ {% include "document/detail-generator.md" %}
105
+
106
+ {% include "document/custom-components.md" %}
107
+
105
108
  </rules>
106
109
 
107
- {% include "../prompts/document/detail-example.md" %}
110
+ {% include "document/detail-example.md" %}
108
111
 
109
112
  <output_schema>
110
113
 
111
114
  1. 输内容为{{nodeName}}的详细文本。
112
115
  2. 直接输出{{nodeName}}内容,不要包含其他信息.
113
116
  3. 仅参考示例中的风格,**以语言 {{locale}} 输出内容 **
114
- </output_schema>
117
+
118
+ </output_schema>
@@ -0,0 +1,80 @@
1
+ When generating document details, you can use the following custom components at appropriate locations based on their descriptions and functionality to enhance document presentation:
2
+ - `<x-card>`
3
+ - `<x-cards>`
4
+
5
+
6
+ ### 1. <x-card> Single Card Component
7
+ Suitable for displaying individual links with a richer and more visually appealing presentation format.
8
+
9
+ Example:
10
+
11
+ ```
12
+ <x-card data-title="Required Title" data-image="Image URL" data-icon="Icon identifier (e.g., lucide:rocket or material-symbols:rocket-outline)" data-href="Navigation link URL" data-horizontal="true/false" data-cta="Button text" >
13
+ Card body content
14
+ </x-card>
15
+ ```
16
+
17
+ Attribute Rules:
18
+
19
+ - data-title (required): Card title.
20
+ - data-icon / data-image (choose one, at least one must be provided):
21
+ - It's recommended to always provide data-icon.
22
+ - Icons should prioritize Lucide (lucide:icon-name). If not available in Lucide, use Iconify (collection:icon-name, e.g., material-symbols:rocket-outline).
23
+ - data-image (optional): Image URL, can coexist with icon.
24
+ - data-href (optional): Navigation link for clicking the card or button.
25
+ - data-horizontal (optional): Whether to use horizontal layout.
26
+ - data-cta (optional): Button text (call to action).
27
+ - Body content: Must be written within <x-card>...</x-card> children.
28
+
29
+
30
+ ### 2. `<x-cards>` Card List Component
31
+
32
+ Suitable for displaying multiple links using a card list format, providing a richer and more visually appealing presentation.
33
+
34
+ Syntax:
35
+
36
+ ```
37
+ <x-cards data-columns="Number of columns">
38
+ <x-card data-title="Title 1" data-icon="lucide:rocket">Content 1</x-card>
39
+ <x-card data-title="Title 2" data-icon="lucide:bolt">Content 2</x-card>
40
+ <x-card data-title="Title 3" data-icon="material-symbols:rocket-outline">Content 3</x-card>
41
+ </x-cards>
42
+ ```
43
+
44
+ Attribute Rules:
45
+ - data-columns (optional): Number of columns, integer (e.g., 2, 3). Default is 2.
46
+ - Must contain multiple <x-card> elements internally.
47
+ - Consistency requirement: All <x-card> elements within the same <x-cards> must maintain visual consistency:
48
+ - Recommended to always provide data-icon for each card.
49
+ - Or all cards should have data-image.
50
+ - Avoid mixing (some with icons, some with only images).
51
+
52
+
53
+ ### 3. Examples
54
+
55
+ Single card:
56
+
57
+ ```
58
+ <x-card data-title="Horizontal card" data-icon="lucide:atom" data-horizontal="true">
59
+ This is an example of a horizontal card.
60
+ </x-card>
61
+ ```
62
+
63
+ Card list (all using icons, recommended approach):
64
+
65
+ ```
66
+ <x-cards data-columns="3">
67
+ <x-card data-title="Feature 1" data-icon="lucide:rocket">Description of Feature 1.</x-card>
68
+ <x-card data-title="Feature 2" data-icon="lucide:bolt">Description of Feature 2.</x-card>
69
+ <x-card data-title="Feature 3" data-icon="material-symbols:rocket-outline">Description of Feature 3.</x-card>
70
+ </x-cards>
71
+ ```
72
+
73
+ Card list (all using images):
74
+
75
+ ```
76
+ <x-cards data-columns="2">
77
+ <x-card data-title="Card A" data-image="https://picsum.photos/id/10/300/300">Content A</x-card>
78
+ <x-card data-title="Card B" data-image="https://picsum.photos/id/11/300/300">Content B</x-card>
79
+ </x-cards>
80
+ ```
@@ -0,0 +1,44 @@
1
+ - 结构图示例:
2
+ ```d2
3
+ App: Application
4
+ API: API Server
5
+ DB: Database
6
+
7
+ App -> API: 调用
8
+ API -> DB: 读写数据
9
+ ```
10
+ - 流程图示例:
11
+ ```d2
12
+ start: 开始
13
+ input: 用户输入
14
+ process: 处理数据
15
+ output: 输出结果
16
+ end: 结束
17
+
18
+ start -> input -> process -> output -> end
19
+ ```
20
+ - 时序图示例:
21
+ ```d2
22
+ User: 用户
23
+ Service: 服务
24
+ DB: 数据库
25
+
26
+ User -> Service: 请求
27
+ Service -> DB: 查询
28
+ DB -> Service: 返回数据
29
+ Service -> User: 响应
30
+ ```
31
+ - 决策树示例:
32
+ ```d2
33
+ start: 开始
34
+ check: 是否有效?
35
+ yes: 是
36
+ no: 否
37
+ end: 结束
38
+
39
+ start -> check
40
+ check -> yes: 有效
41
+ check -> no: 无效
42
+ yes -> end
43
+ no -> end
44
+ ```