sumulige-claude 1.5.1 → 1.6.0

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 (223) 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/README.md +280 -529
  48. package/cli.js +19 -3
  49. package/package.json +29 -3
  50. package/template/.codex/README.md +69 -0
  51. package/template/.codex/config.toml +56 -0
  52. package/template/AGENTS.md +94 -0
  53. package/.claude/.kickoff-hint.txt +0 -52
  54. package/.claude/.sumulige-claude-version +0 -1
  55. package/.claude/.version +0 -1
  56. package/.claude/AGENTS.md +0 -42
  57. package/.claude/ANCHORS.md +0 -40
  58. package/.claude/CLAUDE.md +0 -138
  59. package/.claude/MEMORY.md +0 -69
  60. package/.claude/PROJECT_LOG.md +0 -101
  61. package/.claude/THINKING_CHAIN_GUIDE.md +0 -287
  62. package/.claude/USAGE.md +0 -175
  63. package/.claude/boris-optimizations.md +0 -167
  64. package/.claude/handoffs/INDEX.md +0 -21
  65. package/.claude/handoffs/LATEST.md +0 -76
  66. package/.claude/handoffs/handoff_2026-01-22T13-07-04-757Z.md +0 -76
  67. package/.claude/quality-gate.json +0 -82
  68. package/.claude/rag/skill-index.json +0 -135
  69. package/.claude/settings.json +0 -99
  70. package/.claude/settings.local.json +0 -175
  71. package/.claude/templates/PROJECT_KICKOFF.md +0 -89
  72. package/.claude/templates/PROJECT_PROPOSAL.md +0 -227
  73. package/.claude/templates/TASK_PLAN.md +0 -121
  74. package/.claude/templates/hooks/README.md +0 -302
  75. package/.claude/templates/hooks/hook.sh.template +0 -94
  76. package/.claude/templates/hooks/user-prompt-submit.cjs.template +0 -116
  77. package/.claude/templates/hooks/user-response-submit.cjs.template +0 -94
  78. package/.claude/templates/hooks/validate.js +0 -173
  79. package/.claude/templates/tasks/develop.md +0 -69
  80. package/.claude/templates/tasks/research.md +0 -64
  81. package/.claude/templates/tasks/test.md +0 -96
  82. package/.claude/thinking-routes/.last-sync +0 -1
  83. package/.claude/thinking-routes/QUICKREF.md +0 -98
  84. package/.claude/workflow/document-scanner.js +0 -426
  85. package/.claude/workflow/knowledge-engine.js +0 -941
  86. package/.claude/workflow/notebooklm/browser.js +0 -1028
  87. package/.claude/workflow/phases/phase1-research.js +0 -578
  88. package/.claude/workflow/phases/phase1-research.ts +0 -465
  89. package/.claude/workflow/phases/phase2-approve.js +0 -722
  90. package/.claude/workflow/phases/phase3-plan.js +0 -1200
  91. package/.claude/workflow/phases/phase4-develop.js +0 -894
  92. package/.claude/workflow/search-cache.js +0 -230
  93. package/.claude/workflow/templates/approval.md +0 -315
  94. package/.claude/workflow/templates/development.md +0 -377
  95. package/.claude/workflow/templates/planning.md +0 -328
  96. package/.claude/workflow/templates/research.md +0 -250
  97. package/.claude/workflow/types.js +0 -37
  98. package/.claude/workflow/web-search.js +0 -278
  99. package/.claude-plugin/marketplace.json +0 -71
  100. package/.github/workflows/sync-skills.yml +0 -74
  101. package/.versionrc +0 -25
  102. package/AGENTS.md +0 -580
  103. package/CHANGELOG.md +0 -481
  104. package/CLAUDE-template.md +0 -114
  105. package/DEV_TOOLS_GUIDE.md +0 -190
  106. package/PROJECT_STRUCTURE.md +0 -266
  107. package/Q&A.md +0 -325
  108. package/config/defaults.json +0 -34
  109. package/config/official-skills.json +0 -183
  110. package/config/quality-gate.json +0 -67
  111. package/config/skill-categories.json +0 -40
  112. package/config/version-manifest.json +0 -85
  113. package/demos/power-3d-scatter.html +0 -683
  114. package/development/cache/web-search/search_1193d605f8eb364651fc2f2041b58a31.json +0 -36
  115. package/development/cache/web-search/search_3798bf06960edc125f744a1abb5b72c5.json +0 -36
  116. package/development/cache/web-search/search_37c7d4843a53f0d83f1122a6f908a2a3.json +0 -36
  117. package/development/cache/web-search/search_44166fa0153709ee168485a22aa0ab40.json +0 -36
  118. package/development/cache/web-search/search_4deaebb1f77e86a8ca066dc5a49c59fd.json +0 -36
  119. package/development/cache/web-search/search_94da91789466070a7f545612e73c7372.json +0 -36
  120. package/development/cache/web-search/search_dd5de8491b8b803a3cb01339cd210fb0.json +0 -36
  121. package/development/knowledge-base/.index.clean.json +0 -1
  122. package/development/knowledge-base/.index.json +0 -486
  123. package/development/knowledge-base/test-best-practices.md +0 -29
  124. package/development/projects/proj_mkh1pazz_ixmt1/phase1/feasibility-report.md +0 -160
  125. package/development/projects/proj_mkh4jvnb_z7rwf/phase1/feasibility-report.md +0 -160
  126. package/development/projects/proj_mkh4jxkd_ewz5a/phase1/feasibility-report.md +0 -160
  127. package/development/projects/proj_mkh4k84n_ni73k/phase1/feasibility-report.md +0 -160
  128. package/development/projects/proj_mkh4wfyd_u9w88/phase1/feasibility-report.md +0 -160
  129. package/development/projects/proj_mkh4wsbo_iahvf/development/projects/proj_mkh4xbpg_4na5w/phase1/feasibility-report.md +0 -160
  130. package/development/projects/proj_mkh4wsbo_iahvf/phase1/feasibility-report.md +0 -160
  131. package/development/projects/proj_mkh4xulg_1ka8x/phase1/feasibility-report.md +0 -160
  132. package/development/projects/proj_mkh4xwhj_gch8j/phase1/feasibility-report.md +0 -160
  133. package/development/projects/proj_mkh4y2qk_9lm8z/phase1/feasibility-report.md +0 -160
  134. package/development/projects/proj_mkh4y2qk_9lm8z/phase2/requirements.md +0 -226
  135. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/PRD.md +0 -345
  136. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/TASK_PLAN.md +0 -284
  137. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/prototype/README.md +0 -14
  138. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/DEVELOPMENT_LOG.md +0 -35
  139. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/TASKS.md +0 -34
  140. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/.env.example +0 -5
  141. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/README.md +0 -60
  142. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/package.json +0 -25
  143. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/index.js +0 -70
  144. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/routes/index.js +0 -48
  145. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/health.test.js +0 -20
  146. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/jest.config.js +0 -21
  147. package/development/projects/proj_mkh7veqg_3lypc/phase1/feasibility-report.md +0 -160
  148. package/development/projects/proj_mkh7veqg_3lypc/phase2/requirements.md +0 -226
  149. package/development/projects/proj_mkh7veqg_3lypc/phase3/PRD.md +0 -345
  150. package/development/projects/proj_mkh7veqg_3lypc/phase3/TASK_PLAN.md +0 -284
  151. package/development/projects/proj_mkh7veqg_3lypc/phase3/prototype/README.md +0 -14
  152. package/development/projects/proj_mkh8k8fo_rmqn5/phase1/feasibility-report.md +0 -160
  153. package/development/projects/proj_mkh8xyhy_1vshq/phase1/feasibility-report.md +0 -178
  154. package/development/projects/proj_mkh8zddd_dhamf/phase1/feasibility-report.md +0 -377
  155. package/development/projects/proj_mkh8zddd_dhamf/phase2/requirements.md +0 -442
  156. package/development/projects/proj_mkh8zddd_dhamf/phase3/api-design.md +0 -800
  157. package/development/projects/proj_mkh8zddd_dhamf/phase3/architecture.md +0 -625
  158. package/development/projects/proj_mkh8zddd_dhamf/phase3/data-model.md +0 -830
  159. package/development/projects/proj_mkh8zddd_dhamf/phase3/risks.md +0 -957
  160. package/development/projects/proj_mkh8zddd_dhamf/phase3/wbs.md +0 -381
  161. package/development/todos/.state.json +0 -19
  162. package/development/todos/INDEX.md +0 -63
  163. package/development/todos/active/_README.md +0 -49
  164. package/development/todos/archived/_README.md +0 -11
  165. package/development/todos/backlog/_README.md +0 -11
  166. package/development/todos/backlog/mcp-integration.md +0 -35
  167. package/development/todos/completed/_README.md +0 -11
  168. package/development/todos/completed/boris-optimizations.md +0 -39
  169. package/development/todos/completed/develop/local-knowledge-index.md +0 -85
  170. package/development/todos/completed/develop/todo-system.md +0 -47
  171. package/development/todos/completed/develop/web-search-integration.md +0 -83
  172. package/development/todos/completed/test/phase1-e2e-test.md +0 -103
  173. package/docs/DEVELOPMENT.md +0 -461
  174. package/docs/MARKETPLACE.md +0 -352
  175. package/docs/RELEASE.md +0 -93
  176. package/jest.config.js +0 -63
  177. package/lib/commands.js +0 -3588
  178. package/lib/config-manager.js +0 -441
  179. package/lib/config-schema.js +0 -408
  180. package/lib/config-validator.js +0 -330
  181. package/lib/config.js +0 -122
  182. package/lib/errors.js +0 -305
  183. package/lib/incremental-sync.js +0 -274
  184. package/lib/marketplace.js +0 -487
  185. package/lib/migrations.js +0 -154
  186. package/lib/permission-audit.js +0 -255
  187. package/lib/quality-gate.js +0 -431
  188. package/lib/quality-rules.js +0 -373
  189. package/lib/utils.js +0 -150
  190. package/lib/version-check.js +0 -169
  191. package/lib/version-manifest.js +0 -171
  192. package/project-paradigm.md +0 -313
  193. package/prompts/how-to-find.md +0 -163
  194. package/prompts/linus-architect.md +0 -71
  195. package/prompts/software-architect.md +0 -173
  196. package/prompts/web-designer.md +0 -249
  197. package/scripts/fix-hooks.mjs +0 -97
  198. package/scripts/sync-external.mjs +0 -298
  199. package/scripts/sync-to-home.sh +0 -108
  200. package/scripts/update-registry.mjs +0 -325
  201. package/sources.yaml +0 -83
  202. package/tests/README.md +0 -263
  203. package/tests/commands.test.js +0 -1086
  204. package/tests/config-manager.test.js +0 -677
  205. package/tests/config-schema.test.js +0 -425
  206. package/tests/config-validator.test.js +0 -436
  207. package/tests/config.test.js +0 -100
  208. package/tests/errors.test.js +0 -477
  209. package/tests/manual/phase1-e2e.sh +0 -389
  210. package/tests/manual/phase2-test-cases.md +0 -311
  211. package/tests/manual/phase3-test-cases.md +0 -309
  212. package/tests/manual/phase4-test-cases.md +0 -414
  213. package/tests/manual/test-cases.md +0 -417
  214. package/tests/marketplace.test.js +0 -420
  215. package/tests/migrations.test.js +0 -187
  216. package/tests/quality-gate.test.js +0 -679
  217. package/tests/quality-rules.test.js +0 -619
  218. package/tests/sync-external.test.js +0 -214
  219. package/tests/update-registry.test.js +0 -251
  220. package/tests/utils.test.js +0 -171
  221. package/tests/version-check.test.js +0 -75
  222. package/tests/web-search.test.js +0 -392
  223. 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
- });