opengstack 0.13.9 → 0.14.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 (152) hide show
  1. package/{skills/land-and-deploy/SKILL.md → commands/autoplan.md} +0 -16
  2. package/{skills/benchmark/SKILL.md → commands/benchmark.md} +0 -17
  3. package/{skills/browse/SKILL.md → commands/browse.md} +0 -17
  4. package/{skills/ship/SKILL.md → commands/canary.md} +0 -18
  5. package/{skills/careful/SKILL.md → commands/careful.md} +0 -20
  6. package/{skills/canary/SKILL.md → commands/codex.md} +0 -17
  7. package/{skills/connect-chrome/SKILL.md → commands/connect-chrome.md} +0 -15
  8. package/commands/cso.md +72 -0
  9. package/commands/design-consultation.md +72 -0
  10. package/commands/design-review.md +72 -0
  11. package/commands/design-shotgun.md +72 -0
  12. package/commands/document-release.md +72 -0
  13. package/{skills/freeze/SKILL.md → commands/freeze.md} +0 -26
  14. package/{skills/gstack-upgrade/SKILL.md → commands/gstack-upgrade.md} +0 -14
  15. package/{skills/guard/SKILL.md → commands/guard.md} +0 -31
  16. package/commands/investigate.md +72 -0
  17. package/commands/land-and-deploy.md +72 -0
  18. package/commands/office-hours.md +72 -0
  19. package/commands/plan-ceo-review.md +72 -0
  20. package/commands/plan-design-review.md +72 -0
  21. package/commands/plan-eng-review.md +72 -0
  22. package/commands/qa-only.md +72 -0
  23. package/commands/qa.md +72 -0
  24. package/commands/retro.md +72 -0
  25. package/commands/review.md +72 -0
  26. package/{skills/setup-browser-cookies/SKILL.md → commands/setup-browser-cookies.md} +0 -14
  27. package/commands/setup-deploy.md +72 -0
  28. package/commands/ship.md +72 -0
  29. package/{skills/unfreeze/SKILL.md → commands/unfreeze.md} +0 -12
  30. package/package.json +4 -4
  31. package/scripts/install-commands.js +45 -0
  32. package/scripts/install-skills.js +4 -7
  33. package/skills/autoplan/SKILL.md +0 -96
  34. package/skills/autoplan/SKILL.md.tmpl +0 -694
  35. package/skills/benchmark/SKILL.md.tmpl +0 -222
  36. package/skills/browse/SKILL.md.tmpl +0 -131
  37. package/skills/browse/bin/find-browse +0 -21
  38. package/skills/browse/bin/remote-slug +0 -14
  39. package/skills/browse/scripts/build-node-server.sh +0 -48
  40. package/skills/browse/src/activity.ts +0 -208
  41. package/skills/browse/src/browser-manager.ts +0 -959
  42. package/skills/browse/src/buffers.ts +0 -137
  43. package/skills/browse/src/bun-polyfill.cjs +0 -109
  44. package/skills/browse/src/cli.ts +0 -678
  45. package/skills/browse/src/commands.ts +0 -128
  46. package/skills/browse/src/config.ts +0 -150
  47. package/skills/browse/src/cookie-import-browser.ts +0 -625
  48. package/skills/browse/src/cookie-picker-routes.ts +0 -230
  49. package/skills/browse/src/cookie-picker-ui.ts +0 -688
  50. package/skills/browse/src/find-browse.ts +0 -61
  51. package/skills/browse/src/meta-commands.ts +0 -550
  52. package/skills/browse/src/platform.ts +0 -17
  53. package/skills/browse/src/read-commands.ts +0 -358
  54. package/skills/browse/src/server.ts +0 -1192
  55. package/skills/browse/src/sidebar-agent.ts +0 -280
  56. package/skills/browse/src/sidebar-utils.ts +0 -21
  57. package/skills/browse/src/snapshot.ts +0 -407
  58. package/skills/browse/src/url-validation.ts +0 -95
  59. package/skills/browse/src/write-commands.ts +0 -364
  60. package/skills/browse/test/activity.test.ts +0 -120
  61. package/skills/browse/test/adversarial-security.test.ts +0 -32
  62. package/skills/browse/test/browser-manager-unit.test.ts +0 -17
  63. package/skills/browse/test/bun-polyfill.test.ts +0 -72
  64. package/skills/browse/test/commands.test.ts +0 -2075
  65. package/skills/browse/test/compare-board.test.ts +0 -342
  66. package/skills/browse/test/config.test.ts +0 -316
  67. package/skills/browse/test/cookie-import-browser.test.ts +0 -519
  68. package/skills/browse/test/cookie-picker-routes.test.ts +0 -260
  69. package/skills/browse/test/file-drop.test.ts +0 -271
  70. package/skills/browse/test/find-browse.test.ts +0 -50
  71. package/skills/browse/test/findport.test.ts +0 -191
  72. package/skills/browse/test/fixtures/basic.html +0 -33
  73. package/skills/browse/test/fixtures/cursor-interactive.html +0 -22
  74. package/skills/browse/test/fixtures/dialog.html +0 -15
  75. package/skills/browse/test/fixtures/empty.html +0 -2
  76. package/skills/browse/test/fixtures/forms.html +0 -55
  77. package/skills/browse/test/fixtures/iframe.html +0 -30
  78. package/skills/browse/test/fixtures/network-idle.html +0 -30
  79. package/skills/browse/test/fixtures/qa-eval-checkout.html +0 -108
  80. package/skills/browse/test/fixtures/qa-eval-spa.html +0 -98
  81. package/skills/browse/test/fixtures/qa-eval.html +0 -51
  82. package/skills/browse/test/fixtures/responsive.html +0 -49
  83. package/skills/browse/test/fixtures/snapshot.html +0 -55
  84. package/skills/browse/test/fixtures/spa.html +0 -24
  85. package/skills/browse/test/fixtures/states.html +0 -17
  86. package/skills/browse/test/fixtures/upload.html +0 -25
  87. package/skills/browse/test/gstack-config.test.ts +0 -138
  88. package/skills/browse/test/gstack-update-check.test.ts +0 -514
  89. package/skills/browse/test/handoff.test.ts +0 -235
  90. package/skills/browse/test/path-validation.test.ts +0 -91
  91. package/skills/browse/test/platform.test.ts +0 -37
  92. package/skills/browse/test/server-auth.test.ts +0 -65
  93. package/skills/browse/test/sidebar-agent-roundtrip.test.ts +0 -226
  94. package/skills/browse/test/sidebar-agent.test.ts +0 -199
  95. package/skills/browse/test/sidebar-integration.test.ts +0 -320
  96. package/skills/browse/test/sidebar-unit.test.ts +0 -96
  97. package/skills/browse/test/snapshot.test.ts +0 -467
  98. package/skills/browse/test/state-ttl.test.ts +0 -35
  99. package/skills/browse/test/test-server.ts +0 -57
  100. package/skills/browse/test/url-validation.test.ts +0 -72
  101. package/skills/browse/test/watch.test.ts +0 -129
  102. package/skills/canary/SKILL.md.tmpl +0 -212
  103. package/skills/careful/SKILL.md.tmpl +0 -56
  104. package/skills/careful/bin/check-careful.sh +0 -112
  105. package/skills/codex/SKILL.md +0 -90
  106. package/skills/codex/SKILL.md.tmpl +0 -417
  107. package/skills/connect-chrome/SKILL.md.tmpl +0 -195
  108. package/skills/cso/ACKNOWLEDGEMENTS.md +0 -14
  109. package/skills/cso/SKILL.md +0 -93
  110. package/skills/cso/SKILL.md.tmpl +0 -606
  111. package/skills/design-consultation/SKILL.md +0 -94
  112. package/skills/design-consultation/SKILL.md.tmpl +0 -415
  113. package/skills/design-review/SKILL.md +0 -94
  114. package/skills/design-review/SKILL.md.tmpl +0 -290
  115. package/skills/design-shotgun/SKILL.md +0 -91
  116. package/skills/design-shotgun/SKILL.md.tmpl +0 -285
  117. package/skills/document-release/SKILL.md +0 -91
  118. package/skills/document-release/SKILL.md.tmpl +0 -359
  119. package/skills/freeze/SKILL.md.tmpl +0 -77
  120. package/skills/freeze/bin/check-freeze.sh +0 -79
  121. package/skills/gstack-upgrade/SKILL.md.tmpl +0 -222
  122. package/skills/guard/SKILL.md.tmpl +0 -77
  123. package/skills/investigate/SKILL.md +0 -105
  124. package/skills/investigate/SKILL.md.tmpl +0 -194
  125. package/skills/land-and-deploy/SKILL.md.tmpl +0 -881
  126. package/skills/office-hours/SKILL.md +0 -96
  127. package/skills/office-hours/SKILL.md.tmpl +0 -645
  128. package/skills/plan-ceo-review/SKILL.md +0 -94
  129. package/skills/plan-ceo-review/SKILL.md.tmpl +0 -811
  130. package/skills/plan-design-review/SKILL.md +0 -92
  131. package/skills/plan-design-review/SKILL.md.tmpl +0 -446
  132. package/skills/plan-eng-review/SKILL.md +0 -93
  133. package/skills/plan-eng-review/SKILL.md.tmpl +0 -303
  134. package/skills/qa/SKILL.md +0 -95
  135. package/skills/qa/SKILL.md.tmpl +0 -316
  136. package/skills/qa/references/issue-taxonomy.md +0 -85
  137. package/skills/qa/templates/qa-report-template.md +0 -126
  138. package/skills/qa-only/SKILL.md +0 -89
  139. package/skills/qa-only/SKILL.md.tmpl +0 -101
  140. package/skills/retro/SKILL.md +0 -89
  141. package/skills/retro/SKILL.md.tmpl +0 -820
  142. package/skills/review/SKILL.md +0 -92
  143. package/skills/review/SKILL.md.tmpl +0 -281
  144. package/skills/review/TODOS-format.md +0 -62
  145. package/skills/review/checklist.md +0 -220
  146. package/skills/review/design-checklist.md +0 -132
  147. package/skills/review/greptile-triage.md +0 -220
  148. package/skills/setup-browser-cookies/SKILL.md.tmpl +0 -81
  149. package/skills/setup-deploy/SKILL.md +0 -92
  150. package/skills/setup-deploy/SKILL.md.tmpl +0 -215
  151. package/skills/ship/SKILL.md.tmpl +0 -636
  152. 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
- });