@aigne/doc-smith 0.8.12-beta.3 → 0.8.12-beta.5

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 (169) hide show
  1. package/.aigne/doc-smith/config.yaml +9 -6
  2. package/.aigne/doc-smith/output/structure-plan.json +123 -109
  3. package/.aigne/doc-smith/upload-cache.yaml +48 -0
  4. package/.github/workflows/publish-docs.yml +4 -7
  5. package/.release-please-manifest.json +1 -1
  6. package/CHANGELOG.md +20 -0
  7. package/agents/clear/choose-contents.mjs +22 -5
  8. package/agents/clear/clear-auth-tokens.mjs +2 -4
  9. package/agents/clear/clear-deployment-config.mjs +49 -0
  10. package/agents/clear/clear-document-config.mjs +2 -5
  11. package/agents/clear/clear-document-structure.mjs +2 -6
  12. package/agents/clear/clear-generated-docs.mjs +0 -1
  13. package/agents/generate/check-need-generate-structure.mjs +15 -60
  14. package/agents/generate/document-structure-tools/generate-sub-structure.mjs +131 -0
  15. package/agents/generate/generate-structure-without-tools.yaml +65 -0
  16. package/agents/generate/generate-structure.yaml +7 -1
  17. package/agents/generate/index.yaml +0 -3
  18. package/agents/generate/update-document-structure.yaml +3 -0
  19. package/agents/generate/user-review-document-structure.mjs +7 -5
  20. package/agents/init/index.mjs +15 -15
  21. package/agents/publish/publish-docs.mjs +130 -111
  22. package/agents/translate/index.yaml +1 -1
  23. package/agents/update/batch-generate-document.yaml +1 -1
  24. package/agents/update/batch-update-document.yaml +1 -1
  25. package/agents/update/check-document.mjs +4 -19
  26. package/agents/update/user-review-document.mjs +4 -3
  27. package/agents/utils/load-sources.mjs +54 -151
  28. package/agents/utils/transform-detail-datasources.mjs +14 -18
  29. package/aigne.yaml +2 -0
  30. package/biome.json +1 -1
  31. package/docs/_sidebar.md +13 -15
  32. package/docs/configuration-initial-setup.ja.md +179 -0
  33. package/docs/configuration-initial-setup.md +179 -0
  34. package/docs/configuration-initial-setup.zh-TW.md +179 -0
  35. package/docs/configuration-initial-setup.zh.md +179 -0
  36. package/docs/configuration-managing-preferences.ja.md +100 -0
  37. package/docs/configuration-managing-preferences.md +100 -0
  38. package/docs/configuration-managing-preferences.zh-TW.md +100 -0
  39. package/docs/configuration-managing-preferences.zh.md +100 -0
  40. package/docs/configuration.ja.md +68 -184
  41. package/docs/configuration.md +62 -178
  42. package/docs/configuration.zh-TW.md +70 -186
  43. package/docs/configuration.zh.md +67 -183
  44. package/docs/getting-started.ja.md +46 -78
  45. package/docs/getting-started.md +46 -78
  46. package/docs/getting-started.zh-TW.md +47 -79
  47. package/docs/getting-started.zh.md +47 -79
  48. package/docs/guides-cleaning-up.ja.md +50 -0
  49. package/docs/guides-cleaning-up.md +50 -0
  50. package/docs/guides-cleaning-up.zh-TW.md +50 -0
  51. package/docs/guides-cleaning-up.zh.md +50 -0
  52. package/docs/guides-evaluating-documents.ja.md +66 -0
  53. package/docs/guides-evaluating-documents.md +66 -0
  54. package/docs/guides-evaluating-documents.zh-TW.md +66 -0
  55. package/docs/guides-evaluating-documents.zh.md +66 -0
  56. package/docs/guides-generating-documentation.ja.md +149 -0
  57. package/docs/guides-generating-documentation.md +149 -0
  58. package/docs/guides-generating-documentation.zh-TW.md +149 -0
  59. package/docs/guides-generating-documentation.zh.md +149 -0
  60. package/docs/guides-interactive-chat.ja.md +85 -0
  61. package/docs/guides-interactive-chat.md +85 -0
  62. package/docs/guides-interactive-chat.zh-TW.md +85 -0
  63. package/docs/guides-interactive-chat.zh.md +85 -0
  64. package/docs/guides-managing-history.ja.md +51 -0
  65. package/docs/guides-managing-history.md +51 -0
  66. package/docs/guides-managing-history.zh-TW.md +51 -0
  67. package/docs/guides-managing-history.zh.md +51 -0
  68. package/docs/guides-publishing-your-docs.ja.md +78 -0
  69. package/docs/guides-publishing-your-docs.md +78 -0
  70. package/docs/guides-publishing-your-docs.zh-TW.md +78 -0
  71. package/docs/guides-publishing-your-docs.zh.md +78 -0
  72. package/docs/guides-translating-documentation.ja.md +95 -0
  73. package/docs/guides-translating-documentation.md +95 -0
  74. package/docs/guides-translating-documentation.zh-TW.md +95 -0
  75. package/docs/guides-translating-documentation.zh.md +95 -0
  76. package/docs/guides-updating-documentation.ja.md +77 -0
  77. package/docs/guides-updating-documentation.md +77 -0
  78. package/docs/guides-updating-documentation.zh-TW.md +77 -0
  79. package/docs/guides-updating-documentation.zh.md +77 -0
  80. package/docs/guides.ja.md +32 -0
  81. package/docs/guides.md +32 -0
  82. package/docs/guides.zh-TW.md +32 -0
  83. package/docs/guides.zh.md +32 -0
  84. package/docs/overview.ja.md +39 -60
  85. package/docs/overview.md +39 -60
  86. package/docs/overview.zh-TW.md +39 -60
  87. package/docs/overview.zh.md +39 -60
  88. package/docs/release-notes.ja.md +255 -0
  89. package/docs/release-notes.md +255 -0
  90. package/docs/release-notes.zh-TW.md +255 -0
  91. package/docs/release-notes.zh.md +255 -0
  92. package/package.json +4 -2
  93. package/prompts/common/document/content-rules-core.md +1 -0
  94. package/prompts/common/document-structure/document-structure-rules.md +8 -9
  95. package/prompts/common/document-structure/output-constraints.md +1 -1
  96. package/prompts/structure/document-rules.md +8 -2
  97. package/prompts/structure/generate/system-prompt.md +27 -2
  98. package/prompts/structure/generate/user-prompt.md +18 -0
  99. package/prompts/structure/update/system-prompt.md +12 -0
  100. package/prompts/structure/update/user-prompt.md +3 -0
  101. package/tests/agents/clear/choose-contents.test.mjs +8 -4
  102. package/tests/agents/generate/check-need-generate-structure.test.mjs +53 -63
  103. package/tests/agents/generate/document-structure-tools/generate-sub-structure.test.mjs +277 -0
  104. package/tests/agents/init/init.test.mjs +18 -18
  105. package/tests/agents/publish/publish-docs.test.mjs +79 -0
  106. package/tests/agents/update/check-document.test.mjs +7 -67
  107. package/tests/agents/utils/load-sources.test.mjs +90 -90
  108. package/tests/agents/utils/transform-detail-datasources.test.mjs +153 -196
  109. package/tests/utils/file-utils.test.mjs +309 -1
  110. package/utils/auth-utils.mjs +9 -3
  111. package/utils/constants/index.mjs +3 -1
  112. package/utils/file-utils.mjs +315 -0
  113. package/utils/utils.mjs +89 -50
  114. package/docs/advanced-how-it-works.ja.md +0 -101
  115. package/docs/advanced-how-it-works.md +0 -101
  116. package/docs/advanced-how-it-works.zh-TW.md +0 -101
  117. package/docs/advanced-how-it-works.zh.md +0 -101
  118. package/docs/advanced-quality-assurance.ja.md +0 -92
  119. package/docs/advanced-quality-assurance.md +0 -92
  120. package/docs/advanced-quality-assurance.zh-TW.md +0 -92
  121. package/docs/advanced-quality-assurance.zh.md +0 -92
  122. package/docs/advanced.ja.md +0 -20
  123. package/docs/advanced.md +0 -20
  124. package/docs/advanced.zh-TW.md +0 -20
  125. package/docs/advanced.zh.md +0 -20
  126. package/docs/changelog.ja.md +0 -486
  127. package/docs/changelog.md +0 -486
  128. package/docs/changelog.zh-TW.md +0 -486
  129. package/docs/changelog.zh.md +0 -486
  130. package/docs/cli-reference.ja.md +0 -311
  131. package/docs/cli-reference.md +0 -311
  132. package/docs/cli-reference.zh-TW.md +0 -311
  133. package/docs/cli-reference.zh.md +0 -311
  134. package/docs/configuration-interactive-setup.ja.md +0 -138
  135. package/docs/configuration-interactive-setup.md +0 -138
  136. package/docs/configuration-interactive-setup.zh-TW.md +0 -138
  137. package/docs/configuration-interactive-setup.zh.md +0 -138
  138. package/docs/configuration-language-support.ja.md +0 -64
  139. package/docs/configuration-language-support.md +0 -64
  140. package/docs/configuration-language-support.zh-TW.md +0 -64
  141. package/docs/configuration-language-support.zh.md +0 -64
  142. package/docs/configuration-llm-setup.ja.md +0 -56
  143. package/docs/configuration-llm-setup.md +0 -56
  144. package/docs/configuration-llm-setup.zh-TW.md +0 -56
  145. package/docs/configuration-llm-setup.zh.md +0 -56
  146. package/docs/configuration-preferences.ja.md +0 -144
  147. package/docs/configuration-preferences.md +0 -144
  148. package/docs/configuration-preferences.zh-TW.md +0 -144
  149. package/docs/configuration-preferences.zh.md +0 -144
  150. package/docs/features-generate-documentation.ja.md +0 -95
  151. package/docs/features-generate-documentation.md +0 -95
  152. package/docs/features-generate-documentation.zh-TW.md +0 -95
  153. package/docs/features-generate-documentation.zh.md +0 -95
  154. package/docs/features-publish-your-docs.ja.md +0 -130
  155. package/docs/features-publish-your-docs.md +0 -130
  156. package/docs/features-publish-your-docs.zh-TW.md +0 -130
  157. package/docs/features-publish-your-docs.zh.md +0 -130
  158. package/docs/features-translate-documentation.ja.md +0 -90
  159. package/docs/features-translate-documentation.md +0 -90
  160. package/docs/features-translate-documentation.zh-TW.md +0 -90
  161. package/docs/features-translate-documentation.zh.md +0 -90
  162. package/docs/features-update-and-refine.ja.md +0 -142
  163. package/docs/features-update-and-refine.md +0 -142
  164. package/docs/features-update-and-refine.zh-TW.md +0 -143
  165. package/docs/features-update-and-refine.zh.md +0 -142
  166. package/docs/features.ja.md +0 -62
  167. package/docs/features.md +0 -62
  168. package/docs/features.zh-TW.md +0 -62
  169. package/docs/features.zh.md +0 -62
@@ -35,10 +35,13 @@ describe("check-need-generate-structure", () => {
35
35
  mockOptions = {
36
36
  prompts: {
37
37
  input: mock(async () => ""),
38
- select: mock(async () => ""),
38
+ select: mock(async () => "generate"),
39
39
  },
40
40
  context: {
41
- agents: { refineDocumentStructure: {} },
41
+ agents: {
42
+ generateStructure: {},
43
+ generateStructureWithoutTools: {},
44
+ },
42
45
  invoke: mock(async () => ({
43
46
  documentStructure: originalDocumentStructure,
44
47
  projectName: "Test Project",
@@ -99,69 +102,22 @@ describe("check-need-generate-structure", () => {
99
102
  expect(mockOptions.context.invoke).not.toHaveBeenCalled();
100
103
  });
101
104
 
102
- test("should handle pre-existing feedback without prompting", async () => {
103
- const providedFeedback = "Already provided feedback";
104
-
105
- await checkNeedGenerateStructure(
106
- { originalDocumentStructure, feedback: providedFeedback, docsDir: "./docs" },
107
- mockOptions,
108
- );
109
-
110
- expect(mockOptions.prompts.input).not.toHaveBeenCalled();
111
- expect(mockOptions.context.invoke).toHaveBeenCalled();
112
- });
113
-
114
- test("should handle empty user feedback input", async () => {
115
- mockOptions.prompts.input.mockImplementation(async () => " ");
116
- // Default mock behavior: no sidebar file exists
117
-
105
+ test("should return original structure when it exists and no force regenerate", async () => {
118
106
  const result = await checkNeedGenerateStructure(
119
107
  { originalDocumentStructure, docsDir: "./docs" },
120
108
  mockOptions,
121
109
  );
122
110
 
123
- expect(result.documentStructure).toEqual(originalDocumentStructure);
111
+ expect(mockOptions.prompts.input).not.toHaveBeenCalled();
124
112
  expect(mockOptions.context.invoke).not.toHaveBeenCalled();
125
- });
126
-
127
- test("should regenerate when _sidebar.md exists", async () => {
128
- accessSpy.mockImplementation(() => Promise.resolve());
129
-
130
- await checkNeedGenerateStructure({ originalDocumentStructure, docsDir: "./docs" }, mockOptions);
131
-
132
- expect(mockOptions.context.invoke).toHaveBeenCalled();
133
- });
134
-
135
- test("should not regenerate when _sidebar.md does not exist", async () => {
136
- // Default mock behavior: file access fails
137
-
138
- const result = await checkNeedGenerateStructure(
139
- { originalDocumentStructure, docsDir: "./docs" },
140
- mockOptions,
141
- );
142
-
143
113
  expect(result.documentStructure).toEqual(originalDocumentStructure);
144
- expect(mockOptions.context.invoke).not.toHaveBeenCalled();
145
- });
146
-
147
- test("should check git changes when lastGitHead is provided", async () => {
148
- getCurrentGitHeadSpy.mockImplementation(() => "def456");
149
- hasFileChangesBetweenCommitsSpy.mockImplementation(() => true);
150
-
151
- await checkNeedGenerateStructure(
152
- { originalDocumentStructure, lastGitHead: "abc123", docsDir: "./docs" },
153
- mockOptions,
154
- );
155
-
156
- expect(hasFileChangesBetweenCommitsSpy).toHaveBeenCalledWith("abc123", "def456");
157
- expect(mockOptions.context.invoke).toHaveBeenCalled();
158
114
  });
159
115
 
160
- test("should not regenerate when git head is same", async () => {
161
- getCurrentGitHeadSpy.mockImplementation(() => "abc123");
116
+ test("should handle empty user feedback input", async () => {
117
+ mockOptions.prompts.input.mockImplementation(async () => " ");
162
118
 
163
119
  const result = await checkNeedGenerateStructure(
164
- { originalDocumentStructure, lastGitHead: "abc123", docsDir: "./docs" },
120
+ { originalDocumentStructure, docsDir: "./docs" },
165
121
  mockOptions,
166
122
  );
167
123
 
@@ -178,12 +134,12 @@ describe("check-need-generate-structure", () => {
178
134
  expect(mockOptions.context.invoke).toHaveBeenCalled();
179
135
  });
180
136
 
181
- test("should include user preferences", async () => {
137
+ test("should include user preferences when generating", async () => {
182
138
  const mockRules = [{ rule: "Structure rule 1" }];
183
139
  getActiveRulesForScopeSpy.mockImplementation(() => mockRules);
184
140
 
185
141
  await checkNeedGenerateStructure(
186
- { originalDocumentStructure, feedback: "test", docsDir: "./docs" },
142
+ { originalDocumentStructure, forceRegenerate: true, docsDir: "./docs" },
187
143
  mockOptions,
188
144
  );
189
145
 
@@ -199,7 +155,7 @@ describe("check-need-generate-structure", () => {
199
155
  }));
200
156
 
201
157
  const result = await checkNeedGenerateStructure(
202
- { originalDocumentStructure, feedback: "test", docsDir: "./docs" },
158
+ { originalDocumentStructure, forceRegenerate: true, docsDir: "./docs" },
203
159
  mockOptions,
204
160
  );
205
161
 
@@ -220,7 +176,7 @@ describe("check-need-generate-structure", () => {
220
176
  }));
221
177
 
222
178
  const result = await checkNeedGenerateStructure(
223
- { originalDocumentStructure, feedback: "test", docsDir: "./docs" },
179
+ { originalDocumentStructure, forceRegenerate: true, docsDir: "./docs" },
224
180
  mockOptions,
225
181
  );
226
182
 
@@ -243,16 +199,21 @@ describe("check-need-generate-structure", () => {
243
199
  expect(result.originalDocumentStructure).toEqual(newDocumentStructure);
244
200
  });
245
201
 
246
- test("should clear feedback in result", async () => {
202
+ test("should clear feedback in result when regenerating", async () => {
247
203
  const result = await checkNeedGenerateStructure(
248
- { originalDocumentStructure, feedback: "some feedback", docsDir: "./docs" },
204
+ {
205
+ originalDocumentStructure,
206
+ forceRegenerate: true,
207
+ feedback: "some feedback",
208
+ docsDir: "./docs",
209
+ },
249
210
  mockOptions,
250
211
  );
251
212
 
252
213
  expect(result.feedback).toBe("");
253
214
  });
254
215
 
255
- test("should pass through additional parameters", async () => {
216
+ test("should pass through additional parameters when regenerating", async () => {
256
217
  const additionalParams = {
257
218
  customParam1: "value1",
258
219
  customParam2: "value2",
@@ -261,7 +222,7 @@ describe("check-need-generate-structure", () => {
261
222
  await checkNeedGenerateStructure(
262
223
  {
263
224
  originalDocumentStructure,
264
- feedback: "test",
225
+ forceRegenerate: true,
265
226
  docsDir: "./docs",
266
227
  ...additionalParams,
267
228
  },
@@ -269,7 +230,7 @@ describe("check-need-generate-structure", () => {
269
230
  );
270
231
 
271
232
  expect(mockOptions.context.invoke).toHaveBeenCalledWith(
272
- mockOptions.context.agents.refineDocumentStructure,
233
+ mockOptions.context.agents.generateStructureWithoutTools,
273
234
  expect.objectContaining(additionalParams),
274
235
  );
275
236
  });
@@ -286,4 +247,33 @@ describe("check-need-generate-structure", () => {
286
247
  expect(mockOptions.prompts.select).toHaveBeenCalled();
287
248
  expect(mockOptions.context.invoke).not.toHaveBeenCalled();
288
249
  });
250
+
251
+ test("should use generateStructure agent when isLargeContext is true", async () => {
252
+ await checkNeedGenerateStructure(
253
+ { originalDocumentStructure, forceRegenerate: true, isLargeContext: true, docsDir: "./docs" },
254
+ mockOptions,
255
+ );
256
+
257
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
258
+ mockOptions.context.agents.generateStructure,
259
+ expect.any(Object),
260
+ );
261
+ });
262
+
263
+ test("should use generateStructureWithoutTools agent when isLargeContext is false", async () => {
264
+ await checkNeedGenerateStructure(
265
+ {
266
+ originalDocumentStructure,
267
+ forceRegenerate: true,
268
+ isLargeContext: false,
269
+ docsDir: "./docs",
270
+ },
271
+ mockOptions,
272
+ );
273
+
274
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
275
+ mockOptions.context.agents.generateStructureWithoutTools,
276
+ expect.any(Object),
277
+ );
278
+ });
289
279
  });
@@ -0,0 +1,277 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdir, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import generateSubStructure from "../../../../agents/generate/document-structure-tools/generate-sub-structure.mjs";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ describe("generate-sub-structure", () => {
10
+ let testDir;
11
+
12
+ beforeEach(async () => {
13
+ testDir = join(__dirname, "test-generate-sub-structure");
14
+ await mkdir(testDir, { recursive: true });
15
+ });
16
+
17
+ afterEach(async () => {
18
+ try {
19
+ await rm(testDir, { recursive: true, force: true });
20
+ } catch {
21
+ // Ignore cleanup errors
22
+ }
23
+ });
24
+
25
+ describe("generateSubStructure function", () => {
26
+ test("should return empty subStructure when subSourcePaths is empty", async () => {
27
+ const result = await generateSubStructure(
28
+ {
29
+ parentDocument: {
30
+ title: "Test Parent",
31
+ description: "Test Description",
32
+ path: "/test",
33
+ parentId: "parent-1",
34
+ sourceIds: [],
35
+ },
36
+ subSourcePaths: [],
37
+ },
38
+ {},
39
+ );
40
+
41
+ expect(result).toBeDefined();
42
+ expect(result.subStructure).toBeDefined();
43
+ expect(Array.isArray(result.subStructure)).toBe(true);
44
+ expect(result.subStructure.length).toBe(0);
45
+ expect(result.message).toBeUndefined();
46
+ });
47
+
48
+ test("should process single source path with small context", async () => {
49
+ const testFile = join(testDir, "test.js");
50
+ await writeFile(testFile, "// Small test file\nconst x = 1;");
51
+
52
+ const mockContext = {
53
+ agents: {
54
+ generateStructureWithoutTools: "mock-agent-without-tools",
55
+ },
56
+ invoke: async (agent, params) => {
57
+ expect(agent).toBe("mock-agent-without-tools");
58
+ expect(params.isSubStructure).toBe(true);
59
+ expect(params.parentDocument).toBeDefined();
60
+ expect(params.datasources).toBeDefined();
61
+ expect(params.allFilesPaths).toBeDefined();
62
+ expect(params.isLargeContext).toBe(false);
63
+ expect(params.files).toBeDefined();
64
+ expect(params.totalTokens).toBeGreaterThan(0);
65
+
66
+ return {
67
+ documentStructure: [
68
+ {
69
+ title: "Test Document",
70
+ path: "/test-doc",
71
+ description: "Generated from test file",
72
+ },
73
+ ],
74
+ };
75
+ },
76
+ };
77
+
78
+ const result = await generateSubStructure(
79
+ {
80
+ parentDocument: {
81
+ title: "Parent",
82
+ path: "/parent",
83
+ description: "Parent doc",
84
+ },
85
+ subSourcePaths: [{ path: testFile, reason: "Test file" }],
86
+ },
87
+ { context: mockContext },
88
+ );
89
+
90
+ expect(result).toBeDefined();
91
+ expect(result.subStructure).toBeDefined();
92
+ expect(Array.isArray(result.subStructure)).toBe(true);
93
+ expect(result.subStructure.length).toBe(1);
94
+ expect(result.message).toContain("Generated a sub structure");
95
+ expect(result.message).toContain("/parent");
96
+ });
97
+
98
+ test("should process multiple source paths", async () => {
99
+ const testFile1 = join(testDir, "test1.js");
100
+ const testFile2 = join(testDir, "test2.js");
101
+ await writeFile(testFile1, "// Test file 1\nconst a = 1;");
102
+ await writeFile(testFile2, "// Test file 2\nconst b = 2;");
103
+
104
+ const mockContext = {
105
+ agents: {
106
+ generateStructureWithoutTools: "mock-agent-without-tools",
107
+ },
108
+ invoke: async (_agent, params) => {
109
+ expect(params.files.length).toBe(2);
110
+ expect(params.allFilesPaths).toContain("test1.js");
111
+ expect(params.allFilesPaths).toContain("test2.js");
112
+
113
+ return {
114
+ documentStructure: [
115
+ { title: "Doc 1", path: "/doc1" },
116
+ { title: "Doc 2", path: "/doc2" },
117
+ ],
118
+ };
119
+ },
120
+ };
121
+
122
+ const result = await generateSubStructure(
123
+ {
124
+ parentDocument: { title: "Parent", path: "/parent" },
125
+ subSourcePaths: [
126
+ { path: testFile1, reason: "First test" },
127
+ { path: testFile2, reason: "Second test" },
128
+ ],
129
+ },
130
+ { context: mockContext },
131
+ );
132
+
133
+ expect(result.subStructure.length).toBe(2);
134
+ });
135
+
136
+ test("should use generateStructure agent for large context", async () => {
137
+ const largeContent = `// Large file\n${"const x = 1;\n".repeat(150000)}`;
138
+ const testFile = join(testDir, "large.js");
139
+ await writeFile(testFile, largeContent);
140
+
141
+ const mockContext = {
142
+ agents: {
143
+ generateStructure: "mock-agent-with-tools",
144
+ generateStructureWithoutTools: "mock-agent-without-tools",
145
+ },
146
+ invoke: async (agent, params) => {
147
+ expect(agent).toBe("mock-agent-with-tools");
148
+ expect(params.isLargeContext).toBe(true);
149
+
150
+ return {
151
+ documentStructure: [{ title: "Large Doc", path: "/large" }],
152
+ };
153
+ },
154
+ };
155
+
156
+ const result = await generateSubStructure(
157
+ {
158
+ parentDocument: { title: "Parent", path: "/parent" },
159
+ subSourcePaths: [{ path: testFile, reason: "Large file" }],
160
+ },
161
+ { context: mockContext },
162
+ );
163
+
164
+ expect(result.subStructure).toBeDefined();
165
+ expect(result.subStructure.length).toBe(1);
166
+ });
167
+
168
+ test("should handle custom include and exclude patterns", async () => {
169
+ const srcDir = join(testDir, "src");
170
+ await mkdir(srcDir, { recursive: true });
171
+ await writeFile(join(srcDir, "index.js"), "// index");
172
+ await writeFile(join(srcDir, "test.spec.js"), "// test");
173
+ await writeFile(join(srcDir, "utils.ts"), "// utils");
174
+
175
+ const mockContext = {
176
+ agents: {
177
+ generateStructureWithoutTools: "mock-agent",
178
+ },
179
+ invoke: async (_agent, params) => {
180
+ const hasTestFile = params.files.some((f) => f.includes("test.spec.js"));
181
+ expect(hasTestFile).toBe(false);
182
+
183
+ return { documentStructure: [] };
184
+ },
185
+ };
186
+
187
+ await generateSubStructure(
188
+ {
189
+ parentDocument: { title: "Parent", path: "/parent" },
190
+ subSourcePaths: [{ path: testDir, reason: "Test directory" }],
191
+ includePatterns: ["**/*.js", "**/*.ts"],
192
+ excludePatterns: ["**/*.spec.js"],
193
+ },
194
+ { context: mockContext },
195
+ );
196
+ });
197
+
198
+ test("should deduplicate files in result", async () => {
199
+ const testFile = join(testDir, "test.js");
200
+ await writeFile(testFile, "// test");
201
+
202
+ const mockContext = {
203
+ agents: {
204
+ generateStructureWithoutTools: "mock-agent",
205
+ },
206
+ invoke: async (_agent, params) => {
207
+ const uniqueFiles = new Set(params.files);
208
+ expect(uniqueFiles.size).toBe(params.files.length);
209
+
210
+ return { documentStructure: [] };
211
+ },
212
+ };
213
+
214
+ await generateSubStructure(
215
+ {
216
+ parentDocument: { title: "Parent", path: "/parent" },
217
+ subSourcePaths: [
218
+ { path: testFile, reason: "First" },
219
+ { path: testFile, reason: "Second" },
220
+ ],
221
+ },
222
+ { context: mockContext },
223
+ );
224
+ });
225
+
226
+ test("should pass extra parameters to agent", async () => {
227
+ const testFile = join(testDir, "test.js");
228
+ await writeFile(testFile, "// test");
229
+
230
+ const mockContext = {
231
+ agents: {
232
+ generateStructureWithoutTools: "mock-agent",
233
+ },
234
+ invoke: async (_agent, params) => {
235
+ expect(params.extraParam1).toBe("value1");
236
+ expect(params.extraParam2).toBe("value2");
237
+
238
+ return { documentStructure: [] };
239
+ },
240
+ };
241
+
242
+ await generateSubStructure(
243
+ {
244
+ parentDocument: { title: "Parent", path: "/parent" },
245
+ subSourcePaths: [{ path: testFile, reason: "Test" }],
246
+ extraParam1: "value1",
247
+ extraParam2: "value2",
248
+ },
249
+ { context: mockContext },
250
+ );
251
+ });
252
+
253
+ test("should handle empty documentStructure from agent", async () => {
254
+ const testFile = join(testDir, "test.js");
255
+ await writeFile(testFile, "// test");
256
+
257
+ const mockContext = {
258
+ agents: {
259
+ generateStructureWithoutTools: "mock-agent",
260
+ },
261
+ invoke: async () => {
262
+ return {};
263
+ },
264
+ };
265
+
266
+ const result = await generateSubStructure(
267
+ {
268
+ parentDocument: { title: "Parent", path: "/parent" },
269
+ subSourcePaths: [{ path: testFile, reason: "Test" }],
270
+ },
271
+ { context: mockContext },
272
+ );
273
+
274
+ expect(result.subStructure).toEqual([]);
275
+ });
276
+ });
277
+ });
@@ -986,13 +986,13 @@ describe("init", () => {
986
986
  const mockResponses = {
987
987
  checkbox_1: ["getStarted", "findAnswers"], // Document purpose
988
988
  checkbox_2: ["developers"], // Target audience
989
- input_3: "Custom rules for documentation", // Custom rules
990
- select_4: "domainFamiliar", // Reader knowledge level
991
- select_5: "balancedCoverage", // Documentation depth
992
- select_6: "en", // Primary language
993
- checkbox_7: ["zh", "ja"], // Translation languages
994
- input_8: join(tempDir, "docs"), // Documentation directory
989
+ select_3: "domainFamiliar", // Reader knowledge level
990
+ select_4: "balancedCoverage", // Documentation depth
991
+ select_5: "en", // Primary language
992
+ checkbox_6: ["zh", "ja"], // Translation languages
993
+ input_7: join(tempDir, "docs"), // Documentation directory
995
994
  search: "", // Source paths (empty to finish)
995
+ input_9: "Custom rules for documentation", // Custom rules (last step)
996
996
  };
997
997
 
998
998
  const mockPrompts = createMockPrompts(mockResponses);
@@ -1044,13 +1044,13 @@ describe("init", () => {
1044
1044
  checkbox_1: ["mixedPurpose"], // Document purpose - triggers follow-up
1045
1045
  checkbox: ["completeTasks", "findAnswers"], // Top priorities after mixedPurpose
1046
1046
  checkbox_2: ["developers", "devops"], // Target audience
1047
- input_3: "Custom rules for documentation", // Custom rules
1048
- select_4: "experiencedUsers", // Reader knowledge level
1049
- select_5: "comprehensive", // Documentation depth
1050
- select_6: "zh-CN", // Primary language
1051
- checkbox_7: ["en"], // Translation languages
1052
- input_8: join(tempDir, "documentation"), // Documentation directory
1047
+ select_3: "experiencedUsers", // Reader knowledge level
1048
+ select_4: "comprehensive", // Documentation depth
1049
+ select_5: "zh-CN", // Primary language
1050
+ checkbox_6: ["en"], // Translation languages
1051
+ input_7: join(tempDir, "documentation"), // Documentation directory
1053
1052
  search: "", // Source paths (empty to finish)
1053
+ input_9: "Custom rules for documentation", // Custom rules (last step)
1054
1054
  };
1055
1055
 
1056
1056
  const mockPrompts = createMockPrompts(mockResponses);
@@ -1091,13 +1091,13 @@ describe("init", () => {
1091
1091
  const mockResponses = {
1092
1092
  checkbox_1: ["getStarted"], // Document purpose
1093
1093
  checkbox_2: ["endUsers"], // Target audience
1094
- input_3: "Custom rules for documentation", // Custom rules
1095
- select_4: "completeBeginners", // Reader knowledge level
1096
- select_5: "essentialOnly", // Documentation depth
1097
- select_6: "en", // Primary language
1098
- checkbox_7: [], // No translation languages
1099
- input_8: join(tempDir, "simple-docs"), // Documentation directory
1094
+ select_3: "completeBeginners", // Reader knowledge level
1095
+ select_4: "essentialOnly", // Documentation depth
1096
+ select_5: "en", // Primary language
1097
+ checkbox_6: [], // No translation languages
1098
+ input_7: join(tempDir, "simple-docs"), // Documentation directory
1100
1099
  search: "", // Source paths (empty to finish)
1100
+ input_9: "Custom rules for documentation", // Custom rules (last step)
1101
1101
  };
1102
1102
 
1103
1103
  const mockPrompts = createMockPrompts(mockResponses);
@@ -22,6 +22,12 @@ const mockPublishDocs = {
22
22
  publishDocs: mock(() => Promise.resolve({ success: true, boardId: "new-board-id" })),
23
23
  };
24
24
 
25
+ const mockBrokerClient = {
26
+ checkCacheSession: mock(() => Promise.resolve({ sessionId: null, paymentLink: null })),
27
+ };
28
+
29
+ const mockBrokerClientConstructor = mock(() => mockBrokerClient);
30
+
25
31
  const mockChalk = {
26
32
  bold: mock((text) => text),
27
33
  cyan: mock((text) => text),
@@ -47,6 +53,7 @@ describe("publish-docs", () => {
47
53
 
48
54
  // Spies for internal utils
49
55
  let getAccessTokenSpy;
56
+ let getOfficialAccessTokenSpy;
50
57
  let beforePublishHookSpy;
51
58
  let ensureTmpDirSpy;
52
59
  let getGithubRepoUrlSpy;
@@ -57,6 +64,9 @@ describe("publish-docs", () => {
57
64
  beforeAll(() => {
58
65
  // Apply mocks for external dependencies only
59
66
  mock.module("@aigne/publish-docs", () => mockPublishDocs);
67
+ mock.module("@blocklet/payment-broker-client/node", () => ({
68
+ BrokerClient: mockBrokerClientConstructor,
69
+ }));
60
70
  mock.module("chalk", () => ({ default: mockChalk }));
61
71
  mock.module("fs-extra", () => ({ default: mockFsExtra }));
62
72
  mock.module("node:path", () => mockPath);
@@ -91,8 +101,19 @@ describe("publish-docs", () => {
91
101
  mockChalk.cyan.mockClear();
92
102
  mockChalk.cyan.mockImplementation((text) => text);
93
103
 
104
+ // Reset BrokerClient mock
105
+ mockBrokerClientConstructor.mockClear();
106
+ mockBrokerClientConstructor.mockImplementation(() => mockBrokerClient);
107
+ mockBrokerClient.checkCacheSession.mockClear();
108
+ mockBrokerClient.checkCacheSession.mockImplementation(() =>
109
+ Promise.resolve({ sessionId: null, paymentLink: null }),
110
+ );
111
+
94
112
  // Set up spies for internal utils
95
113
  getAccessTokenSpy = spyOn(authUtils, "getAccessToken").mockResolvedValue("mock-token");
114
+ getOfficialAccessTokenSpy = spyOn(authUtils, "getOfficialAccessToken").mockResolvedValue(
115
+ "official-mock-token",
116
+ );
96
117
  beforePublishHookSpy = spyOn(d2Utils, "beforePublishHook").mockResolvedValue();
97
118
  ensureTmpDirSpy = spyOn(d2Utils, "ensureTmpDir").mockResolvedValue();
98
119
  getGithubRepoUrlSpy = spyOn(utils, "getGithubRepoUrl").mockReturnValue(
@@ -126,6 +147,7 @@ describe("publish-docs", () => {
126
147
 
127
148
  // Restore all spies
128
149
  getAccessTokenSpy?.mockRestore();
150
+ getOfficialAccessTokenSpy?.mockRestore();
129
151
  beforePublishHookSpy?.mockRestore();
130
152
  ensureTmpDirSpy?.mockRestore();
131
153
  getGithubRepoUrlSpy?.mockRestore();
@@ -425,6 +447,55 @@ describe("publish-docs", () => {
425
447
  });
426
448
 
427
449
  // ERROR HANDLING TESTS
450
+ test("should handle failed official access token (null)", async () => {
451
+ loadConfigFromFileSpy.mockResolvedValue({});
452
+ getOfficialAccessTokenSpy.mockResolvedValue(null);
453
+ mockOptions.prompts.select.mockResolvedValue("default");
454
+
455
+ const result = await publishDocs(
456
+ {
457
+ docsDir: "./docs",
458
+ appUrl: "https://docsmith.aigne.io",
459
+ },
460
+ mockOptions,
461
+ );
462
+
463
+ expect(result.message).toBe("❌ Failed to publish docs: Failed to get official access token");
464
+ });
465
+
466
+ test("should handle failed official access token (undefined)", async () => {
467
+ loadConfigFromFileSpy.mockResolvedValue({});
468
+ getOfficialAccessTokenSpy.mockResolvedValue(undefined);
469
+ mockOptions.prompts.select.mockResolvedValue("default");
470
+
471
+ const result = await publishDocs(
472
+ {
473
+ docsDir: "./docs",
474
+ appUrl: "https://docsmith.aigne.io",
475
+ },
476
+ mockOptions,
477
+ );
478
+
479
+ expect(result.message).toBe("❌ Failed to publish docs: Failed to get official access token");
480
+ });
481
+
482
+ test("should handle checkCacheSession failure", async () => {
483
+ loadConfigFromFileSpy.mockResolvedValue({});
484
+ getOfficialAccessTokenSpy.mockResolvedValue("valid-token");
485
+ mockBrokerClient.checkCacheSession.mockRejectedValue(new Error("Cache session failed"));
486
+ mockOptions.prompts.select.mockResolvedValue("default");
487
+
488
+ const result = await publishDocs(
489
+ {
490
+ docsDir: "./docs",
491
+ appUrl: "https://docsmith.aigne.io",
492
+ },
493
+ mockOptions,
494
+ );
495
+
496
+ expect(result.message).toBe("❌ Failed to publish docs: Cache session failed");
497
+ });
498
+
428
499
  test("should handle publish failure", async () => {
429
500
  mockPublishDocs.publishDocs.mockRejectedValue(new Error("Publish failed"));
430
501
 
@@ -605,6 +676,10 @@ describe("publish-docs", () => {
605
676
  loadConfigFromFileSpy.mockResolvedValue({
606
677
  checkoutId: "cached-checkout-123",
607
678
  });
679
+ mockBrokerClient.checkCacheSession.mockResolvedValue({
680
+ sessionId: "cached-checkout-123",
681
+ paymentLink: "https://payment.example.com",
682
+ });
608
683
  mockOptions.prompts.select.mockResolvedValue("default");
609
684
 
610
685
  await publishDocs(
@@ -673,6 +748,10 @@ describe("publish-docs", () => {
673
748
  checkoutId: "cached-checkout-123",
674
749
  paymentUrl: "https://payment.example.com",
675
750
  });
751
+ mockBrokerClient.checkCacheSession.mockResolvedValue({
752
+ sessionId: "cached-checkout-123",
753
+ paymentLink: "https://payment.example.com",
754
+ });
676
755
  mockOptions.prompts.select.mockResolvedValue("new-instance-continue");
677
756
 
678
757
  const consoleSpy = spyOn(console, "log").mockImplementation(() => {});