Skip to content

Commit 82cfda3

Browse files
fix(context): qualify lint snapshot freshness
1 parent a52a7c2 commit 82cfda3

4 files changed

Lines changed: 38 additions & 7 deletions

File tree

‎packages/context/src/lint.ts‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@ const lintCaptureSelection = (
203203
filePath: toWorkspacePath(workspaceRoot, path.resolve(packageRoot, request.filePath)),
204204
};
205205

206+
const lintInputCompleteness = (
207+
workspaceRoot: string,
208+
packageRoot: string,
209+
patterns: string[],
210+
files: LintFileRecord[],
211+
): 'complete' | 'partial' => {
212+
const selectedPaths = new Set(
213+
patterns.map((pattern) => toWorkspacePath(workspaceRoot, path.resolve(packageRoot, pattern))),
214+
);
215+
return selectedPaths.size === files.length && files.every((file) => selectedPaths.has(file.path))
216+
? 'complete'
217+
: 'partial';
218+
};
219+
206220
const ensureWritten = (result: Awaited<ReturnType<typeof writeContextSnapshot>>): void => {
207221
if (!result.written)
208222
throw new Error('Could not write the context snapshot.', {
@@ -336,7 +350,12 @@ const captureLintSnapshot = async (
336350
path: filePath,
337351
digest: fileDigest,
338352
})),
339-
inputCompleteness: 'complete',
353+
inputCompleteness: lintInputCompleteness(
354+
workspaceRoot,
355+
target.packageRoot,
356+
request.patterns ?? ['.'],
357+
files,
358+
),
340359
captureSelection,
341360
};
342361
const status: ContextRunStatus = totals.errors > 0 ? 'fail' : 'pass';

‎packages/context/tests/lint.test.ts‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ await listDiagnostics(${JSON.stringify(workspaceRoot)}).catch(() => undefined);`
112112
});
113113
});
114114

115-
test('captures a deterministic file snapshot with complete inputs and fail status', async () => {
115+
test('captures an open-ended file snapshot with partial inputs and fail status', async () => {
116116
await withTempWorkspace(async (workspaceRoot) => {
117117
const aPath = path.join(workspaceRoot, 'a.ts');
118118
const bPath = path.join(workspaceRoot, 'b.ts');
@@ -176,7 +176,7 @@ test('captures a deterministic file snapshot with complete inputs and fail statu
176176
expect(mocks.closeCalls).toBe(1);
177177
expect(result).toMatchObject({
178178
status: 'fail',
179-
freshness: { state: 'fresh', changedPaths: [] },
179+
freshness: { state: 'partial', changedPaths: [] },
180180
summary: {
181181
files: 2,
182182
errors: 1,
@@ -187,7 +187,7 @@ test('captures a deterministic file snapshot with complete inputs and fail statu
187187
});
188188
expect(stored?.snapshot.source).toMatchObject({
189189
captureSelection: { mode: 'files', patterns: ['.'] },
190-
inputCompleteness: 'complete',
190+
inputCompleteness: 'partial',
191191
inputs: [{ path: 'a.ts' }, { path: 'b.ts' }],
192192
});
193193
expect(stored?.snapshot.facets.lint).toMatchObject({
@@ -312,7 +312,7 @@ test('paginates and filters diagnostics from one frozen snapshot deterministical
312312
});
313313
expect(first).toMatchObject({
314314
snapshotId: capture.snapshotId,
315-
freshness: { state: 'fresh', changedPaths: [] },
315+
freshness: { state: 'partial', changedPaths: [] },
316316
total: 2,
317317
items: [{ path: 'src/a.ts', ruleId: 'a', severity: 'error', message: 'error' }],
318318
});
@@ -487,7 +487,11 @@ test('rejects a malformed diagnostics cursor', async () => {
487487
messages: [],
488488
},
489489
];
490-
const capture = await captureLintSnapshot(workspaceRoot, { mode: 'files' }, createRslint);
490+
const capture = await captureLintSnapshot(
491+
workspaceRoot,
492+
{ mode: 'files', patterns: ['a.ts'] },
493+
createRslint,
494+
);
491495

492496
await expect(
493497
listDiagnostics(workspaceRoot, { snapshotId: capture.snapshotId, cursor: '?' }),
@@ -509,7 +513,11 @@ test('returns snapshot provenance for an empty diagnostics page', async () => {
509513
messages: [],
510514
},
511515
];
512-
const capture = await captureLintSnapshot(workspaceRoot, { mode: 'files' }, createRslint);
516+
const capture = await captureLintSnapshot(
517+
workspaceRoot,
518+
{ mode: 'files', patterns: ['a.ts'] },
519+
createRslint,
520+
);
513521
const stored = await readContextSnapshotById(workspaceRoot, capture.snapshotId);
514522

515523
await expect(

‎website/docs/en/guide/cli/mcp.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ The development tools reuse the same immutable checkout-local store. `snapshot_l
9090

9191
`lint_snapshot` is explicit execution. It runs Rslint once for file patterns (default `['.']`) or one supplied text buffer, then records diagnostics and optional fixed output without writing source files. Its optional `packageRoot` and `configPath` select a monorepo package and Rstack config as described above. It is annotated `readOnlyHint: false`, `destructiveHint: false`, and `openWorldHint: true` because configuration is loaded and lint runs. Ask before calling it.
9292

93+
File-mode lint freshness is `fresh` only when every selected pattern is an explicit file returned by Rslint. Directory, glob, unmatched, and default selections are `partial` because files that match later are not part of the recorded input set.
94+
9395
`test_snapshot` is also explicit execution. It invokes the installed experimental `runRstest()` API once, optionally narrowed by files or a test-name pattern, and records structured summaries without watch mode or console streams. It accepts the same optional checkout-relative `packageRoot` and `configPath` selectors. An optional `execution` object explicitly enables aggregate Istanbul coverage for that one run; its `include` and `exclude` arrays accept at most 200 patterns, and `allowExternal` defaults to false. Coverage is not enabled when `execution` is omitted. The tool is annotated `readOnlyHint: false`, `destructiveHint: true`, and `openWorldHint: true` because project configuration and tests execute. Ask before calling it.
9496

9597
Freshness belongs to each producer and snapshot independently:

‎website/docs/zh/guide/cli/mcp.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ Rsbuild 只会为该命令加载已安装的收集器。构建会在检出目录
8787

8888
`lint_snapshot` 是明确执行操作。它会针对文件模式(默认为 `['.']`)或一个给定文本缓冲区运行一次 Rslint,然后记录诊断和可选的修复输出,但不会写入源文件。可选的 `packageRoot` 和 `configPath` 会按上述规则选择 monorepo 包和 Rstack 配置。其标注为 `readOnlyHint: false`、`destructiveHint: false` 和 `openWorldHint: true`,因为它会加载配置并运行 lint。调用前应先询问用户。
8989

90+
只有当每个选定模式都是 Rslint 实际返回的明确文件时,文件模式 lint 的新鲜度才会是 `fresh`。目录、glob、未匹配模式和默认选择会是 `partial`,因为之后新增的匹配文件不在已记录的输入集合中。
91+
9092
`test_snapshot` 也是明确执行操作。它会调用一次已安装的实验性 `runRstest()` API,可按文件或测试名称模式缩小范围,并记录结构化摘要,不启动 watch 模式,也不记录控制台流。它接受相同的可选检出目录相对路径 `packageRoot` 和 `configPath` 选择器。可选的 `execution` 对象会为这一次运行明确启用 Istanbul 聚合覆盖率;其中 `include` 和 `exclude` 数组最多各接受 200 个模式,`allowExternal` 默认为 false。省略 `execution` 时不会启用覆盖率。其标注为 `readOnlyHint: false`、`destructiveHint: true` 和 `openWorldHint: true`,因为它会执行项目配置和测试。调用前应先询问用户。
9193

9294
新鲜度按生产者和快照分别判断:

0 commit comments

Comments
 (0)