gsd-lite 0.7.3 → 0.7.5
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.
|
@@ -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.7.
|
|
16
|
+
"version": "0.7.5",
|
|
17
17
|
"keywords": [
|
|
18
18
|
"orchestration",
|
|
19
19
|
"mcp",
|
|
@@ -63,33 +63,17 @@ function runChainCLI(args) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
66
|
+
* Rewrite a single registry file to its desired state: exactly one canonical
|
|
67
|
+
* `{id: 'gsd', ...}` entry with the given command, dropping every other entry
|
|
68
|
+
* whose command references gsd-statusline (e.g. ghost `_previous` entries
|
|
69
|
+
* left by code-graph's composite-takeover). Canonical entry is placed before
|
|
70
|
+
* `code-graph` for display priority.
|
|
71
|
+
*
|
|
72
|
+
* Returns true if the registry is now in the desired state (including
|
|
73
|
+
* idempotent no-op), false if the file can't be parsed as an array.
|
|
72
74
|
*/
|
|
73
|
-
function
|
|
74
|
-
const
|
|
75
|
-
if (runChainCLI(['register', 'gsd', command, '--stdin'])) return true;
|
|
76
|
-
|
|
77
|
-
let registryPath = findCompositeRegistry();
|
|
78
|
-
|
|
79
|
-
// If composite statusLine is configured but registry file is missing,
|
|
80
|
-
// create it if the parent directory exists (e.g., code-graph installed
|
|
81
|
-
// but registry was deleted or not yet created).
|
|
82
|
-
if (!registryPath) {
|
|
83
|
-
for (const candidate of REGISTRY_PATHS) {
|
|
84
|
-
const dir = path.dirname(candidate);
|
|
85
|
-
if (fs.existsSync(dir)) {
|
|
86
|
-
registryPath = candidate;
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (!registryPath) return false;
|
|
91
|
-
}
|
|
92
|
-
|
|
75
|
+
function normalizeRegistryFile(registryPath, canonicalCommand) {
|
|
76
|
+
const canonical = { id: 'gsd', command: canonicalCommand, needsStdin: true };
|
|
93
77
|
try {
|
|
94
78
|
let registry;
|
|
95
79
|
try {
|
|
@@ -99,24 +83,24 @@ function registerProvider(statuslineScriptPath) {
|
|
|
99
83
|
}
|
|
100
84
|
if (!Array.isArray(registry)) return false;
|
|
101
85
|
|
|
102
|
-
|
|
86
|
+
// Drop every entry pointing at gsd-statusline, regardless of id. This
|
|
87
|
+
// catches the canonical `gsd` slot AND ghosts like `_previous` that
|
|
88
|
+
// code-graph's id-scoped chain CLI `register` can't see.
|
|
89
|
+
const nonGsd = registry.filter(
|
|
90
|
+
e => !(e.command || '').includes('gsd-statusline'),
|
|
91
|
+
);
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
const cgIdx = nonGsd.findIndex(e => e.id === 'code-graph');
|
|
94
|
+
if (cgIdx >= 0) nonGsd.splice(cgIdx, 0, canonical);
|
|
95
|
+
else nonGsd.unshift(canonical);
|
|
107
96
|
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const cgIdx = registry.findIndex(p => p.id === 'code-graph');
|
|
113
|
-
if (cgIdx >= 0) registry.splice(cgIdx, 0, provider);
|
|
114
|
-
else registry.unshift(provider);
|
|
115
|
-
}
|
|
97
|
+
// Skip write if already in desired state (idempotent re-install).
|
|
98
|
+
const before = JSON.stringify(registry);
|
|
99
|
+
const after = JSON.stringify(nonGsd);
|
|
100
|
+
if (before === after) return true;
|
|
116
101
|
|
|
117
|
-
// Atomic write
|
|
118
102
|
const tmp = registryPath + `.${process.pid}-${Date.now()}.tmp`;
|
|
119
|
-
fs.writeFileSync(tmp, JSON.stringify(
|
|
103
|
+
fs.writeFileSync(tmp, JSON.stringify(nonGsd, null, 2) + '\n');
|
|
120
104
|
fs.renameSync(tmp, registryPath);
|
|
121
105
|
return true;
|
|
122
106
|
} catch {
|
|
@@ -124,6 +108,37 @@ function registerProvider(statuslineScriptPath) {
|
|
|
124
108
|
}
|
|
125
109
|
}
|
|
126
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Register GSD as a provider in the composite statusline registry.
|
|
113
|
+
*
|
|
114
|
+
* Calls code-graph's statusline-chain.js CLI when available, THEN post-scrubs
|
|
115
|
+
* every known registry path. The CLI's `register gsd <cmd>` is id-scoped and
|
|
116
|
+
* silently leaves ghost entries (e.g. `_previous` whose command is our
|
|
117
|
+
* gsd-statusline but whose id isn't `gsd`) in place — that caused
|
|
118
|
+
* double-rendering after upgrades where code-graph had previously promoted a
|
|
119
|
+
* top-level GSD statusLine to `_previous`. Post-normalization guarantees
|
|
120
|
+
* exactly one canonical `gsd` entry per registry regardless of which path
|
|
121
|
+
* succeeded.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} statuslineScriptPath - Absolute path to gsd-statusline.cjs
|
|
124
|
+
* @returns {boolean} true if registered or normalized in at least one registry
|
|
125
|
+
*/
|
|
126
|
+
function registerProvider(statuslineScriptPath) {
|
|
127
|
+
const command = `node ${JSON.stringify(statuslineScriptPath)}`;
|
|
128
|
+
const cliOk = runChainCLI(['register', 'gsd', command, '--stdin']);
|
|
129
|
+
|
|
130
|
+
let anyNormalized = false;
|
|
131
|
+
for (const candidate of REGISTRY_PATHS) {
|
|
132
|
+
// Only touch paths whose parent dir exists — don't create arbitrary
|
|
133
|
+
// ~/.cache/ subtrees on machines without code-graph.
|
|
134
|
+
const dir = path.dirname(candidate);
|
|
135
|
+
if (!fs.existsSync(dir)) continue;
|
|
136
|
+
if (normalizeRegistryFile(candidate, command)) anyNormalized = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return cliOk || anyNormalized;
|
|
140
|
+
}
|
|
141
|
+
|
|
127
142
|
/**
|
|
128
143
|
* Remove GSD entry from composite statusline registry.
|
|
129
144
|
* Prefers code-graph's statusline-chain.js CLI when available; falls back to
|
package/install.js
CHANGED
|
@@ -158,17 +158,45 @@ export function main() {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// 3. Workflows
|
|
168
|
-
copyDir(join(__dirname, 'workflows'), join(CLAUDE_DIR, 'workflows', 'gsd'), 'workflows → ~/.claude/workflows/gsd/');
|
|
161
|
+
// Decide install mode once — plugin-system-managed vs npx/manual.
|
|
162
|
+
// In plugin mode, Claude Code loads commands/agents/workflows/references directly
|
|
163
|
+
// from ~/.claude/plugins/cache/gsd/gsd/<version>/, so writing user-scope copies at
|
|
164
|
+
// ~/.claude/{commands,agents,workflows,references}/gsd/ produces duplicate slash-command
|
|
165
|
+
// entries and silent drift against the plugin cache.
|
|
166
|
+
const isPluginInstall = isInstalledAsPlugin(CLAUDE_DIR);
|
|
169
167
|
|
|
170
|
-
// 4.
|
|
171
|
-
|
|
168
|
+
// 1-4. Commands / agents / workflows / references
|
|
169
|
+
// Only deliver these user-scope copies in non-plugin installs (npx / manual / npm -g),
|
|
170
|
+
// where no plugin cache exists to serve them.
|
|
171
|
+
const userScopeCopies = [
|
|
172
|
+
['commands', 'commands → ~/.claude/commands/gsd/'],
|
|
173
|
+
['agents', 'agents → ~/.claude/agents/gsd/'],
|
|
174
|
+
['workflows', 'workflows → ~/.claude/workflows/gsd/'],
|
|
175
|
+
['references', 'references → ~/.claude/references/gsd/'],
|
|
176
|
+
];
|
|
177
|
+
if (isPluginInstall) {
|
|
178
|
+
// Clean up stale copies left by earlier install.js versions (< 0.7.4) that wrote
|
|
179
|
+
// user-scope copies unconditionally. Keeping them around caused 2× skill-list
|
|
180
|
+
// entries and could shadow plugin-cache versions.
|
|
181
|
+
if (!DRY_RUN) {
|
|
182
|
+
for (const [sub] of userScopeCopies) {
|
|
183
|
+
const dest = join(CLAUDE_DIR, sub, 'gsd');
|
|
184
|
+
if (existsSync(dest)) {
|
|
185
|
+
rmSync(dest, { recursive: true, force: true });
|
|
186
|
+
log(` ✓ Removed legacy user-scope ${sub}/gsd/ (served by plugin cache)`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
for (const [sub] of userScopeCopies) {
|
|
191
|
+
const dest = join(CLAUDE_DIR, sub, 'gsd');
|
|
192
|
+
if (existsSync(dest)) log(` [dry-run] Would remove legacy ${dest}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
for (const [sub, label] of userScopeCopies) {
|
|
197
|
+
copyDir(join(__dirname, sub), join(CLAUDE_DIR, sub, 'gsd'), label);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
172
200
|
|
|
173
201
|
// 5. Hooks (copy scripts only, skip hooks.json to avoid overwriting other plugins)
|
|
174
202
|
for (const hookFile of HOOK_FILES) {
|
|
@@ -213,7 +241,6 @@ export function main() {
|
|
|
213
241
|
// When installed as a plugin, the plugin system handles MCP via .mcp.json,
|
|
214
242
|
// so we skip manual MCP registration to avoid name collisions.
|
|
215
243
|
const settingsPath = join(CLAUDE_DIR, 'settings.json');
|
|
216
|
-
const isPluginInstall = isInstalledAsPlugin(CLAUDE_DIR);
|
|
217
244
|
if (!DRY_RUN) {
|
|
218
245
|
let settings = {};
|
|
219
246
|
try {
|