@aigne/doc-smith 0.1.4 → 0.2.1
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 -1
- package/agents/batch-docs-detail-generator.yaml +7 -2
- package/agents/batch-translate.yaml +1 -1
- package/agents/check-detail-result.mjs +52 -41
- package/agents/{check-detail-generated.mjs → check-detail.mjs} +38 -6
- package/agents/check-structure-plan.mjs +72 -0
- package/agents/check-structure-planning-result.yaml +1 -1
- package/agents/content-detail-generator.yaml +1 -1
- package/agents/detail-generator-and-translate.yaml +1 -1
- package/agents/detail-regenerator.yaml +3 -0
- package/agents/docs-generator.yaml +14 -9
- package/agents/find-item-by-path.mjs +84 -5
- package/agents/input-generator.mjs +181 -38
- package/agents/load-config.mjs +1 -0
- package/agents/load-sources.mjs +44 -63
- package/agents/publish-docs.mjs +169 -58
- package/agents/reflective-structure-planner.yaml +1 -1
- package/agents/save-docs.mjs +12 -6
- package/agents/save-single-doc.mjs +1 -1
- package/agents/structure-planning.yaml +1 -1
- package/agents/team-publish-docs.yaml +3 -0
- package/agents/transform-detail-datasources.mjs +19 -5
- package/aigne.yaml +2 -2
- package/package.json +8 -6
- package/prompts/check-structure-planning-result.md +1 -1
- package/prompts/structure-planning.md +1 -1
- package/utils/constants.mjs +105 -0
- package/utils/utils.mjs +544 -0
- package/agents/check-structure-planning.mjs +0 -33
|
@@ -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.1](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.0...v0.2.1) (2025-08-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* release 0.2.1 ([e3a39ae](https://github.com/AIGNE-io/aigne-doc-smith/commit/e3a39aedcee129deae424e96942f9798b9191663))
|
|
9
|
+
|
|
10
|
+
## [0.2.0](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.1.4...v0.2.0) (2025-08-05)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* support automatic init configuration when calling agents ([24d29db](https://github.com/AIGNE-io/aigne-doc-smith/commit/24d29db4dd86709750aa22ff649e7dacc4124126))
|
|
16
|
+
* 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))
|
|
17
|
+
|
|
3
18
|
## [0.1.4](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.1.3...v0.1.4) (2025-08-04)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -63,8 +63,10 @@ npx --no doc-smith run --entry-agent init
|
|
|
63
63
|
# 生成命令
|
|
64
64
|
npx --no doc-smith run --entry-agent generate --model gemini:gemini-2.5-flash
|
|
65
65
|
|
|
66
|
+
aigne run --path /Users/lban/arcblock/code/aigne-doc-smith/ --entry-agent generate --model gemini:gemini-2.5-flash --input-forceRegenerate=true
|
|
67
|
+
|
|
66
68
|
# 重新生成单篇
|
|
67
|
-
npx --no doc-smith run --entry-agent update --input-path bitnet-getting-started
|
|
69
|
+
npx --no doc-smith run --entry-agent update --input-doc-path bitnet-getting-started
|
|
68
70
|
|
|
69
71
|
# 结构规划优化
|
|
70
72
|
npx --no doc-smith run --entry-agent generate --input-feedback "补充节点的 sourceIds,确保所有节点 sourceIds 都有值" --model gemini:gemini-2.5-pro
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
type: team
|
|
2
|
-
name:
|
|
2
|
+
name: batchDocsDetailGenerator
|
|
3
3
|
description: 批量生成文档详情
|
|
4
4
|
skills:
|
|
5
|
-
- ./check-detail
|
|
5
|
+
- ./check-detail.mjs
|
|
6
6
|
input_schema:
|
|
7
7
|
type: object
|
|
8
8
|
properties:
|
|
@@ -10,5 +10,10 @@ 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
|
|
18
|
+
concurrency: 3
|
|
14
19
|
mode: sequential
|
|
@@ -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,
|
|
@@ -3,12 +3,23 @@ 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
|
-
export default async function
|
|
11
|
-
{
|
|
11
|
+
export default async function checkDetail(
|
|
12
|
+
{
|
|
13
|
+
path,
|
|
14
|
+
docsDir,
|
|
15
|
+
sourceIds,
|
|
16
|
+
originalStructurePlan,
|
|
17
|
+
structurePlan,
|
|
18
|
+
modifiedFiles,
|
|
19
|
+
lastGitHead,
|
|
20
|
+
forceRegenerate,
|
|
21
|
+
...rest
|
|
22
|
+
},
|
|
12
23
|
options
|
|
13
24
|
) {
|
|
14
25
|
// Check if the detail file already exists
|
|
@@ -61,6 +72,21 @@ export default async function checkDetailGenerated(
|
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
// Check if source files have changed since last generation
|
|
76
|
+
let sourceFilesChanged = false;
|
|
77
|
+
if (sourceIds && sourceIds.length > 0 && modifiedFiles) {
|
|
78
|
+
sourceFilesChanged = hasSourceFilesChanged(sourceIds, modifiedFiles);
|
|
79
|
+
|
|
80
|
+
if (sourceFilesChanged) {
|
|
81
|
+
console.log(`Source files changed for ${path}, will regenerate`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If lastGitHead is not set, regenerate
|
|
86
|
+
if (!lastGitHead) {
|
|
87
|
+
sourceFilesChanged = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
64
90
|
// If file exists, check content validation
|
|
65
91
|
let contentValidationFailed = false;
|
|
66
92
|
if (detailGenerated && fileContent && structurePlan) {
|
|
@@ -74,8 +100,14 @@ export default async function checkDetailGenerated(
|
|
|
74
100
|
}
|
|
75
101
|
}
|
|
76
102
|
|
|
77
|
-
// If file exists, sourceIds haven't changed, and content validation passes, no need to regenerate
|
|
78
|
-
if (
|
|
103
|
+
// If file exists, sourceIds haven't changed, source files haven't changed, and content validation passes, no need to regenerate
|
|
104
|
+
if (
|
|
105
|
+
detailGenerated &&
|
|
106
|
+
!sourceIdsChanged &&
|
|
107
|
+
!sourceFilesChanged &&
|
|
108
|
+
!contentValidationFailed &&
|
|
109
|
+
forceRegenerate !== "true"
|
|
110
|
+
) {
|
|
79
111
|
return {
|
|
80
112
|
path,
|
|
81
113
|
docsDir,
|
|
@@ -85,8 +117,8 @@ export default async function checkDetailGenerated(
|
|
|
85
117
|
}
|
|
86
118
|
|
|
87
119
|
const teamAgent = TeamAgent.from({
|
|
88
|
-
name: "
|
|
89
|
-
skills: [options.context.agents["
|
|
120
|
+
name: "generateDetail",
|
|
121
|
+
skills: [options.context.agents["detailGeneratorAndTranslate"]],
|
|
90
122
|
});
|
|
91
123
|
|
|
92
124
|
const result = await options.context.invoke(teamAgent, {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentGitHead,
|
|
3
|
+
hasFileChangesBetweenCommits,
|
|
4
|
+
} from "../utils/utils.mjs";
|
|
5
|
+
|
|
6
|
+
export default async function checkStructurePlan(
|
|
7
|
+
{ originalStructurePlan, feedback, lastGitHead, forceRegenerate, ...rest },
|
|
8
|
+
options
|
|
9
|
+
) {
|
|
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 (
|
|
47
|
+
originalStructurePlan &&
|
|
48
|
+
!feedback &&
|
|
49
|
+
!shouldRegenerate &&
|
|
50
|
+
forceRegenerate !== "true"
|
|
51
|
+
) {
|
|
52
|
+
return {
|
|
53
|
+
structurePlan: originalStructurePlan,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const panningAgent = options.context.agents["reflectiveStructurePlanner"];
|
|
58
|
+
|
|
59
|
+
const result = await options.context.invoke(panningAgent, {
|
|
60
|
+
feedback: finalFeedback || "",
|
|
61
|
+
originalStructurePlan,
|
|
62
|
+
...rest,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
...result,
|
|
67
|
+
feedback: "", // clear feedback
|
|
68
|
+
originalStructurePlan: originalStructurePlan
|
|
69
|
+
? originalStructurePlan
|
|
70
|
+
: JSON.parse(JSON.stringify(result.structurePlan || [])),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -5,18 +5,20 @@ alias:
|
|
|
5
5
|
- g
|
|
6
6
|
description: Automatically generates comprehensive project documentation
|
|
7
7
|
skills:
|
|
8
|
+
- url: ./input-generator.mjs
|
|
9
|
+
default_input:
|
|
10
|
+
skipIfExists: true
|
|
8
11
|
- ./load-config.mjs
|
|
9
12
|
- ./load-sources.mjs
|
|
10
|
-
- ./check-structure-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}])
|
|
18
|
-
- ./save-output.mjs
|
|
13
|
+
- ./check-structure-plan.mjs
|
|
14
|
+
- url: ./save-output.mjs
|
|
15
|
+
default_input:
|
|
16
|
+
saveKey: structurePlan
|
|
17
|
+
savePath:
|
|
18
|
+
$get: outputDir
|
|
19
|
+
fileName: structure-plan.json
|
|
19
20
|
- type: transform
|
|
21
|
+
name: transformData
|
|
20
22
|
jsonata: |
|
|
21
23
|
$merge([
|
|
22
24
|
$,
|
|
@@ -100,6 +102,9 @@ input_schema:
|
|
|
100
102
|
feedback:
|
|
101
103
|
type: string
|
|
102
104
|
description: Feedback for structure planning adjustments
|
|
105
|
+
forceRegenerate:
|
|
106
|
+
type: string
|
|
107
|
+
description: Force regenerate the documentation
|
|
103
108
|
# labels:
|
|
104
109
|
# type: array
|
|
105
110
|
# items:
|
|
@@ -1,10 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export default async function findItemByPath(
|
|
5
|
+
{ "doc-path": docPath, structurePlanResult, boardId, docsDir },
|
|
6
|
+
options
|
|
7
|
+
) {
|
|
6
8
|
let foundItem = null;
|
|
7
9
|
|
|
10
|
+
// If docPath is empty, let user select from available documents
|
|
11
|
+
if (!docPath) {
|
|
12
|
+
try {
|
|
13
|
+
// Get all .md files in docsDir
|
|
14
|
+
const files = await readdir(docsDir);
|
|
15
|
+
|
|
16
|
+
// Filter for main language .md files (exclude _sidebar.md and language-specific files)
|
|
17
|
+
const mainLanguageFiles = files.filter(
|
|
18
|
+
(file) =>
|
|
19
|
+
file.endsWith(".md") &&
|
|
20
|
+
file !== "_sidebar.md" &&
|
|
21
|
+
!file.match(/\.\w+(-\w+)?\.md$/) // Exclude language-specific files like .en.md, .zh-CN.md, etc.
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (mainLanguageFiles.length === 0) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"Please provide a doc-path parameter to specify which document to update"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Let user select a file
|
|
31
|
+
const selectedFile = await options.prompts.search({
|
|
32
|
+
message: "Select a document to update:",
|
|
33
|
+
source: async (input, { signal }) => {
|
|
34
|
+
if (!input || input.trim() === "") {
|
|
35
|
+
return mainLanguageFiles.map((file) => ({
|
|
36
|
+
name: file,
|
|
37
|
+
value: file,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const searchTerm = input.trim().toLowerCase();
|
|
42
|
+
const filteredFiles = mainLanguageFiles.filter((file) =>
|
|
43
|
+
file.toLowerCase().includes(searchTerm)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return filteredFiles.map((file) => ({
|
|
47
|
+
name: file,
|
|
48
|
+
value: file,
|
|
49
|
+
}));
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!selectedFile) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Please provide a doc-path parameter to specify which document to update"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Convert filename back to path
|
|
60
|
+
// Remove .md extension
|
|
61
|
+
const flatName = selectedFile.replace(/\.md$/, "");
|
|
62
|
+
|
|
63
|
+
// Try to find matching item by comparing flattened paths
|
|
64
|
+
let foundItemByFile = null;
|
|
65
|
+
|
|
66
|
+
// First try without boardId prefix
|
|
67
|
+
foundItemByFile = structurePlanResult.find((item) => {
|
|
68
|
+
const itemFlattenedPath = item.path
|
|
69
|
+
.replace(/^\//, "")
|
|
70
|
+
.replace(/\//g, "-");
|
|
71
|
+
return itemFlattenedPath === flatName;
|
|
72
|
+
});
|
|
73
|
+
if (!foundItemByFile) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Please provide a doc-path parameter to specify which document to update"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
docPath = foundItemByFile.path;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
"Please provide a doc-path parameter to specify which document to update"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
8
87
|
// First try direct path matching
|
|
9
88
|
foundItem = structurePlanResult.find((item) => item.path === docPath);
|
|
10
89
|
|