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,514 +0,0 @@
1
- /**
2
- * Tests for bin/gstack-update-check bash script.
3
- *
4
- * Uses Bun.spawnSync to invoke the script with temp dirs and
5
- * GSTACK_DIR / GSTACK_STATE_DIR / GSTACK_REMOTE_URL env overrides
6
- * for full isolation.
7
- */
8
-
9
- import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
10
- import { mkdtempSync, writeFileSync, rmSync, existsSync, readFileSync, mkdirSync, symlinkSync, utimesSync } from 'fs';
11
- import { join } from 'path';
12
- import { tmpdir } from 'os';
13
-
14
- const SCRIPT = join(import.meta.dir, '..', '..', 'bin', 'gstack-update-check');
15
-
16
- let gstackDir: string;
17
- let stateDir: string;
18
-
19
- function run(extraEnv: Record<string, string> = {}, args: string[] = []) {
20
- const result = Bun.spawnSync(['bash', SCRIPT, ...args], {
21
- env: {
22
- ...process.env,
23
- GSTACK_DIR: gstackDir,
24
- GSTACK_STATE_DIR: stateDir,
25
- GSTACK_REMOTE_URL: `file://${join(gstackDir, 'REMOTE_VERSION')}`,
26
- ...extraEnv,
27
- },
28
- stdout: 'pipe',
29
- stderr: 'pipe',
30
- });
31
- return {
32
- exitCode: result.exitCode,
33
- stdout: result.stdout.toString().trim(),
34
- stderr: result.stderr.toString().trim(),
35
- };
36
- }
37
-
38
- beforeEach(() => {
39
- gstackDir = mkdtempSync(join(tmpdir(), 'gstack-upd-test-'));
40
- stateDir = mkdtempSync(join(tmpdir(), 'gstack-state-test-'));
41
- // Link real gstack-config so update_check config check works
42
- const binDir = join(gstackDir, 'bin');
43
- mkdirSync(binDir);
44
- symlinkSync(join(import.meta.dir, '..', '..', 'bin', 'gstack-config'), join(binDir, 'gstack-config'));
45
- });
46
-
47
- afterEach(() => {
48
- rmSync(gstackDir, { recursive: true, force: true });
49
- rmSync(stateDir, { recursive: true, force: true });
50
- });
51
-
52
- function writeSnooze(version: string, level: number, epochSeconds: number) {
53
- writeFileSync(join(stateDir, 'update-snoozed'), `${version} ${level} ${epochSeconds}`);
54
- }
55
-
56
- function writeConfig(content: string) {
57
- writeFileSync(join(stateDir, 'config.yaml'), content);
58
- }
59
-
60
- function nowEpoch(): number {
61
- return Math.floor(Date.now() / 1000);
62
- }
63
-
64
- describe('gstack-update-check', () => {
65
- // ─── Path A: No VERSION file ────────────────────────────────
66
- test('exits 0 with no output when VERSION file is missing', () => {
67
- const { exitCode, stdout } = run();
68
- expect(exitCode).toBe(0);
69
- expect(stdout).toBe('');
70
- });
71
-
72
- // ─── Path B: Empty VERSION file ─────────────────────────────
73
- test('exits 0 with no output when VERSION file is empty', () => {
74
- writeFileSync(join(gstackDir, 'VERSION'), '');
75
- const { exitCode, stdout } = run();
76
- expect(exitCode).toBe(0);
77
- expect(stdout).toBe('');
78
- });
79
-
80
- // ─── Path C: Just-upgraded marker ───────────────────────────
81
- test('outputs JUST_UPGRADED and deletes marker', () => {
82
- writeFileSync(join(gstackDir, 'VERSION'), '0.4.0\n');
83
- writeFileSync(join(stateDir, 'just-upgraded-from'), '0.3.3\n');
84
-
85
- const { exitCode, stdout } = run();
86
- expect(exitCode).toBe(0);
87
- expect(stdout).toBe('JUST_UPGRADED 0.3.3 0.4.0');
88
- // Marker should be deleted
89
- expect(existsSync(join(stateDir, 'just-upgraded-from'))).toBe(false);
90
- // Cache should be written
91
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
92
- expect(cache).toContain('UP_TO_DATE');
93
- });
94
-
95
- // ─── Path C2: Just-upgraded marker + newer remote ──────────
96
- test('just-upgraded marker does not mask newer remote version', () => {
97
- writeFileSync(join(gstackDir, 'VERSION'), '0.4.0\n');
98
- writeFileSync(join(stateDir, 'just-upgraded-from'), '0.3.3\n');
99
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.5.0\n');
100
-
101
- const { exitCode, stdout } = run();
102
- expect(exitCode).toBe(0);
103
- // Should output both the just-upgraded notice AND the new upgrade
104
- expect(stdout).toContain('JUST_UPGRADED 0.3.3 0.4.0');
105
- expect(stdout).toContain('UPGRADE_AVAILABLE 0.4.0 0.5.0');
106
- // Cache should reflect the upgrade available, not UP_TO_DATE
107
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
108
- expect(cache).toContain('UPGRADE_AVAILABLE 0.4.0 0.5.0');
109
- });
110
-
111
- // ─── Path C3: Just-upgraded marker + remote matches local ──
112
- test('just-upgraded with no further updates writes UP_TO_DATE cache', () => {
113
- writeFileSync(join(gstackDir, 'VERSION'), '0.4.0\n');
114
- writeFileSync(join(stateDir, 'just-upgraded-from'), '0.3.3\n');
115
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
116
-
117
- const { exitCode, stdout } = run();
118
- expect(exitCode).toBe(0);
119
- expect(stdout).toBe('JUST_UPGRADED 0.3.3 0.4.0');
120
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
121
- expect(cache).toContain('UP_TO_DATE');
122
- });
123
-
124
- // ─── Path D1: Fresh cache, UP_TO_DATE ───────────────────────
125
- test('exits silently when cache says UP_TO_DATE and is fresh', () => {
126
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
127
- writeFileSync(join(stateDir, 'last-update-check'), 'UP_TO_DATE 0.3.3');
128
-
129
- const { exitCode, stdout } = run();
130
- expect(exitCode).toBe(0);
131
- expect(stdout).toBe('');
132
- });
133
-
134
- // ─── Path D1b: Fresh UP_TO_DATE cache, but local version changed ──
135
- test('re-checks when UP_TO_DATE cache version does not match local', () => {
136
- writeFileSync(join(gstackDir, 'VERSION'), '0.4.0\n');
137
- // Cache says UP_TO_DATE for 0.3.3, but local is now 0.4.0
138
- writeFileSync(join(stateDir, 'last-update-check'), 'UP_TO_DATE 0.3.3');
139
- // Remote says 0.5.0 — should detect upgrade
140
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.5.0\n');
141
-
142
- const { exitCode, stdout } = run();
143
- expect(exitCode).toBe(0);
144
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.4.0 0.5.0');
145
- });
146
-
147
- // ─── Path D2: Fresh cache, UPGRADE_AVAILABLE ────────────────
148
- test('echoes cached UPGRADE_AVAILABLE when cache is fresh', () => {
149
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
150
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
151
-
152
- const { exitCode, stdout } = run();
153
- expect(exitCode).toBe(0);
154
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
155
- });
156
-
157
- // ─── Path D3: Fresh cache, but local version changed ────────
158
- test('re-checks when local version does not match cached old version', () => {
159
- writeFileSync(join(gstackDir, 'VERSION'), '0.4.0\n');
160
- // Cache says 0.3.3 → 0.4.0 but we're already on 0.4.0
161
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
162
- // Remote also says 0.4.0 — should be up to date
163
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
164
-
165
- const { exitCode, stdout } = run();
166
- expect(exitCode).toBe(0);
167
- expect(stdout).toBe(''); // Up to date after re-check
168
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
169
- expect(cache).toContain('UP_TO_DATE');
170
- });
171
-
172
- // ─── Path E: Versions match (remote fetch) ─────────────────
173
- test('writes UP_TO_DATE cache when versions match', () => {
174
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
175
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.3.3\n');
176
-
177
- const { exitCode, stdout } = run();
178
- expect(exitCode).toBe(0);
179
- expect(stdout).toBe('');
180
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
181
- expect(cache).toContain('UP_TO_DATE');
182
- });
183
-
184
- // ─── Path F: Versions differ (remote fetch) ─────────────────
185
- test('outputs UPGRADE_AVAILABLE when versions differ', () => {
186
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
187
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
188
-
189
- const { exitCode, stdout } = run();
190
- expect(exitCode).toBe(0);
191
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
192
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
193
- expect(cache).toContain('UPGRADE_AVAILABLE 0.3.3 0.4.0');
194
- });
195
-
196
- // ─── Path G: Invalid remote response ────────────────────────
197
- test('treats invalid remote response as up to date', () => {
198
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
199
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '<html>404 Not Found</html>\n');
200
-
201
- const { exitCode, stdout } = run();
202
- expect(exitCode).toBe(0);
203
- expect(stdout).toBe('');
204
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
205
- expect(cache).toContain('UP_TO_DATE');
206
- });
207
-
208
- // ─── Path H: Curl fails (bad URL) ──────────────────────────
209
- test('exits silently when remote URL is unreachable', () => {
210
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
211
-
212
- const { exitCode, stdout } = run({
213
- GSTACK_REMOTE_URL: 'file:///nonexistent/path/VERSION',
214
- });
215
- expect(exitCode).toBe(0);
216
- expect(stdout).toBe('');
217
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
218
- expect(cache).toContain('UP_TO_DATE');
219
- });
220
-
221
- // ─── Path I: Corrupt cache file ─────────────────────────────
222
- test('falls through to remote fetch when cache is corrupt', () => {
223
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
224
- writeFileSync(join(stateDir, 'last-update-check'), 'garbage data here');
225
- // Remote says same version — should end up UP_TO_DATE
226
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.3.3\n');
227
-
228
- const { exitCode, stdout } = run();
229
- expect(exitCode).toBe(0);
230
- expect(stdout).toBe('');
231
- // Cache should be overwritten with valid content
232
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
233
- expect(cache).toContain('UP_TO_DATE');
234
- });
235
-
236
- // ─── State dir creation ─────────────────────────────────────
237
- test('creates state dir if it does not exist', () => {
238
- const newStateDir = join(stateDir, 'nested', 'dir');
239
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
240
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.3.3\n');
241
-
242
- const { exitCode } = run({ GSTACK_STATE_DIR: newStateDir });
243
- expect(exitCode).toBe(0);
244
- expect(existsSync(join(newStateDir, 'last-update-check'))).toBe(true);
245
- });
246
-
247
- // ─── E2E regression: always exit 0 ───────────────────────────
248
- // Agents call this on every skill invocation. Exit code 1 breaks
249
- // the preamble and confuses the agent. This test guards against
250
- // regressions like the "exits 1 when up to date" bug.
251
- test('exits 0 with real project VERSION and unreachable remote', () => {
252
- // Simulate agent context: real VERSION file, network unavailable
253
- const projectRoot = join(import.meta.dir, '..', '..');
254
- const versionFile = join(projectRoot, 'VERSION');
255
- if (!existsSync(versionFile)) return; // skip if no VERSION
256
- const version = readFileSync(versionFile, 'utf-8').trim();
257
-
258
- // Copy VERSION into test dir
259
- writeFileSync(join(gstackDir, 'VERSION'), version + '\n');
260
-
261
- // Remote is unreachable (simulates offline / CI / sandboxed agent)
262
- const { exitCode, stdout } = run({
263
- GSTACK_REMOTE_URL: 'file:///nonexistent/path/VERSION',
264
- });
265
- expect(exitCode).toBe(0);
266
- // Should write UP_TO_DATE cache (not crash)
267
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
268
- expect(cache).toContain('UP_TO_DATE');
269
- });
270
-
271
- test('exits 0 when up to date (not exit 1)', () => {
272
- // Regression test: script previously exited 1 when versions matched.
273
- // This broke every skill preamble that called it without || true.
274
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
275
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.3.3\n');
276
-
277
- // First call: fetches remote, writes cache
278
- const first = run();
279
- expect(first.exitCode).toBe(0);
280
- expect(first.stdout).toBe('');
281
-
282
- // Second call: reads fresh cache
283
- const second = run();
284
- expect(second.exitCode).toBe(0);
285
- expect(second.stdout).toBe('');
286
-
287
- // Third call with upgrade available: still exit 0
288
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
289
- rmSync(join(stateDir, 'last-update-check')); // force re-fetch
290
- const third = run();
291
- expect(third.exitCode).toBe(0);
292
- expect(third.stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
293
- });
294
-
295
- // ─── Snooze tests ───────────────────────────────────────────
296
- test('snoozed level 1 within 24h → silent (cached path)', () => {
297
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
298
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
299
- writeSnooze('0.4.0', 1, nowEpoch() - 3600); // 1h ago (within 24h)
300
-
301
- const { exitCode, stdout } = run();
302
- expect(exitCode).toBe(0);
303
- expect(stdout).toBe('');
304
- });
305
-
306
- test('snoozed level 1 expired (25h ago) → outputs UPGRADE_AVAILABLE', () => {
307
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
308
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
309
- writeSnooze('0.4.0', 1, nowEpoch() - 90000); // 25h ago
310
-
311
- const { exitCode, stdout } = run();
312
- expect(exitCode).toBe(0);
313
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
314
- });
315
-
316
- test('snoozed level 2 within 48h → silent', () => {
317
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
318
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
319
- writeSnooze('0.4.0', 2, nowEpoch() - 86400); // 24h ago (within 48h)
320
-
321
- const { exitCode, stdout } = run();
322
- expect(exitCode).toBe(0);
323
- expect(stdout).toBe('');
324
- });
325
-
326
- test('snoozed level 2 expired (49h ago) → outputs', () => {
327
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
328
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
329
- writeSnooze('0.4.0', 2, nowEpoch() - 176400); // 49h ago
330
-
331
- const { exitCode, stdout } = run();
332
- expect(exitCode).toBe(0);
333
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
334
- });
335
-
336
- test('snoozed level 3 within 7d → silent', () => {
337
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
338
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
339
- writeSnooze('0.4.0', 3, nowEpoch() - 518400); // 6d ago (within 7d)
340
-
341
- const { exitCode, stdout } = run();
342
- expect(exitCode).toBe(0);
343
- expect(stdout).toBe('');
344
- });
345
-
346
- test('snoozed level 3 expired (8d ago) → outputs', () => {
347
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
348
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
349
- writeSnooze('0.4.0', 3, nowEpoch() - 691200); // 8d ago
350
-
351
- const { exitCode, stdout } = run();
352
- expect(exitCode).toBe(0);
353
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
354
- });
355
-
356
- test('snooze ignored when version differs (new version resets snooze)', () => {
357
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
358
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.5.0');
359
- // Snoozed for 0.4.0, but remote is now 0.5.0
360
- writeSnooze('0.4.0', 3, nowEpoch() - 60); // very recent
361
-
362
- const { exitCode, stdout } = run();
363
- expect(exitCode).toBe(0);
364
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.5.0');
365
- });
366
-
367
- test('corrupt snooze file → outputs normally', () => {
368
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
369
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
370
- writeFileSync(join(stateDir, 'update-snoozed'), 'garbage');
371
-
372
- const { exitCode, stdout } = run();
373
- expect(exitCode).toBe(0);
374
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
375
- });
376
-
377
- test('non-numeric epoch in snooze file → outputs', () => {
378
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
379
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
380
- writeFileSync(join(stateDir, 'update-snoozed'), '0.4.0 1 abc');
381
-
382
- const { exitCode, stdout } = run();
383
- expect(exitCode).toBe(0);
384
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
385
- });
386
-
387
- test('non-numeric level in snooze file → outputs', () => {
388
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
389
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
390
- writeFileSync(join(stateDir, 'update-snoozed'), `0.4.0 abc ${nowEpoch()}`);
391
-
392
- const { exitCode, stdout } = run();
393
- expect(exitCode).toBe(0);
394
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
395
- });
396
-
397
- test('snooze respected on remote fetch path (no cache)', () => {
398
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
399
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
400
- // No cache file — goes to remote fetch path
401
- writeSnooze('0.4.0', 1, nowEpoch() - 3600); // 1h ago
402
-
403
- const { exitCode, stdout } = run();
404
- expect(exitCode).toBe(0);
405
- expect(stdout).toBe('');
406
- // Cache should still be written
407
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
408
- expect(cache).toContain('UPGRADE_AVAILABLE 0.3.3 0.4.0');
409
- });
410
-
411
- test('just-upgraded clears snooze file', () => {
412
- writeFileSync(join(gstackDir, 'VERSION'), '0.4.0\n');
413
- writeFileSync(join(stateDir, 'just-upgraded-from'), '0.3.3\n');
414
- writeSnooze('0.4.0', 2, nowEpoch() - 3600);
415
-
416
- const { exitCode, stdout } = run();
417
- expect(exitCode).toBe(0);
418
- expect(stdout).toBe('JUST_UPGRADED 0.3.3 0.4.0');
419
- expect(existsSync(join(stateDir, 'update-snoozed'))).toBe(false);
420
- });
421
-
422
- // ─── Config tests ──────────────────────────────────────────
423
- test('update_check: false disables all checks', () => {
424
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
425
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
426
- writeConfig('update_check: false\n');
427
-
428
- const { exitCode, stdout } = run();
429
- expect(exitCode).toBe(0);
430
- expect(stdout).toBe('');
431
- // No cache should be written
432
- expect(existsSync(join(stateDir, 'last-update-check'))).toBe(false);
433
- });
434
-
435
- test('missing config.yaml does not crash', () => {
436
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
437
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
438
- // No config file — should behave normally
439
-
440
- const { exitCode, stdout } = run();
441
- expect(exitCode).toBe(0);
442
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
443
- });
444
-
445
- // ─── --force flag tests ──────────────────────────────────────
446
-
447
- test('--force busts fresh UP_TO_DATE cache', () => {
448
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
449
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
450
- writeFileSync(join(stateDir, 'last-update-check'), 'UP_TO_DATE 0.3.3');
451
-
452
- // Without --force: cache hit, silent
453
- const cached = run();
454
- expect(cached.stdout).toBe('');
455
-
456
- // With --force: cache busted, re-fetches, finds upgrade
457
- const forced = run({}, ['--force']);
458
- expect(forced.exitCode).toBe(0);
459
- expect(forced.stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
460
- });
461
-
462
- test('--force busts fresh UPGRADE_AVAILABLE cache', () => {
463
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
464
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.3.3\n');
465
- writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
466
-
467
- // Without --force: cache hit, outputs stale upgrade
468
- const cached = run();
469
- expect(cached.stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
470
-
471
- // With --force: cache busted, re-fetches, now up to date
472
- const forced = run({}, ['--force']);
473
- expect(forced.exitCode).toBe(0);
474
- expect(forced.stdout).toBe('');
475
- const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
476
- expect(cache).toContain('UP_TO_DATE');
477
- });
478
-
479
- test('--force clears snooze so user can upgrade after snoozing', () => {
480
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
481
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
482
- writeSnooze('0.4.0', 1, nowEpoch() - 60); // snoozed 1 min ago (within 24h)
483
-
484
- // Without --force: snoozed, silent
485
- const snoozed = run();
486
- expect(snoozed.exitCode).toBe(0);
487
- expect(snoozed.stdout).toBe('');
488
-
489
- // With --force: snooze cleared, outputs upgrade
490
- const forced = run({}, ['--force']);
491
- expect(forced.exitCode).toBe(0);
492
- expect(forced.stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
493
- // Snooze file should be deleted
494
- expect(existsSync(join(stateDir, 'update-snoozed'))).toBe(false);
495
- });
496
-
497
- // ─── Split TTL tests ─────────────────────────────────────────
498
-
499
- test('UP_TO_DATE cache expires after 60 min (not 720)', () => {
500
- writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
501
- writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
502
- writeFileSync(join(stateDir, 'last-update-check'), 'UP_TO_DATE 0.3.3');
503
-
504
- // Set cache mtime to 90 minutes ago (past 60-min TTL)
505
- const ninetyMinAgo = new Date(Date.now() - 90 * 60 * 1000);
506
- const cachePath = join(stateDir, 'last-update-check');
507
- utimesSync(cachePath, ninetyMinAgo, ninetyMinAgo);
508
-
509
- // Cache should be stale at 60-min TTL, re-fetches and finds upgrade
510
- const { exitCode, stdout } = run();
511
- expect(exitCode).toBe(0);
512
- expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
513
- });
514
- });