Skip to content

Commit af3e506

Browse files
committed
fix(audits): resolve wildcard and root package exports, ignore deleted files and test-only guards
check:guidance-refs now requires a bare @sim/<pkg> import to have a '.' export, checks that a wildcard export match maps to an existing file, and drops index entries missing from the working tree before matching rule path globs. check:application-graph no longer counts a leftover test file as keeping a non-directory forbidden prefix alive.
1 parent 4c84e79 commit af3e506

2 files changed

Lines changed: 37 additions & 16 deletions

File tree

‎scripts/check-application-graph.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,19 @@ export function findViolations({ root, forbidden }: GuardedRoot): GraphViolation
284284
}
285285

286286
/**
287-
* Whether any path under `apps/sim` starts with `prefix`. A prefix that matches nothing guards
288-
* nothing: the tree was renamed, and the audit would keep passing over it.
287+
* Whether `prefix` names a directory or an importable module under `apps/sim`. A prefix that
288+
* matches nothing guards nothing: the tree was renamed, and the audit would keep passing over it.
289+
* A test file left behind does not count, because no runtime import resolves to it.
289290
*/
290291
function prefixMatchesAnything(prefix: string): boolean {
291292
if (prefix.endsWith('/')) return existsSync(resolve(APP_ROOT, prefix))
292293
const dir = resolve(APP_ROOT, dirname(prefix))
293-
return existsSync(dir) && readdirSync(dir).some((entry) => entry.startsWith(basename(prefix)))
294+
if (!existsSync(dir)) return false
295+
return readdirSync(dir, { withFileTypes: true }).some(
296+
(entry) =>
297+
entry.name.startsWith(basename(prefix)) &&
298+
(entry.isDirectory() || /(?<!\.test)\.tsx?$/.test(entry.name))
299+
)
294300
}
295301

296302
function main(): void {

‎scripts/check-guidance-refs.ts‎

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,18 @@ interface Finding {
3636
ref: string
3737
}
3838

39-
/** Every tracked or untracked-but-not-ignored file, relative to the repo root. */
39+
/**
40+
* Every tracked or untracked-but-not-ignored file on disk, relative to the repo root. A file
41+
* deleted from the working tree but still in the index is dropped.
42+
*/
4043
function repoFiles(): string[] {
4144
return execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], {
4245
cwd: ROOT,
4346
encoding: 'utf8',
4447
maxBuffer: 1 << 28,
4548
})
4649
.split('\n')
47-
.filter(Boolean)
50+
.filter((rel) => rel && existsSync(path.join(ROOT, rel)))
4851
}
4952

5053
/** Every guidance document, deduplicated through symlinks (`AGENTS.md` -> `CLAUDE.md`). */
@@ -67,7 +70,8 @@ function guidanceFiles(files: string[]): string[] {
6770

6871
interface WorkspacePackage {
6972
dir: string
70-
exports: string[]
73+
/** The `exports` map, or null when the package declares none. */
74+
exports: Record<string, unknown> | null
7175
}
7276

7377
interface Workspaces {
@@ -105,9 +109,7 @@ function readWorkspaces(files: string[]): Workspaces {
105109
for (const name of names) workspaces.scripts.add(name)
106110
if (dir && typeof manifest.name === 'string') {
107111
const exports =
108-
manifest.exports && typeof manifest.exports === 'object'
109-
? Object.keys(manifest.exports)
110-
: []
112+
manifest.exports && typeof manifest.exports === 'object' ? manifest.exports : null
111113
workspaces.packages.set(manifest.name, { dir: path.join(ROOT, dir), exports })
112114
}
113115
}
@@ -158,17 +160,30 @@ function importResolves(spec: string, packages: Map<string, WorkspacePackage>):
158160
const [scope, name, ...rest] = clean.split('/')
159161
const pkg = packages.get(`${scope}/${name}`)
160162
if (!pkg) return false
161-
if (rest.length === 0) return true
162-
const subpath = `./${rest.join('/')}`
163-
if (pkg.exports.length === 0) return resolvesFrom(pkg.dir, rest.join('/'))
164-
return pkg.exports.some((key) => {
165-
if (key === subpath) return true
166-
if (!key.includes('*')) return false
163+
if (!pkg.exports) return rest.length === 0 || resolvesFrom(pkg.dir, rest.join('/'))
164+
const subpath = rest.length === 0 ? '.' : `./${rest.join('/')}`
165+
return Object.entries(pkg.exports).some(([key, value]) => {
167166
const [head, tail] = key.split('*')
168-
return subpath.startsWith(head) && subpath.endsWith(tail)
167+
if (tail === undefined) return key === subpath
168+
if (!subpath.startsWith(head) || !subpath.endsWith(tail)) return false
169+
// A wildcard export only proves the pattern; the file it maps to must also exist.
170+
const target = exportTarget(value)
171+
const matched = subpath.slice(head.length, subpath.length - tail.length)
172+
return target !== null && resolvesFrom(pkg.dir, target.replace('*', matched))
169173
})
170174
}
171175

176+
/** The first file path an export condition map points at. */
177+
function exportTarget(value: unknown): string | null {
178+
if (typeof value === 'string') return value
179+
if (!value || typeof value !== 'object') return null
180+
for (const nested of Object.values(value)) {
181+
const target = exportTarget(nested)
182+
if (target) return target
183+
}
184+
return null
185+
}
186+
172187
function skillExists(name: string): boolean {
173188
return existsSync(path.join(SKILLS_DIR, name, 'SKILL.md'))
174189
}

0 commit comments

Comments
 (0)