vskill 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agents-registry.test.d.ts +1 -0
- package/dist/agents/agents-registry.test.js +248 -0
- package/dist/agents/agents-registry.test.js.map +1 -0
- package/dist/api/client.test.d.ts +1 -0
- package/dist/api/client.test.js +428 -0
- package/dist/api/client.test.js.map +1 -0
- package/dist/audit/audit-integration.test.d.ts +1 -0
- package/dist/audit/audit-integration.test.js +92 -0
- package/dist/audit/audit-integration.test.js.map +1 -0
- package/dist/audit/audit-llm.test.d.ts +1 -0
- package/dist/audit/audit-llm.test.js +110 -0
- package/dist/audit/audit-llm.test.js.map +1 -0
- package/dist/audit/audit-patterns.test.d.ts +1 -0
- package/dist/audit/audit-patterns.test.js +91 -0
- package/dist/audit/audit-patterns.test.js.map +1 -0
- package/dist/audit/audit-scanner.test.d.ts +1 -0
- package/dist/audit/audit-scanner.test.js +112 -0
- package/dist/audit/audit-scanner.test.js.map +1 -0
- package/dist/audit/audit-types.test.d.ts +1 -0
- package/dist/audit/audit-types.test.js +140 -0
- package/dist/audit/audit-types.test.js.map +1 -0
- package/dist/audit/config.test.d.ts +1 -0
- package/dist/audit/config.test.js +44 -0
- package/dist/audit/config.test.js.map +1 -0
- package/dist/audit/file-discovery.test.d.ts +1 -0
- package/dist/audit/file-discovery.test.js +120 -0
- package/dist/audit/file-discovery.test.js.map +1 -0
- package/dist/audit/fix-suggestions.test.d.ts +1 -0
- package/dist/audit/fix-suggestions.test.js +35 -0
- package/dist/audit/fix-suggestions.test.js.map +1 -0
- package/dist/audit/formatters/json-formatter.test.d.ts +1 -0
- package/dist/audit/formatters/json-formatter.test.js +49 -0
- package/dist/audit/formatters/json-formatter.test.js.map +1 -0
- package/dist/audit/formatters/report-formatter.test.d.ts +1 -0
- package/dist/audit/formatters/report-formatter.test.js +51 -0
- package/dist/audit/formatters/report-formatter.test.js.map +1 -0
- package/dist/audit/formatters/sarif-formatter.test.d.ts +1 -0
- package/dist/audit/formatters/sarif-formatter.test.js +71 -0
- package/dist/audit/formatters/sarif-formatter.test.js.map +1 -0
- package/dist/audit/formatters/terminal-formatter.test.d.ts +1 -0
- package/dist/audit/formatters/terminal-formatter.test.js +51 -0
- package/dist/audit/formatters/terminal-formatter.test.js.map +1 -0
- package/dist/blocklist/blocklist-e2e.test.d.ts +1 -0
- package/dist/blocklist/blocklist-e2e.test.js +346 -0
- package/dist/blocklist/blocklist-e2e.test.js.map +1 -0
- package/dist/blocklist/blocklist.test.d.ts +1 -0
- package/dist/blocklist/blocklist.test.js +259 -0
- package/dist/blocklist/blocklist.test.js.map +1 -0
- package/dist/commands/__tests__/eval-router.test.d.ts +1 -0
- package/dist/commands/__tests__/eval-router.test.js +60 -0
- package/dist/commands/__tests__/eval-router.test.js.map +1 -0
- package/dist/commands/__tests__/eval-serve.test.d.ts +1 -0
- package/dist/commands/__tests__/eval-serve.test.js +23 -0
- package/dist/commands/__tests__/eval-serve.test.js.map +1 -0
- package/dist/commands/add-blocklist-e2e.test.d.ts +1 -0
- package/dist/commands/add-blocklist-e2e.test.js +397 -0
- package/dist/commands/add-blocklist-e2e.test.js.map +1 -0
- package/dist/commands/add-wizard.test.d.ts +1 -0
- package/dist/commands/add-wizard.test.js +392 -0
- package/dist/commands/add-wizard.test.js.map +1 -0
- package/dist/commands/add.test.d.ts +1 -0
- package/dist/commands/add.test.js +2365 -0
- package/dist/commands/add.test.js.map +1 -0
- package/dist/commands/audit.test.d.ts +1 -0
- package/dist/commands/audit.test.js +79 -0
- package/dist/commands/audit.test.js.map +1 -0
- package/dist/commands/blocklist.test.d.ts +1 -0
- package/dist/commands/blocklist.test.js +158 -0
- package/dist/commands/blocklist.test.js.map +1 -0
- package/dist/commands/eval/__tests__/coverage.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/coverage.test.js +122 -0
- package/dist/commands/eval/__tests__/coverage.test.js.map +1 -0
- package/dist/commands/eval/__tests__/generate-all.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/generate-all.test.js +133 -0
- package/dist/commands/eval/__tests__/generate-all.test.js.map +1 -0
- package/dist/commands/eval/__tests__/init.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/init.test.js +116 -0
- package/dist/commands/eval/__tests__/init.test.js.map +1 -0
- package/dist/commands/eval/__tests__/run.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/run.test.js +186 -0
- package/dist/commands/eval/__tests__/run.test.js.map +1 -0
- package/dist/commands/find.test.d.ts +1 -0
- package/dist/commands/find.test.js +481 -0
- package/dist/commands/find.test.js.map +1 -0
- package/dist/commands/marketplace.test.d.ts +1 -0
- package/dist/commands/marketplace.test.js +129 -0
- package/dist/commands/marketplace.test.js.map +1 -0
- package/dist/commands/remove.test.d.ts +1 -0
- package/dist/commands/remove.test.js +164 -0
- package/dist/commands/remove.test.js.map +1 -0
- package/dist/commands/should-skip.test.d.ts +1 -0
- package/dist/commands/should-skip.test.js +56 -0
- package/dist/commands/should-skip.test.js.map +1 -0
- package/dist/commands/submit.test.d.ts +1 -0
- package/dist/commands/submit.test.js +83 -0
- package/dist/commands/submit.test.js.map +1 -0
- package/dist/commands/update.test.d.ts +1 -0
- package/dist/commands/update.test.js +250 -0
- package/dist/commands/update.test.js.map +1 -0
- package/dist/discovery/github-tree.test.d.ts +1 -0
- package/dist/discovery/github-tree.test.js +372 -0
- package/dist/discovery/github-tree.test.js.map +1 -0
- package/dist/eval/__tests__/activation-tester.test.d.ts +1 -0
- package/dist/eval/__tests__/activation-tester.test.js +203 -0
- package/dist/eval/__tests__/activation-tester.test.js.map +1 -0
- package/dist/eval/__tests__/benchmark-history.test.d.ts +1 -0
- package/dist/eval/__tests__/benchmark-history.test.js +422 -0
- package/dist/eval/__tests__/benchmark-history.test.js.map +1 -0
- package/dist/eval/__tests__/benchmark.test.d.ts +1 -0
- package/dist/eval/__tests__/benchmark.test.js +94 -0
- package/dist/eval/__tests__/benchmark.test.js.map +1 -0
- package/dist/eval/__tests__/comparator.test.d.ts +1 -0
- package/dist/eval/__tests__/comparator.test.js +282 -0
- package/dist/eval/__tests__/comparator.test.js.map +1 -0
- package/dist/eval/__tests__/judge.test.d.ts +1 -0
- package/dist/eval/__tests__/judge.test.js +122 -0
- package/dist/eval/__tests__/judge.test.js.map +1 -0
- package/dist/eval/__tests__/llm.test.d.ts +1 -0
- package/dist/eval/__tests__/llm.test.js +543 -0
- package/dist/eval/__tests__/llm.test.js.map +1 -0
- package/dist/eval/__tests__/mcp-detector.test.d.ts +1 -0
- package/dist/eval/__tests__/mcp-detector.test.js +180 -0
- package/dist/eval/__tests__/mcp-detector.test.js.map +1 -0
- package/dist/eval/__tests__/prompt-builder.test.d.ts +1 -0
- package/dist/eval/__tests__/prompt-builder.test.js +142 -0
- package/dist/eval/__tests__/prompt-builder.test.js.map +1 -0
- package/dist/eval/__tests__/schema.test.d.ts +1 -0
- package/dist/eval/__tests__/schema.test.js +247 -0
- package/dist/eval/__tests__/schema.test.js.map +1 -0
- package/dist/eval/__tests__/skill-scanner.test.d.ts +1 -0
- package/dist/eval/__tests__/skill-scanner.test.js +228 -0
- package/dist/eval/__tests__/skill-scanner.test.js.map +1 -0
- package/dist/eval/__tests__/verdict.test.d.ts +1 -0
- package/dist/eval/__tests__/verdict.test.js +47 -0
- package/dist/eval/__tests__/verdict.test.js.map +1 -0
- package/dist/eval-server/__tests__/benchmark-runner.test.d.ts +1 -0
- package/dist/eval-server/__tests__/benchmark-runner.test.js +301 -0
- package/dist/eval-server/__tests__/benchmark-runner.test.js.map +1 -0
- package/dist/eval-server/__tests__/comparison-sse-events.test.d.ts +1 -0
- package/dist/eval-server/__tests__/comparison-sse-events.test.js +278 -0
- package/dist/eval-server/__tests__/comparison-sse-events.test.js.map +1 -0
- package/dist/eval-server/__tests__/sse-helpers.test.d.ts +1 -0
- package/dist/eval-server/__tests__/sse-helpers.test.js +128 -0
- package/dist/eval-server/__tests__/sse-helpers.test.js.map +1 -0
- package/dist/installer/canonical.test.d.ts +1 -0
- package/dist/installer/canonical.test.js +264 -0
- package/dist/installer/canonical.test.js.map +1 -0
- package/dist/lockfile/lockfile.test.d.ts +1 -0
- package/dist/lockfile/lockfile.test.js +204 -0
- package/dist/lockfile/lockfile.test.js.map +1 -0
- package/dist/lockfile/project-root.test.d.ts +1 -0
- package/dist/lockfile/project-root.test.js +49 -0
- package/dist/lockfile/project-root.test.js.map +1 -0
- package/dist/marketplace/marketplace.test.d.ts +1 -0
- package/dist/marketplace/marketplace.test.js +312 -0
- package/dist/marketplace/marketplace.test.js.map +1 -0
- package/dist/resolvers/source-resolver.test.d.ts +1 -0
- package/dist/resolvers/source-resolver.test.js +104 -0
- package/dist/resolvers/source-resolver.test.js.map +1 -0
- package/dist/resolvers/url-resolver.test.d.ts +1 -0
- package/dist/resolvers/url-resolver.test.js +49 -0
- package/dist/resolvers/url-resolver.test.js.map +1 -0
- package/dist/scanner/dci-integration.test.d.ts +1 -0
- package/dist/scanner/dci-integration.test.js +83 -0
- package/dist/scanner/dci-integration.test.js.map +1 -0
- package/dist/scanner/patterns.test.d.ts +1 -0
- package/dist/scanner/patterns.test.js +832 -0
- package/dist/scanner/patterns.test.js.map +1 -0
- package/dist/scanner/tier1.test.d.ts +1 -0
- package/dist/scanner/tier1.test.js +305 -0
- package/dist/scanner/tier1.test.js.map +1 -0
- package/dist/security/platform-security.test.d.ts +1 -0
- package/dist/security/platform-security.test.js +92 -0
- package/dist/security/platform-security.test.js.map +1 -0
- package/dist/settings/settings.test.d.ts +1 -0
- package/dist/settings/settings.test.js +103 -0
- package/dist/settings/settings.test.js.map +1 -0
- package/dist/updater/source-fetcher.test.d.ts +1 -0
- package/dist/updater/source-fetcher.test.js +192 -0
- package/dist/updater/source-fetcher.test.js.map +1 -0
- package/dist/utils/__tests__/paths.test.d.ts +1 -0
- package/dist/utils/__tests__/paths.test.js +22 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -0
- package/dist/utils/__tests__/resolve-binary.integration.test.d.ts +1 -0
- package/dist/utils/__tests__/resolve-binary.integration.test.js +138 -0
- package/dist/utils/__tests__/resolve-binary.integration.test.js.map +1 -0
- package/dist/utils/__tests__/resolve-binary.test.d.ts +1 -0
- package/dist/utils/__tests__/resolve-binary.test.js +175 -0
- package/dist/utils/__tests__/resolve-binary.test.js.map +1 -0
- package/dist/utils/__tests__/validation.test.d.ts +1 -0
- package/dist/utils/__tests__/validation.test.js +107 -0
- package/dist/utils/__tests__/validation.test.js.map +1 -0
- package/dist/utils/agent-filter.test.d.ts +1 -0
- package/dist/utils/agent-filter.test.js +75 -0
- package/dist/utils/agent-filter.test.js.map +1 -0
- package/dist/utils/output.test.d.ts +1 -0
- package/dist/utils/output.test.js +28 -0
- package/dist/utils/output.test.js.map +1 -0
- package/dist/utils/project-root.test.d.ts +1 -0
- package/dist/utils/project-root.test.js +74 -0
- package/dist/utils/project-root.test.js.map +1 -0
- package/dist/utils/prompts.test.d.ts +1 -0
- package/dist/utils/prompts.test.js +285 -0
- package/dist/utils/prompts.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Mock node:fs
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const mockMkdirSync = vi.hoisted(() => vi.fn());
|
|
6
|
+
const mockWriteFileSync = vi.hoisted(() => vi.fn());
|
|
7
|
+
vi.mock("node:fs", () => ({
|
|
8
|
+
mkdirSync: (...args) => mockMkdirSync(...args),
|
|
9
|
+
writeFileSync: (...args) => mockWriteFileSync(...args),
|
|
10
|
+
}));
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Mock lockfile
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const mockReadLockfile = vi.hoisted(() => vi.fn());
|
|
15
|
+
const mockWriteLockfile = vi.hoisted(() => vi.fn());
|
|
16
|
+
vi.mock("../lockfile/index.js", () => ({
|
|
17
|
+
readLockfile: (...args) => mockReadLockfile(...args),
|
|
18
|
+
writeLockfile: (...args) => mockWriteLockfile(...args),
|
|
19
|
+
}));
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Mock API client (registry fallback)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const mockGetSkill = vi.hoisted(() => vi.fn());
|
|
24
|
+
vi.mock("../api/client.js", () => ({
|
|
25
|
+
getSkill: (...args) => mockGetSkill(...args),
|
|
26
|
+
}));
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Mock source-aware fetcher
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const mockFetchFromSource = vi.hoisted(() => vi.fn());
|
|
31
|
+
vi.mock("../updater/source-fetcher.js", () => ({
|
|
32
|
+
fetchFromSource: (...args) => mockFetchFromSource(...args),
|
|
33
|
+
}));
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Mock agents registry
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
const mockDetectInstalledAgents = vi.hoisted(() => vi.fn());
|
|
38
|
+
vi.mock("../agents/agents-registry.js", () => ({
|
|
39
|
+
detectInstalledAgents: (...args) => mockDetectInstalledAgents(...args),
|
|
40
|
+
}));
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Mock scanner
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
const mockRunTier1Scan = vi.hoisted(() => vi.fn().mockReturnValue({ verdict: "PASS", score: 100, findings: [] }));
|
|
45
|
+
vi.mock("../scanner/index.js", () => ({
|
|
46
|
+
runTier1Scan: (...args) => mockRunTier1Scan(...args),
|
|
47
|
+
}));
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Mock output (suppress console noise)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
vi.mock("../utils/output.js", () => ({
|
|
52
|
+
bold: (s) => s,
|
|
53
|
+
green: (s) => s,
|
|
54
|
+
red: (s) => s,
|
|
55
|
+
yellow: (s) => s,
|
|
56
|
+
dim: (s) => s,
|
|
57
|
+
cyan: (s) => s,
|
|
58
|
+
spinner: () => ({ stop: vi.fn() }),
|
|
59
|
+
}));
|
|
60
|
+
const MOCK_AGENTS = [
|
|
61
|
+
{
|
|
62
|
+
id: "claude-code",
|
|
63
|
+
displayName: "Claude Code",
|
|
64
|
+
localSkillsDir: ".claude/skills",
|
|
65
|
+
globalSkillsDir: "~/.claude/skills",
|
|
66
|
+
isUniversal: false,
|
|
67
|
+
detectInstalled: "which claude",
|
|
68
|
+
parentCompany: "Anthropic",
|
|
69
|
+
featureSupport: { slashCommands: true, hooks: true, mcp: true, customSystemPrompt: true },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "cursor",
|
|
73
|
+
displayName: "Cursor",
|
|
74
|
+
localSkillsDir: ".cursor/skills",
|
|
75
|
+
globalSkillsDir: "~/.cursor/skills",
|
|
76
|
+
isUniversal: true,
|
|
77
|
+
detectInstalled: "which cursor",
|
|
78
|
+
parentCompany: "Anysphere",
|
|
79
|
+
featureSupport: { slashCommands: true, hooks: false, mcp: true, customSystemPrompt: true },
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
const UPDATED_CONTENT = "# Updated Skill";
|
|
83
|
+
const UPDATED_FETCH_RESULT = {
|
|
84
|
+
content: UPDATED_CONTENT,
|
|
85
|
+
version: "2.0.0",
|
|
86
|
+
sha: "new-sha-12345",
|
|
87
|
+
tier: "VERIFIED",
|
|
88
|
+
};
|
|
89
|
+
describe("updateCommand", () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
vi.clearAllMocks();
|
|
92
|
+
mockDetectInstalledAgents.mockResolvedValue(MOCK_AGENTS);
|
|
93
|
+
mockReadLockfile.mockReturnValue({
|
|
94
|
+
version: 1,
|
|
95
|
+
agents: ["claude-code", "cursor"],
|
|
96
|
+
skills: {
|
|
97
|
+
frontend: {
|
|
98
|
+
version: "1.0.0",
|
|
99
|
+
sha: "aaa111bbb222",
|
|
100
|
+
tier: "VERIFIED",
|
|
101
|
+
installedAt: "2026-01-01T00:00:00.000Z",
|
|
102
|
+
source: "github:test/repo",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
106
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
107
|
+
});
|
|
108
|
+
// Source-aware fetcher returns updated result by default
|
|
109
|
+
mockFetchFromSource.mockResolvedValue(UPDATED_FETCH_RESULT);
|
|
110
|
+
// Registry fallback (used when fetchFromSource returns null)
|
|
111
|
+
mockGetSkill.mockResolvedValue({
|
|
112
|
+
content: UPDATED_CONTENT,
|
|
113
|
+
version: "2.0.0",
|
|
114
|
+
sha: "new-sha-12345",
|
|
115
|
+
tier: "VERIFIED",
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it("updates only the filtered agent when --agent is provided", async () => {
|
|
119
|
+
const { updateCommand } = await import("./update.js");
|
|
120
|
+
await updateCommand("frontend", { all: false, agent: "claude-code" });
|
|
121
|
+
// Should only write to claude-code's skill dir, NOT cursor's
|
|
122
|
+
const mkdirCalls = mockMkdirSync.mock.calls.map((c) => c[0]);
|
|
123
|
+
const claudeCalls = mkdirCalls.filter((p) => p.includes(".claude"));
|
|
124
|
+
const cursorCalls = mkdirCalls.filter((p) => p.includes(".cursor"));
|
|
125
|
+
expect(claudeCalls.length).toBeGreaterThan(0);
|
|
126
|
+
expect(cursorCalls.length).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
it("updates all detected agents when --agent is NOT provided", async () => {
|
|
129
|
+
const { updateCommand } = await import("./update.js");
|
|
130
|
+
await updateCommand("frontend", { all: false });
|
|
131
|
+
// Should write to BOTH agent dirs
|
|
132
|
+
const mkdirCalls = mockMkdirSync.mock.calls.map((c) => c[0]);
|
|
133
|
+
const claudeCalls = mkdirCalls.filter((p) => p.includes(".claude"));
|
|
134
|
+
const cursorCalls = mkdirCalls.filter((p) => p.includes(".cursor"));
|
|
135
|
+
expect(claudeCalls.length).toBeGreaterThan(0);
|
|
136
|
+
expect(cursorCalls.length).toBeGreaterThan(0);
|
|
137
|
+
});
|
|
138
|
+
it("exits with error when --agent specifies unknown ID", async () => {
|
|
139
|
+
const mockExit = vi.spyOn(process, "exit").mockImplementation(() => {
|
|
140
|
+
throw new Error("process.exit");
|
|
141
|
+
});
|
|
142
|
+
const { updateCommand } = await import("./update.js");
|
|
143
|
+
await expect(updateCommand("frontend", { all: false, agent: "nonexistent" })).rejects.toThrow("process.exit");
|
|
144
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
145
|
+
mockExit.mockRestore();
|
|
146
|
+
});
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Source-aware routing tests
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
it("uses fetchFromSource instead of getSkill for non-registry sources", async () => {
|
|
151
|
+
const { updateCommand } = await import("./update.js");
|
|
152
|
+
await updateCommand("frontend", { all: false });
|
|
153
|
+
expect(mockFetchFromSource).toHaveBeenCalled();
|
|
154
|
+
// getSkill should NOT be called because fetchFromSource returned a result
|
|
155
|
+
expect(mockGetSkill).not.toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
it("falls back to getSkill when fetchFromSource returns null for unknown source", async () => {
|
|
158
|
+
mockReadLockfile.mockReturnValue({
|
|
159
|
+
version: 1,
|
|
160
|
+
agents: ["claude-code"],
|
|
161
|
+
skills: {
|
|
162
|
+
frontend: {
|
|
163
|
+
version: "1.0.0",
|
|
164
|
+
sha: "aaa111bbb222",
|
|
165
|
+
tier: "VERIFIED",
|
|
166
|
+
installedAt: "2026-01-01T00:00:00.000Z",
|
|
167
|
+
source: "", // empty → unknown type → fallback to registry
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
171
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
172
|
+
});
|
|
173
|
+
mockFetchFromSource.mockResolvedValue(null);
|
|
174
|
+
const { updateCommand } = await import("./update.js");
|
|
175
|
+
await updateCommand("frontend", { all: false });
|
|
176
|
+
expect(mockGetSkill).toHaveBeenCalledWith("frontend");
|
|
177
|
+
expect(mockWriteFileSync).toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
it("skips skill and does not call getSkill for local source when fetchFromSource returns null", async () => {
|
|
180
|
+
mockReadLockfile.mockReturnValue({
|
|
181
|
+
version: 1,
|
|
182
|
+
agents: ["claude-code"],
|
|
183
|
+
skills: {
|
|
184
|
+
sw: {
|
|
185
|
+
version: "1.0.0",
|
|
186
|
+
sha: "aaa111bbb222",
|
|
187
|
+
tier: "COMMUNITY",
|
|
188
|
+
installedAt: "2026-01-01T00:00:00.000Z",
|
|
189
|
+
source: "local:specweave",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
193
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
194
|
+
});
|
|
195
|
+
mockFetchFromSource.mockResolvedValue(null);
|
|
196
|
+
const { updateCommand } = await import("./update.js");
|
|
197
|
+
await updateCommand("sw", { all: false });
|
|
198
|
+
// local sources: no registry fallback, no file writes
|
|
199
|
+
expect(mockGetSkill).not.toHaveBeenCalled();
|
|
200
|
+
expect(mockWriteFileSync).not.toHaveBeenCalled();
|
|
201
|
+
});
|
|
202
|
+
it("skips skill when SHA is unchanged", async () => {
|
|
203
|
+
// fetchFromSource returns same SHA as lockfile
|
|
204
|
+
mockFetchFromSource.mockResolvedValue({
|
|
205
|
+
content: "# Same content",
|
|
206
|
+
version: "1.0.0",
|
|
207
|
+
sha: "aaa111bbb222", // same as lockfile entry
|
|
208
|
+
tier: "VERIFIED",
|
|
209
|
+
});
|
|
210
|
+
const { updateCommand } = await import("./update.js");
|
|
211
|
+
await updateCommand("frontend", { all: false });
|
|
212
|
+
expect(mockWriteFileSync).not.toHaveBeenCalled();
|
|
213
|
+
expect(mockWriteLockfile).toHaveBeenCalledTimes(1); // still writes lockfile
|
|
214
|
+
});
|
|
215
|
+
it("runs tier1 scan on fetched content and skips on FAIL verdict", async () => {
|
|
216
|
+
mockRunTier1Scan.mockReturnValue({ verdict: "FAIL", score: 0, findings: ["malicious"] });
|
|
217
|
+
const { updateCommand } = await import("./update.js");
|
|
218
|
+
await updateCommand("frontend", { all: false });
|
|
219
|
+
expect(mockRunTier1Scan).toHaveBeenCalledWith(UPDATED_CONTENT);
|
|
220
|
+
expect(mockWriteFileSync).not.toHaveBeenCalled();
|
|
221
|
+
});
|
|
222
|
+
it("preserves source field in lockfile after update", async () => {
|
|
223
|
+
const { updateCommand } = await import("./update.js");
|
|
224
|
+
await updateCommand("frontend", { all: false });
|
|
225
|
+
const writtenLock = mockWriteLockfile.mock.calls[0][0];
|
|
226
|
+
expect(writtenLock.skills["frontend"].source).toBe("github:test/repo");
|
|
227
|
+
});
|
|
228
|
+
it("writes lockfile exactly once after the loop", async () => {
|
|
229
|
+
mockReadLockfile.mockReturnValue({
|
|
230
|
+
version: 1,
|
|
231
|
+
agents: ["claude-code"],
|
|
232
|
+
skills: {
|
|
233
|
+
frontend: {
|
|
234
|
+
version: "1.0.0", sha: "aaa111bbb222", tier: "VERIFIED",
|
|
235
|
+
installedAt: "2026-01-01T00:00:00.000Z", source: "github:test/repo",
|
|
236
|
+
},
|
|
237
|
+
backend: {
|
|
238
|
+
version: "1.0.0", sha: "bbb222ccc333", tier: "VERIFIED",
|
|
239
|
+
installedAt: "2026-01-01T00:00:00.000Z", source: "registry:backend",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
243
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
244
|
+
});
|
|
245
|
+
const { updateCommand } = await import("./update.js");
|
|
246
|
+
await updateCommand(undefined, { all: true });
|
|
247
|
+
expect(mockWriteLockfile).toHaveBeenCalledTimes(1);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
//# sourceMappingURL=update.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.test.js","sourceRoot":"","sources":["../../src/commands/update.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Import module under test
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
import { discoverSkills, extractDescription, getDefaultBranch, _resetBranchCache, warnRateLimitOnce, _resetRateLimitWarned, } from "./github-tree.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function makeTreeResponse(paths) {
|
|
10
|
+
return {
|
|
11
|
+
tree: paths.map((path) => ({
|
|
12
|
+
path,
|
|
13
|
+
mode: "100644",
|
|
14
|
+
type: "blob",
|
|
15
|
+
sha: "abc123",
|
|
16
|
+
size: 100,
|
|
17
|
+
})),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function makeBranchResponse(branch = "main") {
|
|
21
|
+
return { ok: true, json: async () => ({ default_branch: branch }) };
|
|
22
|
+
}
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Tests
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
describe("discoverSkills", () => {
|
|
27
|
+
const originalFetch = globalThis.fetch;
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
globalThis.fetch = originalFetch;
|
|
30
|
+
_resetBranchCache();
|
|
31
|
+
});
|
|
32
|
+
// TC-001: Discovers root SKILL.md and skills/*/SKILL.md
|
|
33
|
+
it("discovers root SKILL.md and skills/*/SKILL.md from tree response", async () => {
|
|
34
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
35
|
+
ok: true,
|
|
36
|
+
json: async () => makeTreeResponse([
|
|
37
|
+
"README.md",
|
|
38
|
+
"SKILL.md",
|
|
39
|
+
"skills/foo/SKILL.md",
|
|
40
|
+
"skills/bar/SKILL.md",
|
|
41
|
+
"src/index.ts",
|
|
42
|
+
]),
|
|
43
|
+
});
|
|
44
|
+
const result = await discoverSkills("owner", "repo");
|
|
45
|
+
expect(result).toHaveLength(3);
|
|
46
|
+
expect(result).toEqual(expect.arrayContaining([
|
|
47
|
+
{
|
|
48
|
+
name: "repo",
|
|
49
|
+
path: "SKILL.md",
|
|
50
|
+
rawUrl: "https://raw.githubusercontent.com/owner/repo/main/SKILL.md",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "foo",
|
|
54
|
+
path: "skills/foo/SKILL.md",
|
|
55
|
+
rawUrl: "https://raw.githubusercontent.com/owner/repo/main/skills/foo/SKILL.md",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "bar",
|
|
59
|
+
path: "skills/bar/SKILL.md",
|
|
60
|
+
rawUrl: "https://raw.githubusercontent.com/owner/repo/main/skills/bar/SKILL.md",
|
|
61
|
+
},
|
|
62
|
+
]));
|
|
63
|
+
});
|
|
64
|
+
// TC-002: Returns only root SKILL.md when no skills/ directory
|
|
65
|
+
it("returns only root SKILL.md when no skills/ directory exists", async () => {
|
|
66
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
67
|
+
ok: true,
|
|
68
|
+
json: async () => makeTreeResponse(["SKILL.md", "README.md", "package.json"]),
|
|
69
|
+
});
|
|
70
|
+
const result = await discoverSkills("owner", "repo");
|
|
71
|
+
expect(result).toHaveLength(1);
|
|
72
|
+
expect(result[0]).toEqual({
|
|
73
|
+
name: "repo",
|
|
74
|
+
path: "SKILL.md",
|
|
75
|
+
rawUrl: "https://raw.githubusercontent.com/owner/repo/main/SKILL.md",
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
// TC-003: Returns empty array when no SKILL.md files found
|
|
79
|
+
it("returns empty array when no SKILL.md files found", async () => {
|
|
80
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
81
|
+
ok: true,
|
|
82
|
+
json: async () => makeTreeResponse(["README.md", "package.json", "src/index.ts"]),
|
|
83
|
+
});
|
|
84
|
+
const result = await discoverSkills("owner", "repo");
|
|
85
|
+
expect(result).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
// TC-004: Returns empty array on API error (404, rate-limited)
|
|
88
|
+
it("returns empty array on 404", async () => {
|
|
89
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
90
|
+
ok: false,
|
|
91
|
+
status: 404,
|
|
92
|
+
});
|
|
93
|
+
const result = await discoverSkills("owner", "repo");
|
|
94
|
+
expect(result).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
it("returns empty array on 403 (rate limited)", async () => {
|
|
97
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
98
|
+
ok: false,
|
|
99
|
+
status: 403,
|
|
100
|
+
});
|
|
101
|
+
const result = await discoverSkills("owner", "repo");
|
|
102
|
+
expect(result).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
it("returns empty array on network error", async () => {
|
|
105
|
+
globalThis.fetch = vi.fn().mockRejectedValue(new Error("Network error"));
|
|
106
|
+
const result = await discoverSkills("owner", "repo");
|
|
107
|
+
expect(result).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
// TC-005: Ignores SKILL.md in nested non-skill directories
|
|
110
|
+
it("ignores SKILL.md in nested non-skill directories", async () => {
|
|
111
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
112
|
+
ok: true,
|
|
113
|
+
json: async () => makeTreeResponse([
|
|
114
|
+
"SKILL.md",
|
|
115
|
+
"docs/SKILL.md",
|
|
116
|
+
"examples/SKILL.md",
|
|
117
|
+
"node_modules/SKILL.md",
|
|
118
|
+
"skills/foo/SKILL.md",
|
|
119
|
+
"skills/bar/nested/SKILL.md",
|
|
120
|
+
]),
|
|
121
|
+
});
|
|
122
|
+
const result = await discoverSkills("owner", "repo");
|
|
123
|
+
expect(result).toHaveLength(2);
|
|
124
|
+
const names = result.map((s) => s.name);
|
|
125
|
+
expect(names).toContain("repo");
|
|
126
|
+
expect(names).toContain("foo");
|
|
127
|
+
// Should NOT contain docs, examples, node_modules, or deeply nested skills
|
|
128
|
+
expect(names).not.toContain("docs");
|
|
129
|
+
expect(names).not.toContain("examples");
|
|
130
|
+
expect(names).not.toContain("nested");
|
|
131
|
+
});
|
|
132
|
+
// TC-021: discoverSkills populates descriptions from fetched content
|
|
133
|
+
it("populates description field from SKILL.md content", async () => {
|
|
134
|
+
const mockFetch = vi.fn()
|
|
135
|
+
.mockResolvedValueOnce(makeBranchResponse("main"))
|
|
136
|
+
.mockResolvedValueOnce({
|
|
137
|
+
ok: true,
|
|
138
|
+
json: async () => makeTreeResponse(["skills/foo/SKILL.md", "skills/bar/SKILL.md"]),
|
|
139
|
+
})
|
|
140
|
+
.mockResolvedValue({
|
|
141
|
+
ok: true,
|
|
142
|
+
text: async () => "# Foo Skill\n\nThis skill does X",
|
|
143
|
+
});
|
|
144
|
+
globalThis.fetch = mockFetch;
|
|
145
|
+
const result = await discoverSkills("owner", "repo");
|
|
146
|
+
expect(result).toHaveLength(2);
|
|
147
|
+
// At least one skill should have a description populated
|
|
148
|
+
const hasDescription = result.some((s) => s.description !== undefined);
|
|
149
|
+
expect(hasDescription).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
// Calls correct GitHub Trees API URL
|
|
152
|
+
it("calls the GitHub Trees API with correct URL", async () => {
|
|
153
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
154
|
+
ok: true,
|
|
155
|
+
json: async () => makeTreeResponse(["SKILL.md"]),
|
|
156
|
+
});
|
|
157
|
+
globalThis.fetch = mockFetch;
|
|
158
|
+
await discoverSkills("anthropics", "frontend-design");
|
|
159
|
+
// First call is getDefaultBranch, second is the tree API
|
|
160
|
+
expect(mockFetch).toHaveBeenNthCalledWith(2, "https://api.github.com/repos/anthropics/frontend-design/git/trees/main?recursive=1", expect.objectContaining({
|
|
161
|
+
headers: expect.objectContaining({
|
|
162
|
+
Accept: "application/vnd.github.v3+json",
|
|
163
|
+
}),
|
|
164
|
+
}));
|
|
165
|
+
});
|
|
166
|
+
// Uses the repo's actual default branch (e.g. master)
|
|
167
|
+
it("uses repo default branch instead of hardcoded main", async () => {
|
|
168
|
+
const mockFetch = vi.fn()
|
|
169
|
+
.mockResolvedValueOnce(makeBranchResponse("master"))
|
|
170
|
+
.mockResolvedValueOnce({
|
|
171
|
+
ok: true,
|
|
172
|
+
json: async () => makeTreeResponse(["SKILL.md"]),
|
|
173
|
+
});
|
|
174
|
+
globalThis.fetch = mockFetch;
|
|
175
|
+
const result = await discoverSkills("owner", "repo");
|
|
176
|
+
expect(mockFetch).toHaveBeenNthCalledWith(2, "https://api.github.com/repos/owner/repo/git/trees/master?recursive=1", expect.anything());
|
|
177
|
+
expect(result[0].rawUrl).toBe("https://raw.githubusercontent.com/owner/repo/master/SKILL.md");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe("extractDescription", () => {
|
|
181
|
+
it("returns first non-heading, non-empty line as description", () => {
|
|
182
|
+
const content = "# Title\n\nThis skill does X\n\nMore content";
|
|
183
|
+
expect(extractDescription(content)).toBe("This skill does X");
|
|
184
|
+
});
|
|
185
|
+
it("truncates description at 80 chars with ellipsis", () => {
|
|
186
|
+
const longLine = "A".repeat(120);
|
|
187
|
+
const content = `# Title\n\n${longLine}`;
|
|
188
|
+
const result = extractDescription(content);
|
|
189
|
+
expect(result).toBe("A".repeat(77) + "...");
|
|
190
|
+
expect(result.length).toBe(80);
|
|
191
|
+
});
|
|
192
|
+
it("skips YAML frontmatter delimiters", () => {
|
|
193
|
+
const content = "---\ntitle: foo\n---\n# Title\nDescription here";
|
|
194
|
+
expect(extractDescription(content)).toBe("Description here");
|
|
195
|
+
});
|
|
196
|
+
it("returns undefined when content has only headings", () => {
|
|
197
|
+
const content = "# Title\n## Section\n### Subsection";
|
|
198
|
+
expect(extractDescription(content)).toBeUndefined();
|
|
199
|
+
});
|
|
200
|
+
it("returns undefined for empty content", () => {
|
|
201
|
+
expect(extractDescription("")).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
it("skips blank lines before first content line", () => {
|
|
204
|
+
const content = "\n\n# Title\n\n\nActual description";
|
|
205
|
+
expect(extractDescription(content)).toBe("Actual description");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
describe("getDefaultBranch", () => {
|
|
209
|
+
const originalFetch = globalThis.fetch;
|
|
210
|
+
afterEach(() => {
|
|
211
|
+
globalThis.fetch = originalFetch;
|
|
212
|
+
_resetBranchCache();
|
|
213
|
+
});
|
|
214
|
+
it("returns default_branch from GitHub API", async () => {
|
|
215
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
216
|
+
ok: true,
|
|
217
|
+
json: async () => ({ default_branch: "develop" }),
|
|
218
|
+
});
|
|
219
|
+
const branch = await getDefaultBranch("test-owner", "test-repo-1");
|
|
220
|
+
expect(branch).toBe("develop");
|
|
221
|
+
});
|
|
222
|
+
it("falls back to main on API error", async () => {
|
|
223
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
224
|
+
ok: false,
|
|
225
|
+
status: 404,
|
|
226
|
+
});
|
|
227
|
+
const branch = await getDefaultBranch("test-owner", "test-repo-2");
|
|
228
|
+
expect(branch).toBe("main");
|
|
229
|
+
});
|
|
230
|
+
it("falls back to main on network error", async () => {
|
|
231
|
+
globalThis.fetch = vi.fn().mockRejectedValue(new Error("Network error"));
|
|
232
|
+
const branch = await getDefaultBranch("test-owner", "test-repo-3");
|
|
233
|
+
expect(branch).toBe("main");
|
|
234
|
+
});
|
|
235
|
+
it("caches results per owner/repo", async () => {
|
|
236
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
237
|
+
ok: true,
|
|
238
|
+
json: async () => ({ default_branch: "master" }),
|
|
239
|
+
});
|
|
240
|
+
globalThis.fetch = mockFetch;
|
|
241
|
+
const first = await getDefaultBranch("cached-owner", "cached-repo");
|
|
242
|
+
const second = await getDefaultBranch("cached-owner", "cached-repo");
|
|
243
|
+
expect(first).toBe("master");
|
|
244
|
+
expect(second).toBe("master");
|
|
245
|
+
// Only one fetch call — second was served from cache
|
|
246
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// warnRateLimitOnce
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
describe("warnRateLimitOnce", () => {
|
|
253
|
+
afterEach(() => {
|
|
254
|
+
_resetRateLimitWarned();
|
|
255
|
+
});
|
|
256
|
+
// TC-001: prints warning on 403 with x-ratelimit-remaining: 0
|
|
257
|
+
it("prints warning on 403 with x-ratelimit-remaining: 0", () => {
|
|
258
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
259
|
+
const res = {
|
|
260
|
+
status: 403,
|
|
261
|
+
headers: new Headers({ "x-ratelimit-remaining": "0" }),
|
|
262
|
+
};
|
|
263
|
+
warnRateLimitOnce(res);
|
|
264
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
265
|
+
expect(spy.mock.calls[0][0]).toContain("rate limit");
|
|
266
|
+
spy.mockRestore();
|
|
267
|
+
});
|
|
268
|
+
// TC-002: does not print on 403 without rate-limit header
|
|
269
|
+
it("does not print on 403 without rate-limit header", () => {
|
|
270
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
271
|
+
const res = {
|
|
272
|
+
status: 403,
|
|
273
|
+
headers: new Headers(),
|
|
274
|
+
};
|
|
275
|
+
warnRateLimitOnce(res);
|
|
276
|
+
expect(spy).not.toHaveBeenCalled();
|
|
277
|
+
spy.mockRestore();
|
|
278
|
+
});
|
|
279
|
+
// TC-003: prints warning only once across multiple calls
|
|
280
|
+
it("prints warning only once across multiple calls", () => {
|
|
281
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
282
|
+
const res = {
|
|
283
|
+
status: 403,
|
|
284
|
+
headers: new Headers({ "x-ratelimit-remaining": "0" }),
|
|
285
|
+
};
|
|
286
|
+
warnRateLimitOnce(res);
|
|
287
|
+
warnRateLimitOnce(res);
|
|
288
|
+
warnRateLimitOnce(res);
|
|
289
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
290
|
+
spy.mockRestore();
|
|
291
|
+
});
|
|
292
|
+
// TC-004: _resetRateLimitWarned allows re-warning
|
|
293
|
+
it("_resetRateLimitWarned allows re-warning", () => {
|
|
294
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
295
|
+
const res = {
|
|
296
|
+
status: 403,
|
|
297
|
+
headers: new Headers({ "x-ratelimit-remaining": "0" }),
|
|
298
|
+
};
|
|
299
|
+
warnRateLimitOnce(res);
|
|
300
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
301
|
+
_resetRateLimitWarned();
|
|
302
|
+
warnRateLimitOnce(res);
|
|
303
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
304
|
+
spy.mockRestore();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// Discovery scope guard
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
describe("discovery scope guard", () => {
|
|
311
|
+
const originalFetch = globalThis.fetch;
|
|
312
|
+
afterEach(() => {
|
|
313
|
+
globalThis.fetch = originalFetch;
|
|
314
|
+
_resetBranchCache();
|
|
315
|
+
});
|
|
316
|
+
// TC-024: plugins/foo/SKILL.md not matched by discovery
|
|
317
|
+
it("plugins/foo/SKILL.md not matched by discovery", async () => {
|
|
318
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
319
|
+
ok: true,
|
|
320
|
+
json: async () => makeTreeResponse(["plugins/foo/SKILL.md", "README.md"]),
|
|
321
|
+
});
|
|
322
|
+
const result = await discoverSkills("owner", "repo");
|
|
323
|
+
expect(result).toEqual([]);
|
|
324
|
+
});
|
|
325
|
+
// TC-025: plugins/specweave/skills/pm/SKILL.md not matched
|
|
326
|
+
it("plugins/specweave/skills/pm/SKILL.md not matched", async () => {
|
|
327
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
328
|
+
ok: true,
|
|
329
|
+
json: async () => makeTreeResponse(["plugins/specweave/skills/pm/SKILL.md", "README.md"]),
|
|
330
|
+
});
|
|
331
|
+
const result = await discoverSkills("owner", "repo");
|
|
332
|
+
expect(result).toEqual([]);
|
|
333
|
+
});
|
|
334
|
+
// TC-027: discovers agents/*.md inside skill directories
|
|
335
|
+
it("discovers agents/*.md and attaches agentRawUrls to parent skill", async () => {
|
|
336
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
337
|
+
ok: true,
|
|
338
|
+
json: async () => makeTreeResponse([
|
|
339
|
+
"skills/team-lead/SKILL.md",
|
|
340
|
+
"skills/team-lead/agents/frontend.md",
|
|
341
|
+
"skills/team-lead/agents/backend.md",
|
|
342
|
+
"skills/other/SKILL.md",
|
|
343
|
+
"README.md",
|
|
344
|
+
]),
|
|
345
|
+
});
|
|
346
|
+
const result = await discoverSkills("owner", "repo");
|
|
347
|
+
expect(result).toHaveLength(2);
|
|
348
|
+
const teamLead = result.find((s) => s.name === "team-lead");
|
|
349
|
+
const other = result.find((s) => s.name === "other");
|
|
350
|
+
expect(teamLead?.agentRawUrls).toEqual({
|
|
351
|
+
"agents/frontend.md": "https://raw.githubusercontent.com/owner/repo/main/skills/team-lead/agents/frontend.md",
|
|
352
|
+
"agents/backend.md": "https://raw.githubusercontent.com/owner/repo/main/skills/team-lead/agents/backend.md",
|
|
353
|
+
});
|
|
354
|
+
// Skill without agents/ has no agentRawUrls
|
|
355
|
+
expect(other?.agentRawUrls).toBeUndefined();
|
|
356
|
+
});
|
|
357
|
+
// TC-026: skills/pm/SKILL.md IS matched (positive control)
|
|
358
|
+
it("skills/pm/SKILL.md IS matched (positive control)", async () => {
|
|
359
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
360
|
+
ok: true,
|
|
361
|
+
json: async () => makeTreeResponse(["skills/pm/SKILL.md", "README.md"]),
|
|
362
|
+
});
|
|
363
|
+
const result = await discoverSkills("owner", "repo");
|
|
364
|
+
expect(result).toHaveLength(1);
|
|
365
|
+
expect(result[0]).toEqual({
|
|
366
|
+
name: "pm",
|
|
367
|
+
path: "skills/pm/SKILL.md",
|
|
368
|
+
rawUrl: "https://raw.githubusercontent.com/owner/repo/main/skills/pm/SKILL.md",
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
//# sourceMappingURL=github-tree.test.js.map
|