Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
76cfb05
fix: prevent stale cross-window subtask completion
roomote Aug 31, 2026
4040fec
test(task): cover cross-window handoff failures
roomote Sep 3, 2026
c33c78b
test(task): cover remaining handoff guards
roomote Sep 3, 2026
88696fe
refactor(task): keep mutation scope focused
roomote Sep 3, 2026
69b3a93
refactor(task): fit changed-code mutation cap
roomote Sep 3, 2026
e19fb5f
test(task): align real lock concurrency coverage
roomote Sep 3, 2026
0c3a2c1
refactor(task): compose locked delegation transition
roomote Sep 3, 2026
1cc1c86
test(task): expose delegation suites to mutation gate
roomote Sep 3, 2026
fda4280
fix(task): compensate failed delegated handoffs
roomote Sep 3, 2026
2948cc7
refactor(task): keep compensation mutation-focused
roomote Sep 3, 2026
f302147
refactor(task): fit compensated mutation scope
roomote Sep 3, 2026
a500de4
test(task): close changed-code mutation gaps
roomote Sep 3, 2026
a175ef3
refactor(task): make disk guards mutation-visible
roomote Sep 3, 2026
2221f51
test(task): verify cross-host handoff protocol
roomote Sep 4, 2026
3f26b23
fix(task): address latest concurrency review
roomote Sep 7, 2026
b1e92fa
refactor(task): keep reviewed mutation scope bounded
roomote Sep 7, 2026
5815f0b
test(task): cover caller-held lock rollback
roomote Sep 7, 2026
9c354be
fix(task): integrate latest lifecycle persistence
roomote Sep 7, 2026
ca70997
refactor(task): compose latest locked handoff
roomote Sep 7, 2026
7ed632d
test(task): cover latest locked handoff branches
roomote Sep 7, 2026
3b83445
test(task): cover lock failure without recovery hook
roomote Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 31 additions & 22 deletions docs/architecture/task-lifecycle-model.md

Large diffs are not rendered by default.

481 changes: 481 additions & 0 deletions scripts/check-task-store-concurrency.ts

Large diffs are not rendered by default.

21 changes: 16 additions & 5 deletions scripts/stryker-diff.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export const PACKAGE_CONFIGS = [
vitestConfig: "vitest.config.ts",
vitestRelated: false,
discoverRelatedTests: true,
testFilesBySource: {
"core/webview/ClineProvider.ts": [
"__tests__/history-resume-delegation.spec.ts",
"__tests__/provider-delegation.spec.ts",
],
},
excludedPaths: ["src/esbuild.mjs", "src/eslint.config.mjs", "src/utils/vitest-verbosity.ts"],
},
]
Expand Down Expand Up @@ -292,7 +298,7 @@ export function parseVitestTestFiles(report, runRoot) {
]
}

export function preferDirectTestFiles(testFiles, sourceFiles) {
export function preferDirectTestFiles(testFiles, sourceFiles, testFilesBySource = {}) {
const sourceNames = sourceFiles.map((sourceFile) =>
path.posix.basename(sourceFile, path.posix.extname(sourceFile)).toLowerCase(),
)
Expand All @@ -304,10 +310,14 @@ export function preferDirectTestFiles(testFiles, sourceFiles) {
/\.(?:test|spec)(?:\.[^.]+)?\.[cm]?[jt]sx?$/.test(normalizedTestName)
)
}
if (sourceNames.some((sourceName) => !testFiles.some((testFile) => isDirectMatch(testFile, sourceName)))) {
return testFiles
}
return testFiles.filter((testFile) => sourceNames.some((sourceName) => isDirectMatch(testFile, sourceName)))
const hasIndirectSource = sourceNames.some(
(sourceName) => !testFiles.some((testFile) => isDirectMatch(testFile, sourceName)),
)
const selected = hasIndirectSource
? testFiles
: testFiles.filter((testFile) => sourceNames.some((sourceName) => isDirectMatch(testFile, sourceName)))
const configured = sourceFiles.flatMap((sourceFile) => testFilesBySource[sourceFile] ?? [])
return [...new Set([...selected, ...configured])]
}

export function shouldUseVitestRelated(packageEntry) {
Expand Down Expand Up @@ -360,6 +370,7 @@ export function discoverRelatedTestFiles(repoRoot, packageEntry, reportDirectory
const testFiles = preferDirectTestFiles(
parseVitestTestFiles(JSON.parse(fs.readFileSync(outputFile, "utf8")), runRoot),
sourceFiles,
packageEntry.testFilesBySource,
)
if (testFiles.length === 0)
throw new Error(`${packageEntry.id} has no tests related to the changed executable lines`)
Expand Down
23 changes: 22 additions & 1 deletion scripts/stryker-diff.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,28 @@ describe("preferDirectTestFiles", () => {

assert.deepEqual(preferDirectTestFiles(related, ["src/A.ts", "src/B.ts"]), related)
})

it("adds configured suites only for their mutated source and deduplicates them", () => {
const extension = PACKAGE_CONFIGS.find(({ id }) => id === "extension")
const related = [
"core/webview/__tests__/ClineProvider.spec.ts",
"__tests__/history-resume-delegation.spec.ts",
"__tests__/unrelated.spec.ts",
]

assert.deepEqual(
preferDirectTestFiles(related, ["core/webview/ClineProvider.ts"], extension.testFilesBySource),
[
"core/webview/__tests__/ClineProvider.spec.ts",
"__tests__/history-resume-delegation.spec.ts",
"__tests__/provider-delegation.spec.ts",
],
)
assert.deepEqual(
preferDirectTestFiles(related, ["core/webview/OtherProvider.ts"], extension.testFilesBySource),
related,
)
})
})

describe("shouldUseVitestRelated", () => {
Expand All @@ -249,7 +271,6 @@ describe("shouldUseVitestRelated", () => {
})
})


describe("related-test discovery", () => {
it("keeps Stryker's temp directory relative to each run root", () => {
assert.equal(resolveStrykerTempDir("/repo", "/repo"), ".stryker-tmp")
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/delegation-concurrent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ vi.mock("fs", () => ({
}))

vi.mock("../utils/safeWriteJson", () => ({
lockJsonFile: vi.fn().mockResolvedValue(async () => {}),
safeWriteJson: vi.fn().mockResolvedValue(undefined),
}))

Expand Down
9 changes: 8 additions & 1 deletion src/__tests__/helpers/provider-stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ type ProviderStubFields = {
delegationTransitionLocks?: Map<string, Promise<void>>
cancelledDelegationChildIds?: Set<string>
log?: ReturnType<typeof vi.fn>
taskHistoryStore?: { get: (id: string) => unknown }
taskHistoryStore?: {
get: (id: string) => unknown
withTaskFileLock?: <T>(id: string, callback: () => Promise<T>) => Promise<T>
}
taskRegistry?: TaskRegistry
clineStack?: Task[]
tasks?: Task[]
runDelegationTransition?: unknown
runLockedDelegationTransition?: unknown
removeClineFromStack?: unknown
evictCurrentTask?: unknown
}

type PrivateProviderMethods = {
runDelegationTransition: (this: unknown, ...args: unknown[]) => unknown
runLockedDelegationTransition: (this: unknown, ...args: unknown[]) => unknown
removeClineFromStack: (this: unknown, ...args: unknown[]) => unknown
evictCurrentTask: (this: unknown, ...args: unknown[]) => unknown
}
Expand All @@ -38,6 +43,7 @@ export function makeProviderStub<T extends object>(stub: T): ClineProvider {
s.cancelledDelegationChildIds ??= new Set()
s.log ??= vi.fn()
s.taskHistoryStore ??= { get: () => undefined }
s.taskHistoryStore.withTaskFileLock ??= async (_id, callback) => callback()

// Convert legacy clineStack array into a TaskRegistry
if (!s.taskRegistry) {
Expand All @@ -49,6 +55,7 @@ export function makeProviderStub<T extends object>(stub: T): ClineProvider {
delete s.clineStack

s.runDelegationTransition ??= proto.runDelegationTransition.bind(s)
s.runLockedDelegationTransition ??= proto.runLockedDelegationTransition.bind(s)
s.removeClineFromStack ??= proto.removeClineFromStack.bind(s)
s.evictCurrentTask ??= proto.evictCurrentTask.bind(s)
return s as unknown as ClineProvider
Expand Down
Loading
Loading