@aigne/doc-smith 0.8.11-beta → 0.8.11-beta.2

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 +22 -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
@@ -1,6 +1,5 @@
1
1
  <role>
2
- You are a "Feedback→Rule" converter. Transform one-time natural language feedback into a **single sentence**, **executable**, **reusable** instruction,
3
- and determine whether it needs **persistent saving**, along with its scope (global/structure/document/translation) and whether it should be limited to "input paths range".
2
+ You are a "Feedback→Rule" converter. Transform one-time natural language feedback into a **single sentence**, **executable**, **reusable** instruction, and determine whether it needs **persistent saving**, along with its scope (global/structure/document/translation) and whether it should be limited to "input paths range".
4
3
  </role>
5
4
 
6
5
  <input>
@@ -34,6 +33,7 @@ Save determination rules:
34
33
  **One-time operations (do not save)**:
35
34
  - Only corrects current version/typos/individual phrasing/local factual errors with no stable reusable value → `save=false`
36
35
  - Fixes that are highly specific to a single line or data point and unlikely to recur (e.g., "change the year from 2020 to 2021") → `save=false`
36
+ - Document structure adjustments, adding new documents, and moving document positions are always one-time operations → `save=false`
37
37
 
38
38
  **Reusable policies (save)**:
39
39
  - Writing styles, structural conventions, inclusion/exclusion items, translation conventions that are broadly applicable and should be consistently executed in the future → `save=true`
@@ -41,7 +41,7 @@ Save determination rules:
41
41
  **Duplication check (do not save)**:
42
42
  - If `existingPreferences` already contains **similar or covering** rules for current feedback intent, then `save=false`
43
43
  - Check logic: Compare feedback intent, rule meaning, and applicable scope. If new feedback is already sufficiently covered by existing rules, no need to save duplicates
44
- - If new feedback is **refinement, supplement, or conflicting correction** to existing rules, still can be `save=true`
44
+ - If new feedback is **refinement, supplement, or conflicting correction** to existing rules, it can still be `save=true`
45
45
 
46
46
  **Determination principle**:
47
47
  - Prioritize avoiding duplicate saves; if difficult to determine whether duplicate, prioritize `save=false` to avoid rule redundancy
@@ -10,11 +10,5 @@
10
10
  "include-component-in-tag": false,
11
11
  "prerelease-type": "beta"
12
12
  }
13
- },
14
- "plugins": [
15
- {
16
- "type": "node-workspace",
17
- "updatePeerDependencies": true
18
- }
19
- ]
13
+ }
20
14
  }
@@ -0,0 +1,280 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import chooseContents from "../../../agents/clear/choose-contents.mjs";
3
+
4
+ describe("choose-contents", () => {
5
+ let mockOptions;
6
+ let mockContext;
7
+ let mockClearAgents;
8
+
9
+ beforeEach(() => {
10
+ // Create mock clear agents
11
+ mockClearAgents = {
12
+ clearGeneratedDocs: mock(async () => ({
13
+ message: "Generated docs cleared",
14
+ cleared: true,
15
+ path: "/test/docs",
16
+ })),
17
+ clearDocumentStructure: mock(async () => ({
18
+ message: "Document structure cleared",
19
+ cleared: true,
20
+ path: "/test/structure.json",
21
+ })),
22
+ clearDocumentConfig: mock(async () => ({
23
+ message: "Document config cleared",
24
+ cleared: true,
25
+ path: "/test/config.yaml",
26
+ suggestions: ["Run `aigne doc init` to generate a fresh configuration file."],
27
+ })),
28
+ clearAuthTokens: mock(async () => ({
29
+ message: "Auth tokens cleared",
30
+ clearedCount: 2,
31
+ clearedSites: ["example.com", "test.com"],
32
+ })),
33
+ };
34
+
35
+ mockContext = {
36
+ agents: mockClearAgents,
37
+ invoke: mock(async (agent, input) => {
38
+ return await agent(input);
39
+ }),
40
+ };
41
+
42
+ mockOptions = {
43
+ prompts: {
44
+ checkbox: mock(async () => ["generatedDocs", "documentConfig"]),
45
+ },
46
+ context: mockContext,
47
+ };
48
+
49
+ // Clear mock call history
50
+ mockOptions.prompts.checkbox.mockClear();
51
+ mockContext.invoke.mockClear();
52
+ Object.values(mockClearAgents).forEach((agent) => {
53
+ agent.mockClear();
54
+ });
55
+ });
56
+
57
+ afterEach(() => {
58
+ // Note: Not using mock.restore() to avoid affecting other tests
59
+ });
60
+
61
+ test("should process provided targets correctly", async () => {
62
+ const result = await chooseContents(
63
+ { targets: ["generatedDocs", "documentConfig"] },
64
+ mockOptions,
65
+ );
66
+
67
+ expect(mockContext.invoke).toHaveBeenCalledTimes(2);
68
+ expect(mockClearAgents.clearGeneratedDocs).toHaveBeenCalled();
69
+ expect(mockClearAgents.clearDocumentConfig).toHaveBeenCalled();
70
+ expect(result.message).toContain("Cleanup completed successfully!");
71
+ });
72
+
73
+ test("should normalize target names case-insensitively", async () => {
74
+ const result = await chooseContents(
75
+ { targets: ["GeneratedDocs", "DOCUMENTCONFIG"] },
76
+ mockOptions,
77
+ );
78
+
79
+ expect(mockContext.invoke).toHaveBeenCalledTimes(2);
80
+ expect(mockClearAgents.clearGeneratedDocs).toHaveBeenCalled();
81
+ expect(mockClearAgents.clearDocumentConfig).toHaveBeenCalled();
82
+ expect(result.message).toContain("Cleanup completed successfully!");
83
+ });
84
+
85
+ test("should prompt user when no targets provided", async () => {
86
+ mockOptions.prompts.checkbox.mockResolvedValue(["generatedDocs"]);
87
+
88
+ await chooseContents({}, mockOptions);
89
+
90
+ expect(mockOptions.prompts.checkbox).toHaveBeenCalledWith({
91
+ message: "Select items to clear:",
92
+ choices: expect.arrayContaining([
93
+ expect.objectContaining({
94
+ name: "generated documents",
95
+ value: "generatedDocs",
96
+ }),
97
+ ]),
98
+ validate: expect.any(Function),
99
+ });
100
+ expect(mockClearAgents.clearGeneratedDocs).toHaveBeenCalled();
101
+ });
102
+
103
+ test("should handle empty selection from prompts", async () => {
104
+ mockOptions.prompts.checkbox.mockResolvedValue([]);
105
+
106
+ const result = await chooseContents({}, mockOptions);
107
+
108
+ expect(result.message).toBe("No items selected to clear.");
109
+ expect(mockContext.invoke).not.toHaveBeenCalled();
110
+ });
111
+
112
+ test("should return available options when no prompts available", async () => {
113
+ const optionsWithoutPrompts = { context: mockContext };
114
+
115
+ const result = await chooseContents({}, optionsWithoutPrompts);
116
+
117
+ expect(result.message).toBe(
118
+ "Available options to clear: generatedDocs, documentStructure, documentConfig, authTokens",
119
+ );
120
+ expect(result.availableTargets).toEqual([
121
+ "generatedDocs",
122
+ "documentStructure",
123
+ "documentConfig",
124
+ "authTokens",
125
+ ]);
126
+ });
127
+
128
+ test("should handle agent errors gracefully", async () => {
129
+ mockClearAgents.clearGeneratedDocs.mockResolvedValue({
130
+ error: true,
131
+ message: "Failed to clear docs",
132
+ });
133
+
134
+ const result = await chooseContents({ targets: ["generatedDocs"] }, mockOptions);
135
+
136
+ expect(result.message).toContain("Cleanup finished with some issues.");
137
+ expect(result.message).toContain("Failed to clear docs");
138
+ });
139
+
140
+ test("should collect suggestions from agents", async () => {
141
+ const result = await chooseContents({ targets: ["documentConfig"] }, mockOptions);
142
+
143
+ expect(result.message).toContain(
144
+ "Run `aigne doc init` to generate a fresh configuration file.",
145
+ );
146
+ });
147
+
148
+ test("should handle all valid targets", async () => {
149
+ const result = await chooseContents(
150
+ { targets: ["generatedDocs", "documentStructure", "documentConfig", "authTokens"] },
151
+ mockOptions,
152
+ );
153
+
154
+ expect(mockContext.invoke).toHaveBeenCalledTimes(4);
155
+ expect(result.message).toContain("Cleanup completed successfully!");
156
+ });
157
+
158
+ test("should have correct input schema", () => {
159
+ expect(chooseContents.input_schema).toBeDefined();
160
+ expect(chooseContents.input_schema.type).toBe("object");
161
+ expect(chooseContents.input_schema.properties.targets.type).toBe("array");
162
+ });
163
+
164
+ test("should have correct task metadata", () => {
165
+ expect(chooseContents.taskTitle).toBe("Choose contents to clear");
166
+ expect(chooseContents.description).toBe(
167
+ "Choose contents to clear and execute the appropriate clearing operations",
168
+ );
169
+ });
170
+
171
+ test("should handle unknown targets by filtering them out", async () => {
172
+ // Test that unknown targets are filtered out and valid ones are processed
173
+ // Mix unknown targets with valid ones
174
+ const result = await chooseContents(
175
+ { targets: ["unknownTarget1", "generatedDocs", "invalidTarget", "documentConfig"] },
176
+ mockOptions,
177
+ );
178
+
179
+ // Should process only the valid targets and succeed
180
+ expect(result.message).toContain("Cleanup completed successfully!");
181
+ expect(mockContext.invoke).toHaveBeenCalledTimes(2); // Only valid targets processed
182
+ expect(mockClearAgents.clearGeneratedDocs).toHaveBeenCalled();
183
+ expect(mockClearAgents.clearDocumentConfig).toHaveBeenCalled();
184
+ });
185
+
186
+ test("should handle missing clear agent in context", async () => {
187
+ const optionsWithoutAgent = {
188
+ context: {
189
+ agents: {},
190
+ invoke: mockContext.invoke,
191
+ },
192
+ };
193
+
194
+ const result = await chooseContents({ targets: ["generatedDocs"] }, optionsWithoutAgent);
195
+
196
+ expect(result.message).toContain("Cleanup finished with some issues.");
197
+ expect(result.message).toContain("Clear agent 'clearGeneratedDocs' not found in context");
198
+ });
199
+
200
+ test("should handle agent execution errors", async () => {
201
+ const errorContext = {
202
+ agents: mockClearAgents,
203
+ invoke: mock(async () => {
204
+ throw new Error("Agent execution failed");
205
+ }),
206
+ };
207
+
208
+ const optionsWithError = {
209
+ context: errorContext,
210
+ };
211
+
212
+ const result = await chooseContents({ targets: ["generatedDocs"] }, optionsWithError);
213
+
214
+ expect(result.message).toContain("Cleanup finished with some issues.");
215
+ expect(result.message).toContain("Failed to clear generated documents: Agent execution failed");
216
+ });
217
+
218
+ test("should handle duplicate targets", async () => {
219
+ await chooseContents(
220
+ { targets: ["generatedDocs", "generatedDocs", "documentConfig"] },
221
+ mockOptions,
222
+ );
223
+
224
+ // Should only invoke each agent once due to deduplication
225
+ expect(mockContext.invoke).toHaveBeenCalledTimes(2);
226
+ expect(mockClearAgents.clearGeneratedDocs).toHaveBeenCalledTimes(1);
227
+ expect(mockClearAgents.clearDocumentConfig).toHaveBeenCalledTimes(1);
228
+ });
229
+
230
+ test("should handle empty and whitespace targets", async () => {
231
+ await chooseContents({ targets: ["", " ", "generatedDocs", null, undefined] }, mockOptions);
232
+
233
+ // Should only process the valid target
234
+ expect(mockContext.invoke).toHaveBeenCalledTimes(1);
235
+ expect(mockClearAgents.clearGeneratedDocs).toHaveBeenCalled();
236
+ });
237
+
238
+ test("should add default suggestion when config is cleared", async () => {
239
+ // Mock clearDocumentConfig to return cleared: true but no suggestions
240
+ mockClearAgents.clearDocumentConfig.mockResolvedValue({
241
+ message: "Document config cleared",
242
+ cleared: true,
243
+ path: "/test/config.yaml",
244
+ });
245
+
246
+ const result = await chooseContents({ targets: ["documentConfig"] }, mockOptions);
247
+
248
+ expect(result.message).toContain(
249
+ "Run `aigne doc init` to generate a fresh configuration file.",
250
+ );
251
+ });
252
+
253
+ test("should not duplicate default suggestion when already present", async () => {
254
+ // Mock clearDocumentConfig to return the default suggestion
255
+ mockClearAgents.clearDocumentConfig.mockResolvedValue({
256
+ message: "Document config cleared",
257
+ cleared: true,
258
+ path: "/test/config.yaml",
259
+ suggestions: ["Run `aigne doc init` to generate a fresh configuration file."],
260
+ });
261
+
262
+ const result = await chooseContents({ targets: ["documentConfig"] }, mockOptions);
263
+
264
+ // Should only appear once in the message
265
+ const suggestionCount = (result.message.match(/Run `aigne doc init`/g) || []).length;
266
+ expect(suggestionCount).toBe(1);
267
+ });
268
+
269
+ test("should handle agents that return no cleared status", async () => {
270
+ mockClearAgents.clearGeneratedDocs.mockResolvedValue({
271
+ message: "No docs to clear",
272
+ // No cleared property
273
+ });
274
+
275
+ const result = await chooseContents({ targets: ["generatedDocs"] }, mockOptions);
276
+
277
+ expect(result.message).toContain("Cleanup completed successfully!");
278
+ expect(result.message).toContain("No docs to clear");
279
+ });
280
+ });
@@ -0,0 +1,268 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
2
+ import * as fs from "node:fs";
3
+ import * as fsPromises from "node:fs/promises";
4
+ import clearAuthTokens from "../../../agents/clear/clear-auth-tokens.mjs";
5
+
6
+ describe("clear-auth-tokens", () => {
7
+ let mockOptions;
8
+ let existsSyncSpy;
9
+ let readFileSpy;
10
+ let writeFileSpy;
11
+
12
+ beforeEach(() => {
13
+ mockOptions = {
14
+ prompts: {
15
+ checkbox: mock(async () => ["example.com"]),
16
+ },
17
+ };
18
+
19
+ // Mock file system operations
20
+ existsSyncSpy = spyOn(fs, "existsSync");
21
+ readFileSpy = spyOn(fsPromises, "readFile");
22
+ writeFileSpy = spyOn(fsPromises, "writeFile");
23
+
24
+ // Clear mock call history
25
+ mockOptions.prompts.checkbox.mockClear();
26
+ });
27
+
28
+ afterEach(() => {
29
+ // Restore all mocks
30
+ existsSyncSpy.mockRestore();
31
+ readFileSpy.mockRestore();
32
+ writeFileSpy.mockRestore();
33
+ });
34
+
35
+ test("should accept empty input", async () => {
36
+ // Mock file doesn't exist
37
+ existsSyncSpy.mockReturnValue(false);
38
+
39
+ const result = await clearAuthTokens({}, {});
40
+ expect(result).toBeDefined();
41
+ expect(result.message).toBeDefined();
42
+ expect(typeof result.message).toBe("string");
43
+ expect(result.message).toBe("No site authorizations found to clear");
44
+ });
45
+
46
+ test("should handle missing prompts gracefully", async () => {
47
+ // Mock file exists with some data
48
+ existsSyncSpy.mockReturnValue(true);
49
+ readFileSpy.mockResolvedValue('example.com:\n token: "test-token"');
50
+ writeFileSpy.mockResolvedValue();
51
+
52
+ const result = await clearAuthTokens({}, {});
53
+ expect(result).toBeDefined();
54
+ expect(result.message).toBeDefined();
55
+ expect(result.clearedCount).toBe(1);
56
+ expect(result.clearedSites).toEqual(["example.com"]);
57
+ });
58
+
59
+ test("should handle empty options", async () => {
60
+ // Mock file doesn't exist
61
+ existsSyncSpy.mockReturnValue(false);
62
+
63
+ const result = await clearAuthTokens({});
64
+ expect(result).toBeDefined();
65
+ expect(result.message).toBeDefined();
66
+ expect(result.message).toBe("No site authorizations found to clear");
67
+ });
68
+
69
+ test("should return consistent result structure", async () => {
70
+ // Mock file exists with multiple sites
71
+ existsSyncSpy.mockReturnValue(true);
72
+ readFileSpy.mockResolvedValue(
73
+ 'example.com:\n token: "test-token"\ntest.com:\n token: "test-token2"',
74
+ );
75
+ writeFileSpy.mockResolvedValue();
76
+
77
+ const result = await clearAuthTokens({}, mockOptions);
78
+ expect(result).toBeDefined();
79
+ expect(result).toHaveProperty("message");
80
+ expect(typeof result.message).toBe("string");
81
+
82
+ // Result may have additional properties depending on file state
83
+ if (result.clearedCount !== undefined) {
84
+ expect(typeof result.clearedCount).toBe("number");
85
+ }
86
+ if (result.clearedSites !== undefined) {
87
+ expect(Array.isArray(result.clearedSites)).toBe(true);
88
+ }
89
+ if (result.error !== undefined) {
90
+ expect(typeof result.error).toBe("boolean");
91
+ }
92
+ });
93
+
94
+ test("should handle prompts correctly when file exists (integration test)", async () => {
95
+ // Mock file exists with data
96
+ existsSyncSpy.mockReturnValue(true);
97
+ readFileSpy.mockResolvedValue('example.com:\n token: "test-token"');
98
+ writeFileSpy.mockResolvedValue();
99
+
100
+ const result = await clearAuthTokens({}, mockOptions);
101
+
102
+ expect(result).toBeDefined();
103
+ expect(result.message).toBeDefined();
104
+ expect(result.clearedCount).toBe(1);
105
+ expect(result.clearedSites).toEqual(["example.com"]);
106
+
107
+ // Verify that prompts were called
108
+ expect(mockOptions.prompts.checkbox.mock.calls.length).toBeGreaterThan(0);
109
+ });
110
+
111
+ test("should provide meaningful error messages", async () => {
112
+ // Mock file doesn't exist
113
+ existsSyncSpy.mockReturnValue(false);
114
+
115
+ const result = await clearAuthTokens({}, {});
116
+
117
+ // Should return a user-friendly message regardless of internal state
118
+ expect(result.message).toBeDefined();
119
+ expect(result.message.length).toBeGreaterThan(0);
120
+ expect(typeof result.message).toBe("string");
121
+ expect(result.message).toBe("No site authorizations found to clear");
122
+ });
123
+
124
+ test("should handle undefined input parameters", async () => {
125
+ // Mock file doesn't exist
126
+ existsSyncSpy.mockReturnValue(false);
127
+
128
+ const result = await clearAuthTokens(undefined, undefined);
129
+ expect(result).toBeDefined();
130
+ expect(result.message).toBeDefined();
131
+ expect(result.message).toBe("No site authorizations found to clear");
132
+ });
133
+
134
+ test("should handle null input parameters", async () => {
135
+ // Mock file doesn't exist
136
+ existsSyncSpy.mockReturnValue(false);
137
+
138
+ const result = await clearAuthTokens(null, null);
139
+ expect(result).toBeDefined();
140
+ expect(result.message).toBeDefined();
141
+ expect(result.message).toBe("No site authorizations found to clear");
142
+ });
143
+
144
+ test("should handle various option configurations", async () => {
145
+ // Mock file doesn't exist for all configurations
146
+ existsSyncSpy.mockReturnValue(false);
147
+
148
+ const configs = [
149
+ {},
150
+ { prompts: {} },
151
+ { prompts: { checkbox: undefined } },
152
+ { prompts: { checkbox: null } },
153
+ ];
154
+
155
+ for (const config of configs) {
156
+ const result = await clearAuthTokens({}, config);
157
+ expect(result).toBeDefined();
158
+ expect(result.message).toBeDefined();
159
+ expect(result.message).toBe("No site authorizations found to clear");
160
+ }
161
+ });
162
+
163
+ test("should maintain consistent behavior across calls", async () => {
164
+ // Mock file doesn't exist for both calls
165
+ existsSyncSpy.mockReturnValue(false);
166
+
167
+ const result1 = await clearAuthTokens({}, {});
168
+ const result2 = await clearAuthTokens({}, {});
169
+
170
+ // Both calls should return the same type of result structure
171
+ expect(typeof result1.message).toBe(typeof result2.message);
172
+ expect(result1).toHaveProperty("message");
173
+ expect(result2).toHaveProperty("message");
174
+ expect(result1.message).toBe(result2.message);
175
+ });
176
+
177
+ test("should handle prompt validation function", async () => {
178
+ // Mock file exists with data to trigger prompts
179
+ existsSyncSpy.mockReturnValue(true);
180
+ readFileSpy.mockResolvedValue('example.com:\n token: "test-token"');
181
+ writeFileSpy.mockResolvedValue();
182
+
183
+ // Test that if prompts are called, they have proper validation
184
+ try {
185
+ await clearAuthTokens({}, mockOptions);
186
+
187
+ // If prompts were called, check the validation function exists
188
+ if (mockOptions.prompts.checkbox.mock.calls.length > 0) {
189
+ const callArgs = mockOptions.prompts.checkbox.mock.calls[0][0];
190
+ expect(callArgs).toHaveProperty("validate");
191
+ expect(typeof callArgs.validate).toBe("function");
192
+
193
+ // Test validation function behavior
194
+ const validateFn = callArgs.validate;
195
+ expect(validateFn([])).toBe("Please select at least one site.");
196
+ expect(validateFn(["site1"])).toBe(true);
197
+ expect(validateFn(["site1", "site2"])).toBe(true);
198
+ }
199
+ } catch (error) {
200
+ // If there's an error, ensure it's handled gracefully
201
+ expect(error).toBeDefined();
202
+ }
203
+ });
204
+
205
+ test("should handle file read errors gracefully", async () => {
206
+ // Mock file exists but reading fails
207
+ existsSyncSpy.mockReturnValue(true);
208
+ readFileSpy.mockRejectedValue(new Error("Permission denied"));
209
+
210
+ const result = await clearAuthTokens({}, {});
211
+ expect(result).toBeDefined();
212
+ expect(result.message).toBeDefined();
213
+ expect(result.error).toBe(true);
214
+ expect(result.message).toContain("Failed to clear site authorizations");
215
+ });
216
+
217
+ test("should handle file write errors gracefully", async () => {
218
+ // Mock file exists, reading succeeds, but writing fails
219
+ existsSyncSpy.mockReturnValue(true);
220
+ readFileSpy.mockResolvedValue('example.com:\n token: "test-token"');
221
+ writeFileSpy.mockRejectedValue(new Error("Disk full"));
222
+
223
+ const result = await clearAuthTokens({}, {});
224
+ expect(result).toBeDefined();
225
+ expect(result.message).toBeDefined();
226
+ expect(result.error).toBe(true);
227
+ expect(result.message).toContain("Failed to clear site authorizations");
228
+ });
229
+
230
+ test("should handle empty file content", async () => {
231
+ // Mock file exists but is empty
232
+ existsSyncSpy.mockReturnValue(true);
233
+ readFileSpy.mockResolvedValue("");
234
+
235
+ const result = await clearAuthTokens({}, {});
236
+ expect(result).toBeDefined();
237
+ expect(result.message).toBeDefined();
238
+ expect(result.message).toBe("No site authorizations found to clear");
239
+ });
240
+
241
+ test("should handle malformed YAML content", async () => {
242
+ // Mock file exists with invalid YAML
243
+ existsSyncSpy.mockReturnValue(true);
244
+ readFileSpy.mockResolvedValue("invalid: yaml: content: [");
245
+
246
+ const result = await clearAuthTokens({}, {});
247
+ expect(result).toBeDefined();
248
+ expect(result.message).toBeDefined();
249
+ expect(result.error).toBe(true);
250
+ expect(result.message).toContain("Failed to clear site authorizations");
251
+ });
252
+
253
+ test("should handle empty selection from prompts", async () => {
254
+ // Mock file exists with data but user selects nothing
255
+ existsSyncSpy.mockReturnValue(true);
256
+ readFileSpy.mockResolvedValue('example.com:\n token: "test-token"');
257
+
258
+ const emptySelectionOptions = {
259
+ prompts: {
260
+ checkbox: mock(async () => []), // User selects nothing
261
+ },
262
+ };
263
+
264
+ const result = await clearAuthTokens({}, emptySelectionOptions);
265
+ expect(result).toBeDefined();
266
+ expect(result.message).toBe("No sites selected for clearing authorization");
267
+ });
268
+ });