* fix(#813): apply per-runtime skill path rewrites in applySurface applySurface() re-staged skill artifacts but, unlike installRuntimeArtifacts(), never applied the per-runtime path rewrites. So /gsd:surface (profile/enable/disable/reset) overwrote installed SKILL.md bodies with the converter's default ~/.claude paths instead of the install target (pathPrefix), silently regressing skill path references for every skillsKind runtime until the next reinstall. applySurface now mirrors installRuntimeArtifacts: for kind.kind === 'skills' it derives pathPrefix the same way and applies applyRuntimeContentRewritesInPlace on the staged dir before syncing. - bin/install.js: export applyRuntimeContentRewritesInPlace - runtime-artifact-layout.cts: carry resolved scope on Layout; export getInstallExports; type computePathPrefix/applyRuntimeContentRewritesInPlace on InstallExports - surface.cts: lazily derive pathPrefix (only when a skills kind exists) and apply the rewrite via the shared getInstallExports accessor — single source of truth with install, only skills kinds rewritten (matches install) - tests: regression test parameterized over cursor + codex asserting post-applySurface bodies carry the install pathPrefix, not ~/.claude - CONTEXT.md: glossary updated for the applySurface rewrite parity + scope seam Closes #813 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#813): add changeset fragment for PR #817 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#813): normalize configDir prefix to forward slashes for Windows CI The #813 regression assertion compared skill bodies against a raw ${configDir}/ prefix, but production derives pathPrefix via path.resolve(configDir).replace(/\\/g, '/'). On Windows, mkdtempSync returns backslash paths while the rewritten body uses forward slashes, so the assertion would fail Windows-only (not covered by local gsd-test). Normalize the expected prefix the same way production does. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,8 @@ const _require: NodeRequire = require;
|
||||
|
||||
interface InstallExports {
|
||||
readGsdCommandNames: () => string[];
|
||||
computePathPrefix: (opts: { isGlobal: boolean; isOpencode: boolean; isWindowsHost: boolean; resolvedTarget: string; homeDir: string }) => string;
|
||||
applyRuntimeContentRewritesInPlace: (stagedDir: string, runtime: string, pathPrefix: string) => void;
|
||||
[converterName: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -84,6 +86,7 @@ interface ArtifactKind {
|
||||
interface Layout {
|
||||
runtime: string;
|
||||
configDir: string;
|
||||
scope?: 'local' | 'global';
|
||||
kinds: ArtifactKind[];
|
||||
}
|
||||
|
||||
@@ -371,7 +374,7 @@ function resolveRuntimeArtifactLayout(runtime: string, configDir: string, scope:
|
||||
throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
|
||||
}
|
||||
|
||||
return { runtime, configDir, kinds };
|
||||
return { runtime, configDir, scope, kinds };
|
||||
}
|
||||
|
||||
export = { resolveRuntimeArtifactLayout, findInstallSourceRoot };
|
||||
export = { resolveRuntimeArtifactLayout, findInstallSourceRoot, getInstallExports };
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { platformWriteSync } from './shell-command-projection.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import installProfiles = require('./install-profiles.cjs');
|
||||
@@ -35,7 +36,7 @@ import { CLUSTERS } from './clusters.cjs';
|
||||
import type { ClusterMap } from './clusters.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import runtimeArtifactLayout = require('./runtime-artifact-layout.cjs');
|
||||
const { findInstallSourceRoot } = runtimeArtifactLayout;
|
||||
const { findInstallSourceRoot, getInstallExports } = runtimeArtifactLayout;
|
||||
|
||||
const SURFACE_FILE_NAME = '.gsd-surface.json';
|
||||
|
||||
@@ -53,6 +54,7 @@ interface ArtifactKind {
|
||||
interface Layout {
|
||||
runtime: string;
|
||||
configDir: string;
|
||||
scope?: 'local' | 'global';
|
||||
kinds: ArtifactKind[];
|
||||
}
|
||||
|
||||
@@ -256,8 +258,29 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
|
||||
}
|
||||
const skillManifest = normalizeSkillManifest(layout.configDir, manifest);
|
||||
const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap);
|
||||
// Mirror installRuntimeArtifacts: skills kinds get per-runtime path rewrites
|
||||
// so SKILL.md bodies reference the install target (pathPrefix), not the
|
||||
// converter's default ~/.claude paths (#813). Computed lazily so command-only
|
||||
// runtimes do not trigger the install.js require.
|
||||
let pathPrefix: string | null = null;
|
||||
for (const kind of layout.kinds) {
|
||||
const staged = kind.stage(resolved);
|
||||
if (kind.kind === 'skills') {
|
||||
const installExports = getInstallExports();
|
||||
if (pathPrefix === null) {
|
||||
const scope = layout.scope ?? 'global';
|
||||
const resolvedTarget = path.resolve(layout.configDir).replace(/\\/g, '/');
|
||||
const homeDir = os.homedir().replace(/\\/g, '/');
|
||||
pathPrefix = installExports.computePathPrefix({
|
||||
isGlobal: scope === 'global',
|
||||
isOpencode: layout.runtime === 'opencode',
|
||||
isWindowsHost: process.platform === 'win32',
|
||||
resolvedTarget,
|
||||
homeDir,
|
||||
});
|
||||
}
|
||||
installExports.applyRuntimeContentRewritesInPlace(staged, layout.runtime, pathPrefix);
|
||||
}
|
||||
const dest = path.join(layout.configDir, kind.destSubpath);
|
||||
_syncGsdDir(staged, dest, kind, skillManifest);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user