gsd-lite 0.3.15 → 0.3.17
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +0 -0
- package/README.md +0 -0
- package/agents/debugger.md +0 -0
- package/agents/executor.md +0 -0
- package/agents/researcher.md +0 -0
- package/agents/reviewer.md +0 -0
- package/cli.js +5 -1
- package/commands/prd.md +0 -0
- package/commands/resume.md +0 -0
- package/commands/start.md +0 -0
- package/commands/status.md +0 -0
- package/commands/stop.md +0 -0
- package/hooks/context-monitor.js +0 -0
- package/hooks/gsd-auto-update.cjs +184 -60
- package/hooks/gsd-context-monitor.cjs +0 -0
- package/hooks/gsd-session-init.cjs +28 -10
- package/hooks/gsd-statusline.cjs +0 -0
- package/hooks/hooks.json +0 -0
- package/install.js +0 -0
- package/launcher.js +0 -0
- package/package.json +1 -1
- package/references/anti-rationalization-full.md +0 -0
- package/references/evidence-spec.md +0 -0
- package/references/execution-loop.md +0 -0
- package/references/git-worktrees.md +0 -0
- package/references/questioning.md +0 -0
- package/references/review-classification.md +0 -0
- package/references/state-diagram.md +0 -0
- package/references/testing-patterns.md +0 -0
- package/src/schema.js +0 -0
- package/src/server.js +0 -0
- package/src/tools/orchestrator.js +0 -0
- package/src/tools/state.js +0 -0
- package/src/tools/verify.js +0 -0
- package/src/utils.js +0 -0
- package/uninstall.js +0 -0
- package/workflows/debugging.md +0 -0
- package/workflows/deviation-rules.md +0 -0
- package/workflows/research.md +0 -0
- package/workflows/review-cycle.md +0 -0
- package/workflows/tdd-cycle.md +0 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"name": "gsd",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "AI orchestration tool — GSD management shell + Superpowers quality core. 5 commands, 4 agents, 5 workflows, MCP server, context monitoring.",
|
|
16
|
-
"version": "0.3.
|
|
16
|
+
"version": "0.3.17",
|
|
17
17
|
"keywords": [
|
|
18
18
|
"orchestration",
|
|
19
19
|
"mcp",
|
package/.mcp.json
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/agents/debugger.md
CHANGED
|
File without changes
|
package/agents/executor.md
CHANGED
|
File without changes
|
package/agents/researcher.md
CHANGED
|
File without changes
|
package/agents/reviewer.md
CHANGED
|
File without changes
|
package/cli.js
CHANGED
|
@@ -40,7 +40,11 @@ switch (command) {
|
|
|
40
40
|
if (result?.updated) {
|
|
41
41
|
console.log(`\n✓ Updated: v${result.from} → v${result.to}`);
|
|
42
42
|
} else if (result?.updateAvailable) {
|
|
43
|
-
|
|
43
|
+
if (result.action === 'plugin_update') {
|
|
44
|
+
console.log(`\n! Update available: v${result.to}. Run /plugin update gsd`);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(`\n! Update available v${result.to} but install failed. Try manually.`);
|
|
47
|
+
}
|
|
44
48
|
} else if (!result) {
|
|
45
49
|
console.log('✓ Already up to date');
|
|
46
50
|
}
|
package/commands/prd.md
CHANGED
|
File without changes
|
package/commands/resume.md
CHANGED
|
File without changes
|
package/commands/start.md
CHANGED
|
File without changes
|
package/commands/status.md
CHANGED
|
File without changes
|
package/commands/stop.md
CHANGED
|
File without changes
|
package/hooks/context-monitor.js
CHANGED
|
File without changes
|
|
@@ -20,104 +20,200 @@ const claudeDir =
|
|
|
20
20
|
const runtimeDir = path.join(claudeDir, 'gsd');
|
|
21
21
|
const stateDir = path.join(runtimeDir, 'runtime');
|
|
22
22
|
const STATE_FILE = path.join(stateDir, 'update-state.json');
|
|
23
|
+
const STATE_LOCK_FILE = path.join(stateDir, 'update-state.lock');
|
|
24
|
+
const NOTIFICATION_FILE = path.join(stateDir, 'update-notification.json');
|
|
23
25
|
const pluginRoot = path.resolve(__dirname, '..');
|
|
24
26
|
|
|
27
|
+
const LOCK_STALE_MS = 10_000;
|
|
28
|
+
const LOCK_RETRY_MS = 50;
|
|
29
|
+
const LOCK_MAX_RETRIES = 100;
|
|
30
|
+
|
|
25
31
|
// ── Main Entry ─────────────────────────────────────────────
|
|
26
32
|
async function checkForUpdate(options = {}) {
|
|
27
|
-
const {
|
|
33
|
+
const {
|
|
34
|
+
force = false,
|
|
35
|
+
verbose = false,
|
|
36
|
+
install = true,
|
|
37
|
+
notify = false,
|
|
38
|
+
fetchLatestRelease: fetchLatestReleaseImpl = fetchLatestRelease,
|
|
39
|
+
downloadAndInstall: downloadAndInstallImpl = downloadAndInstall,
|
|
40
|
+
getCurrentVersion: getCurrentVersionImpl = getCurrentVersion,
|
|
41
|
+
} = options;
|
|
42
|
+
const installMode = getInstallMode();
|
|
28
43
|
|
|
29
44
|
try {
|
|
30
45
|
if (!force && shouldSkipUpdateCheck()) {
|
|
31
46
|
if (verbose) console.log('Skipping update check (dev mode or auto-update in progress)');
|
|
32
47
|
return null;
|
|
33
48
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
return await withFileLock(async () => {
|
|
50
|
+
const state = readState();
|
|
51
|
+
if (!force && !shouldCheck(state)) {
|
|
52
|
+
if (state.updateAvailable && state.latestVersion) {
|
|
53
|
+
return {
|
|
54
|
+
updateAvailable: true,
|
|
55
|
+
from: getCurrentVersionImpl(installMode),
|
|
56
|
+
to: state.latestVersion,
|
|
57
|
+
installMode,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (verbose) console.log('Throttled — last check:', state.lastCheck);
|
|
61
|
+
return null;
|
|
43
62
|
}
|
|
44
|
-
if (verbose) console.log('Throttled — last check:', state.lastCheck);
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
63
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Successful fetch — clear rate-limit back-off
|
|
59
|
-
state.rateLimited = false;
|
|
60
|
-
const currentVersion = getCurrentVersion();
|
|
61
|
-
if (verbose) console.log(`Current: v${currentVersion} — Latest: v${latest.version}`);
|
|
64
|
+
if (verbose) console.log('Checking GitHub for latest release...');
|
|
65
|
+
const token = getGitHubToken();
|
|
66
|
+
const latest = await fetchLatestReleaseImpl(token);
|
|
67
|
+
if (!latest) {
|
|
68
|
+
if (latest === false) state.rateLimited = true; // 403 rate-limited
|
|
69
|
+
saveState({ ...state, lastCheck: new Date().toISOString() });
|
|
70
|
+
if (verbose) console.log('Could not fetch latest release');
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
// Successful fetch — clear rate-limit back-off
|
|
75
|
+
state.rateLimited = false;
|
|
76
|
+
const currentVersion = getCurrentVersionImpl(installMode);
|
|
77
|
+
if (verbose) console.log(`Current: v${currentVersion} — Latest: v${latest.version}`);
|
|
78
|
+
|
|
79
|
+
const hasUpdate = compareVersions(latest.version, currentVersion) > 0;
|
|
80
|
+
|
|
81
|
+
if (hasUpdate) {
|
|
82
|
+
if (installMode === 'plugin' && install) {
|
|
83
|
+
saveState({
|
|
84
|
+
...state,
|
|
85
|
+
lastCheck: new Date().toISOString(),
|
|
86
|
+
latestVersion: latest.version,
|
|
87
|
+
updateAvailable: true,
|
|
88
|
+
});
|
|
89
|
+
if (notify) {
|
|
90
|
+
writeNotification({
|
|
91
|
+
kind: 'available',
|
|
92
|
+
from: currentVersion,
|
|
93
|
+
to: latest.version,
|
|
94
|
+
action: 'plugin_update',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
updateAvailable: true,
|
|
99
|
+
from: currentVersion,
|
|
100
|
+
to: latest.version,
|
|
101
|
+
action: 'plugin_update',
|
|
102
|
+
autoInstallSupported: false,
|
|
103
|
+
installMode,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!install) {
|
|
108
|
+
// Check-only mode (used by SessionStart hook)
|
|
109
|
+
saveState({
|
|
110
|
+
...state,
|
|
111
|
+
lastCheck: new Date().toISOString(),
|
|
112
|
+
latestVersion: latest.version,
|
|
113
|
+
updateAvailable: true,
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
updateAvailable: true,
|
|
117
|
+
from: currentVersion,
|
|
118
|
+
to: latest.version,
|
|
119
|
+
installMode,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (verbose) console.log(`Downloading v${latest.version}...`);
|
|
124
|
+
const success = await downloadAndInstallImpl(latest.tarballUrl, verbose, token);
|
|
64
125
|
|
|
65
|
-
if (hasUpdate) {
|
|
66
|
-
if (!install) {
|
|
67
|
-
// Check-only mode (used by SessionStart hook)
|
|
68
126
|
saveState({
|
|
69
127
|
...state,
|
|
70
128
|
lastCheck: new Date().toISOString(),
|
|
71
129
|
latestVersion: latest.version,
|
|
72
|
-
updateAvailable:
|
|
130
|
+
updateAvailable: !success,
|
|
131
|
+
lastUpdate: success ? new Date().toISOString() : state.lastUpdate,
|
|
73
132
|
});
|
|
133
|
+
|
|
134
|
+
if (success && notify) {
|
|
135
|
+
writeNotification({
|
|
136
|
+
kind: 'updated',
|
|
137
|
+
from: currentVersion,
|
|
138
|
+
to: latest.version,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
74
142
|
return {
|
|
75
|
-
updateAvailable:
|
|
143
|
+
updateAvailable: !success,
|
|
144
|
+
updated: success,
|
|
76
145
|
from: currentVersion,
|
|
77
146
|
to: latest.version,
|
|
147
|
+
installMode,
|
|
78
148
|
};
|
|
79
149
|
}
|
|
80
150
|
|
|
81
|
-
|
|
82
|
-
const success = await downloadAndInstall(latest.tarballUrl, verbose, token);
|
|
83
|
-
|
|
151
|
+
// No update needed
|
|
84
152
|
saveState({
|
|
85
153
|
...state,
|
|
86
154
|
lastCheck: new Date().toISOString(),
|
|
87
155
|
latestVersion: latest.version,
|
|
88
|
-
updateAvailable:
|
|
89
|
-
lastUpdate: success ? new Date().toISOString() : state.lastUpdate,
|
|
156
|
+
updateAvailable: false,
|
|
90
157
|
});
|
|
91
|
-
|
|
92
|
-
return
|
|
93
|
-
updateAvailable: !success,
|
|
94
|
-
updated: success,
|
|
95
|
-
from: currentVersion,
|
|
96
|
-
to: latest.version,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// No update needed
|
|
101
|
-
saveState({
|
|
102
|
-
...state,
|
|
103
|
-
lastCheck: new Date().toISOString(),
|
|
104
|
-
latestVersion: latest.version,
|
|
105
|
-
updateAvailable: false,
|
|
158
|
+
if (verbose) console.log('Already up to date');
|
|
159
|
+
return null;
|
|
106
160
|
});
|
|
107
|
-
if (verbose) console.log('Already up to date');
|
|
108
|
-
return null;
|
|
109
161
|
} catch (err) {
|
|
110
162
|
if (verbose) console.error('Update check failed:', err.message);
|
|
111
163
|
return null;
|
|
112
164
|
}
|
|
113
165
|
}
|
|
114
166
|
|
|
167
|
+
async function withFileLock(fn) {
|
|
168
|
+
let acquired = false;
|
|
169
|
+
try {
|
|
170
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
171
|
+
} catch {
|
|
172
|
+
/* best effort */
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
|
|
176
|
+
try {
|
|
177
|
+
fs.writeFileSync(STATE_LOCK_FILE, String(process.pid), { flag: 'wx' });
|
|
178
|
+
acquired = true;
|
|
179
|
+
break;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err.code === 'EEXIST') {
|
|
182
|
+
try {
|
|
183
|
+
const stats = fs.statSync(STATE_LOCK_FILE);
|
|
184
|
+
if (Date.now() - stats.mtimeMs > LOCK_STALE_MS) {
|
|
185
|
+
try { fs.rmSync(STATE_LOCK_FILE, { force: true }); } catch {}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_MS));
|
|
192
|
+
} else {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
return await fn();
|
|
200
|
+
} finally {
|
|
201
|
+
if (acquired) {
|
|
202
|
+
try { fs.rmSync(STATE_LOCK_FILE, { force: true }); } catch {}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
115
207
|
// ── Skip Check Detection ──────────────────────────────────
|
|
116
208
|
// Returns true when update checks should be skipped:
|
|
117
209
|
// 1. PLUGIN_AUTO_UPDATE env set → recursive guard (auto-update already in progress)
|
|
118
210
|
// 2. Running from a git clone → dev mode (developer working on source)
|
|
119
211
|
function shouldSkipUpdateCheck() {
|
|
120
212
|
if (process.env.PLUGIN_AUTO_UPDATE) return true;
|
|
213
|
+
return isDevMode();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function isDevMode() {
|
|
121
217
|
try {
|
|
122
218
|
if (!fs.existsSync(path.join(pluginRoot, '.git'))) return false;
|
|
123
219
|
const pkg = JSON.parse(
|
|
@@ -129,6 +225,13 @@ function shouldSkipUpdateCheck() {
|
|
|
129
225
|
}
|
|
130
226
|
}
|
|
131
227
|
|
|
228
|
+
function getInstallMode() {
|
|
229
|
+
if (isDevMode()) return 'dev';
|
|
230
|
+
return path.resolve(pluginRoot) === path.resolve(claudeDir)
|
|
231
|
+
? 'manual'
|
|
232
|
+
: 'plugin';
|
|
233
|
+
}
|
|
234
|
+
|
|
132
235
|
// ── Throttle ───────────────────────────────────────────────
|
|
133
236
|
function shouldCheck(state) {
|
|
134
237
|
if (!state.lastCheck) return true;
|
|
@@ -196,11 +299,11 @@ function compareVersions(a, b) {
|
|
|
196
299
|
return 0;
|
|
197
300
|
}
|
|
198
301
|
|
|
199
|
-
function getCurrentVersion() {
|
|
200
|
-
|
|
201
|
-
path.join(pluginRoot, 'package.json')
|
|
202
|
-
path.join(runtimeDir, 'package.json')
|
|
203
|
-
|
|
302
|
+
function getCurrentVersion(mode = getInstallMode()) {
|
|
303
|
+
const candidates = mode === 'manual'
|
|
304
|
+
? [path.join(runtimeDir, 'package.json'), path.join(pluginRoot, 'package.json')]
|
|
305
|
+
: [path.join(pluginRoot, 'package.json'), path.join(runtimeDir, 'package.json')];
|
|
306
|
+
for (const p of candidates) {
|
|
204
307
|
try {
|
|
205
308
|
return JSON.parse(fs.readFileSync(p, 'utf8')).version;
|
|
206
309
|
} catch {
|
|
@@ -280,9 +383,30 @@ function saveState(state) {
|
|
|
280
383
|
}
|
|
281
384
|
}
|
|
282
385
|
|
|
386
|
+
function writeNotification(notification) {
|
|
387
|
+
try {
|
|
388
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
389
|
+
const tmpPath = NOTIFICATION_FILE + `.${process.pid}.tmp`;
|
|
390
|
+
fs.writeFileSync(tmpPath, JSON.stringify(notification, null, 2) + '\n');
|
|
391
|
+
fs.renameSync(tmpPath, NOTIFICATION_FILE);
|
|
392
|
+
} catch {
|
|
393
|
+
/* silent */
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
283
397
|
module.exports = {
|
|
284
398
|
checkForUpdate,
|
|
285
399
|
getCurrentVersion,
|
|
286
400
|
compareVersions,
|
|
401
|
+
getInstallMode,
|
|
402
|
+
isDevMode,
|
|
403
|
+
shouldCheck,
|
|
287
404
|
shouldSkipUpdateCheck,
|
|
288
405
|
};
|
|
406
|
+
|
|
407
|
+
// ── CLI Entry Point (for background auto-install) ─────────
|
|
408
|
+
if (require.main === module) {
|
|
409
|
+
checkForUpdate({ install: true, verbose: false, notify: true })
|
|
410
|
+
.catch(() => {})
|
|
411
|
+
.finally(() => process.exit(0));
|
|
412
|
+
}
|
|
File without changes
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// GSD-Lite SessionStart hook
|
|
3
|
-
// 1.
|
|
4
|
-
// 2.
|
|
3
|
+
// 1. Cleans up stale temp files (throttled to once/day).
|
|
4
|
+
// 2. Auto-registers statusLine in settings.json if not already configured.
|
|
5
|
+
// 3. Shows notification if a previous background update completed or found a new version.
|
|
6
|
+
// 4. Spawns background auto-update (detached, non-blocking).
|
|
5
7
|
// Idempotent: skips if statusLine already points to gsd-statusline, preserves
|
|
6
8
|
// third-party statuslines.
|
|
7
9
|
|
|
@@ -64,15 +66,31 @@ setTimeout(() => process.exit(0), 4000).unref();
|
|
|
64
66
|
}
|
|
65
67
|
} catch { /* silent */ }
|
|
66
68
|
|
|
67
|
-
// ── Phase 3:
|
|
68
|
-
// checkForUpdate handles throttling internally via shouldCheck()
|
|
69
|
+
// ── Phase 3: Show notification from previous background auto-install ──
|
|
69
70
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
71
|
+
const notifPath = path.join(claudeDir, 'gsd', 'runtime', 'update-notification.json');
|
|
72
|
+
if (fs.existsSync(notifPath)) {
|
|
73
|
+
const notif = JSON.parse(fs.readFileSync(notifPath, 'utf8'));
|
|
74
|
+
if (notif.kind === 'updated') {
|
|
75
|
+
console.log(`✅ GSD-Lite auto-updated: v${notif.from} → v${notif.to}`);
|
|
76
|
+
} else if (notif.kind === 'available' && notif.action === 'plugin_update') {
|
|
77
|
+
console.log(`📦 GSD-Lite update available: v${notif.from} → v${notif.to}. Run /plugin update gsd`);
|
|
78
|
+
} else if (notif.kind === 'available') {
|
|
79
|
+
console.log(`📦 GSD-Lite update available: v${notif.from} → v${notif.to}. Run gsd update`);
|
|
80
|
+
}
|
|
81
|
+
fs.unlinkSync(notifPath);
|
|
76
82
|
}
|
|
83
|
+
} catch { /* silent */ }
|
|
84
|
+
|
|
85
|
+
// ── Phase 4: Spawn background auto-update (non-blocking) ──
|
|
86
|
+
// Detached child handles check + download + install; throttled by shouldCheck()
|
|
87
|
+
try {
|
|
88
|
+
const { spawn } = require('node:child_process');
|
|
89
|
+
const child = spawn(
|
|
90
|
+
process.execPath,
|
|
91
|
+
[path.join(__dirname, 'gsd-auto-update.cjs')],
|
|
92
|
+
{ detached: true, stdio: 'ignore' },
|
|
93
|
+
);
|
|
94
|
+
child.unref();
|
|
77
95
|
} catch { /* silent — never block session start */ }
|
|
78
96
|
})().catch(() => {});
|
package/hooks/gsd-statusline.cjs
CHANGED
|
File without changes
|
package/hooks/hooks.json
CHANGED
|
File without changes
|
package/install.js
CHANGED
|
File without changes
|
package/launcher.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/schema.js
CHANGED
|
File without changes
|
package/src/server.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/tools/state.js
CHANGED
|
File without changes
|
package/src/tools/verify.js
CHANGED
|
File without changes
|
package/src/utils.js
CHANGED
|
File without changes
|
package/uninstall.js
CHANGED
|
File without changes
|
package/workflows/debugging.md
CHANGED
|
File without changes
|
|
File without changes
|
package/workflows/research.md
CHANGED
|
File without changes
|
|
File without changes
|
package/workflows/tdd-cycle.md
CHANGED
|
File without changes
|