@opengsd/gsd-core 1.4.0-rc.1 → 1.4.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/.claude-plugin/plugin.json +23 -0
- package/GEMINI.md +53 -0
- package/agents/gsd-ai-researcher.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-domain-researcher.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.md +1 -1
- package/agents/gsd-framework-selector.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-pattern-mapper.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.md +1 -1
- package/bin/install.js +1911 -354
- package/commands/gsd/autonomous.md +2 -0
- package/commands/gsd/execute-phase.md +2 -0
- package/commands/gsd/plan-phase.md +2 -0
- package/commands/gsd/progress.md +1 -0
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/update.md +3 -2
- package/gemini-extension.json +6 -0
- package/gsd-core/bin/check-latest-version.cjs +58 -4
- package/gsd-core/bin/lib/install-profiles.cjs +58 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +1 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +67 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
- package/gsd-core/bin/lib/runtime-homes.cjs +32 -11
- package/gsd-core/bin/lib/shell-command-projection.cjs +6 -0
- package/gsd-core/bin/lib/surface.cjs +54 -11
- package/gsd-core/workflows/help/modes/full.md +2 -1
- package/gsd-core/workflows/review.md +2 -2
- package/gsd-core/workflows/update.md +32 -5
- package/hooks/dist/gsd-config-reload.js +133 -0
- package/hooks/dist/gsd-cursor-post-tool.js +75 -0
- package/hooks/dist/gsd-cursor-session-start.js +52 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-config-reload.js +133 -0
- package/hooks/gsd-cursor-post-tool.js +75 -0
- package/hooks/gsd-cursor-session-start.js +52 -0
- package/hooks/hooks.json +69 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +5 -1
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/cli.cjs +53 -10
- package/scripts/ci-test-scope.cjs +120 -18
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/research-profiles.cjs +3 -3
- package/scripts/sync-manifest-versions.cjs +119 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
name: gsd:plan-phase
|
|
3
3
|
description: Create detailed phase plan (PLAN.md) with verification loop
|
|
4
4
|
argument-hint: "[phase] [--auto] [--research] [--skip-research] [--research-phase <N>] [--view] [--gaps] [--skip-verify] [--prd <file>] [--ingest <path-or-glob>] [--ingest-format <auto|nygard|madr|narrative>] [--reviews] [--text] [--tdd] [--mvp]"
|
|
5
|
+
context: fork
|
|
6
|
+
effort: xhigh
|
|
5
7
|
allowed-tools:
|
|
6
8
|
- Read
|
|
7
9
|
- Write
|
package/commands/gsd/progress.md
CHANGED
package/commands/gsd/stats.md
CHANGED
package/commands/gsd/update.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd:update
|
|
3
3
|
description: Update GSD to latest version with changelog display
|
|
4
|
-
argument-hint: "[--sync | --reapply]"
|
|
4
|
+
argument-hint: "[--sync | --reapply | --next | --rc]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -31,6 +31,7 @@ Routes to the update workflow which handles:
|
|
|
31
31
|
<flags>
|
|
32
32
|
- **--sync**: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update. Runs the sync-skills workflow (--from, --to, --dry-run, --apply flags supported).
|
|
33
33
|
- **--reapply**: Reapply local modifications after a GSD update. Uses three-way comparison (pristine baseline, user-modified backup, newly installed version) to merge user customizations back. Runs the reapply-patches workflow.
|
|
34
|
+
- **--next** (alias **--rc**): Target the `@next` RC dist-tag instead of `@latest` so you can install or refresh a release candidate (e.g. `1.4.0-rc.1`) through the normal update flow — scope/runtime detection, changelog preview, custom-file backup, and cache clearing all still apply. Omitting it keeps targeting `@latest` (no change). See ADR #660 for the RC channel.
|
|
34
35
|
- **(no flag)**: Standard update — check for new version, show changelog, install.
|
|
35
36
|
</flags>
|
|
36
37
|
|
|
@@ -38,7 +39,7 @@ Routes to the update workflow which handles:
|
|
|
38
39
|
Parse the first token of $ARGUMENTS:
|
|
39
40
|
- If it is `--sync`: strip the flag, execute the sync-skills workflow (passing remaining args for --from/--to/--dry-run/--apply).
|
|
40
41
|
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow.
|
|
41
|
-
- Otherwise: execute the update workflow end-to-end.
|
|
42
|
+
- Otherwise (including `--next` / `--rc`): execute the update workflow end-to-end, passing `$ARGUMENTS` through so the workflow's parse_update_channel step can select the release channel.
|
|
42
43
|
|
|
43
44
|
</process>
|
|
44
45
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gsd-core",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
|
|
5
|
+
"contextFileName": "GEMINI.md"
|
|
6
|
+
}
|
|
@@ -37,18 +37,64 @@ const CHECK_REASON = Object.freeze({
|
|
|
37
37
|
|
|
38
38
|
const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
39
39
|
|
|
40
|
+
// #815: the one RC channel ADR #660 sanctions, plus the stable default.
|
|
41
|
+
// An allowlist (not a free string) keeps a typo from silently resolving
|
|
42
|
+
// `npm view` to an empty or foreign dist-tag.
|
|
43
|
+
const ALLOWED_TAGS = Object.freeze(['latest', 'next']);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build the `npm view` args for a dist-tag. `latest` keeps the bare package
|
|
47
|
+
* spec so the default invocation is byte-for-byte identical to before tag
|
|
48
|
+
* support existed (#815); any other allowlisted tag appends `@<tag>` so
|
|
49
|
+
* `npm view @opengsd/gsd-core@next version` resolves the RC channel (#660).
|
|
50
|
+
*/
|
|
51
|
+
function buildViewArgs(tag = 'latest') {
|
|
52
|
+
if (!ALLOWED_TAGS.includes(tag)) {
|
|
53
|
+
throw new RangeError(`invalid dist-tag '${tag}'; allowed: ${ALLOWED_TAGS.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
const spec = tag === 'latest' ? PACKAGE_NAME : `${PACKAGE_NAME}@${tag}`;
|
|
56
|
+
return ['view', spec, 'version'];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the requested dist-tag from argv. Defaults to `latest` (no flag =>
|
|
61
|
+
* no behavior change). Restricted to ALLOWED_TAGS so a typo can't silently
|
|
62
|
+
* resolve to an empty/foreign tag (#815 alternative 1).
|
|
63
|
+
*/
|
|
64
|
+
function resolveTag(argv) {
|
|
65
|
+
let val;
|
|
66
|
+
const eq = argv.find((a) => typeof a === 'string' && a.startsWith('--tag='));
|
|
67
|
+
if (eq !== undefined) {
|
|
68
|
+
val = eq.slice('--tag='.length);
|
|
69
|
+
} else {
|
|
70
|
+
const i = argv.indexOf('--tag');
|
|
71
|
+
if (i === -1) return 'latest';
|
|
72
|
+
val = argv[i + 1];
|
|
73
|
+
}
|
|
74
|
+
if (!val || !ALLOWED_TAGS.includes(val)) {
|
|
75
|
+
throw new RangeError(
|
|
76
|
+
`invalid --tag '${val || ''}'; allowed: ${ALLOWED_TAGS.join(', ')}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return val;
|
|
80
|
+
}
|
|
81
|
+
|
|
40
82
|
/**
|
|
41
83
|
* Pure-ish: takes an injected spawn function so tests don't actually run npm.
|
|
42
84
|
* In production, defaults to execNpm() from the shell-projection seam.
|
|
43
85
|
*/
|
|
44
86
|
function checkLatestVersion(opts = {}) {
|
|
87
|
+
const tag = opts.tag || 'latest';
|
|
88
|
+
if (!ALLOWED_TAGS.includes(tag)) {
|
|
89
|
+
throw new RangeError(`invalid dist-tag '${tag}'; allowed: ${ALLOWED_TAGS.join(', ')}`);
|
|
90
|
+
}
|
|
45
91
|
// Default path routes through the shell-projection seam (execNpm owns the
|
|
46
92
|
// Windows shell-flag policy and timeout default). The injection point
|
|
47
93
|
// remains spawnSync-shaped for test compatibility — the adapter below
|
|
48
94
|
// translates { exitCode } → { status } so the consumer logic is unchanged.
|
|
49
95
|
// Bounded at 15s so a hung registry doesn't block /gsd-update (#2993 CR).
|
|
50
96
|
const defaultSpawn = () => {
|
|
51
|
-
const r = execNpm(
|
|
97
|
+
const r = execNpm(buildViewArgs(tag), { timeout: 15_000 });
|
|
52
98
|
return {
|
|
53
99
|
status: r.exitCode,
|
|
54
100
|
stdout: r.stdout,
|
|
@@ -90,8 +136,16 @@ function checkLatestVersion(opts = {}) {
|
|
|
90
136
|
}
|
|
91
137
|
|
|
92
138
|
function main() {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
139
|
+
const argv = process.argv.slice(2);
|
|
140
|
+
const json = argv.includes('--json');
|
|
141
|
+
let tag;
|
|
142
|
+
try {
|
|
143
|
+
tag = resolveTag(argv);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
process.stderr.write(`check-latest-version: ${e.message}\n`);
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
const r = checkLatestVersion({ tag });
|
|
95
149
|
if (json) {
|
|
96
150
|
process.stdout.write(JSON.stringify(r) + '\n');
|
|
97
151
|
} else if (r.ok) {
|
|
@@ -104,4 +158,4 @@ function main() {
|
|
|
104
158
|
|
|
105
159
|
if (require.main === module) runMain(main);
|
|
106
160
|
|
|
107
|
-
module.exports = { checkLatestVersion, CHECK_REASON, PACKAGE_NAME };
|
|
161
|
+
module.exports = { checkLatestVersion, CHECK_REASON, PACKAGE_NAME, ALLOWED_TAGS, buildViewArgs, resolveTag };
|
|
@@ -351,6 +351,63 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
351
351
|
ensureExitCleanup();
|
|
352
352
|
return stageDir;
|
|
353
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Stage converted command files as flat `.md` files.
|
|
356
|
+
*
|
|
357
|
+
* Analogous to `stageSkillsForRuntimeAsSkills` but for runtimes that use a
|
|
358
|
+
* flat commands directory (e.g. Cursor's `.cursor/commands/<name>.md`).
|
|
359
|
+
* Each source `.md` is passed through `converter` and written as a single flat
|
|
360
|
+
* `${stem}.md` file in the staging directory (no subdirectory, no prefix).
|
|
361
|
+
*
|
|
362
|
+
* The `_copyStaged` commands branch in install.js will add the prefix when
|
|
363
|
+
* copying staged files to the destination directory, so staged files must be
|
|
364
|
+
* named with just the stem (e.g. `help.md` not `gsd-help.md`).
|
|
365
|
+
*
|
|
366
|
+
* The `converter` receives `(content, ${prefix}${stem})` so it can embed the
|
|
367
|
+
* full command name (e.g. 'gsd-help') into the document body if needed.
|
|
368
|
+
*
|
|
369
|
+
* Used by the `convertedCommandsKind` layout descriptor in
|
|
370
|
+
* runtime-artifact-layout.cts (#785 — Cursor 1.6 slash commands).
|
|
371
|
+
*
|
|
372
|
+
* @param srcCommandsDir source commands directory (e.g. commands/gsd/)
|
|
373
|
+
* @param resolvedProfile profile filter — '*' for all, Set for subset
|
|
374
|
+
* @param converter (content, commandName) → string pure converter
|
|
375
|
+
* @param prefix command name prefix (for converter arg), e.g. 'gsd-'
|
|
376
|
+
*/
|
|
377
|
+
function stageCommandsForRuntimeFlat(srcCommandsDir, resolvedProfile, converter, prefix) {
|
|
378
|
+
if (!node_fs_1.default.existsSync(srcCommandsDir))
|
|
379
|
+
return srcCommandsDir;
|
|
380
|
+
const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-runtime-commands-'));
|
|
381
|
+
try {
|
|
382
|
+
const entries = node_fs_1.default.readdirSync(srcCommandsDir, { withFileTypes: true });
|
|
383
|
+
for (const entry of entries) {
|
|
384
|
+
if (!entry.isFile())
|
|
385
|
+
continue;
|
|
386
|
+
if (!entry.name.endsWith('.md'))
|
|
387
|
+
continue;
|
|
388
|
+
const stem = entry.name.slice(0, -3);
|
|
389
|
+
if (resolvedProfile.skills !== '*' && !(resolvedProfile.skills).has(stem))
|
|
390
|
+
continue;
|
|
391
|
+
const content = node_fs_1.default.readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8');
|
|
392
|
+
// Pass the full command name (with prefix) to the converter so it can
|
|
393
|
+
// reference the installed command name in the body (e.g. for descriptions).
|
|
394
|
+
// The staged file itself is named without the prefix; _copyStaged adds it.
|
|
395
|
+
const commandName = `${prefix}${stem}`;
|
|
396
|
+
const converted = converter(content, commandName);
|
|
397
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, `${stem}.md`), converted);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
try {
|
|
402
|
+
node_fs_1.default.rmSync(stageDir, { recursive: true, force: true });
|
|
403
|
+
}
|
|
404
|
+
catch { /* best-effort */ }
|
|
405
|
+
throw err;
|
|
406
|
+
}
|
|
407
|
+
STAGED_DIRS.add(stageDir);
|
|
408
|
+
ensureExitCleanup();
|
|
409
|
+
return stageDir;
|
|
410
|
+
}
|
|
354
411
|
// ---------------------------------------------------------------------------
|
|
355
412
|
// Profile marker persistence
|
|
356
413
|
// ---------------------------------------------------------------------------
|
|
@@ -513,6 +570,7 @@ module.exports = {
|
|
|
513
570
|
stageSkillsForProfile,
|
|
514
571
|
stageAgentsForProfile,
|
|
515
572
|
stageSkillsForRuntimeAsSkills,
|
|
573
|
+
stageCommandsForRuntimeFlat,
|
|
516
574
|
STAGED_DIRS,
|
|
517
575
|
readActiveProfile,
|
|
518
576
|
writeActiveProfile,
|
|
@@ -34,7 +34,10 @@ const VALID_CHOICES = ['keep', 'remove'];
|
|
|
34
34
|
exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
|
|
35
35
|
'hooks/gsd-check-update-worker.js',
|
|
36
36
|
'hooks/gsd-check-update.js',
|
|
37
|
+
'hooks/gsd-config-reload.js',
|
|
37
38
|
'hooks/gsd-context-monitor.js',
|
|
39
|
+
'hooks/gsd-cursor-post-tool.js',
|
|
40
|
+
'hooks/gsd-cursor-session-start.js',
|
|
38
41
|
'hooks/gsd-graphify-update.sh',
|
|
39
42
|
'hooks/gsd-phase-boundary.sh',
|
|
40
43
|
'hooks/gsd-prompt-guard.js',
|
|
@@ -24,7 +24,7 @@ const RUNTIME_SURFACES = {
|
|
|
24
24
|
kilo: ['gsd-core', 'command', 'skills', 'agents'],
|
|
25
25
|
copilot: ['gsd-core', 'skills', 'agents'],
|
|
26
26
|
antigravity: ['gsd-core', 'skills', 'agents'],
|
|
27
|
-
cursor: ['gsd-core', 'skills', 'agents'],
|
|
27
|
+
cursor: ['gsd-core', 'skills', 'agents', 'hooks', 'hooks.json'],
|
|
28
28
|
windsurf: ['gsd-core', 'skills', 'agents', 'rules'],
|
|
29
29
|
augment: ['gsd-core', 'skills', 'agents'],
|
|
30
30
|
trae: ['gsd-core', 'skills', 'agents', 'rules'],
|
|
@@ -18,7 +18,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
18
18
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
20
|
const installProfiles = require("./install-profiles.cjs");
|
|
21
|
-
const { stageSkillsForProfile, stageAgentsForProfile, stageSkillsForRuntimeAsSkills, } = installProfiles;
|
|
21
|
+
const { stageSkillsForProfile, stageAgentsForProfile, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
|
|
22
22
|
// In .cts (CommonJS output) files, `require` is available as a global.
|
|
23
23
|
const _require = require;
|
|
24
24
|
/**
|
|
@@ -175,6 +175,34 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir) {
|
|
|
175
175
|
},
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Build a converted-commands kind descriptor for runtimes that use a flat
|
|
180
|
+
* commands directory with per-file conversion (e.g. Cursor 1.6 slash commands).
|
|
181
|
+
*
|
|
182
|
+
* Unlike `commandsKind` (which passes raw source files through), this kind
|
|
183
|
+
* applies `converterName` from bin/install.js exports to each file during
|
|
184
|
+
* staging, writing flat `${prefix}${stem}.md` files to the staged directory.
|
|
185
|
+
*
|
|
186
|
+
* The staged files are then written by `_copyStaged` (commands branch) which
|
|
187
|
+
* handles prefix logic via the existing layout machinery.
|
|
188
|
+
*
|
|
189
|
+
* @param destSubpath destination subpath within configDir (e.g. 'commands')
|
|
190
|
+
* @param prefix filename prefix, e.g. 'gsd-'
|
|
191
|
+
* @param converterName name of converter function in bin/install.js exports
|
|
192
|
+
* @param configDir runtime config dir (for .gsd-source marker resolution)
|
|
193
|
+
*/
|
|
194
|
+
function convertedCommandsKind(destSubpath, prefix, converterName, configDir) {
|
|
195
|
+
return {
|
|
196
|
+
kind: 'commands',
|
|
197
|
+
destSubpath,
|
|
198
|
+
prefix,
|
|
199
|
+
stage: (resolved) => {
|
|
200
|
+
const installExports = getInstallExports();
|
|
201
|
+
const converter = installExports[converterName];
|
|
202
|
+
return stageCommandsForRuntimeFlat(findInstallSourceRoot(configDir), resolved, converter, prefix);
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
178
206
|
// ---------------------------------------------------------------------------
|
|
179
207
|
// Public API
|
|
180
208
|
// ---------------------------------------------------------------------------
|
|
@@ -205,7 +233,14 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
|
205
233
|
}
|
|
206
234
|
break;
|
|
207
235
|
case 'cursor':
|
|
208
|
-
|
|
236
|
+
// Cursor 1.6+ supports two artifact surfaces:
|
|
237
|
+
// 1. skills/gsd-<name>/SKILL.md — rich skills with frontmatter + adapter header
|
|
238
|
+
// 2. commands/gsd-<name>.md — plain markdown slash commands (no frontmatter)
|
|
239
|
+
// accessed via '/' in the Agent input (#785)
|
|
240
|
+
kinds = [
|
|
241
|
+
skillsKind('skills', 'gsd-', 'convertClaudeCommandToCursorSkill', 'cursor', configDir),
|
|
242
|
+
convertedCommandsKind('commands', 'gsd-', 'convertClaudeCommandToCursorCommand', configDir),
|
|
243
|
+
];
|
|
209
244
|
break;
|
|
210
245
|
case 'gemini':
|
|
211
246
|
kinds = [commandsKind('commands/gsd', 'gsd-', configDir)];
|
|
@@ -223,7 +258,10 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
|
223
258
|
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToWindsurfSkill', 'windsurf', configDir)];
|
|
224
259
|
break;
|
|
225
260
|
case 'augment':
|
|
226
|
-
kinds = [
|
|
261
|
+
kinds = [
|
|
262
|
+
commandsKind('commands', 'gsd-', configDir),
|
|
263
|
+
skillsKind('skills', 'gsd-', 'convertClaudeCommandToAugmentSkill', 'augment', configDir),
|
|
264
|
+
];
|
|
227
265
|
break;
|
|
228
266
|
case 'trae':
|
|
229
267
|
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToTraeSkill', 'trae', configDir)];
|
|
@@ -235,20 +273,40 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
|
235
273
|
kinds = [skillsKind('skills/gsd', '', 'convertClaudeCommandToClaudeSkill', 'hermes', configDir)];
|
|
236
274
|
break;
|
|
237
275
|
case 'codebuddy':
|
|
238
|
-
|
|
276
|
+
// CodeBuddy (Tencent) reads two user-level surfaces (codebuddy.ai/docs/cli):
|
|
277
|
+
// 1. commands/gsd-<name>.md — slash commands shown in the '/' menu (#789)
|
|
278
|
+
// 2. skills/gsd-<name>/SKILL.md — model-invocable skills, emitted with
|
|
279
|
+
// user-invocable:false so they stay OUT of '/' (the commands surface is
|
|
280
|
+
// the sole '/' entry point) — avoids a duplicated /gsd-* per workflow.
|
|
281
|
+
// Subagents (~/.codebuddy/agents/) are already emitted by the generic agents
|
|
282
|
+
// block in bin/install.js; MCP is excluded (gsd ships no MCP server).
|
|
283
|
+
kinds = [
|
|
284
|
+
convertedCommandsKind('commands', 'gsd-', 'convertClaudeCommandToCodebuddyCommand', configDir),
|
|
285
|
+
skillsKind('skills', 'gsd-', 'convertClaudeCommandToCodebuddySkill', 'codebuddy', configDir),
|
|
286
|
+
];
|
|
239
287
|
break;
|
|
240
288
|
case 'cline':
|
|
241
|
-
kinds = [];
|
|
289
|
+
kinds = scope === 'global' ? [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClineSkill', 'cline', configDir)] : [];
|
|
242
290
|
break;
|
|
243
291
|
case 'opencode':
|
|
244
|
-
|
|
292
|
+
// OpenCode reads flat slash commands from command/ and on-demand skills
|
|
293
|
+
// from skills/<name>/SKILL.md (https://opencode.ai/docs/skills). Emit both.
|
|
294
|
+
kinds = [
|
|
295
|
+
commandsKind('command', 'gsd-', configDir),
|
|
296
|
+
skillsKind('skills', 'gsd-', 'convertClaudeCommandToOpencodeSkill', 'opencode', configDir),
|
|
297
|
+
];
|
|
245
298
|
break;
|
|
246
299
|
case 'kilo':
|
|
247
|
-
|
|
300
|
+
// Kilo derives from OpenCode and shares the skills/<name>/SKILL.md layout
|
|
301
|
+
// (https://kilo.ai/docs/customize/skills). Emit flat commands + skills.
|
|
302
|
+
kinds = [
|
|
303
|
+
commandsKind('command', 'gsd-', configDir),
|
|
304
|
+
skillsKind('skills', 'gsd-', 'convertClaudeCommandToKiloSkill', 'kilo', configDir),
|
|
305
|
+
];
|
|
248
306
|
break;
|
|
249
307
|
default:
|
|
250
308
|
throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
|
|
251
309
|
}
|
|
252
|
-
return { runtime, configDir, kinds };
|
|
310
|
+
return { runtime, configDir, scope, kinds };
|
|
253
311
|
}
|
|
254
|
-
module.exports = { resolveRuntimeArtifactLayout, findInstallSourceRoot };
|
|
312
|
+
module.exports = { resolveRuntimeArtifactLayout, findInstallSourceRoot, getInstallExports };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Registry
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const REGISTRY = Object.freeze({
|
|
6
|
+
claude: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
|
|
7
|
+
gemini: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
|
|
8
|
+
antigravity: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
|
|
9
|
+
augment: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
|
|
10
|
+
qwen: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
|
|
11
|
+
hermes: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
|
|
12
|
+
codebuddy: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
|
|
13
|
+
opencode: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: 'opencode' }),
|
|
14
|
+
kilo: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: false, finishPermissionWriter: 'kilo' }),
|
|
15
|
+
codex: Object.freeze({ installSurface: 'codex-toml', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
16
|
+
copilot: Object.freeze({ installSurface: 'copilot-instructions', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
17
|
+
cline: Object.freeze({ installSurface: 'cline-rules', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
18
|
+
cursor: Object.freeze({ installSurface: 'cursor-hooks-json', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
19
|
+
windsurf: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
20
|
+
trae: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
21
|
+
});
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Exports
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/** The complete set of 15 supported runtimes for config-adapter dispatch. */
|
|
26
|
+
const ALLOWED_CONFIG_RUNTIMES = new Set(Object.keys(REGISTRY));
|
|
27
|
+
/** All valid installSurface values. */
|
|
28
|
+
const INSTALL_SURFACES = Object.freeze([
|
|
29
|
+
'settings-json',
|
|
30
|
+
'codex-toml',
|
|
31
|
+
'copilot-instructions',
|
|
32
|
+
'cline-rules',
|
|
33
|
+
'cursor-hooks-json',
|
|
34
|
+
'profile-marker-only',
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the config adapter intent for a given runtime.
|
|
38
|
+
*
|
|
39
|
+
* Returns a fresh object each call so callers cannot poison the registry by
|
|
40
|
+
* mutating the returned value.
|
|
41
|
+
*
|
|
42
|
+
* @throws {TypeError} if runtime is not a known supported runtime.
|
|
43
|
+
*/
|
|
44
|
+
function resolveRuntimeConfigIntent(runtime) {
|
|
45
|
+
if (!Object.hasOwn(REGISTRY, runtime)) {
|
|
46
|
+
throw new TypeError(`Unknown runtime for config adapter: ${runtime}`);
|
|
47
|
+
}
|
|
48
|
+
const entry = REGISTRY[runtime];
|
|
49
|
+
return {
|
|
50
|
+
runtime,
|
|
51
|
+
installSurface: entry.installSurface,
|
|
52
|
+
writesSharedSettings: entry.writesSharedSettings,
|
|
53
|
+
finishPermissionWriter: entry.finishPermissionWriter,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
module.exports = { resolveRuntimeConfigIntent, ALLOWED_CONFIG_RUNTIMES, INSTALL_SURFACES };
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* Runtime-specific notes:
|
|
13
13
|
* hermes — GSD skills nest under skills/gsd/<skillName>/ (not the flat
|
|
14
14
|
* skills/<skillName>/ layout used by all other runtimes).
|
|
15
|
-
* cline —
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* cline — Skills-capable since v3.48.0 (#782). SKILL.md files live at
|
|
16
|
+
* ~/.cline/skills/<skillName>/SKILL.md (same flat layout as cursor/codex).
|
|
17
|
+
* .clinerules is also emitted (rules-based compatibility layer).
|
|
18
18
|
*/
|
|
19
19
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
20
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -62,8 +62,15 @@ function resolveAntigravityGlobalDir(opts = {}) {
|
|
|
62
62
|
/**
|
|
63
63
|
* Return the global config base directory for the given runtime.
|
|
64
64
|
* Respects the same env-var overrides as bin/install.js getGlobalDir().
|
|
65
|
+
*
|
|
66
|
+
* @param runtime - The runtime identifier (e.g. 'claude', 'opencode').
|
|
67
|
+
* @param explicitDir - If provided and non-empty, returned immediately after
|
|
68
|
+
* tilde-expansion, overriding all env-var and default logic. This matches
|
|
69
|
+
* the behaviour of bin/install.js getGlobalDir(runtime, explicitDir).
|
|
65
70
|
*/
|
|
66
|
-
function getGlobalConfigDir(runtime) {
|
|
71
|
+
function getGlobalConfigDir(runtime, explicitDir) {
|
|
72
|
+
if (explicitDir)
|
|
73
|
+
return expandTilde(explicitDir);
|
|
67
74
|
const home = node_os_1.default.homedir();
|
|
68
75
|
const env = process.env;
|
|
69
76
|
switch (runtime) {
|
|
@@ -84,7 +91,11 @@ function getGlobalConfigDir(runtime) {
|
|
|
84
91
|
return env['GROK_AGENTS_HOME'] ? expandTilde(env['GROK_AGENTS_HOME']) : node_path_1.default.join(home, '.agents');
|
|
85
92
|
// ── Copilot (VS Code) ────────────────────────────────────────────────────
|
|
86
93
|
case 'copilot':
|
|
87
|
-
|
|
94
|
+
if (env['COPILOT_CONFIG_DIR'])
|
|
95
|
+
return expandTilde(env['COPILOT_CONFIG_DIR']);
|
|
96
|
+
if (env['COPILOT_HOME'])
|
|
97
|
+
return expandTilde(env['COPILOT_HOME']);
|
|
98
|
+
return node_path_1.default.join(home, '.copilot');
|
|
88
99
|
// ── Antigravity ──────────────────────────────────────────────────────────
|
|
89
100
|
case 'antigravity':
|
|
90
101
|
return resolveAntigravityGlobalDir({ env, home });
|
|
@@ -115,6 +126,8 @@ function getGlobalConfigDir(runtime) {
|
|
|
115
126
|
case 'opencode': {
|
|
116
127
|
if (env['OPENCODE_CONFIG_DIR'])
|
|
117
128
|
return expandTilde(env['OPENCODE_CONFIG_DIR']);
|
|
129
|
+
if (env['OPENCODE_CONFIG'])
|
|
130
|
+
return node_path_1.default.dirname(expandTilde(env['OPENCODE_CONFIG']));
|
|
118
131
|
if (env['XDG_CONFIG_HOME'])
|
|
119
132
|
return node_path_1.default.join(expandTilde(env['XDG_CONFIG_HOME']), 'opencode');
|
|
120
133
|
return node_path_1.default.join(home, '.config', 'opencode');
|
|
@@ -123,6 +136,8 @@ function getGlobalConfigDir(runtime) {
|
|
|
123
136
|
case 'kilo': {
|
|
124
137
|
if (env['KILO_CONFIG_DIR'])
|
|
125
138
|
return expandTilde(env['KILO_CONFIG_DIR']);
|
|
139
|
+
if (env['KILO_CONFIG'])
|
|
140
|
+
return node_path_1.default.dirname(expandTilde(env['KILO_CONFIG']));
|
|
126
141
|
if (env['XDG_CONFIG_HOME'])
|
|
127
142
|
return node_path_1.default.join(expandTilde(env['XDG_CONFIG_HOME']), 'kilo');
|
|
128
143
|
return node_path_1.default.join(home, '.config', 'kilo');
|
|
@@ -136,19 +151,25 @@ function getGlobalConfigDir(runtime) {
|
|
|
136
151
|
* Return the global skills base directory for the given runtime.
|
|
137
152
|
* Most runtimes: <configDir>/skills
|
|
138
153
|
* Hermes: <configDir>/skills/gsd (nested category layout — #2841)
|
|
139
|
-
* Cline:
|
|
154
|
+
* Cline ≥ v3.48.0: <configDir>/skills (SKILL.md-based global skills — #782)
|
|
140
155
|
*/
|
|
141
156
|
function getGlobalSkillsBase(runtime) {
|
|
142
|
-
if (runtime === '
|
|
143
|
-
|
|
144
|
-
const configDir = getGlobalConfigDir(runtime);
|
|
145
|
-
if (runtime === 'hermes')
|
|
157
|
+
if (runtime === 'hermes') {
|
|
158
|
+
const configDir = getGlobalConfigDir(runtime);
|
|
146
159
|
return node_path_1.default.join(configDir, 'skills', 'gsd');
|
|
160
|
+
}
|
|
161
|
+
// Kilo Code discovers global skills from ~/.kilo/skills/ (HOME-relative),
|
|
162
|
+
// independent of the XDG-based config dir (~/.config/kilo) used for commands.
|
|
163
|
+
// See: https://kilo.ai/docs/customize/skills
|
|
164
|
+
// "Global skills are located in the `.kilo` directory within your Home
|
|
165
|
+
// directory: ~/.kilo/skills/"
|
|
166
|
+
if (runtime === 'kilo')
|
|
167
|
+
return node_path_1.default.join(node_os_1.default.homedir(), '.kilo', 'skills');
|
|
168
|
+
const configDir = getGlobalConfigDir(runtime);
|
|
147
169
|
return node_path_1.default.join(configDir, 'skills');
|
|
148
170
|
}
|
|
149
171
|
/**
|
|
150
172
|
* Return the full path to a specific skill's directory for the given runtime.
|
|
151
|
-
* Returns null for runtimes that don't use a skills directory (cline).
|
|
152
173
|
*/
|
|
153
174
|
function getGlobalSkillDir(runtime, skillName) {
|
|
154
175
|
const base = getGlobalSkillsBase(runtime);
|
|
@@ -153,6 +153,7 @@ function projectManagedHookCommand({ absoluteRunner, scriptPath, runtime = 'gene
|
|
|
153
153
|
const MANAGED_HOOK_BASENAMES_BY_SURFACE = {
|
|
154
154
|
'settings-json': new Set([
|
|
155
155
|
'gsd-check-update.js',
|
|
156
|
+
'gsd-config-reload.js',
|
|
156
157
|
'gsd-statusline.js',
|
|
157
158
|
'gsd-context-monitor.js',
|
|
158
159
|
'gsd-prompt-guard.js',
|
|
@@ -168,6 +169,7 @@ const MANAGED_HOOK_BASENAMES_BY_SURFACE = {
|
|
|
168
169
|
const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
|
|
169
170
|
'settings-json': new Set([
|
|
170
171
|
'gsd-check-update.js',
|
|
172
|
+
'gsd-config-reload.js',
|
|
171
173
|
'gsd-statusline.js',
|
|
172
174
|
'gsd-context-monitor.js',
|
|
173
175
|
'gsd-prompt-guard.js',
|
|
@@ -188,6 +190,10 @@ const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
|
|
|
188
190
|
// reconcileCodexHooksJsonSessionStart can replace stale node-runner commands
|
|
189
191
|
// with the .cmd shim on reinstall (and vice-versa on cross-platform moves).
|
|
190
192
|
'gsd-check-update.cmd',
|
|
193
|
+
// #772: context-monitor is now registered for Codex SubagentStart/Stop/PostToolUse.
|
|
194
|
+
'gsd-context-monitor.js',
|
|
195
|
+
// #772: Windows .cmd shim for gsd-context-monitor — same #3426 pattern.
|
|
196
|
+
'gsd-context-monitor.cmd',
|
|
191
197
|
]),
|
|
192
198
|
};
|
|
193
199
|
const LEGACY_MANAGED_HOOK_ALIASES_BY_SURFACE = {
|