@withpica/mcp-server-directory 1.1.0 → 1.3.0
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/CHANGELOG.md +43 -26
- package/package.json +4 -2
- package/scripts/build-skills.ts +192 -0
- package/src/__tests__/prompts/index.test.ts +47 -64
- package/src/__tests__/prompts/prompt-eval-harness.test.ts +135 -104
- package/src/__tests__/skills/skills-registry.test.ts +63 -0
- package/src/__tests__/skills/skills-tools.test.ts +74 -0
- package/src/__tests__/tools/chain.test.ts +122 -0
- package/src/__tests__/tools/composability-chains.test.ts +4 -2
- package/src/__tests__/tools/people.test.ts +9 -3
- package/src/__tests__/tools/works.test.ts +32 -3
- package/src/prompts/index.ts +97 -141
- package/src/prompts/public-question-atlas.ts +557 -0
- package/src/server.ts +123 -11
- package/src/skills/find-music-for-sync-brief/SKILL.md +89 -0
- package/src/skills/index.ts +63 -0
- package/src/skills/skills.generated.ts +33 -0
- package/src/tools/chain.ts +118 -0
- package/src/tools/index.ts +15 -0
- package/src/tools/people.ts +22 -41
- package/src/tools/recordings.ts +7 -3
- package/src/tools/search.ts +7 -4
- package/src/tools/skills.ts +146 -0
- package/src/tools/works.ts +39 -46
- package/dist/client.d.ts +0 -12
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -93
- package/dist/client.js.map +0 -1
- package/dist/config.d.ts +0 -9
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -15
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -28
- package/dist/index.js.map +0 -1
- package/dist/prompts/index.d.ts +0 -33
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js +0 -208
- package/dist/prompts/index.js.map +0 -1
- package/dist/resources/llms-primer.d.ts +0 -2
- package/dist/resources/llms-primer.d.ts.map +0 -1
- package/dist/resources/llms-primer.js +0 -35
- package/dist/resources/llms-primer.js.map +0 -1
- package/dist/server.d.ts +0 -13
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -104
- package/dist/server.js.map +0 -1
- package/dist/tools/index.d.ts +0 -26
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -43
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/people.d.ts +0 -14
- package/dist/tools/people.d.ts.map +0 -1
- package/dist/tools/people.js +0 -190
- package/dist/tools/people.js.map +0 -1
- package/dist/tools/recordings.d.ts +0 -12
- package/dist/tools/recordings.d.ts.map +0 -1
- package/dist/tools/recordings.js +0 -141
- package/dist/tools/recordings.js.map +0 -1
- package/dist/tools/search.d.ts +0 -12
- package/dist/tools/search.d.ts.map +0 -1
- package/dist/tools/search.js +0 -53
- package/dist/tools/search.js.map +0 -1
- package/dist/tools/works.d.ts +0 -15
- package/dist/tools/works.d.ts.map +0 -1
- package/dist/tools/works.js +0 -249
- package/dist/tools/works.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -14
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js +0 -48
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/formatting.d.ts +0 -10
- package/dist/utils/formatting.d.ts.map +0 -1
- package/dist/utils/formatting.js +0 -16
- package/dist/utils/formatting.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to `@withpica/mcp-server-directory`
|
|
3
|
+
All notable changes to `@withpica/mcp-server-directory` are documented here.
|
|
4
4
|
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to
|
|
7
|
-
|
|
8
|
-
> **Rule of the road:** every version bump to this package MUST land with a
|
|
9
|
-
> matching entry here in the same commit. See the project's "npm publish
|
|
10
|
-
> discipline" memory entry for the enforcement rationale.
|
|
5
|
+
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to semantic versioning where the major number is incremented
|
|
7
|
+
for breaking tool-surface changes.
|
|
11
8
|
|
|
12
9
|
## [Unreleased]
|
|
13
10
|
|
|
14
|
-
## [1.1.0]
|
|
11
|
+
## [1.1.0] - 2026-04-21
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
Shipped as minor rather than major despite the `*_full` tool removal:
|
|
14
|
+
the removed names had near-zero external adoption (they shipped recently
|
|
15
|
+
and weren't advertised externally), and staying on the 1.x line keeps
|
|
16
|
+
`^1.0.0` consumers on the upgrade path rather than stranding them on
|
|
17
|
+
the broken cascade. Clients that cached the `*_full` names will see a
|
|
18
|
+
tool-not-found error and need to refresh their tool list.
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
3 prompts → 2 skills, each with decision trees, domain knowledge,
|
|
20
|
-
and the shared preamble (tone, discovery principle, read-only note).
|
|
20
|
+
### Breaking
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
- **Removed `directory_lookup_person_full` and `directory_lookup_work_full`.**
|
|
23
|
+
Both tools hit the same upstream endpoints as their non-`_full` counterparts
|
|
24
|
+
with only a formatting difference — there was no capability delta. The
|
|
25
|
+
`_full` variants are now the canonical names (`directory_lookup_person`
|
|
26
|
+
and `directory_lookup_work`); the thin variants have been deleted. Clients
|
|
27
|
+
that cached the `_full` names need to update their tool list; the
|
|
28
|
+
replacements return a superset of the prior data, so no output is lost.
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
`find-music` → `discover-music` (adds equal discovery principle,
|
|
26
|
-
reference track search, similarity exploration).
|
|
27
|
-
`directory-autopilot` routing folded into `research-creator`
|
|
28
|
-
(identifier resolution, browsing, creator lookup all in one skill).
|
|
29
|
-
`research-creator` gains collaborator network, similarity search,
|
|
30
|
-
rights landscape, and identifier routing from the old autopilot.
|
|
30
|
+
### Added
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
- **`directory_chain` tool** — one-call rights-chain lookup. Accepts a
|
|
33
|
+
free-text query, a direct ISWC/ISRC `identifier`, or audio-only filters
|
|
34
|
+
(BPM / key / energy / mood). Returns the already-public `ChainResult`
|
|
35
|
+
shape: work + writers + publishers (names + roles only, no IPIs or
|
|
36
|
+
splits) + linked recording + audio characteristics. Wraps the existing
|
|
37
|
+
public `/api/public/directory/chain` endpoint — no new backing API work.
|
|
38
|
+
- `directory_list_people` now accepts `letter` (A–Z, `#`) for alphabetical
|
|
39
|
+
browsing. The backend has supported this for months; only the MCP tool
|
|
40
|
+
was missing the parameter.
|
|
41
|
+
- `directory_list_works` now accepts `letter`, `sort` (`title` | `recent`),
|
|
42
|
+
and `isrc` — all previously backend-only.
|
|
33
43
|
|
|
34
|
-
###
|
|
44
|
+
### Changed
|
|
35
45
|
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
- **`directory_lookup_person` now matches its description.** Previously the
|
|
47
|
+
tool advertised ISNI / IPI / MusicBrainz ID support but the backing query
|
|
48
|
+
only matched `global_creator_id` — any non-UUID identifier silently 404'd.
|
|
49
|
+
The query now cascades through `global_creator_id` → `isni` →
|
|
50
|
+
`cae_ipi_number` → `musicbrainz_id`, first hit wins.
|
|
51
|
+
- `directory_search` description rewritten to drop the false "ranked" claim.
|
|
52
|
+
The backing endpoint is a fan-out (`listWorks` + `listPeople`,
|
|
53
|
+
half-limit each), not cross-type ranking. Callers should prefer typed
|
|
54
|
+
list tools when they know the entity type.
|
|
55
|
+
- `directory_lookup_isrc` description rewritten to frame it honestly as a
|
|
56
|
+
pinned filter preset on `list_works` rather than a distinct lookup.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withpica/mcp-server-directory",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "MCP Server for PICA Public Directory — enables AI assistants to search verified works and creators",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
"pica-directory-mcp": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
+
"prebuild": "tsx scripts/build-skills.ts",
|
|
11
12
|
"build": "tsc",
|
|
12
13
|
"dev": "tsx src/index.ts",
|
|
13
14
|
"start": "node dist/index.js",
|
|
14
15
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
15
16
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
|
|
16
|
-
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage"
|
|
17
|
+
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
17
19
|
},
|
|
18
20
|
"author": "PICA Platform",
|
|
19
21
|
"license": "MIT",
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ADR-140 Phase 2b — generator for `skills.generated.ts` (directory MCP).
|
|
5
|
+
*
|
|
6
|
+
* Sister of mcp-server/scripts/build-skills.ts. Same shape, scoped to the
|
|
7
|
+
* directory MCP. Public/anonymous surface — every skill body must pass the
|
|
8
|
+
* IP-protection lint (scripts/lint-mcp-tools.ts Rule 14) before publishing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, readdirSync, writeFileSync, existsSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
const ROOT = join(__dirname, "..");
|
|
18
|
+
const SKILLS_ROOT = join(ROOT, "src/skills");
|
|
19
|
+
const OUTPUT_PATH = join(SKILLS_ROOT, "skills.generated.ts");
|
|
20
|
+
|
|
21
|
+
interface SkillFrontmatter {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
triggers: string[];
|
|
25
|
+
audience: string;
|
|
26
|
+
tools_required: string[];
|
|
27
|
+
output: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ParsedSkill extends SkillFrontmatter {
|
|
31
|
+
body: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseFrontmatter(src: string, sourceFile: string): ParsedSkill {
|
|
35
|
+
let i = 0;
|
|
36
|
+
if (src.startsWith("<!--")) {
|
|
37
|
+
const end = src.indexOf("-->");
|
|
38
|
+
if (end === -1) {
|
|
39
|
+
throw new Error(`${sourceFile}: unterminated HTML comment header`);
|
|
40
|
+
}
|
|
41
|
+
i = end + 3;
|
|
42
|
+
while (i < src.length && /\s/.test(src[i])) i++;
|
|
43
|
+
}
|
|
44
|
+
if (src.slice(i, i + 3) !== "---") {
|
|
45
|
+
throw new Error(`${sourceFile}: missing --- frontmatter opener`);
|
|
46
|
+
}
|
|
47
|
+
i += 3;
|
|
48
|
+
while (i < src.length && src[i] !== "\n") i++;
|
|
49
|
+
i++;
|
|
50
|
+
const closeIdx = src.indexOf("\n---", i);
|
|
51
|
+
if (closeIdx === -1) {
|
|
52
|
+
throw new Error(`${sourceFile}: missing --- frontmatter closer`);
|
|
53
|
+
}
|
|
54
|
+
const frontmatterText = src.slice(i, closeIdx);
|
|
55
|
+
const bodyStart = closeIdx + 4;
|
|
56
|
+
const body = src.slice(bodyStart).replace(/^\n+/, "").trimEnd() + "\n";
|
|
57
|
+
|
|
58
|
+
const fm: Record<string, string | string[]> = {};
|
|
59
|
+
const lines = frontmatterText.split("\n");
|
|
60
|
+
let currentListKey: string | null = null;
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
if (line.trim() === "") {
|
|
63
|
+
currentListKey = null;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const listItemMatch = line.match(/^\s+-\s+(.+)$/);
|
|
67
|
+
if (listItemMatch && currentListKey) {
|
|
68
|
+
(fm[currentListKey] as string[]).push(listItemMatch[1].trim());
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const kvMatch = line.match(/^([a-z_][a-z0-9_]*)\s*:\s*(.*)$/i);
|
|
72
|
+
if (!kvMatch) continue;
|
|
73
|
+
const key = kvMatch[1];
|
|
74
|
+
const value = kvMatch[2].trim();
|
|
75
|
+
if (value === "") {
|
|
76
|
+
fm[key] = [];
|
|
77
|
+
currentListKey = key;
|
|
78
|
+
} else {
|
|
79
|
+
fm[key] = value;
|
|
80
|
+
currentListKey = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const required = [
|
|
85
|
+
"name",
|
|
86
|
+
"description",
|
|
87
|
+
"triggers",
|
|
88
|
+
"audience",
|
|
89
|
+
"tools_required",
|
|
90
|
+
"output",
|
|
91
|
+
];
|
|
92
|
+
for (const field of required) {
|
|
93
|
+
if (!(field in fm)) {
|
|
94
|
+
throw new Error(`${sourceFile}: missing required frontmatter field "${field}"`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(fm.triggers) || fm.triggers.length === 0) {
|
|
98
|
+
throw new Error(`${sourceFile}: triggers must be a non-empty list`);
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(fm.tools_required) || fm.tools_required.length === 0) {
|
|
101
|
+
throw new Error(`${sourceFile}: tools_required must be a non-empty list`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
name: fm.name as string,
|
|
106
|
+
description: fm.description as string,
|
|
107
|
+
triggers: fm.triggers as string[],
|
|
108
|
+
audience: fm.audience as string,
|
|
109
|
+
tools_required: fm.tools_required as string[],
|
|
110
|
+
output: fm.output as string,
|
|
111
|
+
body,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function discoverSkills(): ParsedSkill[] {
|
|
116
|
+
if (!existsSync(SKILLS_ROOT)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const entries = readdirSync(SKILLS_ROOT, { withFileTypes: true });
|
|
120
|
+
const skills: ParsedSkill[] = [];
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
if (!entry.isDirectory()) continue;
|
|
123
|
+
const skillFile = join(SKILLS_ROOT, entry.name, "SKILL.md");
|
|
124
|
+
if (!existsSync(skillFile)) continue;
|
|
125
|
+
const src = readFileSync(skillFile, "utf-8");
|
|
126
|
+
const parsed = parseFrontmatter(src, `src/skills/${entry.name}/SKILL.md`);
|
|
127
|
+
if (parsed.name !== entry.name) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`src/skills/${entry.name}/SKILL.md: frontmatter name "${parsed.name}" must match directory name "${entry.name}"`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
skills.push(parsed);
|
|
133
|
+
}
|
|
134
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
135
|
+
return skills;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function emit(skills: ParsedSkill[]): string {
|
|
139
|
+
const header = `// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* GENERATED FILE — DO NOT EDIT BY HAND.
|
|
143
|
+
*
|
|
144
|
+
* Regenerated by \`scripts/build-skills.ts\` from \`src/skills/<name>/SKILL.md\`.
|
|
145
|
+
* Runs in \`prebuild\`. See ADR-140 Phase 2b.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
export interface Skill {
|
|
149
|
+
name: string;
|
|
150
|
+
description: string;
|
|
151
|
+
triggers: string[];
|
|
152
|
+
audience: string;
|
|
153
|
+
tools_required: string[];
|
|
154
|
+
output: string;
|
|
155
|
+
body: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
`;
|
|
159
|
+
const lines: string[] = [header, "export const SKILLS: Record<string, Skill> = {"];
|
|
160
|
+
for (const skill of skills) {
|
|
161
|
+
lines.push(` ${JSON.stringify(skill.name)}: {`);
|
|
162
|
+
lines.push(` name: ${JSON.stringify(skill.name)},`);
|
|
163
|
+
lines.push(` description: ${JSON.stringify(skill.description)},`);
|
|
164
|
+
lines.push(` triggers: ${JSON.stringify(skill.triggers)},`);
|
|
165
|
+
lines.push(` audience: ${JSON.stringify(skill.audience)},`);
|
|
166
|
+
lines.push(` tools_required: ${JSON.stringify(skill.tools_required)},`);
|
|
167
|
+
lines.push(` output: ${JSON.stringify(skill.output)},`);
|
|
168
|
+
lines.push(` body: ${JSON.stringify(skill.body)},`);
|
|
169
|
+
lines.push(` },`);
|
|
170
|
+
}
|
|
171
|
+
lines.push("};");
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push(
|
|
174
|
+
`export const SKILL_NAMES: ReadonlyArray<string> = ${JSON.stringify(skills.map((s) => s.name))};`,
|
|
175
|
+
);
|
|
176
|
+
lines.push("");
|
|
177
|
+
return lines.join("\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function main(): void {
|
|
181
|
+
const skills = discoverSkills();
|
|
182
|
+
const output = emit(skills);
|
|
183
|
+
writeFileSync(OUTPUT_PATH, output, "utf-8");
|
|
184
|
+
console.log(
|
|
185
|
+
`[build-skills/directory] Wrote ${skills.length} skill(s) to src/skills/skills.generated.ts`,
|
|
186
|
+
);
|
|
187
|
+
for (const skill of skills) {
|
|
188
|
+
console.log(` - ${skill.name} (${skill.body.length} bytes body)`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
main();
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Directory
|
|
4
|
+
* Directory Prompt Registry Tests
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { describe, it, expect, beforeAll } from "@jest/globals";
|
|
8
8
|
import { PromptRegistry } from "../../prompts/index";
|
|
9
9
|
|
|
10
|
-
describe("Directory
|
|
10
|
+
describe("Directory Prompt Registry", () => {
|
|
11
11
|
const registry = new PromptRegistry();
|
|
12
12
|
|
|
13
13
|
describe("listPrompts", () => {
|
|
14
|
-
it("returns
|
|
14
|
+
it("returns 3 prompts", () => {
|
|
15
15
|
const prompts = registry.listPrompts();
|
|
16
|
-
expect(prompts).toHaveLength(
|
|
16
|
+
expect(prompts).toHaveLength(3);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
it("includes all expected
|
|
19
|
+
it("includes all expected prompts", () => {
|
|
20
20
|
const names = registry.listPrompts().map((p) => p.name);
|
|
21
|
-
expect(names).toContain("
|
|
21
|
+
expect(names).toContain("find-music");
|
|
22
22
|
expect(names).toContain("research-creator");
|
|
23
|
+
expect(names).toContain("directory-autopilot");
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
it("
|
|
26
|
+
it("prompt names are unique and kebab-case", () => {
|
|
26
27
|
const prompts = registry.listPrompts();
|
|
27
28
|
const names = prompts.map((p) => p.name);
|
|
28
29
|
expect(new Set(names).size).toBe(names.length);
|
|
@@ -33,7 +34,7 @@ describe("Directory Skill Registry", () => {
|
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
describe("getPrompt", () => {
|
|
36
|
-
it("resolves all
|
|
37
|
+
it("resolves all prompts without error", async () => {
|
|
37
38
|
const prompts = registry.listPrompts();
|
|
38
39
|
for (const prompt of prompts) {
|
|
39
40
|
const result = await registry.getPrompt(prompt.name);
|
|
@@ -49,28 +50,50 @@ describe("Directory Skill Registry", () => {
|
|
|
49
50
|
"Prompt not found",
|
|
50
51
|
);
|
|
51
52
|
});
|
|
53
|
+
});
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
describe("directory-autopilot routing", () => {
|
|
56
|
+
let text: string;
|
|
57
|
+
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
const result = await registry.getPrompt("directory-autopilot");
|
|
60
|
+
text = result.messages[0].content.text;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("reads the primer first", () => {
|
|
64
|
+
expect(text).toContain("llms://primer");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("routes sync searches to find-music workflow", () => {
|
|
68
|
+
expect(text).toMatch(/sync|playlist|project/i);
|
|
69
|
+
expect(text).toContain("find-music");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("routes creator research to lookup tools", () => {
|
|
73
|
+
expect(text).toContain("directory_lookup_person");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("routes identifier resolution to specific tools", () => {
|
|
77
|
+
expect(text).toContain("directory_lookup_isrc");
|
|
78
|
+
expect(text).toContain("directory_lookup_work");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("handles browsing requests", () => {
|
|
82
|
+
expect(text).toContain("directory_search");
|
|
83
|
+
expect(text).toContain("directory_list_works");
|
|
61
84
|
});
|
|
62
85
|
});
|
|
63
86
|
|
|
64
|
-
describe("
|
|
87
|
+
describe("find-music workflow", () => {
|
|
65
88
|
it("references audio search tool", async () => {
|
|
66
|
-
const result = await registry.getPrompt("
|
|
89
|
+
const result = await registry.getPrompt("find-music");
|
|
67
90
|
const text = result.messages[0].content.text;
|
|
68
91
|
expect(text).toContain("directory_search_recordings");
|
|
69
|
-
expect(text).toContain("
|
|
92
|
+
expect(text).toContain("directory_lookup_work");
|
|
70
93
|
});
|
|
71
94
|
|
|
72
95
|
it("includes mood-to-parameter translation guide", async () => {
|
|
73
|
-
const result = await registry.getPrompt("
|
|
96
|
+
const result = await registry.getPrompt("find-music");
|
|
74
97
|
const text = result.messages[0].content.text;
|
|
75
98
|
expect(text).toMatch(/upbeat|dark|chill/i);
|
|
76
99
|
expect(text).toContain("energy");
|
|
@@ -78,39 +101,20 @@ describe("Directory Skill Registry", () => {
|
|
|
78
101
|
});
|
|
79
102
|
|
|
80
103
|
it("accepts optional brief argument", async () => {
|
|
81
|
-
const result = await registry.getPrompt("
|
|
104
|
+
const result = await registry.getPrompt("find-music", {
|
|
82
105
|
brief: "dark moody piano",
|
|
83
106
|
});
|
|
84
107
|
const text = result.messages[0].content.text;
|
|
85
108
|
expect(text).toContain("dark moody piano");
|
|
86
109
|
});
|
|
87
|
-
|
|
88
|
-
it("emphasises equal discovery principle", async () => {
|
|
89
|
-
const result = await registry.getPrompt("discover-music");
|
|
90
|
-
const text = result.messages[0].content.text;
|
|
91
|
-
expect(text).toMatch(/equal discovery/i);
|
|
92
|
-
expect(text).toMatch(/not.*popularity|not.*fame/i);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("includes reference track search path", async () => {
|
|
96
|
-
const result = await registry.getPrompt("discover-music");
|
|
97
|
-
const text = result.messages[0].content.text;
|
|
98
|
-
expect(text).toMatch(/reference track/i);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("includes similarity exploration path", async () => {
|
|
102
|
-
const result = await registry.getPrompt("discover-music");
|
|
103
|
-
const text = result.messages[0].content.text;
|
|
104
|
-
expect(text).toMatch(/similar/i);
|
|
105
|
-
});
|
|
106
110
|
});
|
|
107
111
|
|
|
108
|
-
describe("research-creator
|
|
112
|
+
describe("research-creator workflow", () => {
|
|
109
113
|
it("references person lookup tools", async () => {
|
|
110
114
|
const result = await registry.getPrompt("research-creator");
|
|
111
115
|
const text = result.messages[0].content.text;
|
|
112
|
-
expect(text).toContain("
|
|
113
|
-
expect(text).toContain("
|
|
116
|
+
expect(text).toContain("directory_lookup_person");
|
|
117
|
+
expect(text).toContain("directory_lookup_work");
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
it("accepts optional name argument", async () => {
|
|
@@ -120,26 +124,5 @@ describe("Directory Skill Registry", () => {
|
|
|
120
124
|
const text = result.messages[0].content.text;
|
|
121
125
|
expect(text).toContain("Max Martin");
|
|
122
126
|
});
|
|
123
|
-
|
|
124
|
-
it("handles identifier routing from old directory-autopilot", async () => {
|
|
125
|
-
const result = await registry.getPrompt("research-creator");
|
|
126
|
-
const text = result.messages[0].content.text;
|
|
127
|
-
expect(text).toContain("directory_lookup_isrc");
|
|
128
|
-
expect(text).toContain("directory_list_works");
|
|
129
|
-
expect(text).toContain("directory_list_people");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("includes collaborator network exploration", async () => {
|
|
133
|
-
const result = await registry.getPrompt("research-creator");
|
|
134
|
-
const text = result.messages[0].content.text;
|
|
135
|
-
expect(text).toMatch(/collaborator.*network/i);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("includes similarity search", async () => {
|
|
139
|
-
const result = await registry.getPrompt("research-creator");
|
|
140
|
-
const text = result.messages[0].content.text;
|
|
141
|
-
expect(text).toContain("directory_search_recordings");
|
|
142
|
-
expect(text).toMatch(/similar/i);
|
|
143
|
-
});
|
|
144
127
|
});
|
|
145
128
|
});
|