sumulige-claude 1.5.1 → 1.5.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 (219) hide show
  1. package/.claude/hooks/hook-registry.json +0 -15
  2. package/.claude/rules/coding-style.md +18 -7
  3. package/.claude/rules/hooks.md +15 -4
  4. package/.claude/rules/performance.md +15 -5
  5. package/.claude/rules/security.md +140 -4
  6. package/.claude/rules/testing.md +138 -9
  7. package/.claude/rules/web-design-standard.md +16 -5
  8. package/.claude/skills/algorithmic-art/metadata.yaml +28 -0
  9. package/.claude/skills/api-tester/SKILL.md +61 -0
  10. package/.claude/skills/api-tester/examples/basic.md +3 -0
  11. package/.claude/skills/api-tester/metadata.yaml +30 -0
  12. package/.claude/skills/api-tester/templates/default.md +3 -0
  13. package/.claude/skills/brand-guidelines/metadata.yaml +26 -0
  14. package/.claude/skills/canvas-design/metadata.yaml +27 -0
  15. package/.claude/skills/code-reviewer-123/SKILL.md +61 -0
  16. package/.claude/skills/code-reviewer-123/examples/basic.md +3 -0
  17. package/.claude/skills/code-reviewer-123/metadata.yaml +30 -0
  18. package/.claude/skills/code-reviewer-123/templates/default.md +3 -0
  19. package/.claude/skills/doc-coauthoring/metadata.yaml +27 -0
  20. package/.claude/skills/docx/metadata.yaml +30 -0
  21. package/.claude/skills/frontend-design/metadata.yaml +28 -0
  22. package/.claude/skills/internal-comms/metadata.yaml +28 -0
  23. package/.claude/skills/mcp-builder/metadata.yaml +26 -0
  24. package/.claude/skills/my-skill/SKILL.md +61 -0
  25. package/.claude/skills/my-skill/examples/basic.md +3 -0
  26. package/.claude/skills/my-skill/metadata.yaml +30 -0
  27. package/.claude/skills/my-skill/templates/default.md +3 -0
  28. package/.claude/skills/pdf/metadata.yaml +29 -0
  29. package/.claude/skills/pptx/metadata.yaml +29 -0
  30. package/.claude/skills/react-best-practices/metadata.yaml +26 -0
  31. package/.claude/skills/react-node-practices/SKILL.md +409 -0
  32. package/.claude/skills/react-node-practices/metadata.yaml +56 -0
  33. package/.claude/skills/skill-creator/metadata.yaml +25 -0
  34. package/.claude/skills/slack-gif-creator/metadata.yaml +28 -0
  35. package/.claude/skills/test-skill-name/SKILL.md +61 -0
  36. package/.claude/skills/test-skill-name/examples/basic.md +3 -0
  37. package/.claude/skills/test-skill-name/metadata.yaml +30 -0
  38. package/.claude/skills/test-skill-name/templates/default.md +3 -0
  39. package/.claude/skills/test-workflow/metadata.yaml +32 -0
  40. package/.claude/skills/theme-factory/metadata.yaml +26 -0
  41. package/.claude/skills/threejs-fundamentals/metadata.yaml +27 -0
  42. package/.claude/skills/web-artifacts-builder/metadata.yaml +30 -0
  43. package/.claude/skills/web-design-guidelines/metadata.yaml +26 -0
  44. package/.claude/skills/webapp-testing/metadata.yaml +26 -0
  45. package/.claude/skills/xlsx/metadata.yaml +29 -0
  46. package/LICENSE +21 -0
  47. package/cli.js +1 -1
  48. package/package.json +25 -3
  49. package/.claude/.kickoff-hint.txt +0 -52
  50. package/.claude/.sumulige-claude-version +0 -1
  51. package/.claude/.version +0 -1
  52. package/.claude/AGENTS.md +0 -42
  53. package/.claude/ANCHORS.md +0 -40
  54. package/.claude/CLAUDE.md +0 -138
  55. package/.claude/MEMORY.md +0 -69
  56. package/.claude/PROJECT_LOG.md +0 -101
  57. package/.claude/THINKING_CHAIN_GUIDE.md +0 -287
  58. package/.claude/USAGE.md +0 -175
  59. package/.claude/boris-optimizations.md +0 -167
  60. package/.claude/handoffs/INDEX.md +0 -21
  61. package/.claude/handoffs/LATEST.md +0 -76
  62. package/.claude/handoffs/handoff_2026-01-22T13-07-04-757Z.md +0 -76
  63. package/.claude/quality-gate.json +0 -82
  64. package/.claude/rag/skill-index.json +0 -135
  65. package/.claude/settings.json +0 -99
  66. package/.claude/settings.local.json +0 -175
  67. package/.claude/templates/PROJECT_KICKOFF.md +0 -89
  68. package/.claude/templates/PROJECT_PROPOSAL.md +0 -227
  69. package/.claude/templates/TASK_PLAN.md +0 -121
  70. package/.claude/templates/hooks/README.md +0 -302
  71. package/.claude/templates/hooks/hook.sh.template +0 -94
  72. package/.claude/templates/hooks/user-prompt-submit.cjs.template +0 -116
  73. package/.claude/templates/hooks/user-response-submit.cjs.template +0 -94
  74. package/.claude/templates/hooks/validate.js +0 -173
  75. package/.claude/templates/tasks/develop.md +0 -69
  76. package/.claude/templates/tasks/research.md +0 -64
  77. package/.claude/templates/tasks/test.md +0 -96
  78. package/.claude/thinking-routes/.last-sync +0 -1
  79. package/.claude/thinking-routes/QUICKREF.md +0 -98
  80. package/.claude/workflow/document-scanner.js +0 -426
  81. package/.claude/workflow/knowledge-engine.js +0 -941
  82. package/.claude/workflow/notebooklm/browser.js +0 -1028
  83. package/.claude/workflow/phases/phase1-research.js +0 -578
  84. package/.claude/workflow/phases/phase1-research.ts +0 -465
  85. package/.claude/workflow/phases/phase2-approve.js +0 -722
  86. package/.claude/workflow/phases/phase3-plan.js +0 -1200
  87. package/.claude/workflow/phases/phase4-develop.js +0 -894
  88. package/.claude/workflow/search-cache.js +0 -230
  89. package/.claude/workflow/templates/approval.md +0 -315
  90. package/.claude/workflow/templates/development.md +0 -377
  91. package/.claude/workflow/templates/planning.md +0 -328
  92. package/.claude/workflow/templates/research.md +0 -250
  93. package/.claude/workflow/types.js +0 -37
  94. package/.claude/workflow/web-search.js +0 -278
  95. package/.claude-plugin/marketplace.json +0 -71
  96. package/.github/workflows/sync-skills.yml +0 -74
  97. package/.versionrc +0 -25
  98. package/AGENTS.md +0 -580
  99. package/CHANGELOG.md +0 -481
  100. package/CLAUDE-template.md +0 -114
  101. package/DEV_TOOLS_GUIDE.md +0 -190
  102. package/PROJECT_STRUCTURE.md +0 -266
  103. package/Q&A.md +0 -325
  104. package/config/defaults.json +0 -34
  105. package/config/official-skills.json +0 -183
  106. package/config/quality-gate.json +0 -67
  107. package/config/skill-categories.json +0 -40
  108. package/config/version-manifest.json +0 -85
  109. package/demos/power-3d-scatter.html +0 -683
  110. package/development/cache/web-search/search_1193d605f8eb364651fc2f2041b58a31.json +0 -36
  111. package/development/cache/web-search/search_3798bf06960edc125f744a1abb5b72c5.json +0 -36
  112. package/development/cache/web-search/search_37c7d4843a53f0d83f1122a6f908a2a3.json +0 -36
  113. package/development/cache/web-search/search_44166fa0153709ee168485a22aa0ab40.json +0 -36
  114. package/development/cache/web-search/search_4deaebb1f77e86a8ca066dc5a49c59fd.json +0 -36
  115. package/development/cache/web-search/search_94da91789466070a7f545612e73c7372.json +0 -36
  116. package/development/cache/web-search/search_dd5de8491b8b803a3cb01339cd210fb0.json +0 -36
  117. package/development/knowledge-base/.index.clean.json +0 -1
  118. package/development/knowledge-base/.index.json +0 -486
  119. package/development/knowledge-base/test-best-practices.md +0 -29
  120. package/development/projects/proj_mkh1pazz_ixmt1/phase1/feasibility-report.md +0 -160
  121. package/development/projects/proj_mkh4jvnb_z7rwf/phase1/feasibility-report.md +0 -160
  122. package/development/projects/proj_mkh4jxkd_ewz5a/phase1/feasibility-report.md +0 -160
  123. package/development/projects/proj_mkh4k84n_ni73k/phase1/feasibility-report.md +0 -160
  124. package/development/projects/proj_mkh4wfyd_u9w88/phase1/feasibility-report.md +0 -160
  125. package/development/projects/proj_mkh4wsbo_iahvf/development/projects/proj_mkh4xbpg_4na5w/phase1/feasibility-report.md +0 -160
  126. package/development/projects/proj_mkh4wsbo_iahvf/phase1/feasibility-report.md +0 -160
  127. package/development/projects/proj_mkh4xulg_1ka8x/phase1/feasibility-report.md +0 -160
  128. package/development/projects/proj_mkh4xwhj_gch8j/phase1/feasibility-report.md +0 -160
  129. package/development/projects/proj_mkh4y2qk_9lm8z/phase1/feasibility-report.md +0 -160
  130. package/development/projects/proj_mkh4y2qk_9lm8z/phase2/requirements.md +0 -226
  131. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/PRD.md +0 -345
  132. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/TASK_PLAN.md +0 -284
  133. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/prototype/README.md +0 -14
  134. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/DEVELOPMENT_LOG.md +0 -35
  135. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/TASKS.md +0 -34
  136. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/.env.example +0 -5
  137. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/README.md +0 -60
  138. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/package.json +0 -25
  139. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/index.js +0 -70
  140. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/routes/index.js +0 -48
  141. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/health.test.js +0 -20
  142. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/jest.config.js +0 -21
  143. package/development/projects/proj_mkh7veqg_3lypc/phase1/feasibility-report.md +0 -160
  144. package/development/projects/proj_mkh7veqg_3lypc/phase2/requirements.md +0 -226
  145. package/development/projects/proj_mkh7veqg_3lypc/phase3/PRD.md +0 -345
  146. package/development/projects/proj_mkh7veqg_3lypc/phase3/TASK_PLAN.md +0 -284
  147. package/development/projects/proj_mkh7veqg_3lypc/phase3/prototype/README.md +0 -14
  148. package/development/projects/proj_mkh8k8fo_rmqn5/phase1/feasibility-report.md +0 -160
  149. package/development/projects/proj_mkh8xyhy_1vshq/phase1/feasibility-report.md +0 -178
  150. package/development/projects/proj_mkh8zddd_dhamf/phase1/feasibility-report.md +0 -377
  151. package/development/projects/proj_mkh8zddd_dhamf/phase2/requirements.md +0 -442
  152. package/development/projects/proj_mkh8zddd_dhamf/phase3/api-design.md +0 -800
  153. package/development/projects/proj_mkh8zddd_dhamf/phase3/architecture.md +0 -625
  154. package/development/projects/proj_mkh8zddd_dhamf/phase3/data-model.md +0 -830
  155. package/development/projects/proj_mkh8zddd_dhamf/phase3/risks.md +0 -957
  156. package/development/projects/proj_mkh8zddd_dhamf/phase3/wbs.md +0 -381
  157. package/development/todos/.state.json +0 -19
  158. package/development/todos/INDEX.md +0 -63
  159. package/development/todos/active/_README.md +0 -49
  160. package/development/todos/archived/_README.md +0 -11
  161. package/development/todos/backlog/_README.md +0 -11
  162. package/development/todos/backlog/mcp-integration.md +0 -35
  163. package/development/todos/completed/_README.md +0 -11
  164. package/development/todos/completed/boris-optimizations.md +0 -39
  165. package/development/todos/completed/develop/local-knowledge-index.md +0 -85
  166. package/development/todos/completed/develop/todo-system.md +0 -47
  167. package/development/todos/completed/develop/web-search-integration.md +0 -83
  168. package/development/todos/completed/test/phase1-e2e-test.md +0 -103
  169. package/docs/DEVELOPMENT.md +0 -461
  170. package/docs/MARKETPLACE.md +0 -352
  171. package/docs/RELEASE.md +0 -93
  172. package/jest.config.js +0 -63
  173. package/lib/commands.js +0 -3588
  174. package/lib/config-manager.js +0 -441
  175. package/lib/config-schema.js +0 -408
  176. package/lib/config-validator.js +0 -330
  177. package/lib/config.js +0 -122
  178. package/lib/errors.js +0 -305
  179. package/lib/incremental-sync.js +0 -274
  180. package/lib/marketplace.js +0 -487
  181. package/lib/migrations.js +0 -154
  182. package/lib/permission-audit.js +0 -255
  183. package/lib/quality-gate.js +0 -431
  184. package/lib/quality-rules.js +0 -373
  185. package/lib/utils.js +0 -150
  186. package/lib/version-check.js +0 -169
  187. package/lib/version-manifest.js +0 -171
  188. package/project-paradigm.md +0 -313
  189. package/prompts/how-to-find.md +0 -163
  190. package/prompts/linus-architect.md +0 -71
  191. package/prompts/software-architect.md +0 -173
  192. package/prompts/web-designer.md +0 -249
  193. package/scripts/fix-hooks.mjs +0 -97
  194. package/scripts/sync-external.mjs +0 -298
  195. package/scripts/sync-to-home.sh +0 -108
  196. package/scripts/update-registry.mjs +0 -325
  197. package/sources.yaml +0 -83
  198. package/tests/README.md +0 -263
  199. package/tests/commands.test.js +0 -1086
  200. package/tests/config-manager.test.js +0 -677
  201. package/tests/config-schema.test.js +0 -425
  202. package/tests/config-validator.test.js +0 -436
  203. package/tests/config.test.js +0 -100
  204. package/tests/errors.test.js +0 -477
  205. package/tests/manual/phase1-e2e.sh +0 -389
  206. package/tests/manual/phase2-test-cases.md +0 -311
  207. package/tests/manual/phase3-test-cases.md +0 -309
  208. package/tests/manual/phase4-test-cases.md +0 -414
  209. package/tests/manual/test-cases.md +0 -417
  210. package/tests/marketplace.test.js +0 -420
  211. package/tests/migrations.test.js +0 -187
  212. package/tests/quality-gate.test.js +0 -679
  213. package/tests/quality-rules.test.js +0 -619
  214. package/tests/sync-external.test.js +0 -214
  215. package/tests/update-registry.test.js +0 -251
  216. package/tests/utils.test.js +0 -171
  217. package/tests/version-check.test.js +0 -75
  218. package/tests/web-search.test.js +0 -392
  219. package/thinkinglens-silent.md +0 -138
@@ -1,677 +0,0 @@
1
- /**
2
- * Config Manager 模块单元测试
3
- */
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
-
9
- describe('Config Manager Module', () => {
10
- const { ConfigManager } = require('../lib/config-manager');
11
- const { ConfigError } = require('../lib/errors');
12
-
13
- // Temporary test directories
14
- const tempBaseDir = path.join(os.tmpdir(), 'smc-config-test-' + Date.now());
15
- const tempConfigDir = path.join(tempBaseDir, '.claude');
16
- const tempConfigFile = path.join(tempConfigDir, 'config.json');
17
- const tempBackupDir = path.join(tempConfigDir, 'backups');
18
- const tempHistoryFile = path.join(tempConfigDir, 'config-history.jsonl');
19
-
20
- // Sample configs (matching config-schema requirements)
21
- const sampleConfig = {
22
- version: '1.0.7',
23
- model: 'claude-opus-4.5',
24
- agents: {
25
- conductor: { role: 'coordination' },
26
- architect: { role: 'design' }
27
- },
28
- skills: ['anthropics/skills'],
29
- hooks: {
30
- preTask: [],
31
- postTask: []
32
- }
33
- };
34
-
35
- const sampleConfig2 = {
36
- version: '1.0.8',
37
- model: 'claude-opus-4.5',
38
- agents: {
39
- conductor: { role: 'coordination' },
40
- architect: { role: 'design' },
41
- builder: { role: 'implementation' }
42
- },
43
- skills: ['anthropics/skills', 'numman-ali/n-skills'],
44
- hooks: {
45
- preTask: [],
46
- postTask: []
47
- }
48
- };
49
-
50
- beforeAll(() => {
51
- // Create test directory structure
52
- fs.mkdirSync(tempConfigDir, { recursive: true });
53
- fs.mkdirSync(tempBackupDir, { recursive: true });
54
- });
55
-
56
- afterAll(() => {
57
- if (fs.existsSync(tempBaseDir)) {
58
- fs.rmSync(tempBaseDir, { recursive: true, force: true });
59
- }
60
- });
61
-
62
- afterEach(() => {
63
- // Clean up config and history files between tests
64
- if (fs.existsSync(tempConfigFile)) {
65
- fs.unlinkSync(tempConfigFile);
66
- }
67
- if (fs.existsSync(tempHistoryFile)) {
68
- fs.unlinkSync(tempHistoryFile);
69
- }
70
- // Clean up backup files
71
- if (fs.existsSync(tempBackupDir)) {
72
- const backups = fs.readdirSync(tempBackupDir);
73
- backups.forEach(f => fs.unlinkSync(path.join(tempBackupDir, f)));
74
- }
75
- });
76
-
77
- describe('constructor', () => {
78
- it('should create manager with default paths', () => {
79
- const manager = new ConfigManager();
80
-
81
- expect(manager.configDir).toContain('.claude');
82
- expect(manager.configFile).toContain('config.json');
83
- expect(manager.backupDir).toContain('backups');
84
- expect(manager.maxBackups).toBe(10);
85
- });
86
-
87
- it('should accept custom options', () => {
88
- const manager = new ConfigManager({
89
- configDir: tempConfigDir,
90
- configFile: tempConfigFile,
91
- backupDir: tempBackupDir,
92
- maxBackups: 5
93
- });
94
-
95
- expect(manager.configDir).toBe(tempConfigDir);
96
- expect(manager.configFile).toBe(tempConfigFile);
97
- expect(manager.backupDir).toBe(tempBackupDir);
98
- expect(manager.maxBackups).toBe(5);
99
- });
100
-
101
- it('should create directories if they don\'t exist', () => {
102
- const newDir = path.join(os.tmpdir(), 'smc-new-test-' + Date.now());
103
- const newConfigFile = path.join(newDir, 'config.json');
104
-
105
- const manager = new ConfigManager({
106
- configDir: newDir,
107
- configFile: newConfigFile
108
- });
109
-
110
- expect(fs.existsSync(newDir)).toBe(true);
111
-
112
- // Cleanup
113
- fs.rmSync(newDir, { recursive: true, force: true });
114
- });
115
-
116
- it('should initialize validator', () => {
117
- const manager = new ConfigManager();
118
-
119
- expect(manager.validator).toBeDefined();
120
- });
121
-
122
- it('should respect strict option', () => {
123
- const strictManager = new ConfigManager({ strict: true });
124
- const looseManager = new ConfigManager({ strict: false });
125
-
126
- expect(strictManager.validator.strict).toBe(true);
127
- expect(looseManager.validator.strict).toBe(false);
128
- });
129
- });
130
-
131
- describe('load', () => {
132
- let manager;
133
-
134
- beforeEach(() => {
135
- manager = new ConfigManager({
136
- configDir: tempConfigDir,
137
- configFile: tempConfigFile
138
- });
139
- });
140
-
141
- it('should return defaults when config file doesn\'t exist', () => {
142
- const result = manager.load({ useDefaults: true });
143
-
144
- expect(result).toBeDefined();
145
- expect(result.version).toBeDefined();
146
- expect(result.agents).toBeDefined();
147
- });
148
-
149
- it('should throw error when file doesn\'t exist and useDefaults is false', () => {
150
- expect(() => {
151
- manager.load({ useDefaults: false });
152
- }).toThrow(ConfigError);
153
- });
154
-
155
- it('should load existing config file', () => {
156
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
157
-
158
- const result = manager.load();
159
-
160
- expect(result).toEqual(sampleConfig);
161
- });
162
-
163
- it('should expand environment variables', () => {
164
- process.env.TEST_VAR = 'test-value';
165
- process.env.TEST_VAR_WITH_DEFAULT = 'override';
166
-
167
- const configWithEnv = {
168
- version: '1.0.0',
169
- apiKey: '${TEST_VAR}',
170
- url: '${TEST_VAR_WITH_DEFAULT:default-value}',
171
- nested: {
172
- value: '${TEST_VAR}'
173
- }
174
- };
175
-
176
- fs.writeFileSync(tempConfigFile, JSON.stringify(configWithEnv));
177
-
178
- const result = manager.load({ expandEnv: true });
179
-
180
- expect(result.apiKey).toBe('test-value');
181
- expect(result.url).toBe('override');
182
- expect(result.nested.value).toBe('test-value');
183
-
184
- delete process.env.TEST_VAR;
185
- delete process.env.TEST_VAR_WITH_DEFAULT;
186
- });
187
-
188
- it('should handle missing env vars with defaults', () => {
189
- const configWithEnv = {
190
- version: '1.0.0',
191
- value: '${MISSING_VAR:default}'
192
- };
193
-
194
- fs.writeFileSync(tempConfigFile, JSON.stringify(configWithEnv));
195
-
196
- const result = manager.load({ expandEnv: true });
197
-
198
- expect(result.value).toBe('default');
199
- });
200
-
201
- it('should handle missing env vars without defaults', () => {
202
- const configWithEnv = {
203
- version: '1.0.0',
204
- value: '${MISSING_VAR}'
205
- };
206
-
207
- fs.writeFileSync(tempConfigFile, JSON.stringify(configWithEnv));
208
-
209
- const result = manager.load({ expandEnv: true });
210
-
211
- expect(result.value).toBe('');
212
- });
213
-
214
- it('should expand env vars in arrays', () => {
215
- process.env.ARRAY_VAR = 'item1';
216
-
217
- const configWithEnv = {
218
- version: '1.0.0',
219
- items: ['${ARRAY_VAR}', 'static']
220
- };
221
-
222
- fs.writeFileSync(tempConfigFile, JSON.stringify(configWithEnv));
223
-
224
- const result = manager.load({ expandEnv: true });
225
-
226
- expect(result.items[0]).toBe('item1');
227
- expect(result.items[1]).toBe('static');
228
-
229
- delete process.env.ARRAY_VAR;
230
- });
231
-
232
- it('should validate when strict is true', () => {
233
- fs.writeFileSync(tempConfigFile, JSON.stringify({ invalid: 'config' }));
234
-
235
- expect(() => {
236
- manager.load({ strict: true, useDefaults: false });
237
- }).toThrow();
238
- });
239
-
240
- it('should skip validation when strict is false', () => {
241
- const manager = new ConfigManager({
242
- configDir: tempConfigDir,
243
- configFile: tempConfigFile,
244
- strict: false
245
- });
246
-
247
- fs.writeFileSync(tempConfigFile, JSON.stringify({ any: 'config' }));
248
-
249
- const result = manager.load({ strict: false });
250
-
251
- expect(result.any).toBe('config');
252
- });
253
- });
254
-
255
- describe('save', () => {
256
- let manager;
257
-
258
- beforeEach(() => {
259
- manager = new ConfigManager({
260
- configDir: tempConfigDir,
261
- configFile: tempConfigFile,
262
- backupDir: tempBackupDir
263
- });
264
- });
265
-
266
- it('should save config to file', () => {
267
- const result = manager.save(sampleConfig, { backup: false, validate: false });
268
-
269
- expect(result.success).toBe(true);
270
- expect(fs.existsSync(tempConfigFile)).toBe(true);
271
-
272
- const saved = JSON.parse(fs.readFileSync(tempConfigFile, 'utf-8'));
273
- expect(saved).toEqual(sampleConfig);
274
- });
275
-
276
- it('should create backup when backup option is true', () => {
277
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
278
-
279
- const result = manager.save(sampleConfig2, { backup: true });
280
-
281
- expect(result.success).toBe(true);
282
- expect(result.backup).toBeDefined();
283
- expect(fs.existsSync(result.backup)).toBe(true);
284
- });
285
-
286
- it('should not create backup when file doesn\'t exist', () => {
287
- const result = manager.save(sampleConfig, { backup: true });
288
-
289
- expect(result.success).toBe(true);
290
- expect(result.backup).toBeNull();
291
- });
292
-
293
- it('should validate before saving by default', () => {
294
- const invalidConfig = { invalid: 'data' };
295
-
296
- expect(() => {
297
- manager.save(invalidConfig);
298
- }).toThrow(ConfigError);
299
- });
300
-
301
- it('should skip validation when validate is false', () => {
302
- const result = manager.save({ any: 'data' }, { validate: false });
303
-
304
- expect(result.success).toBe(true);
305
- });
306
-
307
- it('should record change in history', () => {
308
- manager.save(sampleConfig, { backup: false, validate: false });
309
-
310
- expect(fs.existsSync(tempHistoryFile)).toBe(true);
311
-
312
- const historyContent = fs.readFileSync(tempHistoryFile, 'utf-8');
313
- expect(historyContent).toContain('"action":"save"');
314
- });
315
- });
316
-
317
- describe('rollback', () => {
318
- let manager;
319
-
320
- beforeEach(() => {
321
- manager = new ConfigManager({
322
- configDir: tempConfigDir,
323
- configFile: tempConfigFile,
324
- backupDir: tempBackupDir
325
- });
326
-
327
- // Create initial config and a backup
328
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
329
- manager.save(sampleConfig, { backup: true, validate: false });
330
- manager.save(sampleConfig2, { backup: true, validate: false });
331
- });
332
-
333
- it('should rollback to latest backup by default', () => {
334
- // Overwrite with different config
335
- fs.writeFileSync(tempConfigFile, JSON.stringify({ version: '1.0.0', model: 'claude-opus-4.5', skills: [] }));
336
-
337
- const result = manager.rollback();
338
-
339
- expect(result.success).toBe(true);
340
- expect(fs.existsSync(tempConfigFile)).toBe(true);
341
-
342
- const rolledBack = JSON.parse(fs.readFileSync(tempConfigFile, 'utf-8'));
343
- // Should rollback to sampleConfig (v1.0.7) which was backed up before saving sampleConfig2
344
- expect(rolledBack.version).toBe('1.0.7');
345
- });
346
-
347
- it('should rollback to specific version', () => {
348
- const backups = manager.listBackups();
349
-
350
- if (backups.length > 1) {
351
- const targetVersion = backups[1].version;
352
- const result = manager.rollback(targetVersion);
353
-
354
- expect(result.success).toBe(true);
355
- expect(result.restoredFrom).toBe(targetVersion);
356
- }
357
- });
358
-
359
- it('should throw error when no backups exist', () => {
360
- const emptyManager = new ConfigManager({
361
- configDir: tempConfigDir,
362
- backupDir: path.join(os.tmpdir(), 'no-backups-' + Date.now())
363
- });
364
-
365
- expect(() => {
366
- emptyManager.rollback();
367
- }).toThrow(ConfigError);
368
- });
369
-
370
- it('should throw error for non-existent version', () => {
371
- expect(() => {
372
- manager.rollback('non-existent-version');
373
- }).toThrow(ConfigError);
374
- });
375
-
376
- it('should backup current config before rollback', () => {
377
- fs.writeFileSync(tempConfigFile, JSON.stringify({ current: 'config' }));
378
-
379
- const backupsBefore = manager.listBackups().length;
380
-
381
- manager.rollback();
382
-
383
- const backupsAfter = manager.listBackups().length;
384
-
385
- // Should have one more backup (pre-rollback)
386
- expect(backupsAfter).toBeGreaterThan(backupsBefore);
387
- });
388
- });
389
-
390
- describe('listBackups', () => {
391
- let manager;
392
-
393
- beforeEach(() => {
394
- manager = new ConfigManager({
395
- configDir: tempConfigDir,
396
- configFile: tempConfigFile,
397
- backupDir: tempBackupDir
398
- });
399
- });
400
-
401
- it('should return empty array when no backups exist', () => {
402
- const backups = manager.listBackups();
403
-
404
- expect(backups).toEqual([]);
405
- });
406
-
407
- it('should return empty array when backup dir doesn\'t exist', () => {
408
- const managerWithoutDir = new ConfigManager({
409
- backupDir: path.join(os.tmpdir(), 'no-dir-' + Date.now())
410
- });
411
-
412
- const backups = managerWithoutDir.listBackups();
413
-
414
- expect(backups).toEqual([]);
415
- });
416
-
417
- it('should list all backups', () => {
418
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
419
- manager.save(sampleConfig, { backup: true, validate: false });
420
-
421
- const backups = manager.listBackups();
422
-
423
- expect(backups.length).toBe(1);
424
- expect(backups[0]).toMatchObject({
425
- file: expect.stringContaining('config-'),
426
- version: expect.any(String),
427
- size: expect.any(Number)
428
- });
429
- expect(backups[0].timestamp).toBeDefined();
430
- expect(typeof backups[0].timestamp.getTime).toBe('function');
431
- });
432
-
433
- it('should sort backups by timestamp (newest first)', () => {
434
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
435
- manager.save(sampleConfig, { backup: true, validate: false });
436
- // Small delay to ensure different timestamp
437
- const start = Date.now();
438
- while (Date.now() - start < 10) {}
439
- manager.save(sampleConfig2, { backup: true, validate: false });
440
-
441
- const backups = manager.listBackups();
442
-
443
- if (backups.length >= 2) {
444
- expect(backups[0].timestamp.getTime()).toBeGreaterThanOrEqual(backups[1].timestamp.getTime());
445
- }
446
- });
447
-
448
- it('should respect maxBackups limit', () => {
449
- const managerWithLimit = new ConfigManager({
450
- configDir: tempConfigDir,
451
- configFile: tempConfigFile,
452
- backupDir: tempBackupDir,
453
- maxBackups: 2
454
- });
455
-
456
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
457
-
458
- // Create 3 backups
459
- managerWithLimit.save(sampleConfig, { backup: true, validate: false });
460
- const start = Date.now();
461
- while (Date.now() - start < 10) {}
462
- managerWithLimit.save(sampleConfig2, { backup: true, validate: false });
463
- const start2 = Date.now();
464
- while (Date.now() - start2 < 10) {}
465
- managerWithLimit.save(sampleConfig, { backup: true, validate: false });
466
-
467
- const backups = managerWithLimit.listBackups();
468
-
469
- // Should only return 2 (limited by maxBackups)
470
- expect(backups.length).toBe(2);
471
- });
472
- });
473
-
474
- describe('diff', () => {
475
- let manager;
476
-
477
- beforeEach(() => {
478
- manager = new ConfigManager({
479
- configDir: tempConfigDir,
480
- configFile: tempConfigFile
481
- });
482
- });
483
-
484
- it('should compute diff between two objects', () => {
485
- const changes = manager.diff(
486
- { version: '1.0.0', key: 'value' },
487
- { version: '1.0.1', key: 'value' }
488
- );
489
-
490
- expect(changes).toHaveLength(1);
491
- expect(changes[0]).toMatchObject({
492
- path: 'version',
493
- from: '1.0.0',
494
- to: '1.0.1',
495
- type: 'changed'
496
- });
497
- });
498
-
499
- it('should detect added properties', () => {
500
- const changes = manager.diff(
501
- { version: '1.0.0' },
502
- { version: '1.0.0', newKey: 'newValue' }
503
- );
504
-
505
- expect(changes).toHaveLength(1);
506
- expect(changes[0].type).toBe('added');
507
- });
508
-
509
- it('should detect removed properties', () => {
510
- const changes = manager.diff(
511
- { version: '1.0.0', oldKey: 'oldValue' },
512
- { version: '1.0.0' }
513
- );
514
-
515
- expect(changes).toHaveLength(1);
516
- expect(changes[0].type).toBe('removed');
517
- });
518
-
519
- it('should handle nested objects', () => {
520
- const changes = manager.diff(
521
- { nested: { a: 1, b: 2 } },
522
- { nested: { a: 1, b: 3 } }
523
- );
524
-
525
- expect(changes).toHaveLength(1);
526
- expect(changes[0].path).toBe('nested.b');
527
- });
528
-
529
- it('should accept file paths', () => {
530
- const file1 = path.join(tempBaseDir, 'config1.json');
531
- const file2 = path.join(tempBaseDir, 'config2.json');
532
-
533
- fs.writeFileSync(file1, JSON.stringify({ version: '1.0.0' }));
534
- fs.writeFileSync(file2, JSON.stringify({ version: '1.0.1' }));
535
-
536
- const changes = manager.diff(file1, file2);
537
-
538
- expect(changes.length).toBeGreaterThan(0);
539
- });
540
-
541
- it('should use current config when right is null', () => {
542
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
543
-
544
- const changes = manager.diff({ version: '1.0.0', model: 'claude-opus-4.5', skills: [] });
545
-
546
- expect(changes).toBeDefined();
547
- expect(changes.length).toBeGreaterThan(0);
548
- });
549
- });
550
-
551
- describe('getHistory', () => {
552
- let manager;
553
-
554
- beforeEach(() => {
555
- manager = new ConfigManager({
556
- configDir: tempConfigDir,
557
- configFile: tempConfigFile
558
- });
559
- });
560
-
561
- it('should return empty array when no history exists', () => {
562
- const history = manager.getHistory();
563
-
564
- expect(history).toEqual([]);
565
- });
566
-
567
- it('should return history entries', () => {
568
- manager.save(sampleConfig, { backup: false, validate: false });
569
-
570
- const history = manager.getHistory();
571
-
572
- expect(history.length).toBeGreaterThan(0);
573
- expect(history[0]).toMatchObject({
574
- timestamp: expect.any(String),
575
- action: expect.any(String)
576
- });
577
- });
578
-
579
- it('should respect limit parameter', () => {
580
- // Create multiple history entries
581
- manager.save(sampleConfig, { backup: false, validate: false });
582
- manager.save(sampleConfig2, { backup: false, validate: false });
583
-
584
- const limitedHistory = manager.getHistory(1);
585
-
586
- expect(limitedHistory.length).toBe(1);
587
- });
588
-
589
- it('should return entries in reverse chronological order', () => {
590
- manager.save(sampleConfig, { backup: false, validate: false });
591
- manager.save(sampleConfig2, { backup: false, validate: false });
592
-
593
- const history = manager.getHistory();
594
-
595
- expect(history).toHaveLength(2);
596
- });
597
- });
598
-
599
- describe('Integration Tests', () => {
600
- it('should handle full workflow: save -> list -> rollback', () => {
601
- const manager = new ConfigManager({
602
- configDir: tempConfigDir,
603
- configFile: tempConfigFile,
604
- backupDir: tempBackupDir
605
- });
606
-
607
- // Save initial config
608
- manager.save(sampleConfig, { backup: false, validate: false });
609
-
610
- // Save with backup
611
- manager.save(sampleConfig2, { backup: true, validate: false });
612
-
613
- // List backups
614
- const backups = manager.listBackups();
615
- expect(backups.length).toBe(1);
616
-
617
- // Modify current config
618
- fs.writeFileSync(tempConfigFile, JSON.stringify({ version: '1.0.0', model: 'claude-opus-4.5', skills: [] }));
619
-
620
- // Rollback
621
- const rollbackResult = manager.rollback();
622
- expect(rollbackResult.success).toBe(true);
623
-
624
- // Verify restored
625
- const restored = JSON.parse(fs.readFileSync(tempConfigFile, 'utf-8'));
626
- // Rollback gets the backup created before saving sampleConfig2, which is sampleConfig (v1.0.7)
627
- expect(restored.version).toBe('1.0.7');
628
- });
629
-
630
- it('should handle env expansion in saved config', () => {
631
- process.env.EXPAND_TEST = 'expanded';
632
-
633
- const manager = new ConfigManager({
634
- configDir: tempConfigDir,
635
- configFile: tempConfigFile
636
- });
637
-
638
- const config = {
639
- version: '1.0.0',
640
- value: '${EXPAND_TEST}'
641
- };
642
-
643
- manager.save(config, { backup: false, validate: false });
644
-
645
- // Load and verify expansion
646
- const loaded = manager.load({ expandEnv: true });
647
- expect(loaded.value).toBe('expanded');
648
-
649
- delete process.env.EXPAND_TEST;
650
- });
651
- });
652
-
653
- describe('Backup Management', () => {
654
- it('should clean old backups beyond maxBackups', () => {
655
- const manager = new ConfigManager({
656
- configDir: tempConfigDir,
657
- configFile: tempConfigFile,
658
- backupDir: tempBackupDir,
659
- maxBackups: 3
660
- });
661
-
662
- fs.writeFileSync(tempConfigFile, JSON.stringify(sampleConfig));
663
-
664
- // Create 5 backups
665
- for (let i = 0; i < 5; i++) {
666
- const start = Date.now();
667
- while (Date.now() - start < 5) {}
668
- manager.save({ ...sampleConfig, iteration: i }, { backup: true, validate: false });
669
- }
670
-
671
- const backupFiles = fs.readdirSync(tempBackupDir);
672
-
673
- // Should keep maxBackups (3) files, possibly plus one from initial write
674
- expect(backupFiles.length).toBeLessThanOrEqual(5);
675
- });
676
- });
677
- });