gsd-lite 0.6.2 → 0.6.3
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/hooks/gsd-session-init.cjs +26 -11
- package/hooks/lib/statusline-composite.cjs +110 -0
- package/install.js +34 -13
- package/package.json +1 -1
- package/uninstall.js +17 -4
|
@@ -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.6.
|
|
16
|
+
"version": "0.6.3",
|
|
17
17
|
"keywords": [
|
|
18
18
|
"orchestration",
|
|
19
19
|
"mcp",
|
|
@@ -13,10 +13,8 @@ const fs = require('node:fs');
|
|
|
13
13
|
const path = require('node:path');
|
|
14
14
|
const os = require('node:os');
|
|
15
15
|
|
|
16
|
-
const pluginRoot = path.resolve(__dirname, '..');
|
|
17
16
|
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
18
17
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
19
|
-
const statuslineScript = path.join(pluginRoot, 'hooks', 'gsd-statusline.cjs');
|
|
20
18
|
|
|
21
19
|
// Safety: exit after 4s regardless (hook timeout is 5s)
|
|
22
20
|
setTimeout(() => process.exit(0), 4000).unref();
|
|
@@ -49,21 +47,38 @@ setTimeout(() => process.exit(0), 4000).unref();
|
|
|
49
47
|
} catch { /* silent */ }
|
|
50
48
|
|
|
51
49
|
// ── Phase 2: StatusLine auto-registration ──
|
|
50
|
+
// StatusLine is a top-level settings.json config that the plugin system
|
|
51
|
+
// (hooks.json) cannot manage. Self-heal if not registered.
|
|
52
52
|
try {
|
|
53
|
-
|
|
53
|
+
const stableStatuslinePath = path.join(claudeDir, 'hooks', 'gsd-statusline.cjs');
|
|
54
|
+
if (fs.existsSync(stableStatuslinePath)) {
|
|
54
55
|
let settings = {};
|
|
55
56
|
try {
|
|
56
57
|
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
57
58
|
} catch { /* Can't read settings — skip registration */ }
|
|
58
59
|
|
|
59
|
-
if (settings
|
|
60
|
-
settings.statusLine
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
if (settings) {
|
|
61
|
+
const current = settings.statusLine?.command || '';
|
|
62
|
+
|
|
63
|
+
if (current.includes('gsd-statusline')) {
|
|
64
|
+
// Already registered — nothing to do
|
|
65
|
+
} else if (!current) {
|
|
66
|
+
// No statusLine — register directly
|
|
67
|
+
settings.statusLine = {
|
|
68
|
+
type: 'command',
|
|
69
|
+
command: `node ${JSON.stringify(stableStatuslinePath)}`
|
|
70
|
+
};
|
|
71
|
+
const tmpPath = settingsPath + `.gsd-tmp-${process.pid}`;
|
|
72
|
+
fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n');
|
|
73
|
+
fs.renameSync(tmpPath, settingsPath);
|
|
74
|
+
} else if (current.includes('statusline-composite')) {
|
|
75
|
+
// Composite system (e.g., code-graph) — register as provider
|
|
76
|
+
try {
|
|
77
|
+
const { registerProvider } = require('./lib/statusline-composite.cjs');
|
|
78
|
+
registerProvider(stableStatuslinePath);
|
|
79
|
+
} catch { /* composite helper not available */ }
|
|
80
|
+
}
|
|
81
|
+
// else: some other statusLine, don't overwrite
|
|
67
82
|
}
|
|
68
83
|
}
|
|
69
84
|
} catch { /* silent */ }
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Detect and register with composite statusline systems (e.g., code-graph).
|
|
3
|
+
// Used by install.js, gsd-session-init.cjs, and uninstall.js.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const os = require('node:os');
|
|
8
|
+
|
|
9
|
+
// Known composite statusline registry paths
|
|
10
|
+
const REGISTRY_PATHS = [
|
|
11
|
+
path.join(os.homedir(), '.cache', 'code-graph', 'statusline-registry.json'),
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function isCompositeStatusLine(command) {
|
|
15
|
+
return typeof command === 'string' && command.includes('statusline-composite');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function findCompositeRegistry() {
|
|
19
|
+
for (const p of REGISTRY_PATHS) {
|
|
20
|
+
if (fs.existsSync(p)) return p;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Register GSD as a provider in the composite statusline registry.
|
|
27
|
+
* Idempotent: updates existing entry or inserts before code-graph.
|
|
28
|
+
* @param {string} statuslineScriptPath - Absolute path to gsd-statusline.cjs
|
|
29
|
+
* @returns {boolean} true if registered/updated
|
|
30
|
+
*/
|
|
31
|
+
function registerProvider(statuslineScriptPath) {
|
|
32
|
+
let registryPath = findCompositeRegistry();
|
|
33
|
+
|
|
34
|
+
// If composite statusLine is configured but registry file is missing,
|
|
35
|
+
// create it if the parent directory exists (e.g., code-graph installed
|
|
36
|
+
// but registry was deleted or not yet created).
|
|
37
|
+
if (!registryPath) {
|
|
38
|
+
for (const candidate of REGISTRY_PATHS) {
|
|
39
|
+
const dir = path.dirname(candidate);
|
|
40
|
+
if (fs.existsSync(dir)) {
|
|
41
|
+
registryPath = candidate;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!registryPath) return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
let registry;
|
|
50
|
+
try {
|
|
51
|
+
registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
52
|
+
} catch {
|
|
53
|
+
registry = []; // File missing or corrupt — start fresh
|
|
54
|
+
}
|
|
55
|
+
if (!Array.isArray(registry)) return false;
|
|
56
|
+
|
|
57
|
+
const command = `node ${JSON.stringify(statuslineScriptPath)}`;
|
|
58
|
+
const provider = { id: 'gsd', command, needsStdin: true };
|
|
59
|
+
|
|
60
|
+
// Find existing GSD entry (by id or command)
|
|
61
|
+
const idx = registry.findIndex(p =>
|
|
62
|
+
p.id === 'gsd' || p.command?.includes('gsd-statusline'));
|
|
63
|
+
|
|
64
|
+
if (idx >= 0) {
|
|
65
|
+
registry[idx] = provider;
|
|
66
|
+
} else {
|
|
67
|
+
// Insert before code-graph for display priority
|
|
68
|
+
const cgIdx = registry.findIndex(p => p.id === 'code-graph');
|
|
69
|
+
if (cgIdx >= 0) registry.splice(cgIdx, 0, provider);
|
|
70
|
+
else registry.unshift(provider);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Atomic write
|
|
74
|
+
const tmp = registryPath + `.${process.pid}-${Date.now()}.tmp`;
|
|
75
|
+
fs.writeFileSync(tmp, JSON.stringify(registry, null, 2) + '\n');
|
|
76
|
+
fs.renameSync(tmp, registryPath);
|
|
77
|
+
return true;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Remove GSD entry from composite statusline registry.
|
|
85
|
+
* @returns {boolean} true if an entry was removed
|
|
86
|
+
*/
|
|
87
|
+
function removeProvider() {
|
|
88
|
+
const registryPath = findCompositeRegistry();
|
|
89
|
+
if (!registryPath) return false;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
93
|
+
if (!Array.isArray(registry)) return false;
|
|
94
|
+
|
|
95
|
+
const idx = registry.findIndex(p =>
|
|
96
|
+
p.id === 'gsd' || p.command?.includes('gsd-statusline'));
|
|
97
|
+
if (idx < 0) return false;
|
|
98
|
+
|
|
99
|
+
registry.splice(idx, 1);
|
|
100
|
+
|
|
101
|
+
const tmp = registryPath + `.${process.pid}-${Date.now()}.tmp`;
|
|
102
|
+
fs.writeFileSync(tmp, JSON.stringify(registry, null, 2) + '\n');
|
|
103
|
+
fs.renameSync(tmp, registryPath);
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { isCompositeStatusLine, findCompositeRegistry, registerProvider, removeProvider };
|
package/install.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createRequire } from 'node:module';
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const _require = createRequire(import.meta.url);
|
|
13
13
|
const { semverSortComparator } = _require('./hooks/lib/semver-sort.cjs');
|
|
14
|
+
const { isCompositeStatusLine, registerProvider: registerCompositeProvider } = _require('./hooks/lib/statusline-composite.cjs');
|
|
14
15
|
const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
15
16
|
const RUNTIME_DIR = join(CLAUDE_DIR, 'gsd');
|
|
16
17
|
const DRY_RUN = process.argv.includes('--dry-run');
|
|
@@ -39,16 +40,35 @@ function isInstalledAsPlugin(claudeDir) {
|
|
|
39
40
|
|
|
40
41
|
function registerStatusLine(settings, statuslineScriptPath) {
|
|
41
42
|
const command = `node ${JSON.stringify(statuslineScriptPath)}`;
|
|
42
|
-
|
|
43
|
-
if (settings.statusLine && typeof settings.statusLine === 'object'
|
|
44
|
-
&& !settings.statusLine.command?.includes('gsd-statusline')) {
|
|
45
|
-
log(' ! Preserved existing statusLine');
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
settings.statusLine = { type: 'command', command };
|
|
43
|
+
|
|
49
44
|
// Clean up legacy format (was incorrectly placed in hooks)
|
|
50
45
|
if (settings.hooks?.StatusLine) delete settings.hooks.StatusLine;
|
|
51
|
-
|
|
46
|
+
|
|
47
|
+
const current = settings.statusLine?.command || '';
|
|
48
|
+
|
|
49
|
+
// Already GSD → update command path
|
|
50
|
+
if (current.includes('gsd-statusline')) {
|
|
51
|
+
settings.statusLine = { type: 'command', command };
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// No statusLine → set GSD directly
|
|
56
|
+
if (!current) {
|
|
57
|
+
settings.statusLine = { type: 'command', command };
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Composite statusLine (e.g., code-graph) → register as provider
|
|
62
|
+
if (isCompositeStatusLine(current)) {
|
|
63
|
+
if (registerCompositeProvider(statuslineScriptPath)) {
|
|
64
|
+
log(' ✓ Registered GSD in composite statusLine registry');
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Other statusLine → don't overwrite
|
|
70
|
+
log(' ! Preserved existing statusLine');
|
|
71
|
+
return false;
|
|
52
72
|
}
|
|
53
73
|
|
|
54
74
|
function registerHookEntry(hooks, { hookType, identifier, matcher, timeout }) {
|
|
@@ -214,15 +234,16 @@ export function main() {
|
|
|
214
234
|
log(' ✓ MCP server registered in settings.json');
|
|
215
235
|
}
|
|
216
236
|
|
|
217
|
-
//
|
|
218
|
-
//
|
|
237
|
+
// StatusLine is a top-level setting that the plugin system (hooks.json)
|
|
238
|
+
// cannot manage. Always register, regardless of install method.
|
|
239
|
+
const statuslinePath = join(CLAUDE_DIR, 'hooks', 'gsd-statusline.cjs');
|
|
240
|
+
let statusLineRegistered = registerStatusLine(settings, statuslinePath);
|
|
241
|
+
|
|
242
|
+
// Hooks are managed by hooks.json via the plugin system for plugin installs.
|
|
219
243
|
// Only register in settings.json for manual installs to avoid double execution.
|
|
220
|
-
let statusLineRegistered = false;
|
|
221
244
|
let hooksRegistered = false;
|
|
222
245
|
if (!isPluginInstall) {
|
|
223
246
|
if (!settings.hooks) settings.hooks = {};
|
|
224
|
-
const statuslinePath = join(CLAUDE_DIR, 'hooks', 'gsd-statusline.cjs');
|
|
225
|
-
statusLineRegistered = registerStatusLine(settings, statuslinePath);
|
|
226
247
|
for (const config of HOOK_REGISTRY) {
|
|
227
248
|
if (registerHookEntry(settings.hooks, config)) hooksRegistered = true;
|
|
228
249
|
}
|
package/package.json
CHANGED
package/uninstall.js
CHANGED
|
@@ -5,6 +5,7 @@ import { existsSync, rmSync, readFileSync, writeFileSync, renameSync } from 'nod
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
7
|
import { pathToFileURL } from 'node:url';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
8
9
|
|
|
9
10
|
const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
10
11
|
const RUNTIME_DIR = join(CLAUDE_DIR, 'gsd');
|
|
@@ -27,6 +28,16 @@ function removeDir(path, label) {
|
|
|
27
28
|
export function main() {
|
|
28
29
|
log('GSD-Lite Uninstaller\n');
|
|
29
30
|
|
|
31
|
+
// Clean up GSD entry from composite statusLine registry before removing files
|
|
32
|
+
try {
|
|
33
|
+
const _require = createRequire(import.meta.url);
|
|
34
|
+
const compositeLib = join(CLAUDE_DIR, 'hooks', 'lib', 'statusline-composite.cjs');
|
|
35
|
+
if (existsSync(compositeLib)) {
|
|
36
|
+
const { removeProvider } = _require(compositeLib);
|
|
37
|
+
if (removeProvider()) log(' ✓ Removed GSD from composite statusLine registry');
|
|
38
|
+
}
|
|
39
|
+
} catch { /* best effort */ }
|
|
40
|
+
|
|
30
41
|
log('Removing files...');
|
|
31
42
|
|
|
32
43
|
removeDir(join(CLAUDE_DIR, 'commands', 'gsd'), 'commands/gsd/');
|
|
@@ -49,10 +60,12 @@ export function main() {
|
|
|
49
60
|
const hookLibDir = join(CLAUDE_DIR, 'hooks', 'lib');
|
|
50
61
|
if (existsSync(hookLibDir)) {
|
|
51
62
|
// Only remove GSD-owned files, not other plugins' libs
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
for (const libFile of ['gsd-finder.cjs', 'statusline-composite.cjs', 'semver-sort.cjs']) {
|
|
64
|
+
const fullPath = join(hookLibDir, libFile);
|
|
65
|
+
if (existsSync(fullPath)) {
|
|
66
|
+
rmSync(fullPath);
|
|
67
|
+
log(` ✓ Removed hooks/lib/${libFile}`);
|
|
68
|
+
}
|
|
56
69
|
}
|
|
57
70
|
}
|
|
58
71
|
|