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.
- package/{skills/land-and-deploy/SKILL.md → commands/autoplan.md} +0 -16
- package/{skills/benchmark/SKILL.md → commands/benchmark.md} +0 -17
- package/{skills/browse/SKILL.md → commands/browse.md} +0 -17
- package/{skills/ship/SKILL.md → commands/canary.md} +0 -18
- package/{skills/careful/SKILL.md → commands/careful.md} +0 -20
- package/{skills/canary/SKILL.md → commands/codex.md} +0 -17
- package/{skills/connect-chrome/SKILL.md → commands/connect-chrome.md} +0 -15
- package/commands/cso.md +72 -0
- package/commands/design-consultation.md +72 -0
- package/commands/design-review.md +72 -0
- package/commands/design-shotgun.md +72 -0
- package/commands/document-release.md +72 -0
- package/{skills/freeze/SKILL.md → commands/freeze.md} +0 -26
- package/{skills/gstack-upgrade/SKILL.md → commands/gstack-upgrade.md} +0 -14
- package/{skills/guard/SKILL.md → commands/guard.md} +0 -31
- package/commands/investigate.md +72 -0
- package/commands/land-and-deploy.md +72 -0
- package/commands/office-hours.md +72 -0
- package/commands/plan-ceo-review.md +72 -0
- package/commands/plan-design-review.md +72 -0
- package/commands/plan-eng-review.md +72 -0
- package/commands/qa-only.md +72 -0
- package/commands/qa.md +72 -0
- package/commands/retro.md +72 -0
- package/commands/review.md +72 -0
- package/{skills/setup-browser-cookies/SKILL.md → commands/setup-browser-cookies.md} +0 -14
- package/commands/setup-deploy.md +72 -0
- package/commands/ship.md +72 -0
- package/{skills/unfreeze/SKILL.md → commands/unfreeze.md} +0 -12
- package/package.json +4 -4
- package/scripts/install-commands.js +45 -0
- package/scripts/install-skills.js +4 -7
- package/skills/autoplan/SKILL.md +0 -96
- package/skills/autoplan/SKILL.md.tmpl +0 -694
- package/skills/benchmark/SKILL.md.tmpl +0 -222
- package/skills/browse/SKILL.md.tmpl +0 -131
- package/skills/browse/bin/find-browse +0 -21
- package/skills/browse/bin/remote-slug +0 -14
- package/skills/browse/scripts/build-node-server.sh +0 -48
- package/skills/browse/src/activity.ts +0 -208
- package/skills/browse/src/browser-manager.ts +0 -959
- package/skills/browse/src/buffers.ts +0 -137
- package/skills/browse/src/bun-polyfill.cjs +0 -109
- package/skills/browse/src/cli.ts +0 -678
- package/skills/browse/src/commands.ts +0 -128
- package/skills/browse/src/config.ts +0 -150
- package/skills/browse/src/cookie-import-browser.ts +0 -625
- package/skills/browse/src/cookie-picker-routes.ts +0 -230
- package/skills/browse/src/cookie-picker-ui.ts +0 -688
- package/skills/browse/src/find-browse.ts +0 -61
- package/skills/browse/src/meta-commands.ts +0 -550
- package/skills/browse/src/platform.ts +0 -17
- package/skills/browse/src/read-commands.ts +0 -358
- package/skills/browse/src/server.ts +0 -1192
- package/skills/browse/src/sidebar-agent.ts +0 -280
- package/skills/browse/src/sidebar-utils.ts +0 -21
- package/skills/browse/src/snapshot.ts +0 -407
- package/skills/browse/src/url-validation.ts +0 -95
- package/skills/browse/src/write-commands.ts +0 -364
- package/skills/browse/test/activity.test.ts +0 -120
- package/skills/browse/test/adversarial-security.test.ts +0 -32
- package/skills/browse/test/browser-manager-unit.test.ts +0 -17
- package/skills/browse/test/bun-polyfill.test.ts +0 -72
- package/skills/browse/test/commands.test.ts +0 -2075
- package/skills/browse/test/compare-board.test.ts +0 -342
- package/skills/browse/test/config.test.ts +0 -316
- package/skills/browse/test/cookie-import-browser.test.ts +0 -519
- package/skills/browse/test/cookie-picker-routes.test.ts +0 -260
- package/skills/browse/test/file-drop.test.ts +0 -271
- package/skills/browse/test/find-browse.test.ts +0 -50
- package/skills/browse/test/findport.test.ts +0 -191
- package/skills/browse/test/fixtures/basic.html +0 -33
- package/skills/browse/test/fixtures/cursor-interactive.html +0 -22
- package/skills/browse/test/fixtures/dialog.html +0 -15
- package/skills/browse/test/fixtures/empty.html +0 -2
- package/skills/browse/test/fixtures/forms.html +0 -55
- package/skills/browse/test/fixtures/iframe.html +0 -30
- package/skills/browse/test/fixtures/network-idle.html +0 -30
- package/skills/browse/test/fixtures/qa-eval-checkout.html +0 -108
- package/skills/browse/test/fixtures/qa-eval-spa.html +0 -98
- package/skills/browse/test/fixtures/qa-eval.html +0 -51
- package/skills/browse/test/fixtures/responsive.html +0 -49
- package/skills/browse/test/fixtures/snapshot.html +0 -55
- package/skills/browse/test/fixtures/spa.html +0 -24
- package/skills/browse/test/fixtures/states.html +0 -17
- package/skills/browse/test/fixtures/upload.html +0 -25
- package/skills/browse/test/gstack-config.test.ts +0 -138
- package/skills/browse/test/gstack-update-check.test.ts +0 -514
- package/skills/browse/test/handoff.test.ts +0 -235
- package/skills/browse/test/path-validation.test.ts +0 -91
- package/skills/browse/test/platform.test.ts +0 -37
- package/skills/browse/test/server-auth.test.ts +0 -65
- package/skills/browse/test/sidebar-agent-roundtrip.test.ts +0 -226
- package/skills/browse/test/sidebar-agent.test.ts +0 -199
- package/skills/browse/test/sidebar-integration.test.ts +0 -320
- package/skills/browse/test/sidebar-unit.test.ts +0 -96
- package/skills/browse/test/snapshot.test.ts +0 -467
- package/skills/browse/test/state-ttl.test.ts +0 -35
- package/skills/browse/test/test-server.ts +0 -57
- package/skills/browse/test/url-validation.test.ts +0 -72
- package/skills/browse/test/watch.test.ts +0 -129
- package/skills/canary/SKILL.md.tmpl +0 -212
- package/skills/careful/SKILL.md.tmpl +0 -56
- package/skills/careful/bin/check-careful.sh +0 -112
- package/skills/codex/SKILL.md +0 -90
- package/skills/codex/SKILL.md.tmpl +0 -417
- package/skills/connect-chrome/SKILL.md.tmpl +0 -195
- package/skills/cso/ACKNOWLEDGEMENTS.md +0 -14
- package/skills/cso/SKILL.md +0 -93
- package/skills/cso/SKILL.md.tmpl +0 -606
- package/skills/design-consultation/SKILL.md +0 -94
- package/skills/design-consultation/SKILL.md.tmpl +0 -415
- package/skills/design-review/SKILL.md +0 -94
- package/skills/design-review/SKILL.md.tmpl +0 -290
- package/skills/design-shotgun/SKILL.md +0 -91
- package/skills/design-shotgun/SKILL.md.tmpl +0 -285
- package/skills/document-release/SKILL.md +0 -91
- package/skills/document-release/SKILL.md.tmpl +0 -359
- package/skills/freeze/SKILL.md.tmpl +0 -77
- package/skills/freeze/bin/check-freeze.sh +0 -79
- package/skills/gstack-upgrade/SKILL.md.tmpl +0 -222
- package/skills/guard/SKILL.md.tmpl +0 -77
- package/skills/investigate/SKILL.md +0 -105
- package/skills/investigate/SKILL.md.tmpl +0 -194
- package/skills/land-and-deploy/SKILL.md.tmpl +0 -881
- package/skills/office-hours/SKILL.md +0 -96
- package/skills/office-hours/SKILL.md.tmpl +0 -645
- package/skills/plan-ceo-review/SKILL.md +0 -94
- package/skills/plan-ceo-review/SKILL.md.tmpl +0 -811
- package/skills/plan-design-review/SKILL.md +0 -92
- package/skills/plan-design-review/SKILL.md.tmpl +0 -446
- package/skills/plan-eng-review/SKILL.md +0 -93
- package/skills/plan-eng-review/SKILL.md.tmpl +0 -303
- package/skills/qa/SKILL.md +0 -95
- package/skills/qa/SKILL.md.tmpl +0 -316
- package/skills/qa/references/issue-taxonomy.md +0 -85
- package/skills/qa/templates/qa-report-template.md +0 -126
- package/skills/qa-only/SKILL.md +0 -89
- package/skills/qa-only/SKILL.md.tmpl +0 -101
- package/skills/retro/SKILL.md +0 -89
- package/skills/retro/SKILL.md.tmpl +0 -820
- package/skills/review/SKILL.md +0 -92
- package/skills/review/SKILL.md.tmpl +0 -281
- package/skills/review/TODOS-format.md +0 -62
- package/skills/review/checklist.md +0 -220
- package/skills/review/design-checklist.md +0 -132
- package/skills/review/greptile-triage.md +0 -220
- package/skills/setup-browser-cookies/SKILL.md.tmpl +0 -81
- package/skills/setup-deploy/SKILL.md +0 -92
- package/skills/setup-deploy/SKILL.md.tmpl +0 -215
- package/skills/ship/SKILL.md.tmpl +0 -636
- 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
|
-
});
|