@aigne/doc-smith 0.8.11-beta → 0.8.11-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 (257) hide show
  1. package/.aigne/doc-smith/config.yaml +2 -0
  2. package/.aigne/doc-smith/output/structure-plan.json +3 -3
  3. package/.aigne/doc-smith/upload-cache.yaml +252 -0
  4. package/.github/workflows/publish-docs.yml +67 -0
  5. package/.release-please-manifest.json +1 -1
  6. package/CHANGELOG.md +15 -0
  7. package/README.md +45 -115
  8. package/agents/clear/choose-contents.mjs +170 -0
  9. package/agents/clear/clear-auth-tokens.mjs +111 -0
  10. package/agents/clear/clear-document-config.mjs +39 -0
  11. package/agents/clear/clear-document-structure.mjs +106 -0
  12. package/agents/clear/clear-generated-docs.mjs +51 -0
  13. package/agents/clear/index.yaml +23 -0
  14. package/agents/evaluate/code-snippet.mjs +93 -0
  15. package/agents/evaluate/document-structure.yaml +70 -0
  16. package/agents/evaluate/document.yaml +79 -0
  17. package/agents/evaluate/generate-report.mjs +78 -0
  18. package/agents/evaluate/index.yaml +39 -0
  19. package/agents/generate/document-structure-tools/add-document.mjs +56 -0
  20. package/agents/generate/document-structure-tools/delete-document.mjs +49 -0
  21. package/agents/generate/document-structure-tools/move-document.mjs +82 -0
  22. package/agents/generate/document-structure-tools/update-document.mjs +50 -0
  23. package/agents/generate/generate-structure.yaml +1 -1
  24. package/agents/generate/update-document-structure.yaml +42 -0
  25. package/agents/generate/user-review-document-structure.mjs +6 -4
  26. package/agents/init/index.mjs +1 -1
  27. package/agents/publish/publish-docs.mjs +12 -3
  28. package/agents/translate/choose-language.mjs +1 -1
  29. package/agents/update/batch-update-document.yaml +7 -0
  30. package/agents/update/check-update-is-single.mjs +38 -0
  31. package/agents/update/document-tools/update-document-content.mjs +293 -0
  32. package/agents/update/index.yaml +4 -10
  33. package/agents/update/update-document-detail.yaml +52 -0
  34. package/agents/update/update-single-document.yaml +15 -0
  35. package/agents/update/user-review-document.mjs +248 -0
  36. package/agents/utils/choose-docs.mjs +4 -2
  37. package/agents/utils/format-document-structure.mjs +12 -2
  38. package/agents/utils/load-document-all-content.mjs +84 -0
  39. package/agents/utils/load-sources.mjs +4 -1
  40. package/aigne.yaml +59 -20
  41. package/assets/report-template/report.html +198 -0
  42. package/biome.json +14 -2
  43. package/docs/advanced-how-it-works.ja.md +101 -0
  44. package/docs/advanced-how-it-works.zh-TW.md +101 -0
  45. package/docs/advanced-how-it-works.zh.md +20 -20
  46. package/docs/advanced-quality-assurance.ja.md +96 -0
  47. package/docs/advanced-quality-assurance.zh-TW.md +96 -0
  48. package/docs/advanced-quality-assurance.zh.md +18 -18
  49. package/docs/advanced.ja.md +16 -0
  50. package/docs/advanced.zh-TW.md +16 -0
  51. package/docs/advanced.zh.md +4 -4
  52. package/docs/changelog.ja.md +309 -0
  53. package/docs/changelog.zh-TW.md +309 -0
  54. package/docs/changelog.zh.md +23 -23
  55. package/docs/cli-reference.ja.md +210 -0
  56. package/docs/cli-reference.zh-TW.md +210 -0
  57. package/docs/cli-reference.zh.md +21 -21
  58. package/docs/configuration-interactive-setup.ja.md +135 -0
  59. package/docs/configuration-interactive-setup.zh-TW.md +135 -0
  60. package/docs/configuration-interactive-setup.zh.md +29 -29
  61. package/docs/configuration-language-support.ja.md +94 -0
  62. package/docs/configuration-language-support.zh-TW.md +94 -0
  63. package/docs/configuration-language-support.zh.md +13 -13
  64. package/docs/configuration-llm-setup.ja.md +54 -0
  65. package/docs/configuration-llm-setup.zh-TW.md +54 -0
  66. package/docs/configuration-llm-setup.zh.md +12 -12
  67. package/docs/configuration-preferences.ja.md +129 -0
  68. package/docs/configuration-preferences.zh-TW.md +129 -0
  69. package/docs/configuration-preferences.zh.md +36 -36
  70. package/docs/configuration.ja.md +172 -0
  71. package/docs/configuration.zh-TW.md +172 -0
  72. package/docs/configuration.zh.md +49 -49
  73. package/docs/features-generate-documentation.ja.md +101 -0
  74. package/docs/features-generate-documentation.zh-TW.md +101 -0
  75. package/docs/features-generate-documentation.zh.md +17 -17
  76. package/docs/features-publish-your-docs.ja.md +107 -0
  77. package/docs/features-publish-your-docs.zh-TW.md +107 -0
  78. package/docs/features-publish-your-docs.zh.md +22 -22
  79. package/docs/features-translate-documentation.ja.md +79 -0
  80. package/docs/features-translate-documentation.zh-TW.md +79 -0
  81. package/docs/features-translate-documentation.zh.md +12 -12
  82. package/docs/features-update-and-refine.ja.md +138 -0
  83. package/docs/features-update-and-refine.zh-TW.md +138 -0
  84. package/docs/features-update-and-refine.zh.md +21 -21
  85. package/docs/features.ja.md +52 -0
  86. package/docs/features.zh-TW.md +52 -0
  87. package/docs/features.zh.md +8 -8
  88. package/docs/getting-started.ja.md +123 -0
  89. package/docs/getting-started.zh-TW.md +123 -0
  90. package/docs/getting-started.zh.md +24 -24
  91. package/docs/overview.ja.md +30 -0
  92. package/docs/overview.zh-TW.md +30 -0
  93. package/docs/overview.zh.md +8 -8
  94. package/package.json +19 -11
  95. package/prompts/common/document/content-rules-core.md +19 -0
  96. package/prompts/common/document/media-handling-rules.md +9 -0
  97. package/prompts/common/document/role-and-personality.md +15 -0
  98. package/prompts/common/document/user-preferences.md +9 -0
  99. package/prompts/common/document-structure/conflict-resolution-guidance.md +16 -0
  100. package/prompts/common/document-structure/document-structure-rules.md +45 -0
  101. package/prompts/common/document-structure/glossary.md +7 -0
  102. package/prompts/common/document-structure/intj-traits.md +5 -0
  103. package/prompts/common/document-structure/output-constraints.md +9 -0
  104. package/prompts/common/document-structure/user-locale-rules.md +10 -0
  105. package/prompts/common/document-structure/user-preferences.md +9 -0
  106. package/prompts/detail/custom/custom-components.md +9 -1
  107. package/prompts/detail/document-rules.md +6 -6
  108. package/prompts/detail/generate-document.md +5 -45
  109. package/prompts/detail/update-document.md +145 -0
  110. package/prompts/evaluate/document-structure.md +94 -0
  111. package/prompts/evaluate/document.md +149 -0
  112. package/prompts/structure/document-rules.md +1 -1
  113. package/prompts/structure/generate-structure-system.md +74 -0
  114. package/prompts/structure/generate-structure-user.md +41 -0
  115. package/prompts/structure/update-document-structure.md +118 -0
  116. package/prompts/translate/translate-document.md +1 -1
  117. package/prompts/utils/feedback-refiner.md +3 -3
  118. package/release-please-config.json +1 -7
  119. package/tests/agents/clear/choose-contents.test.mjs +280 -0
  120. package/tests/agents/clear/clear-auth-tokens.test.mjs +268 -0
  121. package/tests/agents/clear/clear-document-config.test.mjs +167 -0
  122. package/tests/agents/clear/clear-document-structure.test.mjs +374 -0
  123. package/tests/agents/clear/clear-generated-docs.test.mjs +222 -0
  124. package/tests/agents/evaluate/code-snippet.test.mjs +163 -0
  125. package/tests/agents/evaluate/fixtures/api-services.md +87 -0
  126. package/tests/agents/evaluate/fixtures/js-sdk.md +94 -0
  127. package/tests/agents/evaluate/generate-report.test.mjs +312 -0
  128. package/tests/agents/generate/check-document-structure.test.mjs +0 -6
  129. package/tests/agents/generate/document-structure-tools/add-document.test.mjs +449 -0
  130. package/tests/agents/generate/document-structure-tools/delete-document.test.mjs +410 -0
  131. package/tests/agents/generate/document-structure-tools/move-document.test.mjs +476 -0
  132. package/tests/agents/generate/document-structure-tools/update-document.test.mjs +548 -0
  133. package/tests/agents/generate/generate-structure.test.mjs +0 -6
  134. package/tests/agents/generate/user-review-document-structure.test.mjs +9 -9
  135. package/tests/agents/publish/publish-docs.test.mjs +2 -2
  136. package/tests/agents/update/check-update-is-single.test.mjs +300 -0
  137. package/tests/agents/update/document-tools/update-document-content.test.mjs +326 -0
  138. package/tests/agents/update/user-review-document.test.mjs +561 -0
  139. package/tests/agents/utils/format-document-structure.test.mjs +100 -0
  140. package/tests/utils/auth-utils.test.mjs +239 -1
  141. package/tests/utils/blocklet.test.mjs +9 -7
  142. package/tests/utils/constants.test.mjs +1 -1
  143. package/tests/utils/d2-utils.test.mjs +1 -1
  144. package/tests/utils/deploy.test.mjs +310 -366
  145. package/tests/utils/kroki-utils.test.mjs +2 -15
  146. package/tests/utils/linter/fixtures/css/keyword-error.css +1 -0
  147. package/tests/utils/linter/fixtures/css/missing-semicolon.css +1 -0
  148. package/tests/utils/linter/fixtures/css/syntax-error.css +1 -0
  149. package/tests/utils/linter/fixtures/css/undeclare-variable.css +1 -0
  150. package/tests/utils/linter/fixtures/css/unused-variable.css +2 -0
  151. package/tests/utils/linter/fixtures/css/valid-code.css +1 -0
  152. package/tests/utils/linter/fixtures/dockerfile/keyword-error.dockerfile +1 -0
  153. package/tests/utils/linter/fixtures/dockerfile/missing-semicolon.dockerfile +2 -0
  154. package/tests/utils/linter/fixtures/dockerfile/syntax-error.dockerfile +2 -0
  155. package/tests/utils/linter/fixtures/dockerfile/undeclare-variable.dockerfile +1 -0
  156. package/tests/utils/linter/fixtures/dockerfile/unused-variable.dockerfile +1 -0
  157. package/tests/utils/linter/fixtures/dockerfile/valid-code.dockerfile +2 -0
  158. package/tests/utils/linter/fixtures/go/keyword-error.go +5 -0
  159. package/tests/utils/linter/fixtures/go/missing-semicolon.go +5 -0
  160. package/tests/utils/linter/fixtures/go/syntax-error.go +6 -0
  161. package/tests/utils/linter/fixtures/go/undeclare-variable.go +5 -0
  162. package/tests/utils/linter/fixtures/go/unused-variable.go +5 -0
  163. package/tests/utils/linter/fixtures/go/valid-code.go +7 -0
  164. package/tests/utils/linter/fixtures/js/keyword-error.js +3 -0
  165. package/tests/utils/linter/fixtures/js/missing-semicolon.js +6 -0
  166. package/tests/utils/linter/fixtures/js/syntax-error.js +4 -0
  167. package/tests/utils/linter/fixtures/js/undeclare-variable.js +3 -0
  168. package/tests/utils/linter/fixtures/js/unused-variable.js +7 -0
  169. package/tests/utils/linter/fixtures/js/valid-code.js +15 -0
  170. package/tests/utils/linter/fixtures/json/keyword-error.json +1 -0
  171. package/tests/utils/linter/fixtures/json/missing-semicolon.json +1 -0
  172. package/tests/utils/linter/fixtures/json/syntax-error.json +1 -0
  173. package/tests/utils/linter/fixtures/json/undeclare-variable.json +1 -0
  174. package/tests/utils/linter/fixtures/json/unused-variable.json +1 -0
  175. package/tests/utils/linter/fixtures/json/valid-code.json +1 -0
  176. package/tests/utils/linter/fixtures/jsx/keyword-error.jsx +5 -0
  177. package/tests/utils/linter/fixtures/jsx/missing-semicolon.jsx +5 -0
  178. package/tests/utils/linter/fixtures/jsx/syntax-error.jsx +5 -0
  179. package/tests/utils/linter/fixtures/jsx/undeclare-variable.jsx +5 -0
  180. package/tests/utils/linter/fixtures/jsx/unused-variable.jsx +4 -0
  181. package/tests/utils/linter/fixtures/jsx/valid-code.jsx +5 -0
  182. package/tests/utils/linter/fixtures/python/keyword-error.py +3 -0
  183. package/tests/utils/linter/fixtures/python/missing-semicolon.py +2 -0
  184. package/tests/utils/linter/fixtures/python/syntax-error.py +3 -0
  185. package/tests/utils/linter/fixtures/python/undeclare-variable.py +3 -0
  186. package/tests/utils/linter/fixtures/python/unused-variable.py +6 -0
  187. package/tests/utils/linter/fixtures/python/valid-code.py +12 -0
  188. package/tests/utils/linter/fixtures/ruby/keyword-error.rb +2 -0
  189. package/tests/utils/linter/fixtures/ruby/missing-semicolon.rb +1 -0
  190. package/tests/utils/linter/fixtures/ruby/syntax-error.rb +2 -0
  191. package/tests/utils/linter/fixtures/ruby/undeclare-variable.rb +1 -0
  192. package/tests/utils/linter/fixtures/ruby/unused-variable.rb +2 -0
  193. package/tests/utils/linter/fixtures/ruby/valid-code.rb +1 -0
  194. package/tests/utils/linter/fixtures/sass/keyword-error.sass +2 -0
  195. package/tests/utils/linter/fixtures/sass/missing-semicolon.sass +3 -0
  196. package/tests/utils/linter/fixtures/sass/syntax-error.sass +3 -0
  197. package/tests/utils/linter/fixtures/sass/undeclare-variable.sass +2 -0
  198. package/tests/utils/linter/fixtures/sass/unused-variable.sass +4 -0
  199. package/tests/utils/linter/fixtures/sass/valid-code.sass +2 -0
  200. package/tests/utils/linter/fixtures/scss/keyword-error.scss +1 -0
  201. package/tests/utils/linter/fixtures/scss/missing-semicolon.scss +1 -0
  202. package/tests/utils/linter/fixtures/scss/syntax-error.scss +1 -0
  203. package/tests/utils/linter/fixtures/scss/undeclare-variable.scss +1 -0
  204. package/tests/utils/linter/fixtures/scss/unused-variable.scss +2 -0
  205. package/tests/utils/linter/fixtures/scss/valid-code.scss +1 -0
  206. package/tests/utils/linter/fixtures/shell/keyword-error.sh +5 -0
  207. package/tests/utils/linter/fixtures/shell/missing-semicolon.sh +3 -0
  208. package/tests/utils/linter/fixtures/shell/syntax-error.sh +4 -0
  209. package/tests/utils/linter/fixtures/shell/undeclare-variable.sh +3 -0
  210. package/tests/utils/linter/fixtures/shell/unused-variable.sh +4 -0
  211. package/tests/utils/linter/fixtures/shell/valid-code.sh +3 -0
  212. package/tests/utils/linter/fixtures/ts/keyword-error.ts +1 -0
  213. package/tests/utils/linter/fixtures/ts/missing-semicolon.ts +1 -0
  214. package/tests/utils/linter/fixtures/ts/syntax-error.ts +1 -0
  215. package/tests/utils/linter/fixtures/ts/undeclare-variable.ts +1 -0
  216. package/tests/utils/linter/fixtures/ts/unused-variable.ts +3 -0
  217. package/tests/utils/linter/fixtures/ts/valid-code.ts +3 -0
  218. package/tests/utils/linter/fixtures/tsx/keyword-error.tsx +5 -0
  219. package/tests/utils/linter/fixtures/tsx/missing-semicolon.tsx +5 -0
  220. package/tests/utils/linter/fixtures/tsx/syntax-error.tsx +5 -0
  221. package/tests/utils/linter/fixtures/tsx/undeclare-variable.tsx +6 -0
  222. package/tests/utils/linter/fixtures/tsx/unused-variable.tsx +6 -0
  223. package/tests/utils/linter/fixtures/tsx/valid-code.tsx +5 -0
  224. package/tests/utils/linter/fixtures/vue/keyword-error.vue +6 -0
  225. package/tests/utils/linter/fixtures/vue/missing-semicolon.vue +6 -0
  226. package/tests/utils/linter/fixtures/vue/syntax-error.vue +6 -0
  227. package/tests/utils/linter/fixtures/vue/undeclare-variable.vue +6 -0
  228. package/tests/utils/linter/fixtures/vue/unused-variable.vue +7 -0
  229. package/tests/utils/linter/fixtures/vue/valid-code.vue +6 -0
  230. package/tests/utils/linter/fixtures/yaml/keyword-error.yml +1 -0
  231. package/tests/utils/linter/fixtures/yaml/missing-semicolon.yml +2 -0
  232. package/tests/utils/linter/fixtures/yaml/syntax-error.yml +1 -0
  233. package/tests/utils/linter/fixtures/yaml/undeclare-variable.yml +1 -0
  234. package/tests/utils/linter/fixtures/yaml/unused-variable.yml +2 -0
  235. package/tests/utils/linter/fixtures/yaml/valid-code.yml +3 -0
  236. package/tests/utils/linter/index.test.mjs +440 -0
  237. package/tests/utils/linter/scan-results.mjs +42 -0
  238. package/tests/utils/markdown/index.test.mjs +478 -0
  239. package/tests/utils/mermaid-validator.test.mjs +2 -2
  240. package/tests/utils/utils.test.mjs +3 -1
  241. package/types/document-schema.mjs +54 -0
  242. package/types/document-structure-schema.mjs +244 -0
  243. package/utils/auth-utils.mjs +131 -6
  244. package/utils/conflict-detector.mjs +5 -1
  245. package/utils/{constants.mjs → constants/index.mjs} +109 -0
  246. package/utils/constants/linter.mjs +102 -0
  247. package/utils/d2-utils.mjs +2 -4
  248. package/utils/debug.mjs +3 -0
  249. package/utils/deploy.mjs +81 -385
  250. package/utils/evaluate/report-utils.mjs +131 -0
  251. package/utils/file-utils.mjs +36 -1
  252. package/utils/kroki-utils.mjs +1 -1
  253. package/utils/linter/index.mjs +50 -0
  254. package/utils/markdown/index.mjs +26 -0
  255. package/utils/markdown-checker.mjs +1 -1
  256. package/utils/utils.mjs +19 -7
  257. package/prompts/structure/generate-structure.md +0 -161
@@ -0,0 +1,300 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
2
+ import checkUpdateIsSingle from "../../../agents/update/check-update-is-single.mjs";
3
+
4
+ describe("check-update-is-single", () => {
5
+ let mockOptions;
6
+ let consoleSpy;
7
+
8
+ beforeEach(() => {
9
+ mockOptions = {
10
+ context: {
11
+ agents: {
12
+ updateSingleDocument: { mockSingleAgent: true },
13
+ batchUpdateDocument: { mockBatchAgent: true },
14
+ },
15
+ invoke: mock(async () => ({ mockResult: true })),
16
+ },
17
+ };
18
+
19
+ consoleSpy = spyOn(console, "error").mockImplementation(() => {});
20
+
21
+ // Clear context mock call history
22
+ mockOptions.context.invoke.mockClear();
23
+ });
24
+
25
+ afterEach(() => {
26
+ consoleSpy?.mockRestore();
27
+ });
28
+
29
+ // INPUT VALIDATION TESTS
30
+ test("should throw error when selectedDocs is not provided", async () => {
31
+ await expect(checkUpdateIsSingle({}, mockOptions)).rejects.toThrow(
32
+ "selectedDocs must be provided as an array",
33
+ );
34
+ });
35
+
36
+ test("should throw error when selectedDocs is not an array", async () => {
37
+ await expect(checkUpdateIsSingle({ selectedDocs: "not-array" }, mockOptions)).rejects.toThrow(
38
+ "selectedDocs must be provided as an array",
39
+ );
40
+ });
41
+
42
+ test("should throw error when selectedDocs is empty", async () => {
43
+ await expect(checkUpdateIsSingle({ selectedDocs: [] }, mockOptions)).rejects.toThrow(
44
+ "selectedDocs cannot be empty",
45
+ );
46
+ });
47
+
48
+ test("should throw error when selectedDocs is null", async () => {
49
+ await expect(checkUpdateIsSingle({ selectedDocs: null }, mockOptions)).rejects.toThrow(
50
+ "selectedDocs must be provided as an array",
51
+ );
52
+ });
53
+
54
+ test("should throw error when selectedDocs is undefined", async () => {
55
+ await expect(checkUpdateIsSingle({ selectedDocs: undefined }, mockOptions)).rejects.toThrow(
56
+ "selectedDocs must be provided as an array",
57
+ );
58
+ });
59
+
60
+ // AGENT AVAILABILITY TESTS
61
+ test("should throw error when updateSingleDocument agent is not available", async () => {
62
+ const optionsWithoutSingleAgent = {
63
+ context: {
64
+ agents: {
65
+ batchUpdateDocument: { mockBatchAgent: true },
66
+ },
67
+ invoke: mock(),
68
+ },
69
+ };
70
+
71
+ await expect(
72
+ checkUpdateIsSingle({ selectedDocs: ["doc1"] }, optionsWithoutSingleAgent),
73
+ ).rejects.toThrow('Agent "updateSingleDocument" is not available');
74
+ });
75
+
76
+ test("should throw error when batchUpdateDocument agent is not available", async () => {
77
+ const optionsWithoutBatchAgent = {
78
+ context: {
79
+ agents: {
80
+ updateSingleDocument: { mockSingleAgent: true },
81
+ },
82
+ invoke: mock(),
83
+ },
84
+ };
85
+
86
+ await expect(
87
+ checkUpdateIsSingle({ selectedDocs: ["doc1", "doc2"] }, optionsWithoutBatchAgent),
88
+ ).rejects.toThrow('Agent "batchUpdateDocument" is not available');
89
+ });
90
+
91
+ test("should throw error when no agents are available", async () => {
92
+ const optionsWithoutAgents = {
93
+ context: {
94
+ agents: {},
95
+ invoke: mock(),
96
+ },
97
+ };
98
+
99
+ await expect(
100
+ checkUpdateIsSingle({ selectedDocs: ["doc1"] }, optionsWithoutAgents),
101
+ ).rejects.toThrow('Agent "updateSingleDocument" is not available');
102
+ });
103
+
104
+ // SINGLE DOCUMENT ROUTING TESTS
105
+ test("should route to updateSingleDocument when selectedDocs has one item", async () => {
106
+ const result = await checkUpdateIsSingle(
107
+ {
108
+ selectedDocs: ["doc1"],
109
+ customParam: "test",
110
+ },
111
+ mockOptions,
112
+ );
113
+
114
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
115
+ { mockSingleAgent: true },
116
+ {
117
+ selectedDocs: ["doc1"],
118
+ customParam: "test",
119
+ },
120
+ );
121
+ expect(result).toEqual({ mockResult: true });
122
+ });
123
+
124
+ test("should pass through all parameters to single document agent", async () => {
125
+ await checkUpdateIsSingle(
126
+ {
127
+ selectedDocs: [{ path: "/doc1", content: "content" }],
128
+ docsDir: "./docs",
129
+ forceRegenerate: true,
130
+ customData: { key: "value" },
131
+ },
132
+ mockOptions,
133
+ );
134
+
135
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
136
+ { mockSingleAgent: true },
137
+ {
138
+ selectedDocs: [{ path: "/doc1", content: "content" }],
139
+ docsDir: "./docs",
140
+ forceRegenerate: true,
141
+ customData: { key: "value" },
142
+ },
143
+ );
144
+ });
145
+
146
+ // BATCH DOCUMENT ROUTING TESTS
147
+ test("should route to batchUpdateDocument when selectedDocs has multiple items", async () => {
148
+ const result = await checkUpdateIsSingle(
149
+ {
150
+ selectedDocs: ["doc1", "doc2"],
151
+ customParam: "test",
152
+ },
153
+ mockOptions,
154
+ );
155
+
156
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
157
+ { mockBatchAgent: true },
158
+ {
159
+ selectedDocs: ["doc1", "doc2"],
160
+ customParam: "test",
161
+ },
162
+ );
163
+ expect(result).toEqual({ mockResult: true });
164
+ });
165
+
166
+ test("should route to batchUpdateDocument when selectedDocs has three items", async () => {
167
+ await checkUpdateIsSingle(
168
+ {
169
+ selectedDocs: ["doc1", "doc2", "doc3"],
170
+ },
171
+ mockOptions,
172
+ );
173
+
174
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
175
+ { mockBatchAgent: true },
176
+ {
177
+ selectedDocs: ["doc1", "doc2", "doc3"],
178
+ },
179
+ );
180
+ });
181
+
182
+ test("should pass through all parameters to batch document agent", async () => {
183
+ await checkUpdateIsSingle(
184
+ {
185
+ selectedDocs: [
186
+ { path: "/doc1", content: "content1" },
187
+ { path: "/doc2", content: "content2" },
188
+ ],
189
+ docsDir: "./docs",
190
+ forceRegenerate: false,
191
+ batchSize: 10,
192
+ customSettings: { parallel: true },
193
+ },
194
+ mockOptions,
195
+ );
196
+
197
+ expect(mockOptions.context.invoke).toHaveBeenCalledWith(
198
+ { mockBatchAgent: true },
199
+ {
200
+ selectedDocs: [
201
+ { path: "/doc1", content: "content1" },
202
+ { path: "/doc2", content: "content2" },
203
+ ],
204
+ docsDir: "./docs",
205
+ forceRegenerate: false,
206
+ batchSize: 10,
207
+ customSettings: { parallel: true },
208
+ },
209
+ );
210
+ });
211
+
212
+ // ERROR HANDLING TESTS
213
+ test("should handle and re-throw agent invocation errors for single document", async () => {
214
+ const mockError = new Error("Agent execution failed");
215
+ mockOptions.context.invoke.mockRejectedValue(mockError);
216
+
217
+ await expect(checkUpdateIsSingle({ selectedDocs: ["doc1"] }, mockOptions)).rejects.toThrow(
218
+ "Agent execution failed",
219
+ );
220
+
221
+ expect(consoleSpy).toHaveBeenCalledWith(
222
+ "Error invoking updateSingleDocument:",
223
+ "Agent execution failed",
224
+ );
225
+ });
226
+
227
+ test("should handle and re-throw agent invocation errors for batch documents", async () => {
228
+ const mockError = new Error("Batch processing failed");
229
+ mockOptions.context.invoke.mockRejectedValue(mockError);
230
+
231
+ await expect(
232
+ checkUpdateIsSingle({ selectedDocs: ["doc1", "doc2"] }, mockOptions),
233
+ ).rejects.toThrow("Batch processing failed");
234
+
235
+ expect(consoleSpy).toHaveBeenCalledWith(
236
+ "Error invoking batchUpdateDocument:",
237
+ "Batch processing failed",
238
+ );
239
+ });
240
+
241
+ test("should log specific error message for single document failures", async () => {
242
+ const mockError = new Error("Network timeout");
243
+ mockOptions.context.invoke.mockRejectedValue(mockError);
244
+
245
+ try {
246
+ await checkUpdateIsSingle({ selectedDocs: ["doc1"] }, mockOptions);
247
+ } catch {
248
+ // Expected to throw
249
+ }
250
+
251
+ expect(consoleSpy).toHaveBeenCalledWith(
252
+ "Error invoking updateSingleDocument:",
253
+ "Network timeout",
254
+ );
255
+ });
256
+
257
+ test("should log specific error message for batch document failures", async () => {
258
+ const mockError = new Error("Memory limit exceeded");
259
+ mockOptions.context.invoke.mockRejectedValue(mockError);
260
+
261
+ try {
262
+ await checkUpdateIsSingle({ selectedDocs: ["doc1", "doc2", "doc3"] }, mockOptions);
263
+ } catch {
264
+ // Expected to throw
265
+ }
266
+
267
+ expect(consoleSpy).toHaveBeenCalledWith(
268
+ "Error invoking batchUpdateDocument:",
269
+ "Memory limit exceeded",
270
+ );
271
+ });
272
+
273
+ // RESULT PASSING TESTS
274
+ test("should return result from single document agent unchanged", async () => {
275
+ const mockResult = {
276
+ success: true,
277
+ processedDocs: ["doc1"],
278
+ metadata: { timestamp: "2023-01-01" },
279
+ };
280
+ mockOptions.context.invoke.mockResolvedValue(mockResult);
281
+
282
+ const result = await checkUpdateIsSingle({ selectedDocs: ["doc1"] }, mockOptions);
283
+
284
+ expect(result).toEqual(mockResult);
285
+ });
286
+
287
+ test("should return result from batch document agent unchanged", async () => {
288
+ const mockResult = {
289
+ success: true,
290
+ processedDocs: ["doc1", "doc2"],
291
+ errors: [],
292
+ summary: "All documents processed successfully",
293
+ };
294
+ mockOptions.context.invoke.mockResolvedValue(mockResult);
295
+
296
+ const result = await checkUpdateIsSingle({ selectedDocs: ["doc1", "doc2"] }, mockOptions);
297
+
298
+ expect(result).toEqual(mockResult);
299
+ });
300
+ });
@@ -0,0 +1,326 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import updateDocumentContent from "../../../../agents/update/document-tools/update-document-content.mjs";
3
+
4
+ describe("update-document-content", () => {
5
+ // INPUT VALIDATION TESTS
6
+ test("should return error when originalContent is not provided", async () => {
7
+ const result = await updateDocumentContent({ diffPatch: "valid patch" });
8
+ expect(result.success).toBe(false);
9
+ expect(result.error).toContain("originalContent");
10
+ expect(result.message).toBe("Invalid input parameters");
11
+ });
12
+
13
+ test("should return error when originalContent is not a string", async () => {
14
+ const result = await updateDocumentContent({ originalContent: 123, diffPatch: "valid patch" });
15
+ expect(result.success).toBe(false);
16
+ expect(result.error).toContain("originalContent");
17
+ expect(result.message).toBe("Invalid input parameters");
18
+ });
19
+
20
+ test("should return error when originalContent is empty string", async () => {
21
+ const result = await updateDocumentContent({ originalContent: "", diffPatch: "valid patch" });
22
+ expect(result.success).toBe(false);
23
+ expect(result.error).toContain("Original content is required");
24
+ expect(result.message).toBe("Invalid input parameters");
25
+ });
26
+
27
+ test("should return error when diffPatch is not provided", async () => {
28
+ const result = await updateDocumentContent({ originalContent: "original content" });
29
+ expect(result.success).toBe(false);
30
+ expect(result.error).toContain("diffPatch");
31
+ expect(result.message).toBe("Invalid input parameters");
32
+ });
33
+
34
+ test("should return error when diffPatch is not a string", async () => {
35
+ const result = await updateDocumentContent({
36
+ originalContent: "original content",
37
+ diffPatch: null,
38
+ });
39
+ expect(result.success).toBe(false);
40
+ expect(result.error).toContain("diffPatch");
41
+ expect(result.message).toBe("Invalid input parameters");
42
+ });
43
+
44
+ test("should return error when diffPatch is empty string", async () => {
45
+ const result = await updateDocumentContent({
46
+ originalContent: "original content",
47
+ diffPatch: "",
48
+ });
49
+ expect(result.success).toBe(false);
50
+ expect(result.error).toContain("Diff patch is required");
51
+ expect(result.message).toBe("Invalid input parameters");
52
+ });
53
+
54
+ // SUCCESSFUL PATCH APPLICATION TESTS
55
+ test("should successfully apply simple diff patch", async () => {
56
+ const originalContent = "line 1\nline 2\nline 3";
57
+ const diffPatch = "@@ -1,3 +1,3 @@\n line 1\n-line 2\n+updated line 2\n line 3";
58
+
59
+ const result = await updateDocumentContent({ originalContent, diffPatch });
60
+
61
+ expect(result.success).toBe(true);
62
+ expect(result.updatedContent).toBe("line 1\nupdated line 2\nline 3");
63
+ expect(result.message).toBe("Document content updated successfully");
64
+ });
65
+
66
+ test("should handle diff with multiple hunks", async () => {
67
+ const originalContent = "line 1\nline 2\nline 3\nline 4\nline 5";
68
+ const diffPatch =
69
+ "@@ -1,2 +1,2 @@\n-line 1\n+updated line 1\n line 2\n@@ -4,2 +4,2 @@\n line 4\n-line 5\n+updated line 5";
70
+
71
+ const result = await updateDocumentContent({ originalContent, diffPatch });
72
+
73
+ expect(result.success).toBe(true);
74
+ expect(result.updatedContent).toBe("updated line 1\nline 2\nline 3\nline 4\nupdated line 5");
75
+ });
76
+
77
+ test("should handle diff with additions only", async () => {
78
+ const originalContent = "line 1\nline 2";
79
+ const diffPatch = "@@ -2,0 +3,2 @@\n line 2\n+new line 3\n+new line 4";
80
+
81
+ const result = await updateDocumentContent({ originalContent, diffPatch });
82
+
83
+ expect(result.success).toBe(true);
84
+ expect(result.updatedContent).toBe("line 1\nline 2\nnew line 3\nnew line 4");
85
+ });
86
+
87
+ test("should handle diff with deletions only", async () => {
88
+ const originalContent = "line 1\nline 2\nline 3\nline 4";
89
+ const diffPatch = "@@ -2,2 +2,0 @@\n line 1\n-line 2\n-line 3\n line 4";
90
+
91
+ const result = await updateDocumentContent({ originalContent, diffPatch });
92
+
93
+ expect(result.success).toBe(true);
94
+ expect(result.updatedContent).toBe("line 1\nline 4");
95
+ });
96
+
97
+ test("should handle diff with context lines", async () => {
98
+ const originalContent = "line 1\nline 2\nline 3\nline 4\nline 5";
99
+ const diffPatch =
100
+ "@@ -2,3 +2,3 @@\n line 1\n line 2\n-line 3\n+updated line 3\n line 4\n line 5";
101
+
102
+ const result = await updateDocumentContent({ originalContent, diffPatch });
103
+
104
+ expect(result.success).toBe(true);
105
+ expect(result.updatedContent).toBe("line 1\nline 2\nupdated line 3\nline 4\nline 5");
106
+ });
107
+
108
+ // DIFF PATCH PARSING TESTS
109
+ test("should return error for invalid diff format", async () => {
110
+ const originalContent = "line 1\nline 2";
111
+ const diffPatch = "invalid diff format";
112
+
113
+ const result = await updateDocumentContent({ originalContent, diffPatch });
114
+
115
+ expect(result).toEqual({
116
+ success: false,
117
+ error: "No valid hunks found in diff",
118
+ message: "Invalid diff format: No valid hunks found or parsing failed",
119
+ });
120
+ });
121
+
122
+ test("should return error for empty diff", async () => {
123
+ const originalContent = "line 1\nline 2";
124
+ const diffPatch = "\n\n";
125
+
126
+ const result = await updateDocumentContent({ originalContent, diffPatch });
127
+
128
+ expect(result).toEqual({
129
+ success: false,
130
+ error: "No valid hunks found in diff",
131
+ message: "Invalid diff format: No valid hunks found or parsing failed",
132
+ });
133
+ });
134
+
135
+ test("should handle malformed hunk headers gracefully", async () => {
136
+ const originalContent = "line 1\nline 2";
137
+ const diffPatch = "@@malformed header@@\n-line 1\n+updated line 1";
138
+
139
+ const result = await updateDocumentContent({ originalContent, diffPatch });
140
+
141
+ expect(result.success).toBe(false);
142
+ expect(result.message).toBe("Invalid diff format: No valid hunks found or parsing failed");
143
+ });
144
+
145
+ // LINE NUMBER FIXING TESTS
146
+ test("should fix line numbers when patch position is off", async () => {
147
+ const originalContent = "prefix line\nline 1\nline 2\nline 3";
148
+ // This diff patch expects to find "line 1" at line 1, but it's actually at line 2
149
+ const diffPatch = "@@ -1,2 +1,2 @@\n-line 1\n+updated line 1\n line 2";
150
+
151
+ const result = await updateDocumentContent({ originalContent, diffPatch });
152
+
153
+ expect(result.success).toBe(true);
154
+ expect(result.updatedContent).toBe("prefix line\nupdated line 1\nline 2\nline 3");
155
+ });
156
+
157
+ test("should use fuzzy matching when exact match fails", async () => {
158
+ const originalContent = "similar line 1\nsimilar line 2\ndifferent content\nsimilar line 3";
159
+ // This patch refers to lines that are similar but not exactly matching
160
+ const diffPatch = "@@ -10,2 +10,2 @@\n-similar line 1\n+updated line 1\n similar line 2";
161
+
162
+ const result = await updateDocumentContent({ originalContent, diffPatch });
163
+
164
+ expect(result.success).toBe(true);
165
+ expect(result.updatedContent).toBe(
166
+ "updated line 1\nsimilar line 2\ndifferent content\nsimilar line 3",
167
+ );
168
+ });
169
+
170
+ test("should return error when no matching context found", async () => {
171
+ const originalContent = "completely different content\nnothing matches";
172
+ const diffPatch =
173
+ "@@ -1,2 +1,2 @@\n-line that doesn't exist\n+updated line\n another nonexistent line";
174
+
175
+ const result = await updateDocumentContent({ originalContent, diffPatch });
176
+
177
+ expect(result.success).toBe(false);
178
+ expect(result.error).toContain("Cannot find matching context");
179
+ expect(result.message).toBe("Cannot fix diff line number issues");
180
+ });
181
+
182
+ // COMPLEX DIFF SCENARIOS
183
+ test("should handle diff with no context lines", async () => {
184
+ const originalContent = "line 1\nline 2\nline 3";
185
+ const diffPatch = "@@ -2,1 +2,1 @@\n-line 2\n+updated line 2";
186
+
187
+ const result = await updateDocumentContent({ originalContent, diffPatch });
188
+
189
+ expect(result.success).toBe(true);
190
+ expect(result.updatedContent).toBe("line 1\nupdated line 2\nline 3");
191
+ });
192
+
193
+ test("should handle diff with single line count (implicit 1)", async () => {
194
+ const originalContent = "line 1\nline 2\nline 3";
195
+ const diffPatch = "@@ -2 +2 @@\n-line 2\n+updated line 2";
196
+
197
+ const result = await updateDocumentContent({ originalContent, diffPatch });
198
+
199
+ expect(result.success).toBe(true);
200
+ expect(result.updatedContent).toBe("line 1\nupdated line 2\nline 3");
201
+ });
202
+
203
+ test("should handle multiple changes in single hunk", async () => {
204
+ const originalContent = "line 1\nline 2\nline 3\nline 4\nline 5";
205
+ const diffPatch =
206
+ "@@ -2,3 +2,4 @@\n line 1\n-line 2\n-line 3\n+updated line 2\n+new line\n+updated line 3\n line 4";
207
+
208
+ const result = await updateDocumentContent({ originalContent, diffPatch });
209
+
210
+ expect(result.success).toBe(true);
211
+ expect(result.updatedContent).toBe(
212
+ "line 1\nupdated line 2\nnew line\nupdated line 3\nline 4\nline 5",
213
+ );
214
+ });
215
+
216
+ // EDGE CASES
217
+ test("should handle empty lines in diff", async () => {
218
+ const originalContent = "line 1\n\nline 3";
219
+ const diffPatch = "@@ -1,3 +1,3 @@\n line 1\n \n-line 3\n+updated line 3";
220
+
221
+ const result = await updateDocumentContent({ originalContent, diffPatch });
222
+
223
+ expect(result.success).toBe(true);
224
+ expect(result.updatedContent).toBe("line 1\n\nupdated line 3");
225
+ });
226
+
227
+ test("should handle diff with trailing newlines", async () => {
228
+ const originalContent = "line 1\nline 2\n";
229
+ const diffPatch = "@@ -1,2 +1,2 @@\n-line 1\n+updated line 1\n line 2";
230
+
231
+ const result = await updateDocumentContent({ originalContent, diffPatch });
232
+
233
+ expect(result.success).toBe(true);
234
+ expect(result.updatedContent).toBe("updated line 1\nline 2\n");
235
+ });
236
+
237
+ // REALISTIC DOCUMENTATION UPDATE SCENARIOS
238
+ test("should handle updating code blocks", async () => {
239
+ const originalContent =
240
+ "# API Reference\n\n```javascript\nfunction oldFunction() {\n return 'old';\n}\n```\n\nDescription here.";
241
+ const diffPatch =
242
+ "@@ -3,3 +3,3 @@\n # API Reference\n \n ```javascript\n-function oldFunction() {\n- return 'old';\n+function newFunction() {\n+ return 'new';\n }\n ```";
243
+
244
+ const result = await updateDocumentContent({ originalContent, diffPatch });
245
+
246
+ expect(result.success).toBe(true);
247
+ expect(result.updatedContent).toContain("function newFunction()");
248
+ expect(result.updatedContent).toContain("return 'new';");
249
+ });
250
+
251
+ test("should handle adding new sections", async () => {
252
+ const originalContent =
253
+ "# Documentation\n\n## Section 1\n\nContent 1\n\n## Section 3\n\nContent 3";
254
+ const diffPatch =
255
+ "@@ -4,0 +5,3 @@\n Content 1\n \n+## Section 2\n+\n+Content 2\n+\n ## Section 3";
256
+
257
+ const result = await updateDocumentContent({ originalContent, diffPatch });
258
+
259
+ expect(result.success).toBe(true);
260
+ expect(result.updatedContent).toContain("## Section 2");
261
+ expect(result.updatedContent).toContain("Content 2");
262
+ });
263
+
264
+ test("should handle removing outdated content", async () => {
265
+ const originalContent =
266
+ "# Guide\n\n## Current Feature\n\nThis is current.\n\n## Deprecated Feature\n\nThis is deprecated.\n\n## Another Feature\n\nThis is also current.";
267
+ const diffPatch =
268
+ "@@ -5,4 +5,0 @@\n This is current.\n \n-## Deprecated Feature\n-\n-This is deprecated.\n-\n ## Another Feature";
269
+
270
+ const result = await updateDocumentContent({ originalContent, diffPatch });
271
+
272
+ expect(result.success).toBe(true);
273
+ expect(result.updatedContent).not.toContain("Deprecated Feature");
274
+ expect(result.updatedContent).toContain("Current Feature");
275
+ expect(result.updatedContent).toContain("Another Feature");
276
+ });
277
+
278
+ // ERROR SCENARIOS WITH REALISTIC CONTENT
279
+ test("should handle conflicting patches gracefully", async () => {
280
+ const originalContent = "line 1\nline 2\nline 3";
281
+ // This patch tries to modify content that doesn't match exactly
282
+ const diffPatch =
283
+ "@@ -1,3 +1,3 @@\n-different line 1\n-different line 2\n+updated line 1\n+updated line 2\n line 3";
284
+
285
+ const result = await updateDocumentContent({ originalContent, diffPatch });
286
+
287
+ expect(result.success).toBe(false);
288
+ expect(result.error).toContain("Cannot find matching context");
289
+ });
290
+
291
+ test("should handle patches with incorrect line counts", async () => {
292
+ const originalContent = "line 1\nline 2\nline 3\nline 4";
293
+ // Incorrect line count in hunk header (says 5 lines but content has 4)
294
+ const diffPatch = "@@ -1,5 +1,3 @@\n-line 1\n-line 2\n line 3\n line 4";
295
+
296
+ const result = await updateDocumentContent({ originalContent, diffPatch });
297
+
298
+ expect(result.success).toBe(true);
299
+ expect(result.updatedContent).toBe("line 3\nline 4");
300
+ });
301
+
302
+ // FUZZY MATCHING EDGE CASES
303
+ test("should succeed fuzzy matching with high similarity", async () => {
304
+ const originalContent =
305
+ "function calculateTotal(items) {\n let total = 0;\n for (item of items) {\n total += item.price;\n }\n return total;\n}";
306
+ // Patch has slight differences but should match with fuzzy matching
307
+ const diffPatch =
308
+ "@@ -2,2 +2,2 @@\n function calculateTotal(items) {\n- let total = 0;\n+ let sum = 0;\n for (item of items) {";
309
+
310
+ const result = await updateDocumentContent({ originalContent, diffPatch });
311
+
312
+ expect(result.success).toBe(true);
313
+ expect(result.updatedContent).toContain("let sum = 0;");
314
+ });
315
+
316
+ test("should fail fuzzy matching with low similarity", async () => {
317
+ const originalContent = "completely different content\nwith nothing similar\nat all";
318
+ const diffPatch =
319
+ "@@ -1,2 +1,2 @@\n-some totally different text\n+updated text\n that has no relation";
320
+
321
+ const result = await updateDocumentContent({ originalContent, diffPatch });
322
+
323
+ expect(result.success).toBe(false);
324
+ expect(result.error).toContain("Cannot find matching context");
325
+ });
326
+ });