opengstack 0.13.10 → 0.14.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 (189) hide show
  1. package/AGENTS.md +4 -4
  2. package/CLAUDE.md +127 -110
  3. package/README.md +10 -5
  4. package/SKILL.md +500 -70
  5. package/bin/opengstack.js +69 -69
  6. package/{skills/land-and-deploy/SKILL.md → commands/autoplan.md} +7 -25
  7. package/{skills/benchmark/SKILL.md → commands/benchmark.md} +84 -108
  8. package/{skills/browse/SKILL.md → commands/browse.md} +60 -81
  9. package/{skills/ship/SKILL.md → commands/canary.md} +7 -27
  10. package/{skills/careful/SKILL.md → commands/careful.md} +2 -22
  11. package/{skills/canary/SKILL.md → commands/codex.md} +7 -26
  12. package/{skills/connect-chrome/SKILL.md → commands/connect-chrome.md} +7 -24
  13. package/commands/cso.md +70 -0
  14. package/commands/design-consultation.md +70 -0
  15. package/commands/design-review.md +70 -0
  16. package/commands/design-shotgun.md +70 -0
  17. package/commands/document-release.md +70 -0
  18. package/{skills/freeze/SKILL.md → commands/freeze.md} +3 -29
  19. package/{skills/guard/SKILL.md → commands/guard.md} +4 -35
  20. package/commands/investigate.md +70 -0
  21. package/commands/land-and-deploy.md +70 -0
  22. package/commands/office-hours.md +70 -0
  23. package/{skills/gstack-upgrade/SKILL.md → commands/opengstack-upgrade.md} +64 -79
  24. package/commands/plan-ceo-review.md +70 -0
  25. package/commands/plan-design-review.md +70 -0
  26. package/commands/plan-eng-review.md +70 -0
  27. package/commands/qa-only.md +70 -0
  28. package/commands/qa.md +70 -0
  29. package/commands/retro.md +70 -0
  30. package/commands/review.md +70 -0
  31. package/{skills/setup-browser-cookies/SKILL.md → commands/setup-browser-cookies.md} +22 -40
  32. package/commands/setup-deploy.md +70 -0
  33. package/commands/ship.md +70 -0
  34. package/commands/unfreeze.md +25 -0
  35. package/docs/designs/CHROME_VS_CHROMIUM_EXPLORATION.md +9 -9
  36. package/docs/designs/CONDUCTOR_CHROME_SIDEBAR_INTEGRATION.md +2 -2
  37. package/docs/designs/CONDUCTOR_SESSION_API.md +16 -16
  38. package/docs/designs/DESIGN_SHOTGUN.md +74 -74
  39. package/docs/designs/DESIGN_TOOLS_V1.md +111 -111
  40. package/docs/skills.md +483 -202
  41. package/package.json +42 -43
  42. package/scripts/analytics.ts +188 -0
  43. package/scripts/dev-skill.ts +83 -0
  44. package/scripts/discover-skills.ts +39 -0
  45. package/scripts/eval-compare.ts +97 -0
  46. package/scripts/eval-list.ts +117 -0
  47. package/scripts/eval-select.ts +86 -0
  48. package/scripts/eval-summary.ts +188 -0
  49. package/scripts/eval-watch.ts +172 -0
  50. package/scripts/gen-skill-docs.ts +473 -0
  51. package/scripts/resolvers/browse.ts +129 -0
  52. package/scripts/resolvers/codex-helpers.ts +133 -0
  53. package/scripts/resolvers/composition.ts +48 -0
  54. package/scripts/resolvers/confidence.ts +37 -0
  55. package/scripts/resolvers/constants.ts +50 -0
  56. package/scripts/resolvers/design.ts +950 -0
  57. package/scripts/resolvers/index.ts +59 -0
  58. package/scripts/resolvers/learnings.ts +96 -0
  59. package/scripts/resolvers/preamble.ts +505 -0
  60. package/scripts/resolvers/review.ts +884 -0
  61. package/scripts/resolvers/testing.ts +573 -0
  62. package/scripts/resolvers/types.ts +45 -0
  63. package/scripts/resolvers/utility.ts +421 -0
  64. package/scripts/skill-check.ts +190 -0
  65. package/scripts/cleanup.py +0 -100
  66. package/scripts/filter-skills.sh +0 -114
  67. package/scripts/filter_skills.py +0 -164
  68. package/scripts/install-skills.js +0 -60
  69. package/skills/autoplan/SKILL.md +0 -96
  70. package/skills/autoplan/SKILL.md.tmpl +0 -694
  71. package/skills/benchmark/SKILL.md.tmpl +0 -222
  72. package/skills/browse/SKILL.md.tmpl +0 -131
  73. package/skills/browse/bin/find-browse +0 -21
  74. package/skills/browse/bin/remote-slug +0 -14
  75. package/skills/browse/scripts/build-node-server.sh +0 -48
  76. package/skills/browse/src/activity.ts +0 -208
  77. package/skills/browse/src/browser-manager.ts +0 -959
  78. package/skills/browse/src/buffers.ts +0 -137
  79. package/skills/browse/src/bun-polyfill.cjs +0 -109
  80. package/skills/browse/src/cli.ts +0 -678
  81. package/skills/browse/src/commands.ts +0 -128
  82. package/skills/browse/src/config.ts +0 -150
  83. package/skills/browse/src/cookie-import-browser.ts +0 -625
  84. package/skills/browse/src/cookie-picker-routes.ts +0 -230
  85. package/skills/browse/src/cookie-picker-ui.ts +0 -688
  86. package/skills/browse/src/find-browse.ts +0 -61
  87. package/skills/browse/src/meta-commands.ts +0 -550
  88. package/skills/browse/src/platform.ts +0 -17
  89. package/skills/browse/src/read-commands.ts +0 -358
  90. package/skills/browse/src/server.ts +0 -1192
  91. package/skills/browse/src/sidebar-agent.ts +0 -280
  92. package/skills/browse/src/sidebar-utils.ts +0 -21
  93. package/skills/browse/src/snapshot.ts +0 -407
  94. package/skills/browse/src/url-validation.ts +0 -95
  95. package/skills/browse/src/write-commands.ts +0 -364
  96. package/skills/browse/test/activity.test.ts +0 -120
  97. package/skills/browse/test/adversarial-security.test.ts +0 -32
  98. package/skills/browse/test/browser-manager-unit.test.ts +0 -17
  99. package/skills/browse/test/bun-polyfill.test.ts +0 -72
  100. package/skills/browse/test/commands.test.ts +0 -2075
  101. package/skills/browse/test/compare-board.test.ts +0 -342
  102. package/skills/browse/test/config.test.ts +0 -316
  103. package/skills/browse/test/cookie-import-browser.test.ts +0 -519
  104. package/skills/browse/test/cookie-picker-routes.test.ts +0 -260
  105. package/skills/browse/test/file-drop.test.ts +0 -271
  106. package/skills/browse/test/find-browse.test.ts +0 -50
  107. package/skills/browse/test/findport.test.ts +0 -191
  108. package/skills/browse/test/fixtures/basic.html +0 -33
  109. package/skills/browse/test/fixtures/cursor-interactive.html +0 -22
  110. package/skills/browse/test/fixtures/dialog.html +0 -15
  111. package/skills/browse/test/fixtures/empty.html +0 -2
  112. package/skills/browse/test/fixtures/forms.html +0 -55
  113. package/skills/browse/test/fixtures/iframe.html +0 -30
  114. package/skills/browse/test/fixtures/network-idle.html +0 -30
  115. package/skills/browse/test/fixtures/qa-eval-checkout.html +0 -108
  116. package/skills/browse/test/fixtures/qa-eval-spa.html +0 -98
  117. package/skills/browse/test/fixtures/qa-eval.html +0 -51
  118. package/skills/browse/test/fixtures/responsive.html +0 -49
  119. package/skills/browse/test/fixtures/snapshot.html +0 -55
  120. package/skills/browse/test/fixtures/spa.html +0 -24
  121. package/skills/browse/test/fixtures/states.html +0 -17
  122. package/skills/browse/test/fixtures/upload.html +0 -25
  123. package/skills/browse/test/gstack-config.test.ts +0 -138
  124. package/skills/browse/test/gstack-update-check.test.ts +0 -514
  125. package/skills/browse/test/handoff.test.ts +0 -235
  126. package/skills/browse/test/path-validation.test.ts +0 -91
  127. package/skills/browse/test/platform.test.ts +0 -37
  128. package/skills/browse/test/server-auth.test.ts +0 -65
  129. package/skills/browse/test/sidebar-agent-roundtrip.test.ts +0 -226
  130. package/skills/browse/test/sidebar-agent.test.ts +0 -199
  131. package/skills/browse/test/sidebar-integration.test.ts +0 -320
  132. package/skills/browse/test/sidebar-unit.test.ts +0 -96
  133. package/skills/browse/test/snapshot.test.ts +0 -467
  134. package/skills/browse/test/state-ttl.test.ts +0 -35
  135. package/skills/browse/test/test-server.ts +0 -57
  136. package/skills/browse/test/url-validation.test.ts +0 -72
  137. package/skills/browse/test/watch.test.ts +0 -129
  138. package/skills/canary/SKILL.md.tmpl +0 -212
  139. package/skills/careful/SKILL.md.tmpl +0 -56
  140. package/skills/careful/bin/check-careful.sh +0 -112
  141. package/skills/codex/SKILL.md +0 -90
  142. package/skills/codex/SKILL.md.tmpl +0 -417
  143. package/skills/connect-chrome/SKILL.md.tmpl +0 -195
  144. package/skills/cso/ACKNOWLEDGEMENTS.md +0 -14
  145. package/skills/cso/SKILL.md +0 -93
  146. package/skills/cso/SKILL.md.tmpl +0 -606
  147. package/skills/design-consultation/SKILL.md +0 -94
  148. package/skills/design-consultation/SKILL.md.tmpl +0 -415
  149. package/skills/design-review/SKILL.md +0 -94
  150. package/skills/design-review/SKILL.md.tmpl +0 -290
  151. package/skills/design-shotgun/SKILL.md +0 -91
  152. package/skills/design-shotgun/SKILL.md.tmpl +0 -285
  153. package/skills/document-release/SKILL.md +0 -91
  154. package/skills/document-release/SKILL.md.tmpl +0 -359
  155. package/skills/freeze/SKILL.md.tmpl +0 -77
  156. package/skills/freeze/bin/check-freeze.sh +0 -79
  157. package/skills/gstack-upgrade/SKILL.md.tmpl +0 -222
  158. package/skills/guard/SKILL.md.tmpl +0 -77
  159. package/skills/investigate/SKILL.md +0 -105
  160. package/skills/investigate/SKILL.md.tmpl +0 -194
  161. package/skills/land-and-deploy/SKILL.md.tmpl +0 -881
  162. package/skills/office-hours/SKILL.md +0 -96
  163. package/skills/office-hours/SKILL.md.tmpl +0 -645
  164. package/skills/plan-ceo-review/SKILL.md +0 -94
  165. package/skills/plan-ceo-review/SKILL.md.tmpl +0 -811
  166. package/skills/plan-design-review/SKILL.md +0 -92
  167. package/skills/plan-design-review/SKILL.md.tmpl +0 -446
  168. package/skills/plan-eng-review/SKILL.md +0 -93
  169. package/skills/plan-eng-review/SKILL.md.tmpl +0 -303
  170. package/skills/qa/SKILL.md +0 -95
  171. package/skills/qa/SKILL.md.tmpl +0 -316
  172. package/skills/qa/references/issue-taxonomy.md +0 -85
  173. package/skills/qa/templates/qa-report-template.md +0 -126
  174. package/skills/qa-only/SKILL.md +0 -89
  175. package/skills/qa-only/SKILL.md.tmpl +0 -101
  176. package/skills/retro/SKILL.md +0 -89
  177. package/skills/retro/SKILL.md.tmpl +0 -820
  178. package/skills/review/SKILL.md +0 -92
  179. package/skills/review/SKILL.md.tmpl +0 -281
  180. package/skills/review/TODOS-format.md +0 -62
  181. package/skills/review/checklist.md +0 -220
  182. package/skills/review/design-checklist.md +0 -132
  183. package/skills/review/greptile-triage.md +0 -220
  184. package/skills/setup-browser-cookies/SKILL.md.tmpl +0 -81
  185. package/skills/setup-deploy/SKILL.md +0 -92
  186. package/skills/setup-deploy/SKILL.md.tmpl +0 -215
  187. package/skills/ship/SKILL.md.tmpl +0 -636
  188. package/skills/unfreeze/SKILL.md +0 -37
  189. package/skills/unfreeze/SKILL.md.tmpl +0 -36
@@ -1,260 +0,0 @@
1
- /**
2
- * Tests for cookie-picker route handler
3
- *
4
- * Tests the HTTP glue layer directly with mock BrowserManager objects.
5
- * Verifies that all routes return valid JSON (not HTML) with correct CORS headers.
6
- */
7
-
8
- import { describe, test, expect } from 'bun:test';
9
- import { handleCookiePickerRoute } from '../src/cookie-picker-routes';
10
-
11
- // ─── Mock BrowserManager ──────────────────────────────────────
12
-
13
- function mockBrowserManager() {
14
- const addedCookies: any[] = [];
15
- const clearedDomains: string[] = [];
16
- return {
17
- bm: {
18
- getPage: () => ({
19
- context: () => ({
20
- addCookies: (cookies: any[]) => { addedCookies.push(...cookies); },
21
- clearCookies: (opts: { domain: string }) => { clearedDomains.push(opts.domain); },
22
- }),
23
- }),
24
- } as any,
25
- addedCookies,
26
- clearedDomains,
27
- };
28
- }
29
-
30
- function makeUrl(path: string, port = 9470): URL {
31
- return new URL(`http://127.0.0.1:${port}${path}`);
32
- }
33
-
34
- function makeReq(method: string, body?: any): Request {
35
- const opts: RequestInit = { method };
36
- if (body) {
37
- opts.body = JSON.stringify(body);
38
- opts.headers = { 'Content-Type': 'application/json' };
39
- }
40
- return new Request('http://127.0.0.1:9470', opts);
41
- }
42
-
43
- // ─── Tests ──────────────────────────────────────────────────────
44
-
45
- describe('cookie-picker-routes', () => {
46
- describe('CORS', () => {
47
- test('OPTIONS returns 204 with correct CORS headers', async () => {
48
- const { bm } = mockBrowserManager();
49
- const url = makeUrl('/cookie-picker/browsers');
50
- const req = new Request('http://127.0.0.1:9470', { method: 'OPTIONS' });
51
-
52
- const res = await handleCookiePickerRoute(url, req, bm);
53
-
54
- expect(res.status).toBe(204);
55
- expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9470');
56
- expect(res.headers.get('Access-Control-Allow-Methods')).toContain('POST');
57
- });
58
-
59
- test('JSON responses include correct CORS origin with port', async () => {
60
- const { bm } = mockBrowserManager();
61
- const url = makeUrl('/cookie-picker/browsers', 9450);
62
- const req = new Request('http://127.0.0.1:9450', { method: 'GET' });
63
-
64
- const res = await handleCookiePickerRoute(url, req, bm);
65
-
66
- expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9450');
67
- });
68
- });
69
-
70
- describe('JSON responses (not HTML)', () => {
71
- test('GET /cookie-picker/browsers returns JSON', async () => {
72
- const { bm } = mockBrowserManager();
73
- const url = makeUrl('/cookie-picker/browsers');
74
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
75
-
76
- const res = await handleCookiePickerRoute(url, req, bm);
77
-
78
- expect(res.status).toBe(200);
79
- expect(res.headers.get('Content-Type')).toBe('application/json');
80
- const body = await res.json();
81
- expect(body).toHaveProperty('browsers');
82
- expect(Array.isArray(body.browsers)).toBe(true);
83
- });
84
-
85
- test('GET /cookie-picker/domains without browser param returns JSON error', async () => {
86
- const { bm } = mockBrowserManager();
87
- const url = makeUrl('/cookie-picker/domains');
88
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
89
-
90
- const res = await handleCookiePickerRoute(url, req, bm);
91
-
92
- expect(res.status).toBe(400);
93
- expect(res.headers.get('Content-Type')).toBe('application/json');
94
- const body = await res.json();
95
- expect(body).toHaveProperty('error');
96
- expect(body).toHaveProperty('code', 'missing_param');
97
- });
98
-
99
- test('POST /cookie-picker/import with invalid JSON returns JSON error', async () => {
100
- const { bm } = mockBrowserManager();
101
- const url = makeUrl('/cookie-picker/import');
102
- const req = new Request('http://127.0.0.1:9470', {
103
- method: 'POST',
104
- body: 'not json',
105
- headers: { 'Content-Type': 'application/json' },
106
- });
107
-
108
- const res = await handleCookiePickerRoute(url, req, bm);
109
-
110
- expect(res.status).toBe(400);
111
- expect(res.headers.get('Content-Type')).toBe('application/json');
112
- const body = await res.json();
113
- expect(body.code).toBe('bad_request');
114
- });
115
-
116
- test('POST /cookie-picker/import missing browser field returns JSON error', async () => {
117
- const { bm } = mockBrowserManager();
118
- const url = makeUrl('/cookie-picker/import');
119
- const req = makeReq('POST', { domains: ['.example.com'] });
120
-
121
- const res = await handleCookiePickerRoute(url, req, bm);
122
-
123
- expect(res.status).toBe(400);
124
- const body = await res.json();
125
- expect(body.code).toBe('missing_param');
126
- });
127
-
128
- test('POST /cookie-picker/import missing domains returns JSON error', async () => {
129
- const { bm } = mockBrowserManager();
130
- const url = makeUrl('/cookie-picker/import');
131
- const req = makeReq('POST', { browser: 'Chrome' });
132
-
133
- const res = await handleCookiePickerRoute(url, req, bm);
134
-
135
- expect(res.status).toBe(400);
136
- const body = await res.json();
137
- expect(body.code).toBe('missing_param');
138
- });
139
-
140
- test('POST /cookie-picker/remove with invalid JSON returns JSON error', async () => {
141
- const { bm } = mockBrowserManager();
142
- const url = makeUrl('/cookie-picker/remove');
143
- const req = new Request('http://127.0.0.1:9470', {
144
- method: 'POST',
145
- body: '{bad',
146
- headers: { 'Content-Type': 'application/json' },
147
- });
148
-
149
- const res = await handleCookiePickerRoute(url, req, bm);
150
-
151
- expect(res.status).toBe(400);
152
- expect(res.headers.get('Content-Type')).toBe('application/json');
153
- });
154
-
155
- test('POST /cookie-picker/remove missing domains returns JSON error', async () => {
156
- const { bm } = mockBrowserManager();
157
- const url = makeUrl('/cookie-picker/remove');
158
- const req = makeReq('POST', {});
159
-
160
- const res = await handleCookiePickerRoute(url, req, bm);
161
-
162
- expect(res.status).toBe(400);
163
- const body = await res.json();
164
- expect(body.code).toBe('missing_param');
165
- });
166
-
167
- test('GET /cookie-picker/imported returns JSON with domain list', async () => {
168
- const { bm } = mockBrowserManager();
169
- const url = makeUrl('/cookie-picker/imported');
170
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
171
-
172
- const res = await handleCookiePickerRoute(url, req, bm);
173
-
174
- expect(res.status).toBe(200);
175
- expect(res.headers.get('Content-Type')).toBe('application/json');
176
- const body = await res.json();
177
- expect(body).toHaveProperty('domains');
178
- expect(body).toHaveProperty('totalDomains');
179
- expect(body).toHaveProperty('totalCookies');
180
- });
181
- });
182
-
183
- describe('routing', () => {
184
- test('GET /cookie-picker returns HTML', async () => {
185
- const { bm } = mockBrowserManager();
186
- const url = makeUrl('/cookie-picker');
187
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
188
-
189
- const res = await handleCookiePickerRoute(url, req, bm);
190
-
191
- expect(res.status).toBe(200);
192
- expect(res.headers.get('Content-Type')).toContain('text/html');
193
- });
194
-
195
- test('unknown path returns 404', async () => {
196
- const { bm } = mockBrowserManager();
197
- const url = makeUrl('/cookie-picker/nonexistent');
198
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
199
-
200
- const res = await handleCookiePickerRoute(url, req, bm);
201
-
202
- expect(res.status).toBe(404);
203
- });
204
- });
205
-
206
- describe('auth gate security', () => {
207
- test('GET /cookie-picker HTML page works without auth token', async () => {
208
- const { bm } = mockBrowserManager();
209
- const url = makeUrl('/cookie-picker');
210
- // Request with no Authorization header, but authToken is set on the server
211
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
212
-
213
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
214
-
215
- expect(res.status).toBe(200);
216
- expect(res.headers.get('Content-Type')).toContain('text/html');
217
- });
218
-
219
- test('GET /cookie-picker/browsers returns 401 without auth', async () => {
220
- const { bm } = mockBrowserManager();
221
- const url = makeUrl('/cookie-picker/browsers');
222
- // No Authorization header
223
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
224
-
225
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
226
-
227
- expect(res.status).toBe(401);
228
- const body = await res.json();
229
- expect(body.error).toBe('Unauthorized');
230
- });
231
-
232
- test('POST /cookie-picker/import returns 401 without auth', async () => {
233
- const { bm } = mockBrowserManager();
234
- const url = makeUrl('/cookie-picker/import');
235
- const req = makeReq('POST', { browser: 'Chrome', domains: ['.example.com'] });
236
-
237
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
238
-
239
- expect(res.status).toBe(401);
240
- const body = await res.json();
241
- expect(body.error).toBe('Unauthorized');
242
- });
243
-
244
- test('GET /cookie-picker/browsers works with valid auth', async () => {
245
- const { bm } = mockBrowserManager();
246
- const url = makeUrl('/cookie-picker/browsers');
247
- const req = new Request('http://127.0.0.1:9470', {
248
- method: 'GET',
249
- headers: { 'Authorization': 'Bearer test-secret-token' },
250
- });
251
-
252
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
253
-
254
- expect(res.status).toBe(200);
255
- expect(res.headers.get('Content-Type')).toBe('application/json');
256
- const body = await res.json();
257
- expect(body).toHaveProperty('browsers');
258
- });
259
- });
260
- });
@@ -1,271 +0,0 @@
1
- /**
2
- * Tests for the inbox meta-command handler (file drop relay).
3
- *
4
- * Tests the inbox display, --clear flag, and edge cases by creating
5
- * temp directories with test JSON files and calling handleMetaCommand.
6
- */
7
-
8
- import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
9
- import * as fs from 'fs';
10
- import * as path from 'path';
11
- import * as os from 'os';
12
- import { handleMetaCommand } from '../src/meta-commands';
13
- import { BrowserManager } from '../src/browser-manager';
14
-
15
- let tmpDir: string;
16
- let bm: BrowserManager;
17
-
18
- // We need a BrowserManager instance for handleMetaCommand, but inbox
19
- // doesn't use it. We also need to mock git rev-parse to point to our
20
- // temp directory. We'll test the inbox logic directly by manipulating
21
- // the filesystem and using child_process.execSync override.
22
-
23
- // ─── Direct filesystem tests (bypassing handleMetaCommand) ──────
24
- // The inbox handler in meta-commands.ts calls `git rev-parse --show-toplevel`
25
- // to find the inbox directory. Since we can't easily mock that in unit tests,
26
- // we test the inbox parsing logic directly.
27
-
28
- interface InboxMessage {
29
- timestamp: string;
30
- url: string;
31
- userMessage: string;
32
- }
33
-
34
- /** Replicate the inbox file reading logic from meta-commands.ts */
35
- function readInbox(inboxDir: string): InboxMessage[] {
36
- if (!fs.existsSync(inboxDir)) return [];
37
-
38
- const files = fs.readdirSync(inboxDir)
39
- .filter(f => f.endsWith('.json') && !f.startsWith('.'))
40
- .sort()
41
- .reverse();
42
-
43
- if (files.length === 0) return [];
44
-
45
- const messages: InboxMessage[] = [];
46
- for (const file of files) {
47
- try {
48
- const data = JSON.parse(fs.readFileSync(path.join(inboxDir, file), 'utf-8'));
49
- messages.push({
50
- timestamp: data.timestamp || '',
51
- url: data.page?.url || 'unknown',
52
- userMessage: data.userMessage || '',
53
- });
54
- } catch {
55
- // Skip malformed files
56
- }
57
- }
58
- return messages;
59
- }
60
-
61
- /** Replicate the inbox formatting logic from meta-commands.ts */
62
- function formatInbox(messages: InboxMessage[]): string {
63
- if (messages.length === 0) return 'Inbox empty.';
64
-
65
- const lines: string[] = [];
66
- lines.push(`SIDEBAR INBOX (${messages.length} message${messages.length === 1 ? '' : 's'})`);
67
- lines.push('────────────────────────────────');
68
-
69
- for (const msg of messages) {
70
- const ts = msg.timestamp ? `[${msg.timestamp}]` : '[unknown]';
71
- lines.push(`${ts} ${msg.url}`);
72
- lines.push(` "${msg.userMessage}"`);
73
- lines.push('');
74
- }
75
-
76
- lines.push('────────────────────────────────');
77
- return lines.join('\n');
78
- }
79
-
80
- /** Replicate the --clear logic from meta-commands.ts */
81
- function clearInbox(inboxDir: string): number {
82
- const files = fs.readdirSync(inboxDir)
83
- .filter(f => f.endsWith('.json') && !f.startsWith('.'));
84
- for (const file of files) {
85
- try { fs.unlinkSync(path.join(inboxDir, file)); } catch {}
86
- }
87
- return files.length;
88
- }
89
-
90
- function writeTestInboxFile(
91
- inboxDir: string,
92
- message: string,
93
- pageUrl: string,
94
- timestamp: string,
95
- ): string {
96
- fs.mkdirSync(inboxDir, { recursive: true });
97
- const filename = `${timestamp.replace(/:/g, '-')}-observation.json`;
98
- const filePath = path.join(inboxDir, filename);
99
- fs.writeFileSync(filePath, JSON.stringify({
100
- type: 'observation',
101
- timestamp,
102
- page: { url: pageUrl, title: '' },
103
- userMessage: message,
104
- sidebarSessionId: 'test-session',
105
- }, null, 2));
106
- return filePath;
107
- }
108
-
109
- beforeEach(() => {
110
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'file-drop-test-'));
111
- });
112
-
113
- afterEach(() => {
114
- fs.rmSync(tmpDir, { recursive: true, force: true });
115
- });
116
-
117
- // ─── Empty Inbox ─────────────────────────────────────────────────
118
-
119
- describe('inbox — empty states', () => {
120
- test('no .context/sidebar-inbox directory returns empty', () => {
121
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
122
- const messages = readInbox(inboxDir);
123
- expect(messages.length).toBe(0);
124
- expect(formatInbox(messages)).toBe('Inbox empty.');
125
- });
126
-
127
- test('empty inbox directory returns empty', () => {
128
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
129
- fs.mkdirSync(inboxDir, { recursive: true });
130
- const messages = readInbox(inboxDir);
131
- expect(messages.length).toBe(0);
132
- expect(formatInbox(messages)).toBe('Inbox empty.');
133
- });
134
-
135
- test('directory with only dotfiles returns empty', () => {
136
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
137
- fs.mkdirSync(inboxDir, { recursive: true });
138
- fs.writeFileSync(path.join(inboxDir, '.tmp-file.json'), '{}');
139
- const messages = readInbox(inboxDir);
140
- expect(messages.length).toBe(0);
141
- });
142
- });
143
-
144
- // ─── Valid Messages ──────────────────────────────────────────────
145
-
146
- describe('inbox — valid messages', () => {
147
- test('displays formatted output with timestamps and URLs', () => {
148
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
149
- writeTestInboxFile(inboxDir, 'This button is broken', 'https://example.com/page', '2024-06-15T10:30:00.000Z');
150
- writeTestInboxFile(inboxDir, 'Login form fails', 'https://example.com/login', '2024-06-15T10:31:00.000Z');
151
-
152
- const messages = readInbox(inboxDir);
153
- expect(messages.length).toBe(2);
154
-
155
- const output = formatInbox(messages);
156
- expect(output).toContain('SIDEBAR INBOX (2 messages)');
157
- expect(output).toContain('https://example.com/page');
158
- expect(output).toContain('https://example.com/login');
159
- expect(output).toContain('"This button is broken"');
160
- expect(output).toContain('"Login form fails"');
161
- expect(output).toContain('[2024-06-15T10:30:00.000Z]');
162
- expect(output).toContain('[2024-06-15T10:31:00.000Z]');
163
- });
164
-
165
- test('single message uses singular form', () => {
166
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
167
- writeTestInboxFile(inboxDir, 'Just one', 'https://example.com', '2024-06-15T10:30:00.000Z');
168
-
169
- const messages = readInbox(inboxDir);
170
- const output = formatInbox(messages);
171
- expect(output).toContain('1 message)');
172
- expect(output).not.toContain('messages)');
173
- });
174
-
175
- test('messages sorted newest first', () => {
176
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
177
- writeTestInboxFile(inboxDir, 'older', 'https://example.com', '2024-06-15T10:00:00.000Z');
178
- writeTestInboxFile(inboxDir, 'newer', 'https://example.com', '2024-06-15T11:00:00.000Z');
179
-
180
- const messages = readInbox(inboxDir);
181
- // Filenames sort lexicographically, reversed = newest first
182
- expect(messages[0].userMessage).toBe('newer');
183
- expect(messages[1].userMessage).toBe('older');
184
- });
185
- });
186
-
187
- // ─── Malformed Files ─────────────────────────────────────────────
188
-
189
- describe('inbox — malformed files', () => {
190
- test('malformed JSON files are skipped gracefully', () => {
191
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
192
- fs.mkdirSync(inboxDir, { recursive: true });
193
-
194
- // Write a valid message
195
- writeTestInboxFile(inboxDir, 'valid message', 'https://example.com', '2024-06-15T10:30:00.000Z');
196
-
197
- // Write a malformed JSON file
198
- fs.writeFileSync(
199
- path.join(inboxDir, '2024-06-15T10-35-00.000Z-observation.json'),
200
- 'this is not valid json {{{',
201
- );
202
-
203
- const messages = readInbox(inboxDir);
204
- expect(messages.length).toBe(1);
205
- expect(messages[0].userMessage).toBe('valid message');
206
- });
207
-
208
- test('JSON file missing fields uses defaults', () => {
209
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
210
- fs.mkdirSync(inboxDir, { recursive: true });
211
-
212
- // Write a JSON file with missing fields
213
- fs.writeFileSync(
214
- path.join(inboxDir, '2024-06-15T10-30-00.000Z-observation.json'),
215
- JSON.stringify({ type: 'observation' }),
216
- );
217
-
218
- const messages = readInbox(inboxDir);
219
- expect(messages.length).toBe(1);
220
- expect(messages[0].timestamp).toBe('');
221
- expect(messages[0].url).toBe('unknown');
222
- expect(messages[0].userMessage).toBe('');
223
- });
224
- });
225
-
226
- // ─── Clear Flag ──────────────────────────────────────────────────
227
-
228
- describe('inbox — --clear flag', () => {
229
- test('files deleted after clear', () => {
230
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
231
- writeTestInboxFile(inboxDir, 'message 1', 'https://example.com', '2024-06-15T10:30:00.000Z');
232
- writeTestInboxFile(inboxDir, 'message 2', 'https://example.com', '2024-06-15T10:31:00.000Z');
233
-
234
- // Verify files exist
235
- const filesBefore = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
236
- expect(filesBefore.length).toBe(2);
237
-
238
- // Clear
239
- const cleared = clearInbox(inboxDir);
240
- expect(cleared).toBe(2);
241
-
242
- // Verify files deleted
243
- const filesAfter = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
244
- expect(filesAfter.length).toBe(0);
245
- });
246
-
247
- test('clear on empty directory does nothing', () => {
248
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
249
- fs.mkdirSync(inboxDir, { recursive: true });
250
-
251
- const cleared = clearInbox(inboxDir);
252
- expect(cleared).toBe(0);
253
- });
254
-
255
- test('clear preserves dotfiles', () => {
256
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
257
- fs.mkdirSync(inboxDir, { recursive: true });
258
-
259
- // Write a dotfile and a regular file
260
- fs.writeFileSync(path.join(inboxDir, '.keep'), '');
261
- writeTestInboxFile(inboxDir, 'to be cleared', 'https://example.com', '2024-06-15T10:30:00.000Z');
262
-
263
- clearInbox(inboxDir);
264
-
265
- // Dotfile should remain
266
- expect(fs.existsSync(path.join(inboxDir, '.keep'))).toBe(true);
267
- // Regular file should be gone
268
- const jsonFiles = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
269
- expect(jsonFiles.length).toBe(0);
270
- });
271
- });
@@ -1,50 +0,0 @@
1
- /**
2
- * Tests for find-browse binary locator.
3
- */
4
-
5
- import { describe, test, expect } from 'bun:test';
6
- import { locateBinary } from '../src/find-browse';
7
- import { existsSync } from 'fs';
8
-
9
- describe('locateBinary', () => {
10
- test('returns null when no binary exists at known paths', () => {
11
- // This test depends on the test environment — if a real binary exists at
12
- // ~/.claude/skills/gstack/browse/dist/browse, it will find it.
13
- // We mainly test that the function doesn't throw.
14
- const result = locateBinary();
15
- expect(result === null || typeof result === 'string').toBe(true);
16
- });
17
-
18
- test('returns string path when binary exists', () => {
19
- const result = locateBinary();
20
- if (result !== null) {
21
- expect(existsSync(result)).toBe(true);
22
- }
23
- });
24
-
25
- test('priority chain checks .codex, .agents, .claude markers', () => {
26
- // Verify the source code implements the correct priority order.
27
- // We read the function source to confirm the markers array order.
28
- const src = require('fs').readFileSync(require('path').join(__dirname, '../src/find-browse.ts'), 'utf-8');
29
- // The markers array should list .codex first, then .agents, then .claude
30
- const markersMatch = src.match(/const markers = \[([^\]]+)\]/);
31
- expect(markersMatch).not.toBeNull();
32
- const markers = markersMatch![1];
33
- const codexIdx = markers.indexOf('.codex');
34
- const agentsIdx = markers.indexOf('.agents');
35
- const claudeIdx = markers.indexOf('.claude');
36
- // All three must be present
37
- expect(codexIdx).toBeGreaterThanOrEqual(0);
38
- expect(agentsIdx).toBeGreaterThanOrEqual(0);
39
- expect(claudeIdx).toBeGreaterThanOrEqual(0);
40
- // .codex before .agents before .claude
41
- expect(codexIdx).toBeLessThan(agentsIdx);
42
- expect(agentsIdx).toBeLessThan(claudeIdx);
43
- });
44
-
45
- test('function signature accepts no arguments', () => {
46
- // locateBinary should be callable with no arguments
47
- expect(typeof locateBinary).toBe('function');
48
- expect(locateBinary.length).toBe(0);
49
- });
50
- });