@aigne/doc-smith 0.9.8-alpha.7 → 0.9.8-alpha.9

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 (35) hide show
  1. package/README.md +71 -17
  2. package/agents/bash-executor/bash-executor.mjs +280 -0
  3. package/agents/preload-i18n.mjs +73 -0
  4. package/agents/publish/check-docs.mjs +44 -0
  5. package/agents/publish/index.yaml +12 -0
  6. package/agents/publish/init-config.mjs +133 -0
  7. package/agents/publish/publish-docs.mjs +377 -0
  8. package/aigne.yaml +7 -0
  9. package/doc-smith/SKILL.md +77 -26
  10. package/doc-smith/references/changeset-guide.md +170 -0
  11. package/doc-smith/references/document-content-guide.md +15 -74
  12. package/doc-smith/references/document-structure-schema.md +2 -2
  13. package/doc-smith/references/patch-guide.md +96 -0
  14. package/doc-smith/references/structure-planning-guide.md +8 -5
  15. package/doc-smith/references/update-workflow.md +108 -0
  16. package/doc-smith/references/user-intent-guide.md +3 -0
  17. package/doc-smith/references/workspace-initialization.md +326 -0
  18. package/doc-smith.yaml +4 -0
  19. package/feature-design/workspace.md +697 -0
  20. package/package.json +15 -2
  21. package/prompt/doc-smith.md +57 -0
  22. package/utils/auth.mjs +275 -0
  23. package/utils/branding.mjs +83 -0
  24. package/utils/config.mjs +262 -0
  25. package/utils/constants.mjs +38 -0
  26. package/utils/deploy.mjs +86 -0
  27. package/utils/docs.mjs +247 -0
  28. package/utils/files.mjs +76 -0
  29. package/utils/git.mjs +54 -0
  30. package/utils/http.mjs +122 -0
  31. package/utils/project.mjs +76 -0
  32. package/utils/store/index.mjs +45 -0
  33. package/utils/upload.mjs +231 -0
  34. package/doc-smith/references/changeset-schema.md +0 -79
  35. package/doc-smith/references/document-update-guide.md +0 -192
package/README.md CHANGED
@@ -71,28 +71,74 @@ doc-smith-skill/
71
71
 
72
72
  ### 2. 使用 Skill
73
73
 
74
- 在任何项目中打开 Claude Code,输入:
74
+ #### Workspace 模式 (推荐)
75
+
76
+ DocSmith 现在使用独立 workspace 目录,不会污染源仓库。
77
+
78
+ **创建并使用 workspace:**
79
+
80
+ ```bash
81
+ # 1. 创建空目录作为 workspace
82
+ mkdir my-docs-workspace
83
+ cd my-docs-workspace
84
+
85
+ # 2. 打开 Claude Code 并执行 doc-smith
86
+ # 输入: 使用 doc-smith 生成文档
87
+ ```
88
+
89
+ **初始化流程:**
90
+ DocSmith 会引导你完成初始化:
91
+ 1. 询问输出语言(如:zh、en)
92
+ 2. 询问源仓库 Git URL(可选,如果源代码在本地可不提供)
93
+ 3. 自动创建目录结构
94
+ 4. 自动添加源仓库为 git submodule(如果提供了 URL)
95
+ 5. 生成 config.yaml 配置文件
96
+ 6. 初始化 git 仓库并提交
97
+
98
+ **后续操作:**
99
+ DocSmith 会:
100
+ 1. 分析源仓库内容
101
+ 2. 推断用户意图
102
+ 3. 规划文档结构
103
+ 4. 生成结构化的 Markdown 文档
104
+ 5. 询问是否提交到 Git
105
+
106
+ ### 3. Workspace 目录结构
75
107
 
76
108
  ```
77
- 使用 doc-smith skill 为当前仓库生成文档
109
+ my-docs-workspace/ # 独立 workspace 目录
110
+ ├── config.yaml # workspace 配置文件
111
+ ├── sources/ # 源仓库 (git submodule)
112
+ │ └── my-project/
113
+ ├── intent/
114
+ │ └── user-intent.md # 用户意图描述
115
+ ├── planning/
116
+ │ └── document-structure.yaml # 文档结构计划
117
+ ├── docs/ # 生成的文档
118
+ │ ├── overview.md
119
+ │ ├── getting-started.md
120
+ │ └── api/
121
+ │ └── authentication.md
122
+ └── cache/ # 临时数据 (不纳入 git)
78
123
  ```
79
124
 
80
- 然后根据提示操作,DocSmith 会:
81
- 1. 分析你的工作区
82
- 2. 规划文档结构
83
- 3. 生成 `document-structure.yaml`
84
- 4. 创建结构化的 Markdown 文档
125
+ ### 4. 版本管理
85
126
 
86
- ### 3. 查看生成的文档
127
+ Workspace 是一个独立的 Git 仓库,支持完整的版本管理:
87
128
 
88
- 所有文档将生成在:
129
+ ```bash
130
+ # 查看历史
131
+ git log
89
132
 
90
- ```
91
- .aigne/doc-smith/
92
- ├── output/
93
- │ └── document-structure.yaml # 文档结构定义
94
- └── docs/
95
- └── [生成的文档文件]
133
+ # 查看变更
134
+ git diff
135
+
136
+ # 回滚版本
137
+ git revert <commit-hash>
138
+
139
+ # 推送到远程仓库(可选)
140
+ git remote add origin <your-repo-url>
141
+ git push -u origin main
96
142
  ```
97
143
 
98
144
  ## 文档说明
@@ -134,8 +180,16 @@ cp -r doc-smith ~/.claude/skills/
134
180
  ## 注意事项
135
181
 
136
182
  - 确保 Claude Code 已正确安装
137
- - Skill 需要访问工作区文件
138
- - 生成的文档会创建在 `.aigne/doc-smith/` 目录
183
+ - 确保 Git 已安装(用于 submodule 和版本管理)
184
+ - Workspace 需要在空目录中初始化
185
+ - 生成的文档在独立的 workspace 目录中,不会污染源仓库
186
+
187
+ ## 迁移说明
188
+
189
+ 如果你之前使用过旧版本(`.aigne/doc-smith/` 目录结构),建议:
190
+ 1. 创建新的 workspace 目录
191
+ 2. 重新生成文档
192
+ 3. 旧版本数据可以手动迁移到新的 workspace 目录结构中
139
193
 
140
194
  ## 支持
141
195
 
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+
4
+ /**
5
+ * 虚拟 Bash 执行器 - Git 专用
6
+ * 支持的命令类型:
7
+ * - Git 操作: init, clone, config, status, log, diff, branch, show, add, commit, fetch, pull, submodule
8
+ *
9
+ * 安全限制:
10
+ * - 仅支持 git 命令,不支持其他 shell 命令
11
+ * - 所有命令必须来自预定义的安全枚举
12
+ */
13
+
14
+ // 支持的命令枚举
15
+ const ALLOWED_COMMANDS = {
16
+ // Git 命令
17
+ git: {
18
+ // 初始化和克隆
19
+ init: true,
20
+ clone: true,
21
+
22
+ // 配置命令
23
+ config: true,
24
+
25
+ // 查询命令
26
+ status: true,
27
+ log: true,
28
+ diff: true,
29
+ branch: true,
30
+ show: true,
31
+
32
+ // 提交命令
33
+ add: true,
34
+ commit: true,
35
+
36
+ // 远程命令
37
+ fetch: true,
38
+ pull: true,
39
+
40
+ // 子模块命令
41
+ submodule: true,
42
+ },
43
+ };
44
+
45
+ /**
46
+ * 验证命令是否在允许列表中
47
+ */
48
+ function validateCommand(command, args = []) {
49
+ const cmd = command.toLowerCase();
50
+
51
+ // 只支持 git 命令
52
+ if (cmd !== "git") {
53
+ throw new Error(`不支持的命令: ${cmd},仅支持 git 命令`);
54
+ }
55
+
56
+ if (args.length === 0) {
57
+ throw new Error("Git 命令需要指定子命令");
58
+ }
59
+
60
+ const subCommand = args[0].toLowerCase();
61
+ if (!ALLOWED_COMMANDS.git[subCommand]) {
62
+ throw new Error(`不支持的 git 子命令: ${subCommand}`);
63
+ }
64
+
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * 执行单个命令
70
+ */
71
+ function executeCommand(command, args = []) {
72
+ try {
73
+ // 验证命令
74
+ validateCommand(command, args);
75
+
76
+ // 构建完整命令用于显示和日志
77
+ const fullCommand = [command, ...args].join(" ");
78
+
79
+ // 使用 spawnSync 可以同时捕获 stdout 和 stderr
80
+ const result = spawnSync(command, args, {
81
+ encoding: "utf-8",
82
+ maxBuffer: 10 * 1024 * 1024, // 10MB
83
+ timeout: 60000, // 60秒超时
84
+ });
85
+
86
+ // 检查是否执行成功
87
+ if (result.status === 0 && !result.error) {
88
+ return {
89
+ success: true,
90
+ command: fullCommand,
91
+ output: result.stdout?.trim() || "",
92
+ error: result.stderr?.trim() || "", // 即使成功也返回 stderr,可能包含警告信息
93
+ };
94
+ }
95
+
96
+ // 命令执行失败
97
+ return {
98
+ success: false,
99
+ command: fullCommand,
100
+ output: result.stdout?.trim() || "",
101
+ error: result.stderr?.trim() || result.error?.message || "命令执行失败",
102
+ };
103
+ } catch (error) {
104
+ // 验证失败或其他异常
105
+ const fullCommand = [command, ...args].join(" ");
106
+ console.log(`[bash-executor] 异常: ${error.message}`);
107
+ return {
108
+ success: false,
109
+ command: fullCommand,
110
+ output: "",
111
+ error: error.message,
112
+ };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 按顺序执行多个命令
118
+ * 如果某个命令失败,立即停止执行后续命令
119
+ */
120
+ function executeBatch(commands) {
121
+ const results = [];
122
+
123
+ for (const item of commands) {
124
+ const { command, args = [] } = item;
125
+
126
+ if (!command) {
127
+ const errorResult = {
128
+ success: false,
129
+ command: "",
130
+ output: "",
131
+ error: "命令不能为空",
132
+ };
133
+ results.push(errorResult);
134
+ // 命令为空视为失败,停止执行
135
+ break;
136
+ }
137
+
138
+ const result = executeCommand(command, args);
139
+ results.push(result);
140
+
141
+ // 如果命令失败,立即停止执行后续命令
142
+ if (!result.success) {
143
+ break;
144
+ }
145
+ }
146
+
147
+ return results;
148
+ }
149
+
150
+ /**
151
+ * 安全执行预定义的 Shell 命令
152
+ * @param {Object} params - 输入参数
153
+ * @param {Array} params.commands - 命令列表
154
+ * @returns {Object} - 执行结果
155
+ */
156
+ export default function executeSafeShellCommands({ commands }) {
157
+ // 验证输入
158
+ if (!Array.isArray(commands)) {
159
+ return {
160
+ success: false,
161
+ error: "参数 commands 必须是一个数组",
162
+ results: [],
163
+ };
164
+ }
165
+
166
+ if (commands.length === 0) {
167
+ return {
168
+ success: false,
169
+ error: "命令列表不能为空",
170
+ results: [],
171
+ };
172
+ }
173
+
174
+ // 执行命令批次
175
+ const results = executeBatch(commands);
176
+
177
+ // 统计执行结果
178
+ const successCount = results.filter((r) => r.success).length;
179
+ const failureCount = results.length - successCount;
180
+
181
+ return {
182
+ success: successCount > 0,
183
+ total: results.length,
184
+ succeeded: successCount,
185
+ failed: failureCount,
186
+ results,
187
+ };
188
+ }
189
+
190
+ // 添加描述信息,帮助 LLM 理解何时调用此 agent
191
+ executeSafeShellCommands.description =
192
+ "安全执行 Git 命令,支持的子命令包括: init/clone/config/status/log/diff/branch/show/add/commit/fetch/pull/submodule。" +
193
+ "适用于需要批量执行多个有顺序依赖的 git 操作,如果某个命令失败会立即停止执行后续命令。";
194
+
195
+ // 定义输入 schema
196
+ executeSafeShellCommands.input_schema = {
197
+ type: "object",
198
+ required: ["commands"],
199
+ properties: {
200
+ commands: {
201
+ type: "array",
202
+ description: "要执行的 Git 命令列表,按顺序执行",
203
+ items: {
204
+ type: "object",
205
+ required: ["command"],
206
+ properties: {
207
+ command: {
208
+ type: "string",
209
+ description: "命令名称,必须是 git",
210
+ enum: ["git"],
211
+ },
212
+ args: {
213
+ type: "array",
214
+ description:
215
+ "Git 子命令和参数列表,第一个参数必须是支持的子命令(init/clone/config/status/log/diff/branch/show/add/commit/fetch/pull/submodule)",
216
+ items: {
217
+ type: "string",
218
+ },
219
+ minItems: 1,
220
+ },
221
+ },
222
+ },
223
+ minItems: 1,
224
+ },
225
+ },
226
+ };
227
+
228
+ // 定义输出 schema
229
+ executeSafeShellCommands.output_schema = {
230
+ type: "object",
231
+ required: ["success", "results"],
232
+ properties: {
233
+ success: {
234
+ type: "boolean",
235
+ description: "是否至少有一个命令执行成功",
236
+ },
237
+ total: {
238
+ type: "integer",
239
+ description: "总命令数(执行命令时存在)",
240
+ },
241
+ succeeded: {
242
+ type: "integer",
243
+ description: "成功执行的命令数(执行命令时存在)",
244
+ },
245
+ failed: {
246
+ type: "integer",
247
+ description: "失败的命令数(执行命令时存在)",
248
+ },
249
+ error: {
250
+ type: "string",
251
+ description: "全局错误信息(验证失败时存在)",
252
+ },
253
+ results: {
254
+ type: "array",
255
+ description: "每个命令的执行结果",
256
+ items: {
257
+ type: "object",
258
+ required: ["success", "command", "output", "error"],
259
+ properties: {
260
+ success: {
261
+ type: "boolean",
262
+ description: "该命令是否执行成功",
263
+ },
264
+ command: {
265
+ type: "string",
266
+ description: "执行的完整命令",
267
+ },
268
+ output: {
269
+ type: "string",
270
+ description: "命令的标准输出",
271
+ },
272
+ error: {
273
+ type: "string",
274
+ description: "错误信息,成功时为空字符串,失败时包含错误详情",
275
+ },
276
+ },
277
+ },
278
+ },
279
+ },
280
+ };
@@ -0,0 +1,73 @@
1
+ export default async function preloadI18n(_, options) {
2
+ const content = `
3
+ ## 系统架构
4
+
5
+ <!-- afs:image id="architecture-overview" key="snap-kit-architecture" desc="Snap Kit system architecture showing React frontend, Express API, and Crawler Engine components with their interactions" -->
6
+
7
+ Snap Kit 采用清晰的三层架构设计:
8
+
9
+ - **前端层**: React 19.1 + TypeScript + Vite,提供现代化的用户界面和实时仪表板
10
+ - **API 层**: Express 4.21 + TypeScript,提供 RESTful 接口、DID 认证和速率限制
11
+ - **引擎层**: Puppeteer + 队列系统 + SQLite,负责核心自动化和数据存储
12
+ `;
13
+ const resultWriter = await this.afs.write(
14
+ "/modules/doc-smith/docs/getting-started-image.md",
15
+ {
16
+ content,
17
+ }
18
+ );
19
+
20
+ const image = await this.afs.getImageBySlot(
21
+ "/modules/doc-smith/docs/getting-started-image.md",
22
+ "architecture-overview",
23
+ {
24
+ view: {
25
+ format: "jpg",
26
+ },
27
+ context: options.context,
28
+ }
29
+ );
30
+
31
+ const result = await this.afs.read(
32
+ "/modules/doc-smith/docs/getting-started.md",
33
+ {
34
+ view: {
35
+ language: "en",
36
+ },
37
+ context: options.context,
38
+ }
39
+ );
40
+
41
+ return {
42
+ i18n: {
43
+ result,
44
+ image,
45
+ },
46
+ };
47
+ }
48
+
49
+ preloadI18n.afs = {
50
+ modules: [
51
+ {
52
+ module: "local-fs",
53
+ options: {
54
+ name: "doc-smith",
55
+ localPath: "${CWD}/.aigne/doc-smith",
56
+ description:
57
+ "The Doc Smith workspace for storing intermediate and output files",
58
+ },
59
+ },
60
+ ],
61
+ drivers: [
62
+ {
63
+ driver: "i18n",
64
+ options: {
65
+ defaultSourceLanguage: "zh",
66
+ },
67
+ },
68
+ {
69
+ driver: "image-generate",
70
+ options: {},
71
+ },
72
+ ],
73
+ };
@@ -0,0 +1,44 @@
1
+ import chalk from "chalk";
2
+ import { getMainLanguageFiles } from "../../utils/docs.mjs";
3
+
4
+ /**
5
+ * Check if documents exist in the docs directory
6
+ * @param {Object} params
7
+ * @param {string} params.docsDir - Documentation directory
8
+ * @param {string} params.locale - Main language locale
9
+ * @param {Array} params.documentStructure - Document structure
10
+ * @returns {Promise<Object>} - Result object
11
+ */
12
+ export default async function checkDocs({ docsDir = "./docs" }) {
13
+ const mainLanguageFiles = await getMainLanguageFiles(docsDir);
14
+
15
+ if (mainLanguageFiles.length === 0) {
16
+ console.log(`⚠️ No documents found in the docs directory.`);
17
+ console.log(`💡 Please generate documentation first before publishing.`);
18
+ process.exit(0);
19
+ }
20
+
21
+ return {
22
+ message: `Found ${mainLanguageFiles.length} document(s) in the docs directory`,
23
+ };
24
+ }
25
+
26
+ checkDocs.description = "Check if documents exist before publishing";
27
+
28
+ checkDocs.input_schema = {
29
+ type: "object",
30
+ properties: {
31
+ docsDir: {
32
+ type: "string",
33
+ description: "Documentation directory",
34
+ },
35
+ locale: {
36
+ type: "string",
37
+ description: "Main language locale",
38
+ },
39
+ documentStructure: {
40
+ type: "array",
41
+ description: "Document structure",
42
+ },
43
+ },
44
+ };
@@ -0,0 +1,12 @@
1
+ type: team
2
+ name: publish
3
+ alias:
4
+ - pub
5
+ - p
6
+ description: Publish DocSmith generated documentation to the online platform
7
+ skills:
8
+ - url: ./init-config.mjs
9
+ default_input:
10
+ skipIfExists: true
11
+ - url: ./check-docs.mjs
12
+ - url: ./publish-docs.mjs
@@ -0,0 +1,133 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import chalk from "chalk";
5
+ import { loadConfigFromFile, generateConfigYAML } from "../../utils/config.mjs";
6
+ import { getProjectInfo, detectSystemLanguage } from "../../utils/project.mjs";
7
+
8
+ /**
9
+ * Initialize configuration for publishing
10
+ * @param {Object} params
11
+ * @param {string} params.outputPath - Output path for config file
12
+ * @param {string} params.fileName - Config file name
13
+ * @param {boolean} params.skipIfExists - Skip if config file exists
14
+ * @param {boolean} params.checkOnly - Only check if config exists
15
+ * @param {string} params.appUrl - Optional app URL
16
+ * @returns {Promise<Object>} - Configuration object
17
+ */
18
+ export default async function initConfig(
19
+ {
20
+ fileName = "config.yaml",
21
+ skipIfExists = false,
22
+ appUrl,
23
+ checkOnly = false,
24
+ } = {},
25
+ options
26
+ ) {
27
+ // Detect workspace mode (new structure with config.yaml in root)
28
+ const configPath = "./";
29
+
30
+ // Check only mode
31
+ if (checkOnly) {
32
+ const filePath = join(configPath, fileName);
33
+ const configContent = await readFile(filePath, "utf8").catch(() => null);
34
+
35
+ if (!configContent || configContent.trim() === "") {
36
+ console.log("⚠️ No configuration file found.");
37
+ console.log(
38
+ `🚀 Configuration will be created automatically when you run the publish command.`
39
+ );
40
+ process.exit(0);
41
+ }
42
+
43
+ const config = await loadConfigFromFile();
44
+ return { ...config, appUrl };
45
+ }
46
+
47
+ // Skip if exists mode
48
+ if (skipIfExists) {
49
+ const filePath = join(configPath, fileName);
50
+ const configContent = await readFile(filePath, "utf8").catch(() => null);
51
+
52
+ if (configContent && configContent.trim() !== "") {
53
+ const config = await loadConfigFromFile();
54
+ return { ...config, appUrl };
55
+ }
56
+ }
57
+
58
+ // Create new config with default values
59
+ const input = {};
60
+
61
+ // Detect system language
62
+ input.locale = detectSystemLanguage();
63
+
64
+ // Get project info
65
+ const projectInfo = await getProjectInfo();
66
+ input.projectName = projectInfo.name.trim();
67
+ input.projectDesc = projectInfo.description.trim();
68
+ input.projectLogo = projectInfo.icon;
69
+
70
+ input.docsDir = "./docs";
71
+ input.sourcesPath = ["sources/"];
72
+ input.translateLanguages = [];
73
+
74
+ // Generate YAML content
75
+ const yamlContent = generateConfigYAML(input);
76
+
77
+ // Save file
78
+ try {
79
+ const filePath = join(configPath, fileName);
80
+ const dirPath = dirname(filePath);
81
+
82
+ await mkdir(dirPath, { recursive: true });
83
+ await writeFile(filePath, yamlContent, "utf8");
84
+
85
+ console.log(
86
+ `\n✅ Configuration created successfully: ${chalk.cyan(filePath)}`
87
+ );
88
+ console.log(
89
+ "💡 You can edit this file to customize your publishing settings.\n"
90
+ );
91
+
92
+ if (skipIfExists) {
93
+ const config = await loadConfigFromFile();
94
+ return { ...config, appUrl };
95
+ }
96
+
97
+ return {};
98
+ } catch (error) {
99
+ console.error(`❌ Failed to create configuration file: ${error.message}`);
100
+ return {
101
+ inputGeneratorStatus: false,
102
+ inputGeneratorError: error.message,
103
+ };
104
+ }
105
+ }
106
+
107
+ initConfig.description = "Initialize configuration for document publishing";
108
+
109
+ initConfig.input_schema = {
110
+ type: "object",
111
+ properties: {
112
+ outputPath: {
113
+ type: "string",
114
+ description: "Output path for config file",
115
+ },
116
+ fileName: {
117
+ type: "string",
118
+ description: "Config file name",
119
+ },
120
+ skipIfExists: {
121
+ type: "boolean",
122
+ description: "Skip if config file exists",
123
+ },
124
+ checkOnly: {
125
+ type: "boolean",
126
+ description: "Only check if config exists",
127
+ },
128
+ appUrl: {
129
+ type: "string",
130
+ description: "App URL for publishing",
131
+ },
132
+ },
133
+ };