





























@@ -1,11 +1,34 @@
11import os from "node:os";
22import path from "node:path";
33import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4+import type { SkillsChangeEvent } from "./refresh.js";
455-const watchMock = vi.fn(() => ({
6-on: vi.fn(),
7-close: vi.fn(async () => undefined),
8-}));
6+type WatchEvent = "add" | "change" | "unlink" | "unlinkDir" | "error";
7+type WatchCallback = (watchPath: string) => void;
8+9+function createMockWatcher() {
10+const handlers = new Map<WatchEvent, WatchCallback[]>();
11+const watcher = {
12+on: vi.fn((event: WatchEvent, callback: WatchCallback) => {
13+handlers.set(event, [...(handlers.get(event) ?? []), callback]);
14+return watcher;
15+}),
16+close: vi.fn(async () => undefined),
17+emit: (event: WatchEvent, watchPath: string) => {
18+for (const callback of handlers.get(event) ?? []) {
19+callback(watchPath);
20+}
21+},
22+};
23+return watcher;
24+}
25+26+const createdWatchers: Array<ReturnType<typeof createMockWatcher>> = [];
27+const watchMock = vi.fn(() => {
28+const watcher = createMockWatcher();
29+createdWatchers.push(watcher);
30+return watcher;
31+});
9321033let refreshModule: typeof import("./refresh.js");
1134@@ -24,13 +47,15 @@ describe("ensureSkillsWatcher", () => {
24472548beforeEach(() => {
2649watchMock.mockClear();
50+createdWatchers.length = 0;
2751});
28522953afterEach(async () => {
54+vi.useRealTimers();
3055await refreshModule.resetSkillsRefreshForTest();
3156});
325733-it("ignores node_modules, dist, .git, and Python venvs by default", async () => {
58+it("watches skill roots and filters non-skill churn", async () => {
3459refreshModule.ensureSkillsWatcher({ workspaceDir: "/tmp/workspace" });
35603661expect(watchMock).toHaveBeenCalledTimes(1);
@@ -40,49 +65,64 @@ describe("ensureSkillsWatcher", () => {
4065const targets = firstCall?.[0] ?? [];
4166const opts = firstCall?.[1] ?? {};
426743-expect(opts.ignored).toBe(refreshModule.DEFAULT_SKILLS_WATCH_IGNORED);
68+expect(opts.ignored).toBe(refreshModule.shouldIgnoreSkillsWatchPath);
4469const posix = (p: string) => p.replaceAll("\\", "/");
4570expect(targets).toEqual(
4671expect.arrayContaining([
47-posix(path.join("/tmp/workspace", "skills", "SKILL.md")),
48-posix(path.join("/tmp/workspace", "skills", "*", "SKILL.md")),
49-posix(path.join("/tmp/workspace", ".agents", "skills", "SKILL.md")),
50-posix(path.join("/tmp/workspace", ".agents", "skills", "*", "SKILL.md")),
51-posix(path.join(os.homedir(), ".agents", "skills", "SKILL.md")),
52-posix(path.join(os.homedir(), ".agents", "skills", "*", "SKILL.md")),
72+posix(path.join("/tmp/workspace", "skills")),
73+posix(path.join("/tmp/workspace", ".agents", "skills")),
74+posix(path.join(os.homedir(), ".agents", "skills")),
5375]),
5476);
55-expect(targets.every((target) => target.includes("SKILL.md"))).toBe(true);
56-const ignored = refreshModule.DEFAULT_SKILLS_WATCH_IGNORED;
77+expect(targets.every((target) => !target.includes("*"))).toBe(true);
78+const ignored = refreshModule.shouldIgnoreSkillsWatchPath;
57795880// Node/JS paths
59-expect(ignored.some((re) => re.test("/tmp/workspace/skills/node_modules/pkg/index.js"))).toBe(
60-true,
61-);
62-expect(ignored.some((re) => re.test("/tmp/workspace/skills/dist/index.js"))).toBe(true);
63-expect(ignored.some((re) => re.test("/tmp/workspace/skills/.git/config"))).toBe(true);
81+expect(ignored("/tmp/workspace/skills/node_modules/pkg/index.js")).toBe(true);
82+expect(ignored("/tmp/workspace/skills/dist/index.js")).toBe(true);
83+expect(ignored("/tmp/workspace/skills/.git/config")).toBe(true);
64846585// Python virtual environments and caches
66-expect(ignored.some((re) => re.test("/tmp/workspace/skills/scripts/.venv/bin/python"))).toBe(
67-true,
68-);
69-expect(ignored.some((re) => re.test("/tmp/workspace/skills/venv/lib/python3.10/site.py"))).toBe(
70-true,
71-);
72-expect(ignored.some((re) => re.test("/tmp/workspace/skills/__pycache__/module.pyc"))).toBe(
73-true,
74-);
75-expect(ignored.some((re) => re.test("/tmp/workspace/skills/.mypy_cache/3.10/foo.json"))).toBe(
76-true,
77-);
78-expect(ignored.some((re) => re.test("/tmp/workspace/skills/.pytest_cache/v/cache"))).toBe(true);
86+expect(ignored("/tmp/workspace/skills/scripts/.venv/bin/python")).toBe(true);
87+expect(ignored("/tmp/workspace/skills/venv/lib/python3.10/site.py")).toBe(true);
88+expect(ignored("/tmp/workspace/skills/__pycache__/module.pyc")).toBe(true);
89+expect(ignored("/tmp/workspace/skills/.mypy_cache/3.10/foo.json")).toBe(true);
90+expect(ignored("/tmp/workspace/skills/.pytest_cache/v/cache")).toBe(true);
79918092// Build artifacts and caches
81-expect(ignored.some((re) => re.test("/tmp/workspace/skills/build/output.js"))).toBe(true);
82-expect(ignored.some((re) => re.test("/tmp/workspace/skills/.cache/data.json"))).toBe(true);
93+expect(ignored("/tmp/workspace/skills/build/output.js")).toBe(true);
94+expect(ignored("/tmp/workspace/skills/.cache/data.json")).toBe(true);
83958496// Should NOT ignore normal skill files
85-expect(ignored.some((re) => re.test("/tmp/.hidden/skills/index.md"))).toBe(false);
86-expect(ignored.some((re) => re.test("/tmp/workspace/skills/my-skill/SKILL.md"))).toBe(false);
97+expect(ignored("/tmp/.hidden/skills/index.md")).toBe(false);
98+expect(ignored("/tmp/workspace/skills/my-skill", { isDirectory: () => true })).toBe(false);
99+expect(ignored("/tmp/workspace/skills/my-skill/README.md", {})).toBe(true);
100+expect(ignored("/tmp/workspace/skills/my-skill/SKILL.md", {})).toBe(false);
87101});
102+103+it.each(["add", "change", "unlink", "unlinkDir"] as const)(
104+"refreshes skills snapshots on %s",
105+async (event) => {
106+vi.useFakeTimers();
107+const seen: SkillsChangeEvent[] = [];
108+refreshModule.registerSkillsChangeListener((change) => {
109+seen.push(change);
110+});
111+refreshModule.ensureSkillsWatcher({
112+workspaceDir: "/tmp/workspace",
113+config: { skills: { load: { watchDebounceMs: 10 } } },
114+});
115+116+createdWatchers[0]?.emit(event, "/tmp/workspace/skills/demo/SKILL.md");
117+await vi.advanceTimersByTimeAsync(10);
118+119+expect(seen).toEqual([
120+{
121+workspaceDir: "/tmp/workspace",
122+reason: "watch",
123+changedPath: "/tmp/workspace/skills/demo/SKILL.md",
124+},
125+]);
126+},
127+);
88128});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。