fix(skills): normalize contributor-facing paths in the skill guide generator

path.relative returns backslash-separated paths on Windows, so the
stale-artifact and frontmatter diagnostics printed src\cli\... there while
tests and copy-pasteable commands expect forward slashes. Hoist the
normalization into toPosixRelativePath, apply it to both call sites, and
cover the Windows branch on every platform by injecting path.win32.

Co-authored-by: poorpaper <poorpaperdesire@gmail.com>
This commit is contained in:
Neil
2026-08-21 22:53:58 -07:00
co-authored by poorpaper
parent ad95e2bc65
commit 2c472445ff
3 changed files with 25 additions and 2 deletions
+1
View File
@@ -638,6 +638,7 @@ jobs:
src/main/windows/windows-host-job.win32.test.ts
src/main/cli/wsl-cli-powershell-boundary.test.ts
src/main/orca-profiles/profile-index-store.test.ts
config/scripts/generate-bundled-skill-guides.test.mjs
src/main/runtime/repo-worktree-admin-fingerprint.test.ts
src/main/runtime/worktree-scan-admin-fingerprint-gate.test.ts
src/shared/secure-file-fsync-flags.test.ts
@@ -169,6 +169,12 @@ async function assertStubSourcesMatchTopics(repoRoot) {
}
}
// Why: path.relative yields `\` on Windows, but these paths are asserted in tests and pasted into commands.
// split(sep) rather than replaceAll('\\', '/') so a POSIX filename containing a backslash survives intact.
function toPosixRelativePath(repoRoot, filePath, pathModule = path) {
return pathModule.relative(repoRoot, filePath).split(pathModule.sep).join('/')
}
async function buildArtifacts(repoRoot = REPO_ROOT) {
const guideRoot = path.join(repoRoot, 'skill-guides')
const sourceFiles = (await readdir(guideRoot, { withFileTypes: true }))
@@ -193,7 +199,7 @@ async function buildArtifacts(repoRoot = REPO_ROOT) {
// Why: Git may render text with native EOLs despite repository policy; the
// embedded guide and generated projection must have one platform-neutral identity.
const markdown = normalizeMarkdown(await readFile(sourcePath, 'utf8'))
const frontmatter = parseFrontmatter(markdown, path.relative(repoRoot, sourcePath))
const frontmatter = parseFrontmatter(markdown, toPosixRelativePath(repoRoot, sourcePath))
if (frontmatter.name !== name) {
throw new Error(`Guide source ${name}.md declares mismatched name ${frontmatter.name}`)
}
@@ -243,7 +249,7 @@ async function verifyArtifacts(artifacts, repoRoot = REPO_ROOT) {
if (stale.length > 0) {
throw new Error(
`Generated bundled skill guides are stale:\n${stale
.map((filePath) => path.relative(repoRoot, filePath).split(path.sep).join('/'))
.map((filePath) => toPosixRelativePath(repoRoot, filePath))
.join('\n')}\nRun node config/scripts/generate-bundled-skill-guides.mjs --write.`
)
}
@@ -272,6 +278,7 @@ export {
normalizeMarkdown,
parseFrontmatter,
serializeEmbeddedModule,
toPosixRelativePath,
verifyArtifacts,
writeArtifacts
}
@@ -14,6 +14,7 @@ import {
frontmatterBlock,
normalizeMarkdown,
parseFrontmatter,
toPosixRelativePath,
verifyArtifacts,
writeArtifacts
} from './generate-bundled-skill-guides.mjs'
@@ -232,6 +233,20 @@ describe('bundled skill guide generator', () => {
await expect(verifyArtifacts(artifacts, root)).rejects.toThrow('skills/computer-use/SKILL.md')
})
// Why: the stale-artifact assertions above only exercise the Windows separator on Windows,
// and this file is not in pr.yml's Windows test allowlist — inject path.win32 so CI covers it.
it('formats contributor-facing paths with forward slashes on every platform', () => {
expect(
toPosixRelativePath('C:\\repo', 'C:\\repo\\src\\cli\\bundled-skill-guides.ts', path.win32)
).toBe('src/cli/bundled-skill-guides.ts')
expect(
toPosixRelativePath('C:\\repo', 'C:\\repo\\skills\\computer-use\\SKILL.md', path.win32)
).toBe('skills/computer-use/SKILL.md')
expect(toPosixRelativePath('/repo', '/repo/skills/computer-use/SKILL.md', path.posix)).toBe(
'skills/computer-use/SKILL.md'
)
})
it('rejects mismatched source names and ambiguous aliases', async () => {
const root = await createFixture()
await writeFile(