Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ Versions before 0.8.0 are in the [git tags](https://github.com/leemour/cli-core/
Every entry says what changed as a caller sees it, why, and what to watch for — the rules are
[`docs/dev/CONVENTIONS.md`](docs/dev/CONVENTIONS.md#the-changelog).

## Unreleased

### Fixed

- **`cli-dev docs-check` and `markdownFiles` skip what is not a file or a folder.** A device or a
pipe named `*.md` used to be read, and the check crashed. Claude Code's Bash sandbox lays devices
over names in the working folder (`.claude/loop.md`, `.claude/skills`), so the check failed in
every agent session with the sandbox on. A symbolic link to a file is still listed; a link to a
folder is still not followed.

## 0.17.0 — 03.10.2026

### Changed — may break callers
Expand Down
7 changes: 5 additions & 2 deletions src/release/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,11 @@ export const markdownFiles = (directory: string, skip: ReadonlySet<string> = new
? readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
if (skip.has(entry.name)) return []
const path = join(directory, entry.name)
if (entry.isDirectory()) return markdownFiles(path, skip)
return entry.name.endsWith(".md") ? [path] : []
// stat, not the entry's own type: Claude Code's sandbox mounts character devices over names
// like .claude/loop.md, and the directory listing still reports what lies underneath.
const stats = statSync(path, { throwIfNoEntry: false })
if (entry.isDirectory() && stats?.isDirectory()) return markdownFiles(path, skip)
return stats?.isFile() && entry.name.endsWith(".md") ? [path] : []
})
: []

Expand Down
15 changes: 14 additions & 1 deletion src/release/release.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
import { execFileSync } from "node:child_process"
import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { dirname, join } from "node:path"
import { afterEach, beforeEach, describe, expect, it } from "vitest"
Expand Down Expand Up @@ -168,6 +169,18 @@ describe("slug", () => {
})
})

describe("markdownFiles", () => {
it.skipIf(process.platform === "win32")("lists files and links, not a special file with a .md name", () => {
const root = mkdtempSync(join(tmpdir(), "markdown-files-"))
writeFileSync(join(root, "README.md"), "# x\n")
symlinkSync(join(root, "README.md"), join(root, "linked.md"))
execFileSync("mkfifo", [join(root, "loop.md")])

expect(markdownFiles(root).sort()).toEqual([join(root, "README.md"), join(root, "linked.md")])
rmSync(root, { recursive: true, force: true })
})
})

describe("docsProblems", () => {
let root: string
const write = (path: string, text: string) => {
Expand Down
Loading