@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +43 -26
  2. package/package.json +4 -2
  3. package/scripts/build-skills.ts +192 -0
  4. package/src/__tests__/prompts/index.test.ts +47 -64
  5. package/src/__tests__/prompts/prompt-eval-harness.test.ts +135 -104
  6. package/src/__tests__/skills/skills-registry.test.ts +63 -0
  7. package/src/__tests__/skills/skills-tools.test.ts +74 -0
  8. package/src/__tests__/tools/chain.test.ts +122 -0
  9. package/src/__tests__/tools/composability-chains.test.ts +4 -2
  10. package/src/__tests__/tools/people.test.ts +9 -3
  11. package/src/__tests__/tools/works.test.ts +32 -3
  12. package/src/prompts/index.ts +97 -141
  13. package/src/prompts/public-question-atlas.ts +557 -0
  14. package/src/server.ts +123 -11
  15. package/src/skills/find-music-for-sync-brief/SKILL.md +89 -0
  16. package/src/skills/index.ts +63 -0
  17. package/src/skills/skills.generated.ts +33 -0
  18. package/src/tools/chain.ts +118 -0
  19. package/src/tools/index.ts +15 -0
  20. package/src/tools/people.ts +22 -41
  21. package/src/tools/recordings.ts +7 -3
  22. package/src/tools/search.ts +7 -4
  23. package/src/tools/skills.ts +146 -0
  24. package/src/tools/works.ts +39 -46
  25. package/dist/client.d.ts +0 -12
  26. package/dist/client.d.ts.map +0 -1
  27. package/dist/client.js +0 -93
  28. package/dist/client.js.map +0 -1
  29. package/dist/config.d.ts +0 -9
  30. package/dist/config.d.ts.map +0 -1
  31. package/dist/config.js +0 -15
  32. package/dist/config.js.map +0 -1
  33. package/dist/index.d.ts +0 -3
  34. package/dist/index.d.ts.map +0 -1
  35. package/dist/index.js +0 -28
  36. package/dist/index.js.map +0 -1
  37. package/dist/prompts/index.d.ts +0 -33
  38. package/dist/prompts/index.d.ts.map +0 -1
  39. package/dist/prompts/index.js +0 -208
  40. package/dist/prompts/index.js.map +0 -1
  41. package/dist/resources/llms-primer.d.ts +0 -2
  42. package/dist/resources/llms-primer.d.ts.map +0 -1
  43. package/dist/resources/llms-primer.js +0 -35
  44. package/dist/resources/llms-primer.js.map +0 -1
  45. package/dist/server.d.ts +0 -13
  46. package/dist/server.d.ts.map +0 -1
  47. package/dist/server.js +0 -104
  48. package/dist/server.js.map +0 -1
  49. package/dist/tools/index.d.ts +0 -26
  50. package/dist/tools/index.d.ts.map +0 -1
  51. package/dist/tools/index.js +0 -43
  52. package/dist/tools/index.js.map +0 -1
  53. package/dist/tools/people.d.ts +0 -14
  54. package/dist/tools/people.d.ts.map +0 -1
  55. package/dist/tools/people.js +0 -190
  56. package/dist/tools/people.js.map +0 -1
  57. package/dist/tools/recordings.d.ts +0 -12
  58. package/dist/tools/recordings.d.ts.map +0 -1
  59. package/dist/tools/recordings.js +0 -141
  60. package/dist/tools/recordings.js.map +0 -1
  61. package/dist/tools/search.d.ts +0 -12
  62. package/dist/tools/search.d.ts.map +0 -1
  63. package/dist/tools/search.js +0 -53
  64. package/dist/tools/search.js.map +0 -1
  65. package/dist/tools/works.d.ts +0 -15
  66. package/dist/tools/works.d.ts.map +0 -1
  67. package/dist/tools/works.js +0 -249
  68. package/dist/tools/works.js.map +0 -1
  69. package/dist/utils/errors.d.ts +0 -14
  70. package/dist/utils/errors.d.ts.map +0 -1
  71. package/dist/utils/errors.js +0 -48
  72. package/dist/utils/errors.js.map +0 -1
  73. package/dist/utils/formatting.d.ts +0 -10
  74. package/dist/utils/formatting.d.ts.map +0 -1
  75. package/dist/utils/formatting.js +0 -16
  76. 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` will be documented in this file.
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 [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
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] — 2026-04-13
11
+ ## [1.1.0] - 2026-04-21
15
12
 
16
- ### Changed
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
- - **ADR-171: Prompts replaced with workflow-level skills.**
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
- New skills: `discover-music`, `research-creator`.
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
- Retired prompts: `find-music`, `directory-autopilot`.
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
- ## [1.0.0] — 2026-04-06
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
- ### Added
44
+ ### Changed
35
45
 
36
- - Initial release of the PICA Public Directory MCP Server
37
- - 12 read-only tools for searching works, recordings, people, and identifiers
38
- - 3 prompts: `find-music`, `research-creator`, `directory-autopilot`
39
- - Deterministic eval harness for prompt routing validation
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.1.0",
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 Skill Registry Tests
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 Skill Registry", () => {
10
+ describe("Directory Prompt Registry", () => {
11
11
  const registry = new PromptRegistry();
12
12
 
13
13
  describe("listPrompts", () => {
14
- it("returns 2 skills", () => {
14
+ it("returns 3 prompts", () => {
15
15
  const prompts = registry.listPrompts();
16
- expect(prompts).toHaveLength(2);
16
+ expect(prompts).toHaveLength(3);
17
17
  });
18
18
 
19
- it("includes all expected skills", () => {
19
+ it("includes all expected prompts", () => {
20
20
  const names = registry.listPrompts().map((p) => p.name);
21
- expect(names).toContain("discover-music");
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("skill names are unique and kebab-case", () => {
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 skills without error", async () => {
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
- it("every skill includes the shared preamble", async () => {
54
- const prompts = registry.listPrompts();
55
- for (const prompt of prompts) {
56
- const result = await registry.getPrompt(prompt.name);
57
- const text = result.messages[0].content.text;
58
- expect(text).toContain("llms://primer");
59
- expect(text).toContain("Discovery principle");
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("discover-music skill", () => {
87
+ describe("find-music workflow", () => {
65
88
  it("references audio search tool", async () => {
66
- const result = await registry.getPrompt("discover-music");
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("directory_lookup_work_full");
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("discover-music");
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("discover-music", {
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 skill", () => {
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("directory_lookup_person_full");
113
- expect(text).toContain("directory_lookup_work_full");
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
  });