@aigne/doc-smith 0.1.3 → 0.2.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.
- package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- package/CHANGELOG.md +15 -0
- package/README.md +3 -3
- package/agents/batch-docs-detail-generator.yaml +4 -0
- package/agents/check-detail-generated.mjs +33 -3
- package/agents/check-detail-result.mjs +52 -41
- package/agents/check-structure-planning.mjs +43 -9
- package/agents/detail-regenerator.yaml +3 -0
- package/agents/docs-generator.yaml +3 -0
- package/agents/input-generator.mjs +148 -32
- package/agents/load-config.mjs +1 -0
- package/agents/load-sources.mjs +44 -63
- package/agents/publish-docs.mjs +53 -46
- package/agents/save-docs.mjs +9 -0
- package/agents/team-publish-docs.yaml +3 -0
- package/agents/transform-detail-datasources.mjs +19 -5
- package/package.json +7 -7
- package/prompts/check-structure-planning-result.md +1 -1
- package/prompts/structure-planning.md +1 -1
- package/utils/constants.mjs +60 -0
- package/utils/utils.mjs +299 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Related Issue
|
|
2
|
+
|
|
3
|
+
<!-- Use keywords like fixes, closes, resolves, relates to link the issue. In principle, all PRs should be associated with an issue -->
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
<!--
|
|
8
|
+
@example:
|
|
9
|
+
1. Fixed xxx
|
|
10
|
+
2. Improved xxx
|
|
11
|
+
3. Adjusted xxx
|
|
12
|
+
-->
|
|
13
|
+
|
|
14
|
+
### Screenshots
|
|
15
|
+
|
|
16
|
+
<!-- If the changes are related to the UI, whether CLI or WEB, screenshots should be included -->
|
|
17
|
+
|
|
18
|
+
### Test Plan
|
|
19
|
+
|
|
20
|
+
<!-- If this change is not covered by automated tests, what is your test case collection? Please write it as a to-do list below -->
|
|
21
|
+
|
|
22
|
+
### Checklist
|
|
23
|
+
|
|
24
|
+
- [ ] This change requires documentation updates, and I have updated the relevant documentation. If the documentation has not been updated, please create a documentation update issue and link it here
|
|
25
|
+
- [ ] The changes are already covered by tests, and I have adjusted the test coverage for the changed parts
|
|
26
|
+
- [ ] The newly added code logic is also covered by tests
|
|
27
|
+
- [ ] This change adds dependencies, and they are placed in dependencies and devDependencies
|
|
28
|
+
- [ ] This change includes adding or updating npm dependencies, and it does not result in multiple versions of the same dependency [check the diff of pnpm-lock.yaml]
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.1.4...v0.2.0) (2025-08-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* support automatic init configuration when calling agents ([24d29db](https://github.com/AIGNE-io/aigne-doc-smith/commit/24d29db4dd86709750aa22ff649e7dacc4124126))
|
|
9
|
+
* update docs when sources changed ([#9](https://github.com/AIGNE-io/aigne-doc-smith/issues/9)) ([4adcecf](https://github.com/AIGNE-io/aigne-doc-smith/commit/4adcecfb32e72c9e88d0b0bd8ce0a91022847ca7))
|
|
10
|
+
|
|
11
|
+
## [0.1.4](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.1.3...v0.1.4) (2025-08-04)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Miscellaneous Chores
|
|
15
|
+
|
|
16
|
+
* release 0.1.4 ([4122cf5](https://github.com/AIGNE-io/aigne-doc-smith/commit/4122cf5cc0285bef2b96803f393e744121d22acf))
|
|
17
|
+
|
|
3
18
|
## [0.1.3](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.1.2...v0.1.3) (2025-08-04)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -61,13 +61,13 @@ 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 --entry-agent
|
|
64
|
+
npx --no doc-smith run --entry-agent generate --model gemini:gemini-2.5-flash
|
|
65
65
|
|
|
66
66
|
# 重新生成单篇
|
|
67
|
-
npx --no doc-smith run --entry-agent
|
|
67
|
+
npx --no doc-smith run --entry-agent update --input-path bitnet-getting-started
|
|
68
68
|
|
|
69
69
|
# 结构规划优化
|
|
70
|
-
npx --no doc-smith run --entry-agent
|
|
70
|
+
npx --no doc-smith run --entry-agent generate --input-feedback "补充节点的 sourceIds,确保所有节点 sourceIds 都有值" --model gemini:gemini-2.5-pro
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
# 发布文档
|
|
@@ -10,5 +10,9 @@ input_schema:
|
|
|
10
10
|
type: string
|
|
11
11
|
description: 结构规划的上下文,用于辅助结构规划
|
|
12
12
|
structurePlanResult: ./schema/structure-plan-result.yaml
|
|
13
|
+
modifiedFiles:
|
|
14
|
+
type: array
|
|
15
|
+
items: { type: string }
|
|
16
|
+
description: Array of modified files since last generation
|
|
13
17
|
iterate_on: structurePlanResult
|
|
14
18
|
mode: sequential
|
|
@@ -3,12 +3,22 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { TeamAgent } from "@aigne/core";
|
|
5
5
|
import checkDetailResult from "./check-detail-result.mjs";
|
|
6
|
+
import { hasSourceFilesChanged } from "../utils/utils.mjs";
|
|
6
7
|
|
|
7
8
|
// Get current script directory
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
|
|
10
11
|
export default async function checkDetailGenerated(
|
|
11
|
-
{
|
|
12
|
+
{
|
|
13
|
+
path,
|
|
14
|
+
docsDir,
|
|
15
|
+
sourceIds,
|
|
16
|
+
originalStructurePlan,
|
|
17
|
+
structurePlan,
|
|
18
|
+
modifiedFiles,
|
|
19
|
+
lastGitHead,
|
|
20
|
+
...rest
|
|
21
|
+
},
|
|
12
22
|
options
|
|
13
23
|
) {
|
|
14
24
|
// Check if the detail file already exists
|
|
@@ -61,6 +71,21 @@ export default async function checkDetailGenerated(
|
|
|
61
71
|
}
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
// Check if source files have changed since last generation
|
|
75
|
+
let sourceFilesChanged = false;
|
|
76
|
+
if (sourceIds && sourceIds.length > 0 && modifiedFiles) {
|
|
77
|
+
sourceFilesChanged = hasSourceFilesChanged(sourceIds, modifiedFiles);
|
|
78
|
+
|
|
79
|
+
if (sourceFilesChanged) {
|
|
80
|
+
console.log(`Source files changed for ${path}, will regenerate`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// If lastGitHead is not set, regenerate
|
|
85
|
+
if (!lastGitHead) {
|
|
86
|
+
sourceFilesChanged = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
// If file exists, check content validation
|
|
65
90
|
let contentValidationFailed = false;
|
|
66
91
|
if (detailGenerated && fileContent && structurePlan) {
|
|
@@ -74,8 +99,13 @@ export default async function checkDetailGenerated(
|
|
|
74
99
|
}
|
|
75
100
|
}
|
|
76
101
|
|
|
77
|
-
// If file exists, sourceIds haven't changed, and content validation passes, no need to regenerate
|
|
78
|
-
if (
|
|
102
|
+
// If file exists, sourceIds haven't changed, source files haven't changed, and content validation passes, no need to regenerate
|
|
103
|
+
if (
|
|
104
|
+
detailGenerated &&
|
|
105
|
+
!sourceIdsChanged &&
|
|
106
|
+
!sourceFilesChanged &&
|
|
107
|
+
!contentValidationFailed
|
|
108
|
+
) {
|
|
79
109
|
return {
|
|
80
110
|
path,
|
|
81
111
|
docsDir,
|
|
@@ -51,49 +51,35 @@ export default async function checkDetailResult({
|
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const
|
|
55
|
-
// Split text into lines and
|
|
54
|
+
const performAllChecks = (text, source) => {
|
|
55
|
+
// Split text into lines once and perform all checks in a single pass
|
|
56
56
|
const lines = text.split("\n");
|
|
57
|
-
for (let i = 0; i < lines.length; i++) {
|
|
58
|
-
const line = lines[i];
|
|
59
|
-
if (tableSeparatorRegex.test(line)) {
|
|
60
|
-
isApproved = false;
|
|
61
|
-
detailFeedback.push(
|
|
62
|
-
`Found an incorrect table separator in ${source} at line ${
|
|
63
|
-
i + 1
|
|
64
|
-
}: ${line.trim()}`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
// Count newline characters to check if content is only on one line
|
|
72
|
-
const newlineCount = (text.match(/\n/g) || []).length;
|
|
73
|
-
if (newlineCount === 0 && text.trim().length > 0) {
|
|
74
|
-
isApproved = false;
|
|
75
|
-
detailFeedback.push(
|
|
76
|
-
`Found single line content in ${source}: content appears to be on only one line, check for missing line breaks`
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const checkCodeBlockIndentation = (text, source) => {
|
|
82
|
-
// Split text into lines and check each line
|
|
83
|
-
const lines = text.split("\n");
|
|
58
|
+
// State variables for different checks
|
|
84
59
|
let inCodeBlock = false;
|
|
85
60
|
let codeBlockIndentLevel = 0;
|
|
86
61
|
let codeBlockStartLine = 0;
|
|
62
|
+
let inMermaidBlock = false;
|
|
63
|
+
let mermaidStartLine = 0;
|
|
87
64
|
|
|
88
65
|
for (let i = 0; i < lines.length; i++) {
|
|
89
66
|
const line = lines[i];
|
|
67
|
+
const lineNumber = i + 1;
|
|
90
68
|
|
|
91
|
-
// Check
|
|
69
|
+
// Check table separators
|
|
70
|
+
if (tableSeparatorRegex.test(line)) {
|
|
71
|
+
isApproved = false;
|
|
72
|
+
detailFeedback.push(
|
|
73
|
+
`Found an incorrect table separator in ${source} at line ${lineNumber}: ${line.trim()}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check code block markers and indentation
|
|
92
78
|
if (codeBlockRegex.test(line)) {
|
|
93
79
|
if (!inCodeBlock) {
|
|
94
80
|
// Starting a new code block
|
|
95
81
|
inCodeBlock = true;
|
|
96
|
-
codeBlockStartLine =
|
|
82
|
+
codeBlockStartLine = lineNumber;
|
|
97
83
|
// Calculate indentation level of the code block marker
|
|
98
84
|
const match = line.match(/^(\s*)(```)/);
|
|
99
85
|
codeBlockIndentLevel = match ? match[1].length : 0;
|
|
@@ -102,11 +88,8 @@ export default async function checkDetailResult({
|
|
|
102
88
|
inCodeBlock = false;
|
|
103
89
|
codeBlockIndentLevel = 0;
|
|
104
90
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// If we're inside a code block, check if content has proper indentation
|
|
109
|
-
if (inCodeBlock) {
|
|
91
|
+
} else if (inCodeBlock) {
|
|
92
|
+
// If we're inside a code block, check if content has proper indentation
|
|
110
93
|
const contentIndentLevel = line.match(/^(\s*)/)[1].length;
|
|
111
94
|
|
|
112
95
|
// If code block marker has indentation, content should have at least the same indentation
|
|
@@ -116,23 +99,51 @@ export default async function checkDetailResult({
|
|
|
116
99
|
) {
|
|
117
100
|
isApproved = false;
|
|
118
101
|
detailFeedback.push(
|
|
119
|
-
`Found code block with inconsistent indentation in ${source} at line ${codeBlockStartLine}: code block marker has ${codeBlockIndentLevel} spaces indentation but content at line ${
|
|
120
|
-
i + 1
|
|
121
|
-
} has only ${contentIndentLevel} spaces indentation`
|
|
102
|
+
`Found code block with inconsistent indentation in ${source} at line ${codeBlockStartLine}: code block marker has ${codeBlockIndentLevel} spaces indentation but content at line ${lineNumber} has only ${contentIndentLevel} spaces indentation`
|
|
122
103
|
);
|
|
123
104
|
// Reset to avoid multiple errors for the same code block
|
|
124
105
|
inCodeBlock = false;
|
|
125
106
|
codeBlockIndentLevel = 0;
|
|
126
107
|
}
|
|
127
108
|
}
|
|
109
|
+
|
|
110
|
+
// Check mermaid block markers
|
|
111
|
+
if (/^\s*```mermaid\s*$/.test(line)) {
|
|
112
|
+
inMermaidBlock = true;
|
|
113
|
+
mermaidStartLine = lineNumber;
|
|
114
|
+
} else if (inMermaidBlock && /^\s*```\s*$/.test(line)) {
|
|
115
|
+
inMermaidBlock = false;
|
|
116
|
+
} else if (inMermaidBlock) {
|
|
117
|
+
// If we're inside a mermaid block, check for backticks in node labels
|
|
118
|
+
// Check for node definitions with backticks in labels
|
|
119
|
+
// Pattern: A["label with backticks"] or A{"label with backticks"}
|
|
120
|
+
const nodeLabelRegex =
|
|
121
|
+
/[A-Za-z0-9_]+\["([^"]*`[^"]*)"\]|[A-Za-z0-9_]+{"([^}]*`[^}]*)"}/g;
|
|
122
|
+
let match;
|
|
123
|
+
|
|
124
|
+
while ((match = nodeLabelRegex.exec(line)) !== null) {
|
|
125
|
+
const label = match[1] || match[2];
|
|
126
|
+
isApproved = false;
|
|
127
|
+
detailFeedback.push(
|
|
128
|
+
`Found backticks in Mermaid node label in ${source} at line ${lineNumber}: "${label}" - backticks in node labels cause rendering issues in Mermaid diagrams`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check single line content (this needs to be done after the loop)
|
|
135
|
+
const newlineCount = (text.match(/\n/g) || []).length;
|
|
136
|
+
if (newlineCount === 0 && text.trim().length > 0) {
|
|
137
|
+
isApproved = false;
|
|
138
|
+
detailFeedback.push(
|
|
139
|
+
`Found single line content in ${source}: content appears to be on only one line, check for missing line breaks`
|
|
140
|
+
);
|
|
128
141
|
}
|
|
129
142
|
};
|
|
130
143
|
|
|
131
144
|
// Check content
|
|
132
145
|
checkLinks(reviewContent, "result");
|
|
133
|
-
|
|
134
|
-
checkSingleLine(reviewContent, "result");
|
|
135
|
-
checkCodeBlockIndentation(reviewContent, "result");
|
|
146
|
+
performAllChecks(reviewContent, "result");
|
|
136
147
|
|
|
137
148
|
return {
|
|
138
149
|
isApproved,
|
|
@@ -1,15 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1
|
+
import {
|
|
2
|
+
getCurrentGitHead,
|
|
3
|
+
hasFileChangesBetweenCommits,
|
|
4
|
+
} from "../utils/utils.mjs";
|
|
6
5
|
|
|
7
6
|
export default async function checkStructurePlanning(
|
|
8
|
-
{ originalStructurePlan, feedback, ...rest },
|
|
7
|
+
{ originalStructurePlan, feedback, lastGitHead, ...rest },
|
|
9
8
|
options
|
|
10
9
|
) {
|
|
11
|
-
//
|
|
12
|
-
|
|
10
|
+
// Check if we need to regenerate structure plan
|
|
11
|
+
let shouldRegenerate = false;
|
|
12
|
+
let finalFeedback = feedback;
|
|
13
|
+
|
|
14
|
+
// If no feedback and originalStructurePlan exists, check for git changes
|
|
15
|
+
if (originalStructurePlan) {
|
|
16
|
+
// If no lastGitHead, regenerate by default
|
|
17
|
+
if (!lastGitHead) {
|
|
18
|
+
shouldRegenerate = true;
|
|
19
|
+
} else {
|
|
20
|
+
// Check if there are relevant file changes since last generation
|
|
21
|
+
const currentGitHead = getCurrentGitHead();
|
|
22
|
+
if (currentGitHead && currentGitHead !== lastGitHead) {
|
|
23
|
+
const hasChanges = hasFileChangesBetweenCommits(
|
|
24
|
+
lastGitHead,
|
|
25
|
+
currentGitHead
|
|
26
|
+
);
|
|
27
|
+
if (hasChanges) {
|
|
28
|
+
shouldRegenerate = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (shouldRegenerate) {
|
|
34
|
+
finalFeedback = `
|
|
35
|
+
${finalFeedback || ""}
|
|
36
|
+
|
|
37
|
+
根据最新的 DataSources 更新结构规划:
|
|
38
|
+
1. 对于新增的内容,可以根据需要新增节点,或补充到原有节点展示
|
|
39
|
+
2. 谨慎删除节点,除非节点关联 sourceIds 都被删除了
|
|
40
|
+
3. 不能修改原有节点的 path
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// If no regeneration needed, return original structure plan
|
|
46
|
+
if (originalStructurePlan && !feedback && !shouldRegenerate) {
|
|
13
47
|
return {
|
|
14
48
|
structurePlan: originalStructurePlan,
|
|
15
49
|
};
|
|
@@ -18,7 +52,7 @@ export default async function checkStructurePlanning(
|
|
|
18
52
|
const panningAgent = options.context.agents["reflective-structure-planner"];
|
|
19
53
|
|
|
20
54
|
const result = await options.context.invoke(panningAgent, {
|
|
21
|
-
feedback:
|
|
55
|
+
feedback: finalFeedback || "",
|
|
22
56
|
originalStructurePlan,
|
|
23
57
|
...rest,
|
|
24
58
|
});
|
|
@@ -1,6 +1,38 @@
|
|
|
1
|
-
import { writeFile, mkdir } from "node:fs/promises";
|
|
1
|
+
import { writeFile, mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
|
|
4
|
+
// Predefined document generation styles
|
|
5
|
+
const DOCUMENT_STYLES = {
|
|
6
|
+
actionFirst: {
|
|
7
|
+
name: "Action-First Style",
|
|
8
|
+
rules:
|
|
9
|
+
"Action-first and task-oriented; steps first, copyable examples, minimal context; second person, active voice, short sentences",
|
|
10
|
+
},
|
|
11
|
+
conceptFirst: {
|
|
12
|
+
name: "Concept-First Style",
|
|
13
|
+
rules:
|
|
14
|
+
"Why/What before How; precise and restrained, provide trade-offs and comparisons; support with architecture/flow/sequence diagrams",
|
|
15
|
+
},
|
|
16
|
+
specReference: {
|
|
17
|
+
name: "Spec-Reference Style",
|
|
18
|
+
rules:
|
|
19
|
+
"Objective and precise, no rhetoric; tables/Schema focused, authoritative fields and defaults; clear error codes and multi-language examples",
|
|
20
|
+
},
|
|
21
|
+
custom: {
|
|
22
|
+
name: "Custom Rules",
|
|
23
|
+
rules: "Enter your own documentation generation rules",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Predefined target audiences
|
|
28
|
+
const TARGET_AUDIENCES = {
|
|
29
|
+
actionFirst: "Developers, Implementation Engineers, DevOps",
|
|
30
|
+
conceptFirst:
|
|
31
|
+
"Architects, Technical Leads, Developers interested in principles",
|
|
32
|
+
generalUsers: "General Users",
|
|
33
|
+
custom: "Enter your own target audience",
|
|
34
|
+
};
|
|
35
|
+
|
|
4
36
|
/**
|
|
5
37
|
* Guide users through multi-turn dialogue to collect information and generate YAML configuration
|
|
6
38
|
* @param {Object} params
|
|
@@ -9,48 +41,125 @@ import { join, dirname } from "node:path";
|
|
|
9
41
|
* @returns {Promise<Object>}
|
|
10
42
|
*/
|
|
11
43
|
export default async function init(
|
|
12
|
-
{ outputPath = "./doc-smith", fileName = "config.yaml" },
|
|
44
|
+
{ outputPath = "./doc-smith", fileName = "config.yaml", skipIfExists = false },
|
|
13
45
|
options
|
|
14
46
|
) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
47
|
+
if (skipIfExists) {
|
|
48
|
+
const filePath = join(outputPath, fileName);
|
|
49
|
+
if (await readFile(filePath, "utf8").catch(() => null)) {
|
|
50
|
+
return {}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log("🚀 Welcome to AIGNE Doc Smith!");
|
|
55
|
+
console.log("Let's create your documentation configuration.\n");
|
|
19
56
|
|
|
20
57
|
// Collect user information
|
|
21
58
|
const input = {};
|
|
22
59
|
|
|
23
|
-
// 1. Document generation rules
|
|
24
|
-
console.log("
|
|
25
|
-
|
|
26
|
-
|
|
60
|
+
// 1. Document generation rules with style selection
|
|
61
|
+
console.log("📝 Step 1/6: Document Generation Rules");
|
|
62
|
+
|
|
63
|
+
// Let user select a document style
|
|
64
|
+
const styleChoice = await options.prompts.select({
|
|
65
|
+
message: "Choose your documentation style:",
|
|
66
|
+
choices: Object.entries(DOCUMENT_STYLES).map(([key, style]) => ({
|
|
67
|
+
name: `${style.name} - ${style.rules}`,
|
|
68
|
+
value: key,
|
|
69
|
+
})),
|
|
27
70
|
});
|
|
28
|
-
input.rules = rulesInput.trim();
|
|
29
71
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
72
|
+
let rules;
|
|
73
|
+
if (styleChoice === "custom") {
|
|
74
|
+
// User wants to input custom rules
|
|
75
|
+
rules = await options.prompts.input({
|
|
76
|
+
message: "Enter your custom documentation rules:",
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
// Use predefined style directly
|
|
80
|
+
rules = DOCUMENT_STYLES[styleChoice].rules;
|
|
81
|
+
console.log(`✅ Selected: ${DOCUMENT_STYLES[styleChoice].name}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
input.rules = rules.trim();
|
|
85
|
+
|
|
86
|
+
// 2. Target audience selection
|
|
87
|
+
console.log("\n👥 Step 2/6: Target Audience");
|
|
88
|
+
|
|
89
|
+
// Let user select target audience
|
|
90
|
+
const audienceChoice = await options.prompts.select({
|
|
91
|
+
message: "Who is your target audience?",
|
|
92
|
+
choices: Object.entries(TARGET_AUDIENCES).map(([key, audience]) => ({
|
|
93
|
+
name: audience,
|
|
94
|
+
value: key,
|
|
95
|
+
})),
|
|
35
96
|
});
|
|
36
|
-
|
|
97
|
+
|
|
98
|
+
let targetAudience;
|
|
99
|
+
if (audienceChoice === "custom") {
|
|
100
|
+
// User wants to input custom audience
|
|
101
|
+
targetAudience = await options.prompts.input({
|
|
102
|
+
message: "Enter your custom target audience:",
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
// Use predefined audience directly
|
|
106
|
+
targetAudience = TARGET_AUDIENCES[audienceChoice];
|
|
107
|
+
console.log(`✅ Selected: ${TARGET_AUDIENCES[audienceChoice]}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
input.targetAudience = targetAudience.trim();
|
|
37
111
|
|
|
38
112
|
// 3. Language settings
|
|
39
|
-
console.log("\n
|
|
113
|
+
console.log("\n🌐 Step 3/6: Primary Language");
|
|
40
114
|
const localeInput = await options.prompts.input({
|
|
41
|
-
message:
|
|
115
|
+
message:
|
|
116
|
+
"Primary documentation language (e.g., en, zh, press Enter for 'en'):",
|
|
42
117
|
});
|
|
43
118
|
input.locale = localeInput.trim() || "en";
|
|
44
119
|
|
|
45
120
|
// 4. Translation languages
|
|
46
|
-
console.log("\n
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
121
|
+
console.log("\n🔄 Step 4/6: Translation Languages");
|
|
122
|
+
console.log(
|
|
123
|
+
"Enter additional languages for translation (press Enter to skip):"
|
|
124
|
+
);
|
|
125
|
+
const translateLanguages = [];
|
|
126
|
+
while (true) {
|
|
127
|
+
const langInput = await options.prompts.input({
|
|
128
|
+
message: `Language ${translateLanguages.length + 1} (e.g., zh, ja, fr):`,
|
|
129
|
+
});
|
|
130
|
+
if (!langInput.trim()) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
translateLanguages.push(langInput.trim());
|
|
134
|
+
}
|
|
135
|
+
input.translateLanguages = translateLanguages;
|
|
136
|
+
|
|
137
|
+
// 5. Documentation directory
|
|
138
|
+
console.log("\n📁 Step 5/6: Output Directory");
|
|
139
|
+
const docsDirInput = await options.prompts.input({
|
|
140
|
+
message: `Where to save generated docs (press Enter for '${outputPath}/docs'):`,
|
|
50
141
|
});
|
|
51
|
-
input.
|
|
52
|
-
|
|
53
|
-
|
|
142
|
+
input.docsDir = docsDirInput.trim() || `${outputPath}/docs`;
|
|
143
|
+
|
|
144
|
+
// 6. Source code paths
|
|
145
|
+
console.log("\n🔍 Step 6/6: Source Code Paths");
|
|
146
|
+
console.log(
|
|
147
|
+
"Enter paths to analyze for documentation (press Enter to use './'):"
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const sourcePaths = [];
|
|
151
|
+
while (true) {
|
|
152
|
+
const pathInput = await options.prompts.input({
|
|
153
|
+
message: `Path ${sourcePaths.length + 1} (e.g., ./src, ./lib):`,
|
|
154
|
+
});
|
|
155
|
+
if (!pathInput.trim()) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
sourcePaths.push(pathInput.trim());
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If no paths entered, use default
|
|
162
|
+
input.sourcesPath = sourcePaths.length > 0 ? sourcePaths : ["./"];
|
|
54
163
|
|
|
55
164
|
// Generate YAML content
|
|
56
165
|
const yamlContent = generateYAML(input, outputPath);
|
|
@@ -64,12 +173,17 @@ export default async function init(
|
|
|
64
173
|
await mkdir(dirPath, { recursive: true });
|
|
65
174
|
|
|
66
175
|
await writeFile(filePath, yamlContent, "utf8");
|
|
67
|
-
console.log(`\n
|
|
176
|
+
console.log(`\n🎉 Configuration saved to: ${filePath}`);
|
|
177
|
+
console.log(
|
|
178
|
+
"💡 You can edit the configuration file anytime to modify settings."
|
|
179
|
+
);
|
|
180
|
+
console.log(
|
|
181
|
+
"🚀 Run 'aigne doc generate' to start documentation generation!"
|
|
182
|
+
);
|
|
68
183
|
|
|
69
184
|
return {
|
|
70
185
|
inputGeneratorStatus: true,
|
|
71
186
|
inputGeneratorPath: filePath,
|
|
72
|
-
inputGeneratorContent: yamlContent,
|
|
73
187
|
};
|
|
74
188
|
} catch (error) {
|
|
75
189
|
console.error(`❌ Failed to save configuration file: ${error.message}`);
|
|
@@ -121,11 +235,13 @@ function generateYAML(input, outputPath) {
|
|
|
121
235
|
yaml += `# - en # Example: English translation\n`;
|
|
122
236
|
}
|
|
123
237
|
|
|
124
|
-
// Add
|
|
125
|
-
yaml += `docsDir: ${
|
|
126
|
-
yaml += `outputDir: ${outputPath}/output # Directory to save output files\n`;
|
|
238
|
+
// Add directory and source path configurations
|
|
239
|
+
yaml += `docsDir: ${input.docsDir} # Directory to save generated documentation\n`;
|
|
240
|
+
// yaml += `outputDir: ${outputPath}/output # Directory to save output files\n`;
|
|
127
241
|
yaml += `sourcesPath: # Source code paths to analyze\n`;
|
|
128
|
-
|
|
242
|
+
input.sourcesPath.forEach((path) => {
|
|
243
|
+
yaml += ` - ${path}\n`;
|
|
244
|
+
});
|
|
129
245
|
|
|
130
246
|
return yaml;
|
|
131
247
|
}
|
package/agents/load-config.mjs
CHANGED