Skip to content

Commit 3bd889d

Browse files
committed
cache package manifests during intent discovery
1 parent 25e6131 commit 3bd889d

13 files changed

Lines changed: 255 additions & 92 deletions

File tree

packages/intent/src/commands/list.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ function printListDebug(result: IntentSkillList): void {
2727
['skills', result.debug.skillCount],
2828
['warnings', result.debug.warningCount],
2929
['conflicts', result.debug.conflictCount],
30+
['packageJsonReadCount', result.debug.scan.packageJsonReadCount],
31+
['packageJsonCacheHits', result.debug.scan.packageJsonCacheHits],
3032
])
3133
}
3234

packages/intent/src/commands/load.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ function printLoadDebug(loaded: LoadedIntentSkill | ResolvedIntentSkill): void {
2828
['skill', loaded.debug.skillName],
2929
['path', loaded.debug.path],
3030
['warnings', loaded.debug.warningCount],
31+
['packageJsonReadCount', loaded.debug.scan.packageJsonReadCount],
32+
['packageJsonCacheHits', loaded.debug.scan.packageJsonCacheHits],
3133
])
3234
}
3335

packages/intent/src/core.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
isPackageExcluded,
77
warningMentionsPackage,
88
} from './core/excludes.js'
9+
import { createIntentFsCache, type IntentFsCache } from './fs-cache.js'
910
import { rewriteLoadedSkillMarkdownDestinations } from './core/markdown.js'
1011
import { resolveSkillUseFastPath } from './core/load-resolution.js'
1112
import { resolveProjectContext } from './core/project-context.js'
@@ -80,6 +81,13 @@ function getScanScope(options: ScanOptions): ScanScope {
8081
return options.scope ?? (options.includeGlobal ? 'local-and-global' : 'local')
8182
}
8283

84+
function withFsCache(
85+
options: ScanOptions,
86+
fsCache: IntentFsCache,
87+
): ScanOptions & { fsCache: IntentFsCache } {
88+
return { ...options, fsCache }
89+
}
90+
8391
function resolveCoreCwd(options: IntentCoreOptions): string {
8492
return resolve(process.cwd(), options.cwd ?? process.cwd())
8593
}
@@ -89,8 +97,9 @@ export function listIntentSkills(
8997
): IntentSkillList {
9098
const cwd = resolveCoreCwd(options)
9199
const scanOptions = toScanOptions(options)
100+
const fsCache = createIntentFsCache()
92101
const projectContext = resolveProjectContext({ cwd })
93-
const scanResult = scanForIntents(cwd, scanOptions)
102+
const scanResult = scanForIntents(cwd, withFsCache(scanOptions, fsCache))
94103
const excludePatterns = getEffectiveExcludePatterns(options, projectContext)
95104
const excludeMatchers = compileExcludePatterns(excludePatterns)
96105
const excludedPackages = scanResult.packages
@@ -144,6 +153,7 @@ export function listIntentSkills(
144153
skillCount: result.skills.length,
145154
warningCount: result.warnings.length,
146155
conflictCount: result.conflicts.length,
156+
scan: scanResult.stats ?? fsCache.getStats(),
147157
}
148158
}
149159

@@ -220,12 +230,14 @@ function toResolvedIntentSkill(
220230
function createLoadedSkillDebug({
221231
cwd,
222232
excludes,
233+
scan,
223234
resolution,
224235
resolved,
225236
scope,
226237
}: {
227238
cwd: string
228239
excludes: Array<string>
240+
scan: LoadedIntentSkillDebug['scan']
229241
resolution: LoadedIntentSkillDebug['resolution']
230242
resolved: ResolveSkillResult
231243
scope: ScanScope
@@ -241,6 +253,7 @@ function createLoadedSkillDebug({
241253
source: resolved.source,
242254
path: resolved.path,
243255
warningCount: resolved.warnings.length,
256+
scan,
244257
}
245258
}
246259

@@ -263,6 +276,7 @@ function resolveIntentSkillInCwd(
263276
)
264277
}
265278

279+
const fsCache = createIntentFsCache()
266280
const projectContext = resolveProjectContext({ cwd })
267281
const excludePatterns = getEffectiveExcludePatterns(options, projectContext)
268282
const excludeMatchers = compileExcludePatterns(excludePatterns)
@@ -281,6 +295,7 @@ function resolveIntentSkillInCwd(
281295
options,
282296
projectContext,
283297
cwd,
298+
fsCache,
284299
)
285300
if (fastPathResolved) {
286301
return toResolvedIntentSkill(
@@ -293,13 +308,14 @@ function resolveIntentSkillInCwd(
293308
excludes: excludePatterns,
294309
resolution: 'fast-path',
295310
resolved: fastPathResolved,
311+
scan: fsCache.getStats(),
296312
scope,
297313
})
298314
: undefined,
299315
)
300316
}
301317

302-
const scanResult = scanForIntents(cwd, scanOptions)
318+
const scanResult = scanForIntents(cwd, withFsCache(scanOptions, fsCache))
303319
let resolved: ReturnType<typeof resolveSkillUse>
304320
try {
305321
resolved = resolveSkillUse(use, scanResult)
@@ -322,6 +338,7 @@ function resolveIntentSkillInCwd(
322338
excludes: excludePatterns,
323339
resolution: 'full-scan',
324340
resolved,
341+
scan: scanResult.stats ?? fsCache.getStats(),
325342
scope,
326343
})
327344
: undefined,

packages/intent/src/core/load-resolution.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { existsSync } from 'node:fs'
22
import { dirname, join, resolve } from 'node:path'
3+
import { createIntentFsCache, type IntentFsCache } from '../fs-cache.js'
34
import { resolveSkillEntry, type ResolveSkillResult } from '../resolver.js'
45
import { scanIntentPackageAtRoot } from '../scanner.js'
5-
import { resolveWorkspacePackages } from '../workspace-patterns.js'
6+
import { findWorkspacePackages } from '../workspace-patterns.js'
67
import { getDeps, resolveDepDir } from '../utils.js'
78
import { warningMentionsPackage } from './excludes.js'
8-
import { readPackageJson } from './package-json.js'
99
import {
1010
resolveProjectContext,
1111
type ProjectContext,
@@ -21,6 +21,7 @@ interface WorkspacePackageInfo {
2121

2222
function readWorkspacePackageInfos(
2323
context: ProjectContext,
24+
fsCache: IntentFsCache,
2425
): Array<WorkspacePackageInfo> {
2526
const dirs = new Set<string>()
2627

@@ -31,16 +32,13 @@ function readWorkspacePackageInfos(
3132
if (context.workspaceRoot) {
3233
dirs.add(context.workspaceRoot)
3334

34-
for (const dir of resolveWorkspacePackages(
35-
context.workspaceRoot,
36-
context.workspacePatterns,
37-
)) {
35+
for (const dir of findWorkspacePackages(context.workspaceRoot)) {
3836
dirs.add(dir)
3937
}
4038
}
4139

4240
return [...dirs].flatMap((dir) => {
43-
const packageJson = readPackageJson(dir)
41+
const packageJson = fsCache.readPackageJson(dir)
4442
if (!packageJson) return []
4543

4644
return [
@@ -154,10 +152,11 @@ function getDirectLoadFastPathCandidateDirs(
154152
function getWorkspaceLoadFastPathCandidateDirs(
155153
packageName: string,
156154
context: ProjectContext,
155+
fsCache: IntentFsCache,
157156
): Array<string> {
158157
const candidates: Array<string> = []
159158
const seen = new Set<string>()
160-
const workspacePackages = readWorkspacePackageInfos(context)
159+
const workspacePackages = readWorkspacePackageInfos(context, fsCache)
161160

162161
for (const pkg of workspacePackages) {
163162
if (pkg.name === packageName) {
@@ -212,10 +211,12 @@ function resolveFromPackageRoots(
212211
packageRoots: Array<string>,
213212
parsedUse: SkillUse,
214213
cwd: string,
214+
fsCache: IntentFsCache,
215215
): ResolveSkillResult | null {
216216
for (const packageRoot of packageRoots) {
217217
const scanned = scanIntentPackageAtRoot(packageRoot, {
218218
fallbackName: parsedUse.packageName,
219+
fsCache,
219220
projectRoot: cwd,
220221
skillNameHint: parsedUse.skillName,
221222
})
@@ -225,6 +226,7 @@ function resolveFromPackageRoots(
225226
if (scanned.package?.name === parsedUse.packageName) {
226227
const fallbackScanned = scanIntentPackageAtRoot(packageRoot, {
227228
fallbackName: parsedUse.packageName,
229+
fsCache,
228230
projectRoot: cwd,
229231
})
230232
const fallbackResolved = resolveScannedPackageSkill(
@@ -243,6 +245,7 @@ export function resolveSkillUseFastPath(
243245
options: IntentCoreOptions,
244246
context = resolveProjectContext({ cwd: process.cwd() }),
245247
cwd = context.cwd,
248+
fsCache = createIntentFsCache(),
246249
): ResolveSkillResult | null {
247250
if (options.globalOnly) return null
248251
if (shouldSkipFastPathForYarnPnp(context, cwd)) return null
@@ -251,6 +254,7 @@ export function resolveSkillUseFastPath(
251254
getDirectLoadFastPathCandidateDirs(parsedUse.packageName, context, cwd),
252255
parsedUse,
253256
cwd,
257+
fsCache,
254258
)
255259
if (directResolved) return directResolved
256260

@@ -259,8 +263,13 @@ export function resolveSkillUseFastPath(
259263
}
260264

261265
return resolveFromPackageRoots(
262-
getWorkspaceLoadFastPathCandidateDirs(parsedUse.packageName, context),
266+
getWorkspaceLoadFastPathCandidateDirs(
267+
parsedUse.packageName,
268+
context,
269+
fsCache,
270+
),
263271
parsedUse,
264272
cwd,
273+
fsCache,
265274
)
266275
}

packages/intent/src/core/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { IntentPackage, ScanScope, VersionConflict } from '../types.js'
1+
import type {
2+
IntentPackage,
3+
ScanScope,
4+
ScanStats,
5+
VersionConflict,
6+
} from '../types.js'
27

38
export interface IntentCoreOptions {
49
cwd?: string
@@ -60,6 +65,7 @@ export interface IntentSkillListDebug {
6065
skillCount: number
6166
warningCount: number
6267
conflictCount: number
68+
scan: IntentScanDebugStats
6369
}
6470

6571
export interface LoadedIntentSkillDebug {
@@ -73,8 +79,11 @@ export interface LoadedIntentSkillDebug {
7379
source: IntentPackage['source']
7480
path: string
7581
warningCount: number
82+
scan: IntentScanDebugStats
7683
}
7784

85+
export interface IntentScanDebugStats extends ScanStats {}
86+
7887
export type IntentCoreErrorCode =
7988
| 'invalid-options'
8089
| 'invalid-skill-use'

packages/intent/src/discovery/walk.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import { existsSync, readFileSync } from 'node:fs'
1+
import { existsSync } from 'node:fs'
22
import { join } from 'node:path'
33
import { listNodeModulesPackageDirs, resolveDepDir, getDeps } from '../utils.js'
4-
import {
5-
readWorkspacePatterns,
6-
resolveWorkspacePackages,
7-
} from '../workspace-patterns.js'
4+
import { findWorkspacePackages } from '../workspace-patterns.js'
5+
import type { IntentFsCache } from '../fs-cache.js'
86
import type { IntentPackage } from '../types.js'
97

108
type PackageJson = Record<string, unknown>
119

1210
export interface CreateDependencyWalkerOptions {
11+
fsCache: IntentFsCache
1312
projectRoot: string
1413
readPkgJson: (dirPath: string) => PackageJson | null
1514
tryRegister: (dirPath: string, fallbackName: string) => boolean
@@ -83,29 +82,26 @@ export function createDependencyWalker(opts: CreateDependencyWalkerOptions) {
8382
dirPath: string,
8483
label: string,
8584
): PackageJson | null {
86-
try {
87-
return JSON.parse(
88-
readFileSync(join(dirPath, 'package.json'), 'utf8'),
89-
) as PackageJson
90-
} catch (err) {
91-
const code = (err as NodeJS.ErrnoException).code
85+
const result = opts.fsCache.readPackageJsonResult(dirPath)
86+
if (!result.packageJson) {
87+
const code = (result.error as NodeJS.ErrnoException | null)?.code
9288
if (code !== 'ENOENT') {
9389
opts.warnings.push(
94-
`Could not read ${label} package.json at ${dirPath}: ${(err as Error).message}`,
90+
`Could not read ${label} package.json at ${dirPath}: ${
91+
result.error instanceof Error
92+
? result.error.message
93+
: 'invalid package.json'
94+
}`,
9595
)
9696
}
9797
return null
9898
}
99+
100+
return result.packageJson
99101
}
100102

101103
function walkWorkspacePackages(): void {
102-
const workspacePatterns = readWorkspacePatterns(opts.projectRoot)
103-
if (!workspacePatterns) return
104-
105-
for (const wsDir of resolveWorkspacePackages(
106-
opts.projectRoot,
107-
workspacePatterns,
108-
)) {
104+
for (const wsDir of findWorkspacePackages(opts.projectRoot)) {
109105
const wsNodeModules = join(wsDir, 'node_modules')
110106
if (existsSync(wsNodeModules)) {
111107
for (const dirPath of listNodeModulesPackageDirs(wsNodeModules)) {

0 commit comments

Comments
 (0)