@aigne/doc-smith 0.8.15-beta.6 โ 0.8.15-beta.7
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/CHANGELOG.md +13 -0
- package/agents/clear/choose-contents.mjs +4 -4
- package/agents/clear/clear-auth-tokens.mjs +8 -8
- package/agents/clear/clear-deployment-config.mjs +2 -2
- package/agents/clear/clear-document-config.mjs +3 -3
- package/agents/clear/clear-document-structure.mjs +10 -10
- package/agents/clear/clear-generated-docs.mjs +103 -14
- package/agents/clear/clear-media-description.mjs +7 -7
- package/agents/translate/index.yaml +1 -1
- package/agents/translate/record-translation-history.mjs +6 -2
- package/agents/update/save-and-translate-document.mjs +11 -0
- package/agents/utils/choose-docs.mjs +2 -1
- package/agents/utils/load-sources.mjs +2 -25
- package/agents/utils/save-doc.mjs +0 -9
- package/package.json +1 -1
- package/prompts/detail/custom/custom-components.md +38 -3
- package/prompts/translate/code-block.md +13 -3
- package/utils/docs-finder-utils.mjs +48 -0
- package/utils/history-utils.mjs +20 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.15-beta.7](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.6...v0.8.15-beta.7) (2025-10-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add web-smith powered web-pages ([#229](https://github.com/AIGNE-io/aigne-doc-smith/issues/229)) ([c3c00c1](https://github.com/AIGNE-io/aigne-doc-smith/commit/c3c00c12f092b125b6adb1a13ed5ff9720fbdab7))
|
|
9
|
+
* support cleaning specific documents ([#231](https://github.com/AIGNE-io/aigne-doc-smith/issues/231)) ([67607c9](https://github.com/AIGNE-io/aigne-doc-smith/commit/67607c9ff3852cc81a29e5a11b2151d26879b000))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* tune custom component prompt and batch update history ([#228](https://github.com/AIGNE-io/aigne-doc-smith/issues/228)) ([ab13b97](https://github.com/AIGNE-io/aigne-doc-smith/commit/ab13b9737e5f111d0939f9c39ee76e13c0692a68))
|
|
15
|
+
|
|
3
16
|
## [0.8.15-beta.6](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.5...v0.8.15-beta.6) (2025-10-30)
|
|
4
17
|
|
|
5
18
|
|
|
@@ -10,7 +10,7 @@ const TARGET_METADATA = {
|
|
|
10
10
|
generatedDocs: {
|
|
11
11
|
label: "Generated Documents",
|
|
12
12
|
description: ({ docsDir }) =>
|
|
13
|
-
`
|
|
13
|
+
`Select and delete specific generated documents in './${toDisplayPath(docsDir)}'. The documentation structure will be preserved.`,
|
|
14
14
|
agent: "clearGeneratedDocs",
|
|
15
15
|
},
|
|
16
16
|
documentStructure: {
|
|
@@ -150,9 +150,9 @@ export default async function chooseContents(input = {}, options = {}) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
const header = hasError
|
|
153
|
-
? "Cleanup finished with some issues
|
|
154
|
-
: "Cleanup completed successfully
|
|
155
|
-
const detailLines = results.map((item) =>
|
|
153
|
+
? "๐งน Cleanup finished with some issues.\n"
|
|
154
|
+
: "๐งน Cleanup completed successfully!\n";
|
|
155
|
+
const detailLines = results.map((item) => `${item.message}`).join("\n\n");
|
|
156
156
|
|
|
157
157
|
const suggestions = [];
|
|
158
158
|
results.forEach((result) => {
|
|
@@ -9,7 +9,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
9
9
|
// Check if the file exists
|
|
10
10
|
if (!existsSync(DOC_SMITH_ENV_FILE)) {
|
|
11
11
|
return {
|
|
12
|
-
message: "No site authorizations found to clear",
|
|
12
|
+
message: "๐ No site authorizations found to clear",
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -23,7 +23,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
23
23
|
|
|
24
24
|
if (siteHostnames.length === 0) {
|
|
25
25
|
return {
|
|
26
|
-
message: "No site authorizations found to clear",
|
|
26
|
+
message: "๐ No site authorizations found to clear",
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -58,7 +58,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
58
58
|
|
|
59
59
|
if (selectedSites.length === 0) {
|
|
60
60
|
return {
|
|
61
|
-
message: "No sites selected for clearing authorization",
|
|
61
|
+
message: "๐ No sites selected for clearing authorization",
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -68,7 +68,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
68
68
|
if (selectedSites.includes("__ALL__")) {
|
|
69
69
|
// Clear all site authorizations
|
|
70
70
|
await writeFile(DOC_SMITH_ENV_FILE, stringify({}));
|
|
71
|
-
results.push(
|
|
71
|
+
results.push(`โ Cleared site authorization for all sites (${siteHostnames.length} sites)`);
|
|
72
72
|
clearedCount = siteHostnames.length;
|
|
73
73
|
} else {
|
|
74
74
|
// Clear site authorizations for selected sites
|
|
@@ -79,7 +79,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
79
79
|
// Remove the entire site object
|
|
80
80
|
delete updatedEnvs[hostname];
|
|
81
81
|
|
|
82
|
-
results.push(
|
|
82
|
+
results.push(`โ Cleared site authorization for ${chalk.cyan(hostname)}`);
|
|
83
83
|
clearedCount++;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -87,8 +87,8 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
87
87
|
await writeFile(DOC_SMITH_ENV_FILE, stringify(updatedEnvs));
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const header =
|
|
91
|
-
const detailLines = results.join("\n");
|
|
90
|
+
const header = `๐ Successfully cleared site authorizations!`;
|
|
91
|
+
const detailLines = results.map((item) => ` ${item}`).join("\n");
|
|
92
92
|
|
|
93
93
|
const message = [header, "", detailLines, ""].filter(Boolean).join("\n");
|
|
94
94
|
|
|
@@ -99,7 +99,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
99
99
|
};
|
|
100
100
|
} catch (error) {
|
|
101
101
|
return {
|
|
102
|
-
message:
|
|
102
|
+
message: `โ ๏ธ Failed to clear site authorizations: ${error.message}`,
|
|
103
103
|
error: true,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
@@ -36,12 +36,12 @@ export default async function clearDeploymentConfig(input = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
|
-
message:
|
|
39
|
+
message: `๐ฆ Cleared appUrl from config file (${displayPath})`,
|
|
40
40
|
};
|
|
41
41
|
} catch (error) {
|
|
42
42
|
return {
|
|
43
43
|
error: true,
|
|
44
|
-
message:
|
|
44
|
+
message: `โ ๏ธ Failed to clear deployment config: ${error.message}`,
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -10,8 +10,8 @@ export default async function clearDocumentConfig({ workDir }) {
|
|
|
10
10
|
await rm(documentConfigPath, { recursive: true, force: true });
|
|
11
11
|
|
|
12
12
|
const message = existed
|
|
13
|
-
?
|
|
14
|
-
:
|
|
13
|
+
? `โ๏ธ Cleared document configuration (${displayPath})`
|
|
14
|
+
: `โ๏ธ Document configuration already empty (${displayPath})`;
|
|
15
15
|
|
|
16
16
|
const suggestions = existed
|
|
17
17
|
? ["Run `aigne doc init` to generate a fresh configuration file."]
|
|
@@ -25,7 +25,7 @@ export default async function clearDocumentConfig({ workDir }) {
|
|
|
25
25
|
};
|
|
26
26
|
} catch (error) {
|
|
27
27
|
return {
|
|
28
|
-
message:
|
|
28
|
+
message: `โ ๏ธ Failed to clear document configuration: ${error.message}`,
|
|
29
29
|
error: true,
|
|
30
30
|
path: displayPath,
|
|
31
31
|
};
|
|
@@ -16,8 +16,8 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
16
16
|
|
|
17
17
|
const structureDisplayPath = toDisplayPath(structurePlanPath);
|
|
18
18
|
const structureMessage = structureExists
|
|
19
|
-
?
|
|
20
|
-
:
|
|
19
|
+
? `โ Cleared documentation structure (${structureDisplayPath})`
|
|
20
|
+
: `โข Documentation structure already empty (${structureDisplayPath})`;
|
|
21
21
|
|
|
22
22
|
results.push({
|
|
23
23
|
type: "structure",
|
|
@@ -29,7 +29,7 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
29
29
|
results.push({
|
|
30
30
|
type: "structure",
|
|
31
31
|
error: true,
|
|
32
|
-
message:
|
|
32
|
+
message: `โ Failed to clear documentation structure: ${error.message}`,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -41,8 +41,8 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
41
41
|
|
|
42
42
|
const docsDisplayPath = toDisplayPath(docsDir);
|
|
43
43
|
const docsMessage = docsExists
|
|
44
|
-
?
|
|
45
|
-
:
|
|
44
|
+
? `โ Cleared documents directory (${docsDisplayPath})`
|
|
45
|
+
: `โข Documents directory already empty (${docsDisplayPath})`;
|
|
46
46
|
|
|
47
47
|
results.push({
|
|
48
48
|
type: "documents",
|
|
@@ -54,7 +54,7 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
54
54
|
results.push({
|
|
55
55
|
type: "documents",
|
|
56
56
|
error: true,
|
|
57
|
-
message:
|
|
57
|
+
message: `โ Failed to clear documents directory: ${error.message}`,
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -65,14 +65,14 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
65
65
|
|
|
66
66
|
let header;
|
|
67
67
|
if (errorItems > 0) {
|
|
68
|
-
header = "Documentation Structure cleanup finished with some issues.";
|
|
68
|
+
header = "โ ๏ธ Documentation Structure cleanup finished with some issues.";
|
|
69
69
|
} else if (clearedItems > 0) {
|
|
70
|
-
header = "Documentation Structure cleared successfully!";
|
|
70
|
+
header = "๐ Documentation Structure cleared successfully!";
|
|
71
71
|
} else {
|
|
72
|
-
header = "Documentation Structure already empty.";
|
|
72
|
+
header = "๐ Documentation Structure already empty.";
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const detailLines = results.map((item) =>
|
|
75
|
+
const detailLines = results.map((item) => ` ${item.message}`).join("\n");
|
|
76
76
|
const message = [header, "", detailLines].filter(Boolean).join("\n");
|
|
77
77
|
|
|
78
78
|
return {
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { pathExists, resolveToAbsolute, toDisplayPath } from "../../utils/file-utils.mjs";
|
|
4
|
+
import {
|
|
5
|
+
pathToFlatName,
|
|
6
|
+
generateFileName,
|
|
7
|
+
loadDocumentStructure,
|
|
8
|
+
} from "../../utils/docs-finder-utils.mjs";
|
|
9
|
+
import chooseDocs from "../utils/choose-docs.mjs";
|
|
3
10
|
|
|
4
|
-
export default async function clearGeneratedDocs(input = {},
|
|
5
|
-
const { docsDir } = input;
|
|
11
|
+
export default async function clearGeneratedDocs(input = {}, options = {}) {
|
|
12
|
+
const { docsDir, outputDir, locale, translateLanguages } = input;
|
|
6
13
|
|
|
7
14
|
if (!docsDir) {
|
|
8
15
|
return {
|
|
9
|
-
message: "No generated documents directory specified",
|
|
16
|
+
message: "๐ No generated documents directory specified",
|
|
10
17
|
};
|
|
11
18
|
}
|
|
12
19
|
|
|
@@ -14,23 +21,104 @@ export default async function clearGeneratedDocs(input = {}, _options = {}) {
|
|
|
14
21
|
const displayPath = toDisplayPath(generatedDocsPath);
|
|
15
22
|
|
|
16
23
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
24
|
+
const dirExists = await pathExists(generatedDocsPath);
|
|
25
|
+
if (!dirExists) {
|
|
26
|
+
return {
|
|
27
|
+
message: `๐ Generated documents directory does not exist (${displayPath})`,
|
|
28
|
+
cleared: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
19
31
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
const documentExecutionStructure = (await loadDocumentStructure(outputDir)) || [];
|
|
33
|
+
// select documents interactively
|
|
34
|
+
const chooseResult = await chooseDocs(
|
|
35
|
+
{
|
|
36
|
+
docs: [], // Empty to trigger interactive selection
|
|
37
|
+
documentExecutionStructure,
|
|
38
|
+
docsDir: generatedDocsPath,
|
|
39
|
+
locale: locale || "en",
|
|
40
|
+
isTranslate: false,
|
|
41
|
+
title: "Select documents to delete:",
|
|
42
|
+
feedback: "Skip feedback",
|
|
43
|
+
requiredFeedback: false,
|
|
44
|
+
},
|
|
45
|
+
options,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (!chooseResult?.selectedDocs || chooseResult.selectedDocs.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
message: "๐ No documents selected for deletion",
|
|
51
|
+
cleared: false,
|
|
52
|
+
path: displayPath,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extract file names
|
|
57
|
+
const filesToDelete = new Set();
|
|
58
|
+
const allLanguages = [locale || "en", ...(translateLanguages || [])];
|
|
59
|
+
|
|
60
|
+
for (const selectedDoc of chooseResult.selectedDocs) {
|
|
61
|
+
// Convert path to flat filename format using utility function
|
|
62
|
+
const flatName = pathToFlatName(selectedDoc.path);
|
|
63
|
+
|
|
64
|
+
// Generate file names for all languages
|
|
65
|
+
for (const lang of allLanguages) {
|
|
66
|
+
const fileName = generateFileName(flatName, lang);
|
|
67
|
+
filesToDelete.add(fileName);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (filesToDelete.size === 0) {
|
|
72
|
+
return {
|
|
73
|
+
message: "๐ No documents were deleted.",
|
|
74
|
+
cleared: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Delete selected files (including all language versions)
|
|
79
|
+
const deletedFiles = [];
|
|
80
|
+
const failedFiles = [];
|
|
81
|
+
let hasError = false;
|
|
82
|
+
|
|
83
|
+
for (const file of filesToDelete) {
|
|
84
|
+
try {
|
|
85
|
+
const filePath = join(generatedDocsPath, file);
|
|
86
|
+
await rm(filePath, { force: true });
|
|
87
|
+
deletedFiles.push(file);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
hasError = true;
|
|
90
|
+
failedFiles.push({ file, error: error.message });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Build result message
|
|
95
|
+
const deletedCount = deletedFiles.length;
|
|
96
|
+
const failedCount = failedFiles.length;
|
|
97
|
+
|
|
98
|
+
let message = "";
|
|
99
|
+
if (deletedCount > 0) {
|
|
100
|
+
const lastIndex = deletedFiles.length - 1;
|
|
101
|
+
message = `๐ Deleted ${deletedCount} document(s) in "${displayPath}":\n${deletedFiles
|
|
102
|
+
.map((f, i) => ` ${i === lastIndex ? "โโ" : "โโ"} ${f}`)
|
|
103
|
+
.join("\n")}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (failedCount > 0) {
|
|
107
|
+
const lastIndex = failedFiles.length - 1;
|
|
108
|
+
message = `โ ๏ธ Failed to delete ${failedCount} document(s) in "${displayPath}":\n${failedFiles
|
|
109
|
+
.map((f, i) => ` ${i === lastIndex ? "โโ" : "โโ"} ${f.file}: ${f.error}`)
|
|
110
|
+
.join("\n")}`;
|
|
111
|
+
}
|
|
23
112
|
|
|
24
113
|
return {
|
|
25
114
|
message,
|
|
26
|
-
cleared:
|
|
27
|
-
|
|
115
|
+
cleared: deletedCount > 0,
|
|
116
|
+
error: hasError,
|
|
28
117
|
};
|
|
29
118
|
} catch (error) {
|
|
30
119
|
return {
|
|
31
|
-
message:
|
|
120
|
+
message: `โ ๏ธ Failed to clear generated documents: ${error.message}`,
|
|
32
121
|
error: true,
|
|
33
|
-
path: displayPath,
|
|
34
122
|
};
|
|
35
123
|
}
|
|
36
124
|
}
|
|
@@ -46,5 +134,6 @@ clearGeneratedDocs.input_schema = {
|
|
|
46
134
|
required: ["docsDir"],
|
|
47
135
|
};
|
|
48
136
|
|
|
49
|
-
clearGeneratedDocs.taskTitle = "Clear
|
|
50
|
-
clearGeneratedDocs.description =
|
|
137
|
+
clearGeneratedDocs.taskTitle = "Clear generated documents";
|
|
138
|
+
clearGeneratedDocs.description =
|
|
139
|
+
"Select and delete specific generated documents from the docs directory";
|
|
@@ -11,7 +11,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
11
11
|
// Check if the cache file exists
|
|
12
12
|
if (!existsSync(cacheFilePath)) {
|
|
13
13
|
return {
|
|
14
|
-
message: "No media descriptions found to clear",
|
|
14
|
+
message: "๐ผ๏ธ No media descriptions found to clear",
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -26,7 +26,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
26
26
|
|
|
27
27
|
if (mediaHashes.length === 0) {
|
|
28
28
|
return {
|
|
29
|
-
message: "No media descriptions found to clear",
|
|
29
|
+
message: "๐ผ๏ธ No media descriptions found to clear",
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -68,7 +68,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
68
68
|
|
|
69
69
|
if (selectedHashes.length === 0) {
|
|
70
70
|
return {
|
|
71
|
-
message: "No media files selected for clearing descriptions",
|
|
71
|
+
message: "๐ผ๏ธ No media files selected for clearing descriptions",
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -84,7 +84,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
84
84
|
lastUpdated: new Date().toISOString(),
|
|
85
85
|
}),
|
|
86
86
|
);
|
|
87
|
-
results.push(
|
|
87
|
+
results.push(`โ Cleared descriptions for all media files (${mediaHashes.length} files)`);
|
|
88
88
|
clearedCount = mediaHashes.length;
|
|
89
89
|
} else {
|
|
90
90
|
// Clear descriptions for selected files
|
|
@@ -94,7 +94,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
94
94
|
if (updatedCache[hash]) {
|
|
95
95
|
const filename = path.basename(updatedCache[hash].path);
|
|
96
96
|
delete updatedCache[hash];
|
|
97
|
-
results.push(
|
|
97
|
+
results.push(`โ Cleared description for ${chalk.cyan(filename)}`);
|
|
98
98
|
clearedCount++;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
@@ -108,8 +108,8 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const header =
|
|
112
|
-
const detailLines = results.join("\n");
|
|
111
|
+
const header = `๐ผ๏ธ Successfully cleared media descriptions`;
|
|
112
|
+
const detailLines = results.map((item) => ` ${item}`).join("\n");
|
|
113
113
|
|
|
114
114
|
const message = [header, "", detailLines, ""].filter(Boolean).join("\n");
|
|
115
115
|
|
|
@@ -33,12 +33,12 @@ skills:
|
|
|
33
33
|
name: batchTranslateDocument
|
|
34
34
|
skills:
|
|
35
35
|
- ../translate/translate-multilingual.yaml
|
|
36
|
-
- url: ./record-translation-history.mjs
|
|
37
36
|
iterate_on: selectedDocs
|
|
38
37
|
concurrency: 10
|
|
39
38
|
- url: ../utils/check-feedback-refiner.mjs
|
|
40
39
|
default_input:
|
|
41
40
|
stage: translation_refine
|
|
41
|
+
- url: ./record-translation-history.mjs
|
|
42
42
|
- url: ../utils/action-success.mjs
|
|
43
43
|
default_input:
|
|
44
44
|
action: 'โ
Translation completed'
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { recordUpdate } from "../../utils/history-utils.mjs";
|
|
2
2
|
|
|
3
|
-
export default function recordTranslationHistory({
|
|
3
|
+
export default function recordTranslationHistory({ selectedPaths, feedback }) {
|
|
4
4
|
// Skip if no feedback provided
|
|
5
5
|
if (!feedback?.trim()) {
|
|
6
6
|
return {};
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
if (!Array.isArray(selectedPaths) || selectedPaths.length === 0) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
// Record translation history for this document
|
|
10
14
|
recordUpdate({
|
|
11
15
|
operation: "translation_update",
|
|
12
16
|
feedback: feedback.trim(),
|
|
13
|
-
|
|
17
|
+
docPaths: selectedPaths,
|
|
14
18
|
});
|
|
15
19
|
|
|
16
20
|
return {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pMap from "p-map";
|
|
2
|
+
import { recordUpdate } from "../../utils/history-utils.mjs";
|
|
2
3
|
|
|
3
4
|
export default async function saveAndTranslateDocument(input, options) {
|
|
4
5
|
const { selectedDocs, docsDir, translateLanguages, locale } = input;
|
|
@@ -7,6 +8,16 @@ export default async function saveAndTranslateDocument(input, options) {
|
|
|
7
8
|
return {};
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
// Record history if feedback is provided
|
|
12
|
+
const doc = selectedDocs[0];
|
|
13
|
+
if (doc.feedback?.trim()) {
|
|
14
|
+
recordUpdate({
|
|
15
|
+
operation: "document_update",
|
|
16
|
+
feedback: doc.feedback.trim(),
|
|
17
|
+
docPaths: selectedDocs.map((v) => v.path),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
// Only prompt user if translation is actually needed
|
|
11
22
|
let shouldTranslate = false;
|
|
12
23
|
if (
|
|
@@ -18,6 +18,7 @@ export default async function chooseDocs(
|
|
|
18
18
|
locale,
|
|
19
19
|
reset = false,
|
|
20
20
|
requiredFeedback = true,
|
|
21
|
+
title,
|
|
21
22
|
},
|
|
22
23
|
options,
|
|
23
24
|
) {
|
|
@@ -65,7 +66,7 @@ export default async function chooseDocs(
|
|
|
65
66
|
|
|
66
67
|
// Let user select multiple files
|
|
67
68
|
selectedFiles = await options.prompts.checkbox({
|
|
68
|
-
message: getActionText(isTranslate, "Select documents to {action}:"),
|
|
69
|
+
message: title || getActionText(isTranslate, "Select documents to {action}:"),
|
|
69
70
|
source: (term) => {
|
|
70
71
|
if (!term) return choices;
|
|
71
72
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
DEFAULT_INCLUDE_PATTERNS,
|
|
22
22
|
} from "../../utils/constants/index.mjs";
|
|
23
23
|
import { isOpenAPISpecFile } from "../../utils/openapi/index.mjs";
|
|
24
|
+
import { loadDocumentStructure } from "../../utils/docs-finder-utils.mjs";
|
|
24
25
|
|
|
25
26
|
export default async function loadSources(
|
|
26
27
|
{
|
|
@@ -231,31 +232,7 @@ export default async function loadSources(
|
|
|
231
232
|
const allFilesPaths = sourceFiles.map((x) => `- ${toRelativePath(x.sourceId)}`).join("\n");
|
|
232
233
|
|
|
233
234
|
// Get the last documentation structure
|
|
234
|
-
|
|
235
|
-
if (outputDir) {
|
|
236
|
-
const documentStructurePath = path.join(outputDir, "structure-plan.json");
|
|
237
|
-
try {
|
|
238
|
-
const documentExecutionStructure = await readFile(documentStructurePath, "utf8");
|
|
239
|
-
if (documentExecutionStructure?.trim()) {
|
|
240
|
-
try {
|
|
241
|
-
// Validate that the content looks like JSON before parsing
|
|
242
|
-
const trimmedContent = documentExecutionStructure.trim();
|
|
243
|
-
if (trimmedContent.startsWith("{") || trimmedContent.startsWith("[")) {
|
|
244
|
-
originalDocumentStructure = JSON.parse(documentExecutionStructure);
|
|
245
|
-
} else {
|
|
246
|
-
console.warn(`structure-plan.json contains non-JSON content, skipping parse`);
|
|
247
|
-
}
|
|
248
|
-
} catch (err) {
|
|
249
|
-
console.error(`Failed to parse structure-plan.json: ${err.message}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
} catch (err) {
|
|
253
|
-
if (err.code !== "ENOENT") {
|
|
254
|
-
console.warn(`Error reading structure-plan.json: ${err.message}`);
|
|
255
|
-
}
|
|
256
|
-
// The file does not exist or is not readable, originalDocumentStructure remains undefined
|
|
257
|
-
}
|
|
258
|
-
}
|
|
235
|
+
const originalDocumentStructure = await loadDocumentStructure(outputDir);
|
|
259
236
|
|
|
260
237
|
// Get the last output result of the specified path
|
|
261
238
|
let content;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { recordUpdate } from "../../utils/history-utils.mjs";
|
|
2
1
|
import { shutdownMermaidWorkerPool } from "../../utils/mermaid-worker-pool.mjs";
|
|
3
2
|
import { saveDoc as _saveDoc } from "../../utils/utils.mjs";
|
|
4
3
|
|
|
@@ -20,14 +19,6 @@ export default async function saveDoc({
|
|
|
20
19
|
locale,
|
|
21
20
|
});
|
|
22
21
|
|
|
23
|
-
if (feedback?.trim()) {
|
|
24
|
-
recordUpdate({
|
|
25
|
-
operation: "document_update",
|
|
26
|
-
feedback: feedback.trim(),
|
|
27
|
-
documentPath: path,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
22
|
if (isShowMessage) {
|
|
32
23
|
// Shutdown mermaid worker pool to ensure clean exit
|
|
33
24
|
try {
|
package/package.json
CHANGED
|
@@ -72,12 +72,11 @@ Suitable for displaying multiple links using a card list format, providing a ric
|
|
|
72
72
|
|
|
73
73
|
### Attributes
|
|
74
74
|
|
|
75
|
-
- data-columns (optional):
|
|
76
|
-
- Must contain multiple <x-card> elements internally.
|
|
75
|
+
- data-columns (optional): Must be an **integer โฅ 2**. Values below 2 are disallowed. Default is 2.
|
|
77
76
|
|
|
78
77
|
### Children
|
|
79
78
|
|
|
80
|
-
- Must contain multiple
|
|
79
|
+
- Must contain multiple `<x-card>` elements internally.
|
|
81
80
|
|
|
82
81
|
### Usage Rules
|
|
83
82
|
|
|
@@ -107,6 +106,25 @@ Example 2: Two-column cards with images
|
|
|
107
106
|
</x-cards>
|
|
108
107
|
```
|
|
109
108
|
|
|
109
|
+
### Bad Examples
|
|
110
|
+
|
|
111
|
+
Example 1: Using a single-column layout (`data-columns="1"`) is not allowed
|
|
112
|
+
|
|
113
|
+
```md
|
|
114
|
+
<x-cards data-columns="1">
|
|
115
|
+
<x-card data-title="Feature 1" data-icon="lucide:rocket">Description of Feature 1.</x-card>
|
|
116
|
+
<x-card data-title="Feature 2" data-icon="lucide:bolt">Description of Feature 2.</x-card>
|
|
117
|
+
</x-cards>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Example 2: Contains only one `<x-card>` (must include multiple cards)
|
|
121
|
+
|
|
122
|
+
```md
|
|
123
|
+
<x-cards data-columns="2">
|
|
124
|
+
<x-card data-title="Card A" data-image="https://picsum.photos/id/10/300/300">Content A</x-card>
|
|
125
|
+
</x-cards>
|
|
126
|
+
```
|
|
127
|
+
|
|
110
128
|
## XField: Structured data field
|
|
111
129
|
|
|
112
130
|
Suitable for displaying API parameters, return values, context data, and any structured data with metadata in a clean, organized format. Supports nested structures for complex data types.
|
|
@@ -414,6 +432,7 @@ Used to group multiple related `<x-field>` elements at the top level, indicating
|
|
|
414
432
|
|
|
415
433
|
- **Top-Level Only**: Used only at the top level for grouping related `<x-field>` elements. Cannot be nested inside other `<x-field>` or `<x-field-group>` elements
|
|
416
434
|
- **Structured Data Only**: Use `<x-field-group>` for fields **other than simple types** (`string`, `number`, `boolean`, `symbol`), e.g., Properties, Context, Parameters, Return values. For simple-type fields, use plain Markdown text.
|
|
435
|
+
- **Spacing Around**: Always insert a blank line before and after `<x-field-group>` when itโs adjacent to Markdown content.
|
|
417
436
|
|
|
418
437
|
### Good Examples
|
|
419
438
|
|
|
@@ -482,4 +501,20 @@ Example 5: Using x-field-group for simple-type (violates "Structured Data Only"
|
|
|
482
501
|
</x-field-group>
|
|
483
502
|
```
|
|
484
503
|
|
|
504
|
+
Example 6: Missing blank line before x-field-group (violates "Spacing Around" rule)
|
|
505
|
+
|
|
506
|
+
```md
|
|
507
|
+
**Parameters**
|
|
508
|
+
<x-field-group>
|
|
509
|
+
<x-field data-name="initialState" data-type="any" data-required="false">
|
|
510
|
+
<x-field-desc markdown>The initial state value.</x-field-desc>
|
|
511
|
+
</x-field>
|
|
512
|
+
</x-field-group>
|
|
513
|
+
|
|
514
|
+
`useReducer` returns an array with two items:
|
|
515
|
+
<x-field-group>
|
|
516
|
+
<x-field data-name="dispatch" data-type="function" data-desc="A function that you can call with an action to update the state."></x-field>
|
|
517
|
+
</x-field-group>
|
|
518
|
+
```
|
|
519
|
+
|
|
485
520
|
</custom_components_usage>
|
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
The following formats are considered Code Blocks:
|
|
3
3
|
|
|
4
4
|
- Wrapped with ```
|
|
5
|
-
- Supports configurations: language, title, icon
|
|
5
|
+
- Supports configurations: language, optional title, optional icon (icon uses key=value)
|
|
6
|
+
- title is free text placed after the language (not as title=xxx), may contain spaces, and **must NEVER be wrapped in quotes**
|
|
6
7
|
- content can be code, command line examples, text or any other content
|
|
7
8
|
|
|
8
9
|
<code_block_sample>
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
- `language`: javascript
|
|
12
|
+
- `title`: Modern: Using createRoot()
|
|
13
|
+
- `icon`: logos:javascript
|
|
14
|
+
|
|
15
|
+
```javascript Modern: Using createRoot() icon=logos:javascript
|
|
16
|
+
import { createRoot } from 'react-dom/client'
|
|
17
|
+
|
|
18
|
+
const container = document.getElementById('root')
|
|
19
|
+
const root = createRoot(container)
|
|
20
|
+
|
|
21
|
+
root.unmount()
|
|
12
22
|
```
|
|
13
23
|
|
|
14
24
|
</code_block_sample>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { pathExists } from "./file-utils.mjs";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Get action-specific text based on isTranslate flag
|
|
@@ -276,3 +277,50 @@ export function addFeedbackToItems(items, feedback) {
|
|
|
276
277
|
feedback: feedback.trim(),
|
|
277
278
|
}));
|
|
278
279
|
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Load document execution structure from structure-plan.json
|
|
283
|
+
* @param {string} outputDir - Output directory containing structure-plan.json
|
|
284
|
+
* @returns {Promise<Array|null>} Document execution structure array or null if not found/failed
|
|
285
|
+
*/
|
|
286
|
+
export async function loadDocumentStructure(outputDir) {
|
|
287
|
+
if (!outputDir) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const structurePlanPath = join(outputDir, "structure-plan.json");
|
|
293
|
+
const structureExists = await pathExists(structurePlanPath);
|
|
294
|
+
|
|
295
|
+
if (!structureExists) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const structureContent = await readFile(structurePlanPath, "utf8");
|
|
300
|
+
if (!structureContent?.trim()) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
// Validate that the content looks like JSON before parsing
|
|
306
|
+
const trimmedContent = structureContent.trim();
|
|
307
|
+
if (!trimmedContent.startsWith("[") && !trimmedContent.startsWith("{")) {
|
|
308
|
+
console.warn("structure-plan.json contains non-JSON content, skipping parse");
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const parsed = JSON.parse(structureContent);
|
|
313
|
+
// Return array if it's an array, otherwise return null
|
|
314
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
315
|
+
} catch (parseError) {
|
|
316
|
+
console.error(`Failed to parse structure-plan.json: ${parseError.message}`);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
} catch (readError) {
|
|
320
|
+
// Only warn if it's not a "file not found" error
|
|
321
|
+
if (readError.code !== "ENOENT") {
|
|
322
|
+
console.warn(`Error reading structure-plan.json: ${readError.message}`);
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
package/utils/history-utils.mjs
CHANGED
|
@@ -99,7 +99,7 @@ function recordUpdateGit({ feedback }) {
|
|
|
99
99
|
/**
|
|
100
100
|
* Records an update in the YAML file.
|
|
101
101
|
*/
|
|
102
|
-
function recordUpdateYaml({ operation, feedback,
|
|
102
|
+
function recordUpdateYaml({ operation, feedback, docPaths = null }) {
|
|
103
103
|
try {
|
|
104
104
|
const docSmithDir = join(process.cwd(), DOC_SMITH_DIR);
|
|
105
105
|
if (!existsSync(docSmithDir)) {
|
|
@@ -125,9 +125,9 @@ function recordUpdateYaml({ operation, feedback, documentPath = null }) {
|
|
|
125
125
|
feedback,
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
// Add document
|
|
129
|
-
if (
|
|
130
|
-
entry.
|
|
128
|
+
// Add document paths if provided
|
|
129
|
+
if (Array.isArray(docPaths) && docPaths.length > 0) {
|
|
130
|
+
entry.docPaths = docPaths;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// Add to beginning (newest first)
|
|
@@ -153,14 +153,14 @@ function recordUpdateYaml({ operation, feedback, documentPath = null }) {
|
|
|
153
153
|
* @param {Object} params
|
|
154
154
|
* @param {string} params.operation - The type of operation (e.g., 'document_update', 'structure_update', 'translation_update').
|
|
155
155
|
* @param {string} params.feedback - The user's feedback text.
|
|
156
|
-
* @param {string} params.
|
|
156
|
+
* @param {string[]} [params.docPaths] - Document path list for updates.
|
|
157
157
|
*/
|
|
158
|
-
export function recordUpdate({ operation, feedback,
|
|
158
|
+
export function recordUpdate({ operation, feedback, docPaths = null }) {
|
|
159
159
|
// Skip if no feedback
|
|
160
160
|
if (!feedback?.trim()) return;
|
|
161
161
|
|
|
162
162
|
// Always record in YAML
|
|
163
|
-
recordUpdateYaml({ operation, feedback,
|
|
163
|
+
recordUpdateYaml({ operation, feedback, docPaths });
|
|
164
164
|
|
|
165
165
|
// Also record in git if git is available and not in a git repository
|
|
166
166
|
if (isGitAvailable() && !isInGitRepository(process.cwd())) {
|
|
@@ -183,7 +183,19 @@ export function getHistory() {
|
|
|
183
183
|
|
|
184
184
|
try {
|
|
185
185
|
const content = readFileSync(historyPath, "utf8");
|
|
186
|
-
|
|
186
|
+
const parsed = parse(content) || { entries: [] };
|
|
187
|
+
const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
188
|
+
|
|
189
|
+
const normalized = entries.map((entry) => {
|
|
190
|
+
if (!entry) return entry;
|
|
191
|
+
// Normalize legacy entries: documentPath -> docPaths: [documentPath]
|
|
192
|
+
if (!entry.docPaths && entry.documentPath) {
|
|
193
|
+
return { ...entry, docPaths: [entry.documentPath] };
|
|
194
|
+
}
|
|
195
|
+
return entry;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return { ...parsed, entries: normalized };
|
|
187
199
|
} catch (error) {
|
|
188
200
|
console.warn("Could not read the history:", error.message);
|
|
189
201
|
return { entries: [] };
|