@aigne/doc-smith 0.8.9 → 0.8.10-beta.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.
Files changed (48) hide show
  1. package/.aigne/doc-smith/config.yaml +3 -1
  2. package/.aigne/doc-smith/preferences.yml +4 -4
  3. package/.aigne/doc-smith/upload-cache.yaml +180 -0
  4. package/.github/workflows/release.yml +6 -2
  5. package/.release-please-manifest.json +3 -0
  6. package/CHANGELOG.md +20 -0
  7. package/LICENSE +93 -0
  8. package/README.md +6 -2
  9. package/RELEASE.md +10 -0
  10. package/agents/generate/check-need-generate-structure.mjs +40 -18
  11. package/agents/generate/index.yaml +1 -0
  12. package/agents/generate/user-review-document-structure.mjs +168 -0
  13. package/agents/update/generate-and-translate-document.yaml +3 -3
  14. package/agents/utils/check-feedback-refiner.mjs +2 -0
  15. package/agents/utils/save-docs.mjs +16 -22
  16. package/aigne.yaml +1 -0
  17. package/docs/advanced-how-it-works.md +26 -24
  18. package/docs/advanced-how-it-works.zh.md +32 -30
  19. package/docs/advanced-quality-assurance.md +6 -9
  20. package/docs/advanced-quality-assurance.zh.md +19 -22
  21. package/docs/cli-reference.md +35 -76
  22. package/docs/cli-reference.zh.md +48 -89
  23. package/docs/configuration-interactive-setup.md +18 -18
  24. package/docs/configuration-interactive-setup.zh.md +33 -33
  25. package/docs/configuration-language-support.md +12 -12
  26. package/docs/configuration-language-support.zh.md +20 -20
  27. package/docs/configuration-llm-setup.md +14 -13
  28. package/docs/configuration-llm-setup.zh.md +16 -15
  29. package/docs/configuration-preferences.md +22 -27
  30. package/docs/configuration-preferences.zh.md +35 -40
  31. package/docs/configuration.md +31 -45
  32. package/docs/configuration.zh.md +48 -62
  33. package/docs/features-generate-documentation.md +6 -7
  34. package/docs/features-generate-documentation.zh.md +21 -22
  35. package/docs/features-publish-your-docs.md +38 -30
  36. package/docs/features-publish-your-docs.zh.md +45 -37
  37. package/docs/features-translate-documentation.md +14 -74
  38. package/docs/features-translate-documentation.zh.md +19 -79
  39. package/docs/features-update-and-refine.md +19 -18
  40. package/docs/features-update-and-refine.zh.md +33 -32
  41. package/docs-mcp/get-docs-structure.mjs +1 -1
  42. package/package.json +3 -3
  43. package/prompts/detail/document-rules.md +1 -1
  44. package/prompts/structure/check-document-structure.md +11 -15
  45. package/release-please-config.json +13 -0
  46. package/tests/agents/generate/check-need-generate-structure.test.mjs +21 -24
  47. package/tests/agents/generate/user-review-document-structure.test.mjs +294 -0
  48. package/utils/auth-utils.mjs +2 -2
@@ -0,0 +1,294 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
2
+ import userReviewDocumentStructure from "../../../agents/generate/user-review-document-structure.mjs";
3
+
4
+ import * as preferencesUtils from "../../../utils/preferences-utils.mjs";
5
+
6
+ describe("user-review-document-structure", () => {
7
+ let mockOptions;
8
+ let documentStructure;
9
+
10
+ // Spies for internal utils
11
+ let getActiveRulesForScopeSpy;
12
+ let consoleSpy;
13
+
14
+ beforeEach(() => {
15
+ // Reset all mocks
16
+ mock.restore();
17
+
18
+ documentStructure = [
19
+ {
20
+ path: "/getting-started",
21
+ title: "Getting Started",
22
+ description: "Introduction and setup guide",
23
+ parentId: null,
24
+ },
25
+ {
26
+ path: "/getting-started/installation",
27
+ title: "Installation",
28
+ description: "How to install the project",
29
+ parentId: "/getting-started",
30
+ },
31
+ {
32
+ path: "/api",
33
+ title: "API Reference",
34
+ description: "Complete API documentation",
35
+ parentId: null,
36
+ },
37
+ ];
38
+
39
+ mockOptions = {
40
+ prompts: {
41
+ select: mock(async () => "no"),
42
+ input: mock(async () => ""),
43
+ },
44
+ context: {
45
+ agents: {
46
+ refineDocumentStructure: {},
47
+ checkFeedbackRefiner: {},
48
+ },
49
+ invoke: mock(async () => ({
50
+ documentStructure: documentStructure,
51
+ })),
52
+ },
53
+ };
54
+
55
+ // Set up spies for internal utils
56
+ getActiveRulesForScopeSpy = spyOn(preferencesUtils, "getActiveRulesForScope").mockReturnValue(
57
+ [],
58
+ );
59
+
60
+ consoleSpy = spyOn(console, "log").mockImplementation(() => {});
61
+
62
+ // Clear prompts mock call history
63
+ mockOptions.prompts.select.mockClear();
64
+ mockOptions.prompts.input.mockClear();
65
+ mockOptions.context.invoke.mockClear();
66
+ });
67
+
68
+ afterEach(() => {
69
+ // Restore all spies
70
+ getActiveRulesForScopeSpy?.mockRestore();
71
+ consoleSpy?.mockRestore();
72
+ });
73
+
74
+ test("should return original structure when no document structure provided", async () => {
75
+ const result = await userReviewDocumentStructure({}, mockOptions);
76
+
77
+ expect(result).toBeDefined();
78
+ expect(result.documentStructure).toBeUndefined();
79
+ expect(mockOptions.prompts.select).not.toHaveBeenCalled();
80
+ expect(consoleSpy).toHaveBeenCalledWith("No document structure was generated to review.");
81
+ });
82
+
83
+ test("should return original structure when empty array provided", async () => {
84
+ const result = await userReviewDocumentStructure({ documentStructure: [] }, mockOptions);
85
+
86
+ expect(result).toBeDefined();
87
+ expect(result.documentStructure).toEqual([]);
88
+ expect(mockOptions.prompts.select).not.toHaveBeenCalled();
89
+ expect(consoleSpy).toHaveBeenCalledWith("No document structure was generated to review.");
90
+ });
91
+
92
+ test("should return original structure when user chooses not to review", async () => {
93
+ mockOptions.prompts.select.mockImplementation(async () => "no");
94
+
95
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
96
+
97
+ expect(result).toBeDefined();
98
+ expect(result.documentStructure).toEqual(documentStructure);
99
+ expect(mockOptions.prompts.select).toHaveBeenCalled();
100
+ expect(mockOptions.prompts.input).not.toHaveBeenCalled();
101
+ });
102
+
103
+ test("should enter review loop when user chooses to review", async () => {
104
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
105
+ mockOptions.prompts.input.mockImplementation(async () => ""); // Empty feedback to exit loop
106
+
107
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
108
+
109
+ expect(result).toBeDefined();
110
+ expect(result.documentStructure).toEqual(documentStructure);
111
+ expect(mockOptions.prompts.select).toHaveBeenCalled();
112
+ expect(mockOptions.prompts.input).toHaveBeenCalled();
113
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Current Document Structure"));
114
+ });
115
+
116
+ test("should process user feedback and call refineDocumentStructure agent", async () => {
117
+ const feedback = "Please add more details to the API section";
118
+ const refinedStructure = [
119
+ ...documentStructure,
120
+ {
121
+ path: "/api/authentication",
122
+ title: "Authentication",
123
+ description: "How to authenticate API requests",
124
+ parentId: "/api",
125
+ },
126
+ ];
127
+
128
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
129
+ mockOptions.prompts.input
130
+ .mockImplementationOnce(async () => feedback)
131
+ .mockImplementationOnce(async () => ""); // Exit loop on second call
132
+
133
+ mockOptions.context.invoke.mockImplementation(async () => ({
134
+ documentStructure: refinedStructure,
135
+ }));
136
+
137
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
138
+
139
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
140
+ mockOptions.context.agents.refineDocumentStructure,
141
+ expect.objectContaining({
142
+ feedback: feedback,
143
+ originalDocumentStructure: documentStructure,
144
+ userPreferences: "",
145
+ }),
146
+ );
147
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
148
+ mockOptions.context.agents.checkFeedbackRefiner,
149
+ expect.objectContaining({
150
+ documentStructureFeedback: feedback,
151
+ stage: "structure",
152
+ }),
153
+ );
154
+ expect(result.documentStructure).toEqual(refinedStructure);
155
+ });
156
+
157
+ test("should include user preferences in refinement call", async () => {
158
+ const mockRules = [{ rule: "Keep sections concise" }, { rule: "Use clear headings" }];
159
+ const expectedPreferences = "Keep sections concise\n\nUse clear headings";
160
+
161
+ getActiveRulesForScopeSpy
162
+ .mockImplementationOnce(() => mockRules) // structure rules
163
+ .mockImplementationOnce(() => []); // global rules
164
+
165
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
166
+ mockOptions.prompts.input
167
+ .mockImplementationOnce(async () => "Add more examples")
168
+ .mockImplementationOnce(async () => "");
169
+
170
+ await userReviewDocumentStructure({ documentStructure }, mockOptions);
171
+
172
+ expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("structure", []);
173
+ expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
174
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
175
+ mockOptions.context.agents.refineDocumentStructure,
176
+ expect.objectContaining({
177
+ userPreferences: expectedPreferences,
178
+ }),
179
+ );
180
+ });
181
+
182
+ test("should handle missing refineDocumentStructure agent", async () => {
183
+ mockOptions.context.agents = {}; // No refineDocumentStructure agent
184
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
185
+ mockOptions.prompts.input.mockImplementation(async () => "Some feedback");
186
+
187
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
188
+
189
+ expect(result.documentStructure).toEqual(documentStructure);
190
+ expect(consoleSpy).toHaveBeenCalledWith(
191
+ "Unable to process your feedback - the structure refinement feature is unavailable.",
192
+ );
193
+ });
194
+
195
+ test("should handle errors from refineDocumentStructure agent", async () => {
196
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
197
+ mockOptions.prompts.input
198
+ .mockImplementationOnce(async () => "Some feedback")
199
+ .mockImplementationOnce(async () => "");
200
+
201
+ mockOptions.context.invoke.mockImplementation(async () => {
202
+ throw new Error("Agent failed");
203
+ });
204
+
205
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
206
+
207
+ expect(result.documentStructure).toEqual(documentStructure);
208
+ expect(consoleSpy).toHaveBeenCalledWith(
209
+ "\nPlease try rephrasing your feedback or continue with the current structure.",
210
+ );
211
+ });
212
+
213
+ test("should handle multiple feedback rounds", async () => {
214
+ const firstFeedback = "Add authentication section";
215
+ const secondFeedback = "Reorganize API structure";
216
+
217
+ const firstRefinedStructure = [
218
+ ...documentStructure,
219
+ {
220
+ path: "/api/auth",
221
+ title: "Authentication",
222
+ description: "Auth guide",
223
+ parentId: "/api",
224
+ },
225
+ ];
226
+
227
+ const finalRefinedStructure = [
228
+ ...firstRefinedStructure,
229
+ {
230
+ path: "/api/endpoints",
231
+ title: "Endpoints",
232
+ description: "API endpoints",
233
+ parentId: "/api",
234
+ },
235
+ ];
236
+
237
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
238
+ mockOptions.prompts.input
239
+ .mockImplementationOnce(async () => firstFeedback)
240
+ .mockImplementationOnce(async () => secondFeedback)
241
+ .mockImplementationOnce(async () => ""); // Exit loop
242
+
243
+ mockOptions.context.invoke
244
+ .mockImplementationOnce(async () => ({ documentStructure: firstRefinedStructure })) // refineDocumentStructure 1st call
245
+ .mockImplementationOnce(async () => ({})) // checkFeedbackRefiner 1st call
246
+ .mockImplementationOnce(async () => ({ documentStructure: finalRefinedStructure })) // refineDocumentStructure 2nd call
247
+ .mockImplementationOnce(async () => ({})); // checkFeedbackRefiner 2nd call
248
+
249
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
250
+
251
+ expect(mockOptions.context.invoke).toHaveBeenCalledTimes(4); // 2 refine + 2 feedback refiner calls
252
+ expect(result.documentStructure).toEqual(finalRefinedStructure);
253
+ });
254
+
255
+ test("should handle missing checkFeedbackRefiner agent gracefully", async () => {
256
+ const feedback = "Some feedback";
257
+ mockOptions.context.agents = { refineDocumentStructure: {} }; // No checkFeedbackRefiner agent
258
+
259
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
260
+ mockOptions.prompts.input
261
+ .mockImplementationOnce(async () => feedback)
262
+ .mockImplementationOnce(async () => "");
263
+
264
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
265
+
266
+ expect(result.documentStructure).toEqual(documentStructure);
267
+ expect(mockOptions.context.invoke).toHaveBeenCalledTimes(1); // Only refineDocumentStructure called
268
+ });
269
+
270
+ test("should handle checkFeedbackRefiner errors gracefully", async () => {
271
+ const feedback = "Some feedback";
272
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
273
+
274
+ mockOptions.prompts.select.mockImplementation(async () => "yes");
275
+ mockOptions.prompts.input
276
+ .mockImplementationOnce(async () => feedback)
277
+ .mockImplementationOnce(async () => "");
278
+
279
+ mockOptions.context.invoke
280
+ .mockImplementationOnce(async () => ({ documentStructure })) // refineDocumentStructure
281
+ .mockImplementationOnce(async () => {
282
+ throw new Error("Refiner failed");
283
+ }); // checkFeedbackRefiner
284
+
285
+ const result = await userReviewDocumentStructure({ documentStructure }, mockOptions);
286
+
287
+ expect(result.documentStructure).toEqual(documentStructure);
288
+ expect(warnSpy).toHaveBeenCalledWith(
289
+ "Your feedback was applied but not saved as a preference.",
290
+ );
291
+
292
+ warnSpy.mockRestore();
293
+ });
294
+ });
@@ -76,7 +76,7 @@ export async function getAccessToken(appUrl, ltToken = "") {
76
76
  `• Network connection issues\n` +
77
77
  `• Server temporarily unavailable\n` +
78
78
  `• Incorrect URL address\n\n` +
79
- `${chalk.green("Suggestion:")} Please check your network connection and URL address, then try again`,
79
+ `${chalk.green("Suggestion:")} Please check your network connection and URL, then try again`,
80
80
  );
81
81
  }
82
82
  }
@@ -91,7 +91,7 @@ export async function getAccessToken(appUrl, ltToken = "") {
91
91
  source: `AIGNE DocSmith connect to website`,
92
92
  closeOnSuccess: true,
93
93
  appName: "AIGNE DocSmith",
94
- appLogo: "https://docsmith.aigne.io/image-bin/uploads/a7910a71364ee15a27e86f869ad59009.svg",
94
+ appLogo: "https://docsmith.aigne.io/image-bin/uploads/9645caf64b4232699982c4d940b03b90.svg",
95
95
  openPage: (pageUrl) => {
96
96
  const url = new URL(pageUrl);
97
97
  if (ltToken) {