Files
deepseek-harness/scripts/verify-package-paths.ts
Tianyi Cui d11b713e0a Merge remote-tracking branch 'origin/master' into codex/trim-ai-prose
# Conflicts:
#	docs/config-catalog.md
#	scripts/gen-persistence-catalog.ts
#	scripts/jsdoc.ts
#	scripts/verify-md-wrap.ts
#	scripts/verify-package-paths.ts
2026-07-14 00:47:30 +08:00

96 lines
3.5 KiB
TypeScript

/**
* Find stale root-relative `packages/...` references in repo-authored prose and
* TypeScript. A missing path is reported only when it names a real package leaf;
* globs, placeholders, hypothetical packages, and unbuilt `lib/` output are
* outside the check.
*/
import { existsSync, readdirSync } from 'node:fs'
import { resolve } from 'node:path'
import { findReferenceViolations, uniqueRepoFiles, type ReferenceViolation as Violation } from './repo-files.ts'
const root = resolve(import.meta.dirname, '..')
/** Markdown + repo-authored TypeScript that may cite package paths. */
const PATTERNS = [
'README.md',
'docs/**/*.md',
'packages/*/*.md',
'packages/*/*/*.md',
'AGENTS.md',
'packages/AGENTS.md',
'packages/**/*.ts',
'examples/**/*.ts',
]
/** Paths excluded from the scan: built output and vendored upstream source. */
const isExcluded = (p: string): boolean =>
p.includes('/lib/') || p.endsWith('.d.ts') || p.startsWith('vendor/')
/**
* Directory names of every real package, `packages/<group>/<pkg>`. A broken
* reference is only flagged when one of its segments is in this set — that is
* what scopes the gate to DRIFT (a moved real package) rather than typos or
* not-yet-existing packages named in a proposal.
*/
function realPackageNames(): Set<string> {
const names = new Set<string>()
const pkgRoot = resolve(root, 'packages')
for (const group of readdirSync(pkgRoot, { withFileTypes: true })) {
if (!group.isDirectory()) continue
for (const pkg of readdirSync(resolve(pkgRoot, group.name), { withFileTypes: true })) {
if (pkg.isDirectory()) names.add(pkg.name)
}
}
return names
}
const packageNames = realPackageNames()
/**
* Match a `packages/<path>` reference token. The character class is plain path
* characters only, so a glob (`*`), placeholder (`<`, `>`), or brace expansion
* (`{`, `}`, `,`) terminates the match before those chars and is never probed —
* those are patterns, not real paths. A trailing `.`/`/` (e.g. a sentence-ending
* period) is trimmed before the existence check.
*/
const PKG_REF = /\bpackages\/[A-Za-z0-9._/-]+/g
function isDriftedPackageReference(ref: string): boolean {
if (existsSync(resolve(root, ref))) return false
// Ignore unbuilt `lib/` paths only under an existing depth-two package root:
// CI runs this gate before build, while stale group-less paths must still fail.
const parts = ref.split('/')
const libAt = parts.indexOf('lib')
if (libAt === 3 && existsSync(resolve(root, parts.slice(0, 3).join('/')))) return false
// A missing reference is drift only when a path segment names a live package.
return ref.split('/').slice(1).some(segment => packageNames.has(segment))
}
/** Find missing package references whose path names a live package; bare paths, typos, and illustrative skeletons do not count. */
function findViolations(absPath: string): Violation[] {
return findReferenceViolations(
root,
absPath,
PKG_REF,
// Remove trailing separators or sentence punctuation matched greedily.
ref => ref.replace(/[./]+$/, ''),
isDriftedPackageReference,
)
}
const files = uniqueRepoFiles(root, PATTERNS, isExcluded)
const all = files.flatMap(file => findViolations(file.real))
const checked = files.length
if (all.length === 0) {
console.log(`verify-package-paths: ${checked} file(s) checked, all packages/* references resolve.`)
process.exit(0)
}
console.error('verify-package-paths: broken packages/* references found (target does not exist):')
for (const v of all) {
console.error(` ${v.file}:${v.line} ${v.ref}`)
}
process.exit(1)