























@@ -657,6 +657,7 @@ describe("ensureSkillsWatcher", () => {
657657workspaceDir: "/tmp/ws-a",
658658config: { skills: { load: { extraDirs: ["/tmp/shared"], watch: false } } },
659659});
660+seen.length = 0;
660661661662const callPaths = (watchMock.mock.calls as unknown as Array<[string]>).map((call) => call[0]);
662663const sharedIndex = callPaths.findIndex((target) => target.includes("/tmp/shared"));
@@ -673,6 +674,99 @@ describe("ensureSkillsWatcher", () => {
673674expect(seen.some((change) => change.workspaceDir === "/tmp/ws-a")).toBe(false);
674675});
675676677+it("clears workspace version state on watch disable without losing pending invalidation", () => {
678+vi.useFakeTimers();
679+vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
680+const workspaceDir = "/tmp/workspace-version-cleanup";
681+refreshModule.ensureSkillsWatcher({
682+ workspaceDir,
683+config: { skills: { load: { watchDebounceMs: 10 } } },
684+});
685+686+const firstVersion = refreshModule.bumpSkillsSnapshotVersion({
687+ workspaceDir,
688+reason: "watch",
689+changedPath: `${workspaceDir}/skills/demo/SKILL.md`,
690+});
691+refreshModule.ensureSkillsWatcher({
692+ workspaceDir,
693+config: { skills: { load: { watch: false } } },
694+});
695+696+const nextVersion = refreshModule.getSkillsSnapshotVersion(workspaceDir);
697+expect(nextVersion).toBeGreaterThan(firstVersion);
698+expect(refreshModule.shouldRefreshSnapshotForVersion(firstVersion, nextVersion)).toBe(true);
699+vi.setSystemTime(new Date(nextVersion));
700+const followupVersion = refreshModule.bumpSkillsSnapshotVersion({
701+ workspaceDir,
702+reason: "watch",
703+});
704+expect(followupVersion).toBeGreaterThan(nextVersion);
705+});
706+707+it("evicts idle workspace subscriptions on a later ensure call", () => {
708+vi.useFakeTimers();
709+vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
710+const idleWorkspaceDir = "/tmp/workspace-idle";
711+refreshModule.ensureSkillsWatcher({
712+workspaceDir: idleWorkspaceDir,
713+config: { skills: { load: { watchDebounceMs: 10 } } },
714+});
715+const callPaths = (watchMock.mock.calls as unknown as Array<[string]>).map((call) => call[0]);
716+const idleSkillsIndex = callPaths.findIndex(
717+(target) => target === `${idleWorkspaceDir}/skills`,
718+);
719+expect(idleSkillsIndex).toBeGreaterThanOrEqual(0);
720+const firstVersion = refreshModule.bumpSkillsSnapshotVersion({
721+workspaceDir: idleWorkspaceDir,
722+reason: "watch",
723+});
724+725+vi.advanceTimersByTime(60 * 60_000 + 1_000);
726+refreshModule.ensureSkillsWatcher({
727+workspaceDir: "/tmp/workspace-active",
728+config: { skills: { load: { watchDebounceMs: 10 } } },
729+});
730+731+expect(createdWatchers[idleSkillsIndex]?.close).toHaveBeenCalledTimes(1);
732+const evictedVersion = refreshModule.getSkillsSnapshotVersion(idleWorkspaceDir);
733+expect(evictedVersion).toBeGreaterThan(firstVersion);
734+expect(refreshModule.shouldRefreshSnapshotForVersion(firstVersion, evictedVersion)).toBe(true);
735+vi.setSystemTime(new Date(evictedVersion));
736+const followupVersion = refreshModule.bumpSkillsSnapshotVersion({
737+workspaceDir: idleWorkspaceDir,
738+});
739+expect(followupVersion).toBeGreaterThan(evictedVersion);
740+});
741+742+it("keeps refreshed workspace subscriptions within the idle TTL", () => {
743+vi.useFakeTimers();
744+vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
745+const activeWorkspaceDir = "/tmp/workspace-active-refresh";
746+refreshModule.ensureSkillsWatcher({
747+workspaceDir: activeWorkspaceDir,
748+config: { skills: { load: { watchDebounceMs: 10 } } },
749+});
750+const callPaths = (watchMock.mock.calls as unknown as Array<[string]>).map((call) => call[0]);
751+const activeSkillsIndex = callPaths.findIndex(
752+(target) => target === `${activeWorkspaceDir}/skills`,
753+);
754+expect(activeSkillsIndex).toBeGreaterThanOrEqual(0);
755+756+vi.advanceTimersByTime(30 * 60_000);
757+refreshModule.ensureSkillsWatcher({
758+workspaceDir: activeWorkspaceDir,
759+config: { skills: { load: { watchDebounceMs: 10 } } },
760+});
761+vi.advanceTimersByTime(31 * 60_000);
762+refreshModule.ensureSkillsWatcher({
763+workspaceDir: "/tmp/workspace-other",
764+config: { skills: { load: { watchDebounceMs: 10 } } },
765+});
766+767+expect(createdWatchers[activeSkillsIndex]?.close).not.toHaveBeenCalled();
768+});
769+676770it("rebuilds a shared watcher with last-writer debounce while preserving subscribers", async () => {
677771vi.useFakeTimers();
678772const seen: SkillsChangeEvent[] = [];
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。