From c202f93fd7791f0c84bcaa1fc50662a333d2b461 Mon Sep 17 00:00:00 2001 From: Day Dreamer <87640074+HugoXK@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:08:44 +0800 Subject: [PATCH] test(docs): a repo path named in inline code must exist on disk Both root READMEs told a reader the DSH pin lives in `dev/harness`; there is no such directory, the pin is `core/dev/harness/package.json` (#1397). This test reads backticked tokens outside fenced blocks on the two root READMEs and reports every one that does not resolve at the repo root, so the next such path fails a suite instead of a reader's `cd`. The file list is the two root READMEs: the docs and package READMEs write inline paths relative to their own package, which a repo-root check would report as broken (#1403 allows narrowing it). --- README.md | 2 +- README.zh.md | 2 +- core/test/check-doc-paths.test.js | 72 +++++++++++++++++++ .../ds-bot/files/test/check-doc-paths.test.js | 72 +++++++++++++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 core/test/check-doc-paths.test.js create mode 100644 patches/ds-bot/files/test/check-doc-paths.test.js diff --git a/README.md b/README.md index b76a5736..f90d6229 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ The first line is the FlyBot core (package `ds-bot`); the two plugins are what t On DSH Desktop: **Plugins → Add plugin**, paste one `github:` address at a time, then **Install** and **Enable now**. -`0.2.0-rc.2` is the DeepSeek Harness release FlyBot is built and tested against — the same version `dev/harness` +`0.2.0-rc.2` is the DeepSeek Harness release FlyBot is built and tested against — the same version `core/dev/harness` pins and CI runs, so keep it in all four commands. **3. Run it** diff --git a/README.zh.md b/README.zh.md index 5b51db51..ac658eb8 100644 --- a/README.zh.md +++ b/README.zh.md @@ -102,7 +102,7 @@ npx -y @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add "github:ThinkFlowLab 桌面版:**插件 → 添加插件**,一次粘一个 `github:` 地址,然后**安装**并**立即启用**。 -`0.2.0-rc.2` 是 FlyBot 构建与测试所用的 DeepSeek Harness 版本,也正是 `dev/harness` 钉住、CI 在跑的那一版, +`0.2.0-rc.2` 是 FlyBot 构建与测试所用的 DeepSeek Harness 版本,也正是 `core/dev/harness` 钉住、CI 在跑的那一版, 四条命令保持一致。 **3. 跑起来** diff --git a/core/test/check-doc-paths.test.js b/core/test/check-doc-paths.test.js new file mode 100644 index 00000000..c1e9e535 --- /dev/null +++ b/core/test/check-doc-paths.test.js @@ -0,0 +1,72 @@ +// A repo path written as inline code rots silently: `README.md:118` said `dev/harness` while the DSH +// pin is `core/dev/harness/package.json`, and nothing read it (#1403, #1397). The two root READMEs are +// the scanned surface on purpose — `docs/**`, `core/README.md` and the plugin READMEs write their +// inline paths relative to their own package or to the runtime home, so judging them against the repo +// root reports paths that are not broken. #1403 allows narrowing the file list; it forbids an allowlist. +import assert from 'node:assert/strict' +import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { test } from 'node:test' +import { fileURLToPath } from 'node:url' + +const ROOT = fileURLToPath(new URL('../../', import.meta.url)) +const DOCUMENTS = ['README.md', 'README.zh.md'] + +// A token claims to be a repo path when it holds a slash, no space, and its first segment names an +// entry at the repo root: that is what makes `dev/harness` a claim about this tree, while `src/host/` +// (package-relative), `origin/main` (a ref) and `ThinkFlowLab/FlyBot` (a remote) are not repo paths. +export function isRepoPath(token, root = ROOT) { + if (!token.includes('/') || /\s/.test(token)) return false + if (/^[a-z][a-z0-9+.-]*:\/\//i.test(token)) return false // URL + if (/[*<>{}]/.test(token)) return false // glob or placeholder + if (/^[\w.-]+\/[\w.-]+#\d+$/.test(token)) return false // owner/repo#123 + if (/^@/.test(token)) return false // npm scope + if (/^[/~$]/.test(token)) return false // absolute, home or env path + if (/[%:]/.test(token)) return false // env path or a namespaced id + if (/[=()|&;]/.test(token)) return false // a command line + const first = token.replace(/\/+$/, '').split('/')[0] + if (first === '.' || first === '..') return false + return existsSync(join(root, first)) +} + +// Every backticked token outside a fenced block, as `file:line: token` for the ones that miss on disk. +export function docPathMisses(root = ROOT, documents = DOCUMENTS) { + const misses = [] + for (const file of documents) { + let fenced = false + readFileSync(join(root, file), 'utf8').split('\n').forEach((line, index) => { + if (/^\s*(```|~~~)/.test(line)) { fenced = !fenced; return } + if (fenced) return + for (const [, token] of line.matchAll(/`([^`]+)`/g)) { + const path = token.trim().replace(/\/+$/, '') + if (!isRepoPath(path, root) || existsSync(join(root, path))) continue + misses.push(`${file}:${index + 1}: ${token.trim()}`) + } + }) + } + return misses +} + +test('every repo path named in inline code on the root READMEs exists on disk', () => { + assert.deepEqual(docPathMisses(), []) +}) + +test('the tokenizer skips refs, URLs, globs, placeholders, npm scopes and absolute paths, and names a miss', () => { + const root = mkdtempSync(join(tmpdir(), 'doc-paths-')) + mkdirSync(join(root, 'dev', 'harness'), { recursive: true }) + mkdirSync(join(root, 'docs'), { recursive: true }) + writeFileSync(join(root, 'docs', 'install.md'), '# install\n') + writeFileSync(join(root, 'README.md'), [ + 'A fenced block is prose, not a claim:', + '```', + 'dev/not-real', + '```', + 'Real: `dev/harness`, `dev/harness/`, `docs/install.md`.', + 'Not repo paths: `https://example.com/dev/x`, `docs/*.md`, `/dev`, `{id}/x`, `origin/main`,', + '`ThinkFlowLab/FlyBot#1403`, `@flybot/workflow`, `/etc/harness`, `../core/dev`, `container:x/y`,', + '`npm run verify`.', + 'One miss: `dev/nope`.', + ].join('\n')) + assert.deepEqual(docPathMisses(root, ['README.md']), ['README.md:9: dev/nope']) +}) diff --git a/patches/ds-bot/files/test/check-doc-paths.test.js b/patches/ds-bot/files/test/check-doc-paths.test.js new file mode 100644 index 00000000..c1e9e535 --- /dev/null +++ b/patches/ds-bot/files/test/check-doc-paths.test.js @@ -0,0 +1,72 @@ +// A repo path written as inline code rots silently: `README.md:118` said `dev/harness` while the DSH +// pin is `core/dev/harness/package.json`, and nothing read it (#1403, #1397). The two root READMEs are +// the scanned surface on purpose — `docs/**`, `core/README.md` and the plugin READMEs write their +// inline paths relative to their own package or to the runtime home, so judging them against the repo +// root reports paths that are not broken. #1403 allows narrowing the file list; it forbids an allowlist. +import assert from 'node:assert/strict' +import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { test } from 'node:test' +import { fileURLToPath } from 'node:url' + +const ROOT = fileURLToPath(new URL('../../', import.meta.url)) +const DOCUMENTS = ['README.md', 'README.zh.md'] + +// A token claims to be a repo path when it holds a slash, no space, and its first segment names an +// entry at the repo root: that is what makes `dev/harness` a claim about this tree, while `src/host/` +// (package-relative), `origin/main` (a ref) and `ThinkFlowLab/FlyBot` (a remote) are not repo paths. +export function isRepoPath(token, root = ROOT) { + if (!token.includes('/') || /\s/.test(token)) return false + if (/^[a-z][a-z0-9+.-]*:\/\//i.test(token)) return false // URL + if (/[*<>{}]/.test(token)) return false // glob or placeholder + if (/^[\w.-]+\/[\w.-]+#\d+$/.test(token)) return false // owner/repo#123 + if (/^@/.test(token)) return false // npm scope + if (/^[/~$]/.test(token)) return false // absolute, home or env path + if (/[%:]/.test(token)) return false // env path or a namespaced id + if (/[=()|&;]/.test(token)) return false // a command line + const first = token.replace(/\/+$/, '').split('/')[0] + if (first === '.' || first === '..') return false + return existsSync(join(root, first)) +} + +// Every backticked token outside a fenced block, as `file:line: token` for the ones that miss on disk. +export function docPathMisses(root = ROOT, documents = DOCUMENTS) { + const misses = [] + for (const file of documents) { + let fenced = false + readFileSync(join(root, file), 'utf8').split('\n').forEach((line, index) => { + if (/^\s*(```|~~~)/.test(line)) { fenced = !fenced; return } + if (fenced) return + for (const [, token] of line.matchAll(/`([^`]+)`/g)) { + const path = token.trim().replace(/\/+$/, '') + if (!isRepoPath(path, root) || existsSync(join(root, path))) continue + misses.push(`${file}:${index + 1}: ${token.trim()}`) + } + }) + } + return misses +} + +test('every repo path named in inline code on the root READMEs exists on disk', () => { + assert.deepEqual(docPathMisses(), []) +}) + +test('the tokenizer skips refs, URLs, globs, placeholders, npm scopes and absolute paths, and names a miss', () => { + const root = mkdtempSync(join(tmpdir(), 'doc-paths-')) + mkdirSync(join(root, 'dev', 'harness'), { recursive: true }) + mkdirSync(join(root, 'docs'), { recursive: true }) + writeFileSync(join(root, 'docs', 'install.md'), '# install\n') + writeFileSync(join(root, 'README.md'), [ + 'A fenced block is prose, not a claim:', + '```', + 'dev/not-real', + '```', + 'Real: `dev/harness`, `dev/harness/`, `docs/install.md`.', + 'Not repo paths: `https://example.com/dev/x`, `docs/*.md`, `/dev`, `{id}/x`, `origin/main`,', + '`ThinkFlowLab/FlyBot#1403`, `@flybot/workflow`, `/etc/harness`, `../core/dev`, `container:x/y`,', + '`npm run verify`.', + 'One miss: `dev/nope`.', + ].join('\n')) + assert.deepEqual(docPathMisses(root, ['README.md']), ['README.md:9: dev/nope']) +})