Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cbb0d90
fix: prevent stale cross-window subtask completion
roomote Aug 31, 2026
a7841a6
test(task): cover cross-window handoff failures
roomote Sep 3, 2026
7ac69d6
test(task): cover remaining handoff guards
roomote Sep 3, 2026
75aa0a7
refactor(task): keep mutation scope focused
roomote Sep 3, 2026
6f7e910
refactor(task): fit changed-code mutation cap
roomote Sep 3, 2026
cbbe885
test(task): align real lock concurrency coverage
roomote Sep 3, 2026
a05054d
refactor(task): compose locked delegation transition
roomote Sep 3, 2026
9fab85b
test(task): expose delegation suites to mutation gate
roomote Sep 3, 2026
b8e8300
fix(task): compensate failed delegated handoffs
roomote Sep 3, 2026
d7c07d5
refactor(task): keep compensation mutation-focused
roomote Sep 3, 2026
89de317
refactor(task): fit compensated mutation scope
roomote Sep 3, 2026
9ea959e
test(task): close changed-code mutation gaps
roomote Sep 3, 2026
669726e
refactor(task): make disk guards mutation-visible
roomote Sep 3, 2026
86ef94d
test(task): verify cross-host handoff protocol
roomote Sep 4, 2026
2f27b8d
fix(task): address latest concurrency review
roomote Sep 7, 2026
1b1de95
refactor(task): keep reviewed mutation scope bounded
roomote Sep 7, 2026
f315216
test(task): cover caller-held lock rollback
roomote Sep 7, 2026
fb2a8ce
fix(task): integrate latest lifecycle persistence
roomote Sep 7, 2026
ab14cbd
refactor(task): compose latest locked handoff
roomote Sep 7, 2026
99f2a8d
test(task): cover latest locked handoff branches
roomote Sep 7, 2026
124ce68
test(task): cover lock failure without recovery hook
roomote Sep 7, 2026
befd4b8
fix(task): retain backup after lock compromise
roomote Sep 10, 2026
9cf9cca
refactor(task): keep compromised backup guard narrow
roomote Sep 10, 2026
5a08c1d
refactor(task): minimize retained backup path
roomote Sep 10, 2026
071304f
refactor(task): log retained backup compactly
roomote Sep 10, 2026
8ef1483
fix(task): retain backup after rollback failure
roomote Sep 10, 2026
76e1334
test(task): verify delegated child startup
roomote Sep 10, 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