acp-vscode 0.3.8 → 0.3.9

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.
@@ -0,0 +1,274 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const axios = require('axios');
5
+ jest.mock('axios');
6
+
7
+ let fetcher;
8
+ let fetchIndex;
9
+ let diskPaths;
10
+ const cache = require('../src/cache');
11
+
12
+ describe('fetcher verbose logging integration tests', () => {
13
+ const tmp = path.join(os.tmpdir(), `acp-fetcher-integration-${Date.now()}`);
14
+ const origCwd = process.cwd();
15
+ let origNodeEnv;
16
+
17
+ beforeAll(async () => {
18
+ await fs.ensureDir(tmp);
19
+ process.chdir(tmp);
20
+ origNodeEnv = process.env.NODE_ENV;
21
+ process.env.NODE_ENV = 'test';
22
+ // require after chdir so diskPaths uses tmp cwd
23
+ fetcher = require('../src/fetcher');
24
+ fetchIndex = fetcher.fetchIndex;
25
+ diskPaths = fetcher.diskPaths;
26
+ });
27
+
28
+ afterAll(async () => {
29
+ process.chdir(origCwd);
30
+ if (origNodeEnv === undefined) {
31
+ delete process.env.NODE_ENV;
32
+ } else {
33
+ process.env.NODE_ENV = origNodeEnv;
34
+ }
35
+ await fs.remove(tmp);
36
+ });
37
+
38
+ beforeEach(async () => {
39
+ cache.del('index');
40
+ const { DISK_CACHE_DIR } = diskPaths();
41
+ await fs.remove(DISK_CACHE_DIR).catch(() => {});
42
+ axios.get.mockReset();
43
+ // Clear env vars
44
+ delete process.env.ACP_INDEX_JSON;
45
+ delete process.env.ACP_REPOS_JSON;
46
+ });
47
+
48
+ test('verbose logging works through complete flow: env var -> parse -> fetch -> cache', async () => {
49
+ const testRepoConfig = [
50
+ {
51
+ id: 'test-acp-cli',
52
+ treeUrl: 'https://api.github.com/repos/AndyG-0/test-acp-cli/git/trees/main?recursive=1',
53
+ rawBase: 'https://raw.githubusercontent.com/AndyG-0/test-acp-cli/main'
54
+ }
55
+ ];
56
+ process.env.ACP_REPOS_JSON = JSON.stringify(testRepoConfig);
57
+
58
+ const logs = [];
59
+ const originalLog = console.log;
60
+ console.log = jest.fn(msg => logs.push(msg));
61
+
62
+ try {
63
+ // Mock the GitHub API responses
64
+ axios.get.mockImplementation((url) => {
65
+ if (url === 'https://api.github.com/repos/AndyG-0/test-acp-cli/git/trees/main?recursive=1') {
66
+ return Promise.resolve({
67
+ status: 200,
68
+ data: {
69
+ tree: [
70
+ { type: 'blob', path: 'prompts/sample-prompt.prompt.md' },
71
+ { type: 'blob', path: 'agents/sample-agent.agent.md' },
72
+ { type: 'blob', path: 'instructions/sample-instructions.instructions.md' },
73
+ { type: 'blob', path: 'skills/sample-skill/skill.md' },
74
+ { type: 'tree', path: 'skills/sample-skill' }
75
+ ]
76
+ }
77
+ });
78
+ }
79
+ // Mock content fetches
80
+ if (url.includes('raw.githubusercontent.com')) {
81
+ return Promise.resolve({
82
+ status: 200,
83
+ data: '---\ntitle: "Test Sample"\nversion: 1.0.0\n---\nContent here'
84
+ });
85
+ }
86
+ return Promise.reject(new Error('Unexpected URL: ' + url));
87
+ });
88
+
89
+ const result = await fetchIndex({ verbose: true });
90
+
91
+ // Verify the verbose logs captured key events
92
+ expect(logs.some(l => l.includes('fetchIndex called'))).toBe(true);
93
+ expect(logs.some(l => l.includes('ACP_REPOS_JSON environment variable detected'))).toBe(true);
94
+ expect(logs.some(l => l.includes('successfully parsed ACP_REPOS_JSON'))).toBe(true);
95
+ expect(logs.some(l => l.includes('using repos from ACP_REPOS_JSON: test-acp-cli'))).toBe(true);
96
+ expect(logs.some(l => l.includes('fetching tree for repo: test-acp-cli'))).toBe(true);
97
+ expect(logs.some(l => l.includes('successfully fetched tree for repo: test-acp-cli'))).toBe(true);
98
+ expect(logs.some(l => l.includes('blob items'))).toBe(true);
99
+ expect(logs.some(l => l.includes('prompts'))).toBe(true);
100
+ expect(logs.some(l => l.includes('agents'))).toBe(true);
101
+ expect(logs.some(l => l.includes('combined index contains:'))).toBe(true);
102
+ expect(logs.some(l => l.includes('disk cache written'))).toBe(true);
103
+ expect(logs.some(l => l.includes('index successfully built and cached'))).toBe(true);
104
+
105
+ // Verify the index was built correctly
106
+ expect(result).toHaveProperty('prompts');
107
+ expect(result).toHaveProperty('agents');
108
+ expect(result).toHaveProperty('instructions');
109
+ expect(result).toHaveProperty('skills');
110
+ expect(result).toHaveProperty('_repos');
111
+ // Now includes both the default awesome-copilot repo and test-acp-cli
112
+ expect(result._repos.length).toBeGreaterThanOrEqual(2);
113
+ expect(result._repos.some(r => r.id === 'test-acp-cli')).toBe(true);
114
+ expect(result._repos.some(r => r.id === 'awesome-copilot')).toBe(true);
115
+ } finally {
116
+ console.log = originalLog;
117
+ }
118
+ });
119
+
120
+ test('verbose logging shows configuration source priority', async () => {
121
+ // Test 1: With ACP_REPOS_JSON env var (highest priority)
122
+ const testRepoConfig = [
123
+ { id: 'from-env', treeUrl: 'https://api.test/env', rawBase: 'https://raw.test/env' }
124
+ ];
125
+ process.env.ACP_REPOS_JSON = JSON.stringify(testRepoConfig);
126
+
127
+ const logs1 = [];
128
+ const originalLog = console.log;
129
+ console.log = jest.fn(msg => logs1.push(msg));
130
+
131
+ try {
132
+ axios.get.mockResolvedValue({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p.prompt.md' }] } });
133
+
134
+ await fetchIndex({ verbose: true });
135
+
136
+ expect(logs1.some(l => l.includes('ACP_REPOS_JSON environment variable detected'))).toBe(true);
137
+ expect(logs1.some(l => l.includes('from-env'))).toBe(true);
138
+ } finally {
139
+ console.log = originalLog;
140
+ }
141
+
142
+ // Clear env var and test file-based config
143
+ delete process.env.ACP_REPOS_JSON;
144
+ cache.del('index');
145
+ const { DISK_CACHE_DIR } = diskPaths();
146
+ await fs.remove(DISK_CACHE_DIR);
147
+
148
+ // Test 2: With acp-repos.json file (second priority)
149
+ const baseDir = process.cwd();
150
+ const repoFile = path.join(baseDir, 'acp-repos.json');
151
+ const fileRepoConfig = [
152
+ { id: 'from-file', treeUrl: 'https://api.test/file', rawBase: 'https://raw.test/file' }
153
+ ];
154
+ await fs.writeJson(repoFile, fileRepoConfig);
155
+
156
+ const logs2 = [];
157
+ console.log = jest.fn(msg => logs2.push(msg));
158
+
159
+ try {
160
+ axios.get.mockResolvedValue({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p.prompt.md' }] } });
161
+
162
+ await fetchIndex({ verbose: true });
163
+
164
+ expect(logs2.some(l => l.includes('acp-repos.json found'))).toBe(true);
165
+ expect(logs2.some(l => l.includes('from-file'))).toBe(true);
166
+ } finally {
167
+ console.log = originalLog;
168
+ }
169
+
170
+ // Clear file and test default config
171
+ await fs.remove(repoFile);
172
+ cache.del('index');
173
+ await fs.remove(DISK_CACHE_DIR);
174
+
175
+ // Test 3: With defaults (lowest priority)
176
+ const logs3 = [];
177
+ console.log = jest.fn(msg => logs3.push(msg));
178
+
179
+ try {
180
+ axios.get.mockResolvedValue({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p.prompt.md' }] } });
181
+
182
+ await fetchIndex({ verbose: true });
183
+
184
+ expect(logs3.some(l => l.includes('awesome-copilot'))).toBe(true);
185
+ // Now the final repos list should include awesome-copilot plus any configured repos
186
+ expect(logs3.some(l => l.includes('final repos list:'))).toBe(true);
187
+ } finally {
188
+ console.log = originalLog;
189
+ }
190
+ });
191
+
192
+ test('verbose logging captures and reports specific repo fetch errors', async () => {
193
+ const testRepoConfig = [
194
+ {
195
+ id: 'good-repo',
196
+ treeUrl: 'https://api.test/good',
197
+ rawBase: 'https://raw.test/good'
198
+ },
199
+ {
200
+ id: 'bad-repo-invalid-json',
201
+ treeUrl: 'https://api.test/badjson',
202
+ rawBase: 'https://raw.test/badjson'
203
+ },
204
+ {
205
+ id: 'bad-repo-network',
206
+ treeUrl: 'https://api.test/badnetwork',
207
+ rawBase: 'https://raw.test/badnetwork'
208
+ }
209
+ ];
210
+ process.env.ACP_REPOS_JSON = JSON.stringify(testRepoConfig);
211
+
212
+ const logs = [];
213
+ const originalLog = console.log;
214
+ console.log = jest.fn(msg => logs.push(msg));
215
+
216
+ try {
217
+ axios.get.mockImplementation((url) => {
218
+ if (url === 'https://api.test/good') {
219
+ return Promise.resolve({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p.prompt.md' }] } });
220
+ }
221
+ if (url === 'https://api.test/badjson') {
222
+ return Promise.resolve({ status: 500, data: 'Server error' });
223
+ }
224
+ if (url === 'https://api.test/badnetwork') {
225
+ return Promise.reject(new Error('Network timeout'));
226
+ }
227
+ return Promise.reject(new Error('Unexpected URL'));
228
+ });
229
+
230
+ await fetchIndex({ verbose: true });
231
+
232
+ // Verify we see success for good repo
233
+ expect(logs.some(l => l.includes('fetching tree for repo: good-repo'))).toBe(true);
234
+ expect(logs.some(l => l.includes('successfully fetched tree for repo: good-repo'))).toBe(true);
235
+
236
+ // Verify we see error for bad repos
237
+ expect(logs.some(l => l.includes('fetching tree for repo: bad-repo-invalid-json'))).toBe(true);
238
+ expect(logs.some(l => l.includes('fetching tree for repo: bad-repo-network'))).toBe(true);
239
+ expect(logs.some(l => l.includes('failed to fetch tree'))).toBe(true);
240
+ } finally {
241
+ console.log = originalLog;
242
+ }
243
+ });
244
+
245
+ test('verbose logs show transition from disk cache to fresh fetch', async () => {
246
+ const logs1 = [];
247
+ const originalLog = console.log;
248
+
249
+ try {
250
+ // First fetch - write fresh cache
251
+ axios.get.mockResolvedValue({ status: 200, data: { prompts: [{ id: 'p1' }] } });
252
+
253
+ console.log = jest.fn(msg => logs1.push(msg));
254
+ const result1 = await fetchIndex({ verbose: true });
255
+
256
+ expect(logs1.some(l => l.includes('disk cache written'))).toBe(true);
257
+ expect(result1.prompts).toHaveLength(1);
258
+
259
+ // Second fetch - use in-memory cache
260
+ cache.del('index'); // Clear in-memory but disk cache still valid
261
+ logs1.length = 0;
262
+
263
+ console.log = jest.fn(msg => logs1.push(msg));
264
+ const result2 = await fetchIndex({ verbose: true });
265
+
266
+ expect(logs1.some(l => l.includes('reading disk cache from'))).toBe(true);
267
+ expect(logs1.some(l => l.includes('disk cache read successfully'))).toBe(true);
268
+ expect(logs1.some(l => l.includes('using fresh disk cache'))).toBe(true);
269
+ expect(result2.prompts).toHaveLength(1);
270
+ } finally {
271
+ console.log = originalLog;
272
+ }
273
+ });
274
+ });
@@ -0,0 +1,316 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const axios = require('axios');
5
+ jest.mock('axios');
6
+
7
+ let fetcher;
8
+ let fetchIndex;
9
+ let diskPaths;
10
+ const cache = require('../src/cache');
11
+
12
+ describe('fetcher verbose logging', () => {
13
+ const tmp = path.join(os.tmpdir(), `acp-fetcher-verbose-${Date.now()}`);
14
+ const origCwd = process.cwd();
15
+
16
+ beforeAll(async () => {
17
+ await fs.ensureDir(tmp);
18
+ process.chdir(tmp);
19
+ // require after chdir so diskPaths uses tmp cwd
20
+ fetcher = require('../src/fetcher');
21
+ fetchIndex = fetcher.fetchIndex;
22
+ diskPaths = fetcher.diskPaths;
23
+ });
24
+
25
+ afterAll(async () => {
26
+ process.chdir(origCwd);
27
+ await fs.remove(tmp);
28
+ });
29
+
30
+ beforeEach(async () => {
31
+ cache.del('index');
32
+ const { DISK_CACHE_DIR } = diskPaths();
33
+ await fs.remove(DISK_CACHE_DIR).catch(() => {});
34
+ axios.get.mockReset();
35
+ // Clear env vars
36
+ delete process.env.ACP_INDEX_JSON;
37
+ delete process.env.ACP_REPOS_JSON;
38
+ });
39
+
40
+ test('logs when using ACP_INDEX_JSON environment variable', async () => {
41
+ const indexData = { prompts: [{ id: 'p1', name: 'Prompt One' }] };
42
+ process.env.ACP_INDEX_JSON = JSON.stringify(indexData);
43
+
44
+ const logs = [];
45
+ const originalLog = console.log;
46
+ console.log = jest.fn(msg => logs.push(msg));
47
+
48
+ try {
49
+ await fetchIndex({ verbose: true });
50
+
51
+ expect(logs.some(l => l.includes('ACP_INDEX_JSON environment variable detected'))).toBe(true);
52
+ expect(logs.some(l => l.includes('successfully parsed ACP_INDEX_JSON'))).toBe(true);
53
+ } finally {
54
+ console.log = originalLog;
55
+ }
56
+ });
57
+
58
+ test('logs error when ACP_INDEX_JSON is malformed', async () => {
59
+ process.env.ACP_INDEX_JSON = 'not valid json {';
60
+
61
+ const logs = [];
62
+ const originalLog = console.log;
63
+ console.log = jest.fn(msg => logs.push(msg));
64
+
65
+ try {
66
+ // Mock the default repo call
67
+ axios.get.mockResolvedValue({ status: 200, data: { prompts: [{ id: 'p1' }] } });
68
+
69
+ await fetchIndex({ verbose: true });
70
+
71
+ expect(logs.some(l => l.includes('ACP_INDEX_JSON environment variable detected'))).toBe(true);
72
+ expect(logs.some(l => l.includes('failed to parse ACP_INDEX_JSON'))).toBe(true);
73
+ } finally {
74
+ console.log = originalLog;
75
+ }
76
+ });
77
+
78
+ test('logs when using ACP_REPOS_JSON environment variable', async () => {
79
+ const reposData = [
80
+ { id: 'test-repo', treeUrl: 'https://api.github.test/repo1', rawBase: 'https://raw.test/repo1' }
81
+ ];
82
+ process.env.ACP_REPOS_JSON = JSON.stringify(reposData);
83
+
84
+ const logs = [];
85
+ const originalLog = console.log;
86
+ console.log = jest.fn(msg => logs.push(msg));
87
+
88
+ try {
89
+ axios.get.mockResolvedValue({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p1.prompt.md' }] } });
90
+
91
+ await fetchIndex({ verbose: true });
92
+
93
+ expect(logs.some(l => l.includes('ACP_REPOS_JSON environment variable detected'))).toBe(true);
94
+ expect(logs.some(l => l.includes('successfully parsed ACP_REPOS_JSON with 1 repo(s)'))).toBe(true);
95
+ expect(logs.some(l => l.includes('using repos from ACP_REPOS_JSON: test-repo'))).toBe(true);
96
+ } finally {
97
+ console.log = originalLog;
98
+ }
99
+ });
100
+
101
+ test('logs error when ACP_REPOS_JSON is malformed', async () => {
102
+ process.env.ACP_REPOS_JSON = 'not valid json {';
103
+
104
+ const logs = [];
105
+ const originalLog = console.log;
106
+ console.log = jest.fn(msg => logs.push(msg));
107
+
108
+ try {
109
+ axios.get.mockResolvedValue({ status: 200, data: { prompts: [{ id: 'p1' }] } });
110
+
111
+ await fetchIndex({ verbose: true });
112
+
113
+ expect(logs.some(l => l.includes('ACP_REPOS_JSON environment variable detected'))).toBe(true);
114
+ expect(logs.some(l => l.includes('failed to parse ACP_REPOS_JSON'))).toBe(true);
115
+ } finally {
116
+ console.log = originalLog;
117
+ }
118
+ });
119
+
120
+ test('logs when loading acp-repos.json file', async () => {
121
+ const reposData = [
122
+ { id: 'file-repo', treeUrl: 'https://api.github.test/repo2', rawBase: 'https://raw.test/repo2' }
123
+ ];
124
+
125
+ const baseDir = process.cwd();
126
+ const repoFile = path.join(baseDir, 'acp-repos.json');
127
+ await fs.writeJson(repoFile, reposData);
128
+
129
+ const logs = [];
130
+ const originalLog = console.log;
131
+ console.log = jest.fn(msg => logs.push(msg));
132
+
133
+ try {
134
+ axios.get.mockResolvedValue({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p1.prompt.md' }] } });
135
+
136
+ await fetchIndex({ verbose: true });
137
+
138
+ expect(logs.some(l => l.includes('checking for acp-repos.json at'))).toBe(true);
139
+ expect(logs.some(l => l.includes('acp-repos.json found, reading...'))).toBe(true);
140
+ expect(logs.some(l => l.includes('successfully loaded 1 repo(s) from acp-repos.json'))).toBe(true);
141
+ expect(logs.some(l => l.includes('using repos from acp-repos.json: file-repo'))).toBe(true);
142
+ } finally {
143
+ console.log = originalLog;
144
+ }
145
+ });
146
+
147
+ test('logs error when acp-repos.json is malformed', async () => {
148
+ const baseDir = process.cwd();
149
+ const repoFile = path.join(baseDir, 'acp-repos.json');
150
+ await fs.writeFile(repoFile, 'not valid json {');
151
+
152
+ const logs = [];
153
+ const originalLog = console.log;
154
+ console.log = jest.fn(msg => logs.push(msg));
155
+
156
+ try {
157
+ axios.get.mockResolvedValue({ status: 200, data: { prompts: [{ id: 'p1' }] } });
158
+
159
+ await fetchIndex({ verbose: true });
160
+
161
+ expect(logs.some(l => l.includes('checking for acp-repos.json at'))).toBe(true);
162
+ expect(logs.some(l => l.includes('failed to read acp-repos.json'))).toBe(true);
163
+ } finally {
164
+ console.log = originalLog;
165
+ }
166
+ });
167
+
168
+ test('logs when using default repos', async () => {
169
+ const logs = [];
170
+ const originalLog = console.log;
171
+ console.log = jest.fn(msg => logs.push(msg));
172
+
173
+ try {
174
+ axios.get.mockResolvedValue({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p1.prompt.md' }] } });
175
+
176
+ await fetchIndex({ verbose: true });
177
+
178
+ expect(logs.some(l => l.includes('checking for acp-repos.json at'))).toBe(true);
179
+ // The key assertion is that we either see "found" or "not found"
180
+ expect(logs.some(l => l.includes('acp-repos.json') && (l.includes('not found') || l.includes('found')))).toBe(true);
181
+ expect(logs.some(l => l.includes('final repos list:'))).toBe(true);
182
+ expect(logs.some(l => l.includes('awesome-copilot'))).toBe(true);
183
+ expect(logs.some(l => l.includes('fetching repos'))).toBe(true);
184
+ } finally {
185
+ console.log = originalLog;
186
+ }
187
+ });
188
+
189
+ test('logs repo fetching successes and failures', async () => {
190
+ const reposData = [
191
+ { id: 'success-repo', treeUrl: 'https://api.github.test/success', rawBase: 'https://raw.test/success' },
192
+ { id: 'fail-repo', treeUrl: 'https://api.github.test/fail', rawBase: 'https://raw.test/fail' }
193
+ ];
194
+ process.env.ACP_REPOS_JSON = JSON.stringify(reposData);
195
+
196
+ const logs = [];
197
+ const originalLog = console.log;
198
+ console.log = jest.fn(msg => logs.push(msg));
199
+
200
+ try {
201
+ axios.get.mockImplementation((url) => {
202
+ if (url === 'https://api.github.test/success') {
203
+ return Promise.resolve({ status: 200, data: { tree: [{ type: 'blob', path: 'prompts/p1.prompt.md' }] } });
204
+ }
205
+ if (url === 'https://api.github.test/fail') {
206
+ return Promise.reject(new Error('Network error'));
207
+ }
208
+ return Promise.reject(new Error('Unexpected URL'));
209
+ });
210
+
211
+ await fetchIndex({ verbose: true });
212
+
213
+ expect(logs.some(l => l.includes('fetching tree for repo: success-repo'))).toBe(true);
214
+ expect(logs.some(l => l.includes('successfully fetched tree for repo: success-repo'))).toBe(true);
215
+ expect(logs.some(l => l.includes('fetching tree for repo: fail-repo'))).toBe(true);
216
+ expect(logs.some(l => l.includes('failed to fetch tree for repo fail-repo'))).toBe(true);
217
+ } finally {
218
+ console.log = originalLog;
219
+ }
220
+ });
221
+
222
+ test('logs item count summary', async () => {
223
+ const logs = [];
224
+ const originalLog = console.log;
225
+ console.log = jest.fn(msg => logs.push(msg));
226
+
227
+ try {
228
+ axios.get.mockImplementation((url) => {
229
+ if (url.includes('tree')) {
230
+ return Promise.resolve({
231
+ status: 200,
232
+ data: {
233
+ tree: [
234
+ { type: 'blob', path: 'prompts/p1.prompt.md' },
235
+ { type: 'blob', path: 'agents/a1.agent.md' }
236
+ ]
237
+ }
238
+ });
239
+ }
240
+ return Promise.reject(new Error('Unexpected URL'));
241
+ });
242
+
243
+ await fetchIndex({ verbose: true });
244
+
245
+ expect(logs.some(l => l.includes('combined index contains:'))).toBe(true);
246
+ expect(logs.some(l => l.includes('prompts:'))).toBe(true);
247
+ expect(logs.some(l => l.includes('agents:'))).toBe(true);
248
+ } finally {
249
+ console.log = originalLog;
250
+ }
251
+ });
252
+
253
+ test('does not log when verbose is false', async () => {
254
+ const logs = [];
255
+ const originalLog = console.log;
256
+ console.log = jest.fn(msg => logs.push(msg));
257
+
258
+ try {
259
+ axios.get.mockResolvedValue({ status: 200, data: { prompts: [{ id: 'p1' }] } });
260
+
261
+ await fetchIndex({ verbose: false });
262
+
263
+ // Should not have verbose logs (only warnings about baseline-browser-mapping)
264
+ const verboseLogs = logs.filter(l => l.includes('verbose:'));
265
+ expect(verboseLogs.length).toBe(0);
266
+ } finally {
267
+ console.log = originalLog;
268
+ }
269
+ });
270
+
271
+ test('does not log when options is not provided', async () => {
272
+ const logs = [];
273
+ const originalLog = console.log;
274
+ console.log = jest.fn(msg => logs.push(msg));
275
+
276
+ try {
277
+ axios.get.mockResolvedValue({ status: 200, data: { prompts: [{ id: 'p1' }] } });
278
+
279
+ await fetchIndex();
280
+
281
+ // Should not have verbose logs
282
+ const verboseLogs = logs.filter(l => l.includes('verbose:'));
283
+ expect(verboseLogs.length).toBe(0);
284
+ } finally {
285
+ console.log = originalLog;
286
+ }
287
+ });
288
+
289
+ test('logs disk cache operations', async () => {
290
+ const logs = [];
291
+ const originalLog = console.log;
292
+ console.log = jest.fn(msg => logs.push(msg));
293
+
294
+ try {
295
+ axios.get.mockResolvedValue({ status: 200, data: { prompts: [{ id: 'p1' }] } });
296
+
297
+ // First call should write cache
298
+ await fetchIndex({ verbose: true });
299
+
300
+ expect(logs.some(l => l.includes('disk cache written to'))).toBe(true);
301
+
302
+ // Clear in-memory cache to force disk read on second call
303
+ cache.del('index');
304
+ logs.length = 0;
305
+
306
+ // Second call should read from disk
307
+ await fetchIndex({ verbose: true });
308
+
309
+ expect(logs.some(l => l.includes('reading disk cache from'))).toBe(true);
310
+ expect(logs.some(l => l.includes('disk cache read successfully'))).toBe(true);
311
+ expect(logs.some(l => l.includes('using fresh disk cache'))).toBe(true);
312
+ } finally {
313
+ console.log = originalLog;
314
+ }
315
+ });
316
+ });
@@ -99,11 +99,11 @@ describe('fetcher', () => {
99
99
 
100
100
  const idx = await fetchIndex();
101
101
 
102
- // repos metadata should be present
103
- expect(idx._repos).toEqual([
104
- { id: 'r1', treeUrl: 'https://api/repo1', rawBase: 'https://raw1' },
105
- { id: 'r2', treeUrl: 'https://api/repo2', rawBase: 'https://raw2' }
106
- ]);
102
+ // repos metadata should be present - now includes the default awesome-copilot repo
103
+ expect(idx._repos).toHaveLength(3);
104
+ expect(idx._repos.map(r => r.id)).toEqual(expect.arrayContaining(['awesome-copilot', 'r1', 'r2']));
105
+ expect(idx._repos.some(r => r.id === 'r1' && r.treeUrl === 'https://api/repo1')).toBe(true);
106
+ expect(idx._repos.some(r => r.id === 'r2' && r.treeUrl === 'https://api/repo2')).toBe(true);
107
107
 
108
108
  // prompts should include items from both repos
109
109
  const pids = idx.prompts.map(p => `${p.repo}:${p.id}`).sort();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-vscode",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "CLI to install GitHub Awesome Copilot agents, prompts, instructions, and skills into VS Code workspace or user profile",
5
5
  "bin": {
6
6
  "acp-vscode": "./bin/acp-vscode.js"