@nebutra/play-loader 0.1.0 → 0.1.1
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/.nebutra/debug/play-loader.jsonl +7 -7
- package/.turbo/turbo-build.log +6 -6
- package/.turbo/turbo-test.log +5 -5
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-7JPGPQKB.js → chunk-TVBZH43E.js} +7 -18
- package/dist/chunk-TVBZH43E.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/package.json +5 -3
- package/src/index.test.ts +9 -0
- package/src/index.ts +7 -22
- package/dist/chunk-7JPGPQKB.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
{"at":"2026-05-
|
|
2
|
-
{"at":"2026-05-
|
|
3
|
-
{"at":"2026-05-
|
|
4
|
-
{"at":"2026-05-
|
|
5
|
-
{"at":"2026-05-
|
|
6
|
-
{"at":"2026-05-
|
|
7
|
-
{"at":"2026-05-
|
|
1
|
+
{"at":"2026-05-19T08:15:22.732Z","type":"reload","plays":0}
|
|
2
|
+
{"at":"2026-05-19T08:15:22.765Z","type":"reload","plays":1}
|
|
3
|
+
{"at":"2026-05-19T08:15:22.768Z","type":"write","play":"launch_page"}
|
|
4
|
+
{"at":"2026-05-19T08:15:22.780Z","type":"load","play":"launch_page"}
|
|
5
|
+
{"at":"2026-05-19T08:15:22.825Z","type":"reload","plays":2}
|
|
6
|
+
{"at":"2026-05-19T08:15:22.825Z","type":"write","play":"launch_page_copy"}
|
|
7
|
+
{"at":"2026-05-19T08:15:22.844Z","type":"load","play":"launch_page_copy"}
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @nebutra/play-loader@0.1.
|
|
2
|
+
> @nebutra/play-loader@0.1.1 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[32mESM[39m [1mdist/cli.js [22m[32m2.28 KB[39m
|
|
13
13
|
[32mESM[39m [1mdist/index.js [22m[32m226.00 B[39m
|
|
14
|
-
[32mESM[39m [1mdist/chunk-
|
|
14
|
+
[32mESM[39m [1mdist/chunk-TVBZH43E.js [22m[32m7.56 KB[39m
|
|
15
15
|
[32mESM[39m [1mdist/cli.js.map [22m[32m4.29 KB[39m
|
|
16
16
|
[32mESM[39m [1mdist/index.js.map [22m[32m71.00 B[39m
|
|
17
|
-
[32mESM[39m [1mdist/chunk-
|
|
18
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
+
[32mESM[39m [1mdist/chunk-TVBZH43E.js.map [22m[32m15.16 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 81ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 6327ms
|
|
21
21
|
[32mDTS[39m [1mdist/cli.d.ts [22m[32m13.00 B[39m
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.91 KB[39m
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
> @nebutra/play-loader@0.1.
|
|
2
|
+
> @nebutra/play-loader@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader
|
|
3
3
|
> vitest run
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.4 [39m[90m/home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader[39m
|
|
7
7
|
|
|
8
|
-
[32m✓[39m src/index.test.ts [2m([22m[
|
|
8
|
+
[32m✓[39m src/index.test.ts [2m([22m[2m5 tests[22m[2m)[22m[33m 309[2mms[22m[39m
|
|
9
9
|
|
|
10
10
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
11
|
-
[2m Tests [22m [1m[
|
|
12
|
-
[2m Start at [22m
|
|
13
|
-
[2m Duration [22m 2.
|
|
11
|
+
[2m Tests [22m [1m[32m5 passed[39m[22m[90m (5)[39m
|
|
12
|
+
[2m Start at [22m 08:15:20
|
|
13
|
+
[2m Duration [22m 2.26s[2m (transform 925ms, setup 0ms, import 1.18s, tests 309ms, environment 0ms)[22m
|
|
14
14
|
|
package/CHANGELOG.md
ADDED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
3
3
|
import { dirname, join, relative } from "path";
|
|
4
|
+
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
4
5
|
import { CapabilityError } from "@nebutra/errors";
|
|
5
|
-
import {
|
|
6
|
+
import { parseSkillMarkdown } from "@nebutra/tool-registry";
|
|
6
7
|
async function walk(dir) {
|
|
7
8
|
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
8
9
|
const files = [];
|
|
@@ -25,24 +26,11 @@ function asRecord(value) {
|
|
|
25
26
|
function normalizePlayName(name) {
|
|
26
27
|
return name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
27
28
|
}
|
|
28
|
-
function playDebugPath() {
|
|
29
|
-
return join(process.cwd(), ".nebutra", "debug", "play-loader.jsonl");
|
|
30
|
-
}
|
|
31
29
|
async function appendPlayDebug(entry) {
|
|
32
|
-
|
|
33
|
-
await mkdir(dirname(path), { recursive: true });
|
|
34
|
-
await writeFile(path, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
|
|
35
|
-
`, {
|
|
36
|
-
flag: "a"
|
|
37
|
-
});
|
|
30
|
+
await appendCapabilityDebug("play-loader", entry);
|
|
38
31
|
}
|
|
39
32
|
async function readPlayDebug(limit = 10) {
|
|
40
|
-
|
|
41
|
-
const raw = await readFile(playDebugPath(), "utf8");
|
|
42
|
-
return raw.trim().split("\n").filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
|
|
43
|
-
} catch {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
33
|
+
return readCapabilityDebug("play-loader", { limit });
|
|
46
34
|
}
|
|
47
35
|
function parseSubagents(value) {
|
|
48
36
|
if (!Array.isArray(value)) return [];
|
|
@@ -65,7 +53,7 @@ function parsePlayDependencies(value) {
|
|
|
65
53
|
}
|
|
66
54
|
function parsePlayMarkdown(markdown) {
|
|
67
55
|
const loaded = parseSkillMarkdown(markdown);
|
|
68
|
-
const { frontmatter } =
|
|
56
|
+
const { frontmatter } = loaded;
|
|
69
57
|
if (frontmatter.kind !== "play") {
|
|
70
58
|
throw new CapabilityError("play-loader", "Play SKILL.md must set kind: play", {
|
|
71
59
|
suggestion: "Set `kind: play` in frontmatter or keep this document in tool-registry only.",
|
|
@@ -76,6 +64,7 @@ function parsePlayMarkdown(markdown) {
|
|
|
76
64
|
return {
|
|
77
65
|
meta: { ...loaded.meta, kind: "play" },
|
|
78
66
|
body: loaded.body,
|
|
67
|
+
frontmatter,
|
|
79
68
|
requiredSkills: asStringArray(frontmatter.required_skills),
|
|
80
69
|
subAgents: parseSubagents(frontmatter.sub_agents),
|
|
81
70
|
dependsOnPlays: parsePlayDependencies(frontmatter.depends_on_plays)
|
|
@@ -230,4 +219,4 @@ export {
|
|
|
230
219
|
resolvePlayChain,
|
|
231
220
|
PlayLoader
|
|
232
221
|
};
|
|
233
|
-
//# sourceMappingURL=chunk-
|
|
222
|
+
//# sourceMappingURL=chunk-TVBZH43E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { type ParsedFrontmatter, parseSkillMarkdown, type SkillMeta } from \"@nebutra/tool-registry\";\n\nexport interface PlayDependency {\n readonly play: string;\n readonly outputs: readonly string[];\n}\n\nexport interface PlaySubagent {\n readonly role: string;\n readonly allowedSkills: readonly string[];\n}\n\nexport interface PlayMeta extends SkillMeta {\n readonly kind: \"play\";\n}\n\nexport interface LoadedPlay {\n readonly meta: PlayMeta;\n readonly body: string;\n readonly frontmatter: ParsedFrontmatter;\n readonly requiredSkills: readonly string[];\n readonly subAgents: readonly PlaySubagent[];\n readonly dependsOnPlays: readonly PlayDependency[];\n}\n\nexport interface PlayNode {\n readonly name: string;\n readonly dependsOn: readonly string[];\n}\n\nexport interface PlayTestReport {\n readonly name: string;\n readonly ok: boolean;\n readonly checks: readonly string[];\n readonly suggestion?: string;\n}\n\nexport interface PlayLoaderDoctorReport {\n readonly ok: boolean;\n readonly plays: number;\n readonly root: string;\n readonly suggestion?: string;\n}\n\nasync function walk(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n const files: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) files.push(...(await walk(full)));\n else files.push(full);\n }\n return files;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : [];\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction normalizePlayName(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\nasync function appendPlayDebug(entry: Record<string, unknown>): Promise<void> {\n await appendCapabilityDebug(\"play-loader\", entry);\n}\n\nexport async function readPlayDebug(limit = 10): Promise<unknown[]> {\n return readCapabilityDebug(\"play-loader\", { limit });\n}\n\nfunction parseSubagents(value: unknown): PlaySubagent[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlaySubagent[] => {\n const record = asRecord(item);\n const role = asString(record?.role);\n if (!record || !role) return [];\n return [{ role, allowedSkills: asStringArray(record.allowed_skills) }];\n });\n}\n\nfunction parsePlayDependencies(value: unknown): PlayDependency[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlayDependency[] => {\n if (typeof item === \"string\") return [{ play: item, outputs: [] }];\n const record = asRecord(item);\n const play = asString(record?.play);\n if (!record || !play) return [];\n return [{ play, outputs: asStringArray(record.outputs) }];\n });\n}\n\nexport function parsePlayMarkdown(markdown: string): LoadedPlay {\n const loaded = parseSkillMarkdown(markdown);\n const { frontmatter } = loaded;\n if (frontmatter.kind !== \"play\") {\n throw new CapabilityError(\"play-loader\", \"Play SKILL.md must set kind: play\", {\n suggestion: \"Set `kind: play` in frontmatter or keep this document in tool-registry only.\",\n statusCode: 400,\n metadata: { name: loaded.meta.name, kind: frontmatter.kind },\n });\n }\n\n return {\n meta: { ...loaded.meta, kind: \"play\" },\n body: loaded.body,\n frontmatter,\n requiredSkills: asStringArray(frontmatter.required_skills),\n subAgents: parseSubagents(frontmatter.sub_agents),\n dependsOnPlays: parsePlayDependencies(frontmatter.depends_on_plays),\n };\n}\n\nexport function resolvePlayChain(nodes: readonly PlayNode[]): readonly string[] {\n const byName = new Map(nodes.map((node) => [node.name, node]));\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const ordered: string[] = [];\n\n const visit = (name: string): void => {\n if (visited.has(name)) return;\n if (visiting.has(name)) {\n throw new CapabilityError(\"play-loader\", \"Play dependency cycle detected\", {\n suggestion: \"Remove the cycle or merge mutually dependent plays into one declaration.\",\n statusCode: 400,\n metadata: { name },\n });\n }\n visiting.add(name);\n const node = byName.get(name);\n for (const dependency of node?.dependsOn ?? []) visit(dependency);\n visiting.delete(name);\n visited.add(name);\n ordered.push(name);\n };\n\n for (const node of nodes) visit(node.name);\n return ordered;\n}\n\nexport class PlayLoader {\n readonly #root: string;\n readonly #plays = new Map<string, { readonly meta: PlayMeta; readonly path: string }>();\n\n private constructor(root: string) {\n this.#root = root;\n }\n\n static async open(root: string): Promise<PlayLoader> {\n const loader = new PlayLoader(root);\n await mkdir(loader.playsRoot(), { recursive: true });\n await loader.reload();\n return loader;\n }\n\n playsRoot(): string {\n return join(this.#root, \"plays\");\n }\n\n async writePlay(name: string, markdown: string): Promise<void> {\n const normalized = normalizePlayName(name);\n const parsed = parsePlayMarkdown(markdown);\n if (parsed.meta.name !== normalized) {\n throw new CapabilityError(\"play-loader\", \"Play name does not match path\", {\n suggestion: \"Use the same lowercase codename in the path and SKILL.md frontmatter.\",\n metadata: { pathName: normalized, playName: parsed.meta.name },\n statusCode: 400,\n });\n }\n const path = join(this.playsRoot(), normalized, \"SKILL.md\");\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, markdown, \"utf8\");\n await this.reload();\n await appendPlayDebug({ type: \"write\", play: normalized });\n }\n\n async newPlay(name: string): Promise<LoadedPlay> {\n const normalized = normalizePlayName(name);\n const markdown = [\n \"---\",\n `name: ${normalized}`,\n \"kind: play\",\n \"version: 1.0.0\",\n `description: ${normalized.replaceAll(\"_\", \" \")} play`,\n \"required_skills: []\",\n \"sub_agents: []\",\n \"depends_on_plays: []\",\n \"---\",\n \"\",\n \"## What this play does\",\n \"\",\n \"Describe the user story and expected output.\",\n ].join(\"\\n\");\n await this.writePlay(normalized, markdown);\n return this.load(normalized);\n }\n\n async reload(): Promise<void> {\n this.#plays.clear();\n for (const file of await walk(this.playsRoot())) {\n if (!file.endsWith(\"SKILL.md\")) continue;\n const raw = await readFile(file, \"utf8\");\n const play = parsePlayMarkdown(raw);\n this.#plays.set(play.meta.name, { meta: play.meta, path: relative(this.#root, file) });\n }\n await appendPlayDebug({ type: \"reload\", plays: this.#plays.size });\n }\n\n async list(): Promise<PlayMeta[]> {\n return [...this.#plays.values()]\n .map((entry) => entry.meta)\n .sort((left, right) => left.name.localeCompare(right.name));\n }\n\n async load(name: string): Promise<LoadedPlay> {\n const play = this.#plays.get(normalizePlayName(name));\n if (!play) {\n throw new CapabilityError(\"play-loader\", \"Play not found\", {\n suggestion: \"Run `pnpm play:list` or create it with `pnpm play:new <name>`.\",\n metadata: { name },\n statusCode: 404,\n });\n }\n const loaded = parsePlayMarkdown(await readFile(join(this.#root, play.path), \"utf8\"));\n await appendPlayDebug({ type: \"load\", play: loaded.meta.name });\n return loaded;\n }\n\n async install(source: string): Promise<LoadedPlay> {\n if (!source.startsWith(\"file:\")) {\n throw new CapabilityError(\"play-loader\", \"Only file installs are enabled locally\", {\n suggestion:\n \"Download remote plays through a reviewed distribution workflow, then install with file:<path>.\",\n metadata: { source },\n statusCode: 400,\n });\n }\n const raw = await readFile(source.slice(\"file:\".length), \"utf8\");\n const play = parsePlayMarkdown(raw);\n await this.writePlay(play.meta.name, raw);\n return this.load(play.meta.name);\n }\n\n async test(name: string): Promise<PlayTestReport> {\n try {\n const play = await this.load(name);\n return {\n name: play.meta.name,\n ok: play.body.length > 0,\n checks: [\"frontmatter\", \"kind:play\", \"body\", \"dependency-dag\"],\n ...(!play.body ? { suggestion: \"Add a markdown body after the frontmatter.\" } : {}),\n };\n } catch (error) {\n return {\n name,\n ok: false,\n checks: [],\n suggestion:\n error instanceof CapabilityError\n ? \"Create the play with `pnpm play:new <name>` or fix its SKILL.md metadata.\"\n : \"Run `pnpm play:list` and verify the play directory exists.\",\n };\n }\n }\n\n async doctor(): Promise<PlayLoaderDoctorReport> {\n const plays = this.#plays.size;\n return {\n ok: plays > 0,\n plays,\n root: this.#root,\n ...(plays === 0 ? { suggestion: \"Create a play with `pnpm play:new <name>`.\" } : {}),\n };\n }\n}\n"],"mappings":";AAAA,SAAS,OAAO,SAAS,UAAU,iBAAiB;AACpD,SAAS,SAAS,MAAM,gBAAgB;AACxC,SAAS,uBAAuB,2BAA2B;AAC3D,SAAS,uBAAuB;AAChC,SAAiC,0BAA0C;AA4C3E,eAAe,KAAK,KAAgC;AAClD,QAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AAC1E,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,UAAM,OAAO,KAAK,KAAK,MAAM,IAAI;AACjC,QAAI,MAAM,YAAY,EAAG,OAAM,KAAK,GAAI,MAAM,KAAK,IAAI,CAAE;AAAA,QACpD,OAAM,KAAK,IAAI;AAAA,EACtB;AACA,SAAO;AACT;AAEA,SAAS,SAAS,OAAoC;AACpD,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAS,cAAc,OAA0B;AAC/C,SAAO,MAAM,QAAQ,KAAK,IACtB,MAAM,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,IAC/D,CAAC;AACP;AAEA,SAAS,SAAS,OAAqD;AACrE,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC5D,QACD;AACN;AAEA,SAAS,kBAAkB,MAAsB;AAC/C,SAAO,KACJ,KAAK,EACL,YAAY,EACZ,QAAQ,iBAAiB,GAAG,EAC5B,QAAQ,YAAY,EAAE;AAC3B;AAEA,eAAe,gBAAgB,OAA+C;AAC5E,QAAM,sBAAsB,eAAe,KAAK;AAClD;AAEA,eAAsB,cAAc,QAAQ,IAAwB;AAClE,SAAO,oBAAoB,eAAe,EAAE,MAAM,CAAC;AACrD;AAEA,SAAS,eAAe,OAAgC;AACtD,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,SAAO,MAAM,QAAQ,CAAC,SAAyB;AAC7C,UAAM,SAAS,SAAS,IAAI;AAC5B,UAAM,OAAO,SAAS,QAAQ,IAAI;AAClC,QAAI,CAAC,UAAU,CAAC,KAAM,QAAO,CAAC;AAC9B,WAAO,CAAC,EAAE,MAAM,eAAe,cAAc,OAAO,cAAc,EAAE,CAAC;AAAA,EACvE,CAAC;AACH;AAEA,SAAS,sBAAsB,OAAkC;AAC/D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,SAAO,MAAM,QAAQ,CAAC,SAA2B;AAC/C,QAAI,OAAO,SAAS,SAAU,QAAO,CAAC,EAAE,MAAM,MAAM,SAAS,CAAC,EAAE,CAAC;AACjE,UAAM,SAAS,SAAS,IAAI;AAC5B,UAAM,OAAO,SAAS,QAAQ,IAAI;AAClC,QAAI,CAAC,UAAU,CAAC,KAAM,QAAO,CAAC;AAC9B,WAAO,CAAC,EAAE,MAAM,SAAS,cAAc,OAAO,OAAO,EAAE,CAAC;AAAA,EAC1D,CAAC;AACH;AAEO,SAAS,kBAAkB,UAA8B;AAC9D,QAAM,SAAS,mBAAmB,QAAQ;AAC1C,QAAM,EAAE,YAAY,IAAI;AACxB,MAAI,YAAY,SAAS,QAAQ;AAC/B,UAAM,IAAI,gBAAgB,eAAe,qCAAqC;AAAA,MAC5E,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,UAAU,EAAE,MAAM,OAAO,KAAK,MAAM,MAAM,YAAY,KAAK;AAAA,IAC7D,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO;AAAA,IACrC,MAAM,OAAO;AAAA,IACb;AAAA,IACA,gBAAgB,cAAc,YAAY,eAAe;AAAA,IACzD,WAAW,eAAe,YAAY,UAAU;AAAA,IAChD,gBAAgB,sBAAsB,YAAY,gBAAgB;AAAA,EACpE;AACF;AAEO,SAAS,iBAAiB,OAA+C;AAC9E,QAAM,SAAS,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7D,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,WAAW,oBAAI,IAAY;AACjC,QAAM,UAAoB,CAAC;AAE3B,QAAM,QAAQ,CAAC,SAAuB;AACpC,QAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,QAAI,SAAS,IAAI,IAAI,GAAG;AACtB,YAAM,IAAI,gBAAgB,eAAe,kCAAkC;AAAA,QACzE,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,UAAU,EAAE,KAAK;AAAA,MACnB,CAAC;AAAA,IACH;AACA,aAAS,IAAI,IAAI;AACjB,UAAM,OAAO,OAAO,IAAI,IAAI;AAC5B,eAAW,cAAc,MAAM,aAAa,CAAC,EAAG,OAAM,UAAU;AAChE,aAAS,OAAO,IAAI;AACpB,YAAQ,IAAI,IAAI;AAChB,YAAQ,KAAK,IAAI;AAAA,EACnB;AAEA,aAAW,QAAQ,MAAO,OAAM,KAAK,IAAI;AACzC,SAAO;AACT;AAEO,IAAM,aAAN,MAAM,YAAW;AAAA,EACb;AAAA,EACA,SAAS,oBAAI,IAAgE;AAAA,EAE9E,YAAY,MAAc;AAChC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,aAAa,KAAK,MAAmC;AACnD,UAAM,SAAS,IAAI,YAAW,IAAI;AAClC,UAAM,MAAM,OAAO,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,UAAM,OAAO,OAAO;AACpB,WAAO;AAAA,EACT;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK,KAAK,OAAO,OAAO;AAAA,EACjC;AAAA,EAEA,MAAM,UAAU,MAAc,UAAiC;AAC7D,UAAM,aAAa,kBAAkB,IAAI;AACzC,UAAM,SAAS,kBAAkB,QAAQ;AACzC,QAAI,OAAO,KAAK,SAAS,YAAY;AACnC,YAAM,IAAI,gBAAgB,eAAe,iCAAiC;AAAA,QACxE,YAAY;AAAA,QACZ,UAAU,EAAE,UAAU,YAAY,UAAU,OAAO,KAAK,KAAK;AAAA,QAC7D,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AACA,UAAM,OAAO,KAAK,KAAK,UAAU,GAAG,YAAY,UAAU;AAC1D,UAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,UAAM,UAAU,MAAM,UAAU,MAAM;AACtC,UAAM,KAAK,OAAO;AAClB,UAAM,gBAAgB,EAAE,MAAM,SAAS,MAAM,WAAW,CAAC;AAAA,EAC3D;AAAA,EAEA,MAAM,QAAQ,MAAmC;AAC/C,UAAM,aAAa,kBAAkB,IAAI;AACzC,UAAM,WAAW;AAAA,MACf;AAAA,MACA,SAAS,UAAU;AAAA,MACnB;AAAA,MACA;AAAA,MACA,gBAAgB,WAAW,WAAW,KAAK,GAAG,CAAC;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,KAAK,UAAU,YAAY,QAAQ;AACzC,WAAO,KAAK,KAAK,UAAU;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAwB;AAC5B,SAAK,OAAO,MAAM;AAClB,eAAW,QAAQ,MAAM,KAAK,KAAK,UAAU,CAAC,GAAG;AAC/C,UAAI,CAAC,KAAK,SAAS,UAAU,EAAG;AAChC,YAAM,MAAM,MAAM,SAAS,MAAM,MAAM;AACvC,YAAM,OAAO,kBAAkB,GAAG;AAClC,WAAK,OAAO,IAAI,KAAK,KAAK,MAAM,EAAE,MAAM,KAAK,MAAM,MAAM,SAAS,KAAK,OAAO,IAAI,EAAE,CAAC;AAAA,IACvF;AACA,UAAM,gBAAgB,EAAE,MAAM,UAAU,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA,EACnE;AAAA,EAEA,MAAM,OAA4B;AAChC,WAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAC5B,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK,CAAC,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,KAAK,MAAmC;AAC5C,UAAM,OAAO,KAAK,OAAO,IAAI,kBAAkB,IAAI,CAAC;AACpD,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,gBAAgB,eAAe,kBAAkB;AAAA,QACzD,YAAY;AAAA,QACZ,UAAU,EAAE,KAAK;AAAA,QACjB,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AACA,UAAM,SAAS,kBAAkB,MAAM,SAAS,KAAK,KAAK,OAAO,KAAK,IAAI,GAAG,MAAM,CAAC;AACpF,UAAM,gBAAgB,EAAE,MAAM,QAAQ,MAAM,OAAO,KAAK,KAAK,CAAC;AAC9D,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,QAAqC;AACjD,QAAI,CAAC,OAAO,WAAW,OAAO,GAAG;AAC/B,YAAM,IAAI,gBAAgB,eAAe,0CAA0C;AAAA,QACjF,YACE;AAAA,QACF,UAAU,EAAE,OAAO;AAAA,QACnB,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AACA,UAAM,MAAM,MAAM,SAAS,OAAO,MAAM,QAAQ,MAAM,GAAG,MAAM;AAC/D,UAAM,OAAO,kBAAkB,GAAG;AAClC,UAAM,KAAK,UAAU,KAAK,KAAK,MAAM,GAAG;AACxC,WAAO,KAAK,KAAK,KAAK,KAAK,IAAI;AAAA,EACjC;AAAA,EAEA,MAAM,KAAK,MAAuC;AAChD,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,KAAK,IAAI;AACjC,aAAO;AAAA,QACL,MAAM,KAAK,KAAK;AAAA,QAChB,IAAI,KAAK,KAAK,SAAS;AAAA,QACvB,QAAQ,CAAC,eAAe,aAAa,QAAQ,gBAAgB;AAAA,QAC7D,GAAI,CAAC,KAAK,OAAO,EAAE,YAAY,6CAA6C,IAAI,CAAC;AAAA,MACnF;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL;AAAA,QACA,IAAI;AAAA,QACJ,QAAQ,CAAC;AAAA,QACT,YACE,iBAAiB,kBACb,8EACA;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAA0C;AAC9C,UAAM,QAAQ,KAAK,OAAO;AAC1B,WAAO;AAAA,MACL,IAAI,QAAQ;AAAA,MACZ;AAAA,MACA,MAAM,KAAK;AAAA,MACX,GAAI,UAAU,IAAI,EAAE,YAAY,6CAA6C,IAAI,CAAC;AAAA,IACpF;AAAA,EACF;AACF;","names":[]}
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SkillMeta } from '@nebutra/tool-registry';
|
|
1
|
+
import { SkillMeta, ParsedFrontmatter } from '@nebutra/tool-registry';
|
|
2
2
|
|
|
3
3
|
interface PlayDependency {
|
|
4
4
|
readonly play: string;
|
|
@@ -14,6 +14,7 @@ interface PlayMeta extends SkillMeta {
|
|
|
14
14
|
interface LoadedPlay {
|
|
15
15
|
readonly meta: PlayMeta;
|
|
16
16
|
readonly body: string;
|
|
17
|
+
readonly frontmatter: ParsedFrontmatter;
|
|
17
18
|
readonly requiredSkills: readonly string[];
|
|
18
19
|
readonly subAgents: readonly PlaySubagent[];
|
|
19
20
|
readonly dependsOnPlays: readonly PlayDependency[];
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/play-loader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Declarative SKILL.md play loader and dependency planner",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"featureId": "play-loader",
|
|
20
20
|
"category": "ai",
|
|
21
|
+
"surface": "product-orchestration",
|
|
21
22
|
"summary": "SKILL.md play parser, dependency-chain planner, and agent-runtime runner seam"
|
|
22
23
|
},
|
|
23
24
|
"main": "./src/index.ts",
|
|
@@ -26,8 +27,9 @@
|
|
|
26
27
|
".": "./src/index.ts"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@nebutra/
|
|
30
|
-
"@nebutra/
|
|
30
|
+
"@nebutra/capability-kit": "0.2.1",
|
|
31
|
+
"@nebutra/errors": "0.1.1",
|
|
32
|
+
"@nebutra/tool-registry": "0.1.1"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
33
35
|
"@types/node": "^22.19.15",
|
package/src/index.test.ts
CHANGED
|
@@ -56,6 +56,15 @@ describe("play-loader", () => {
|
|
|
56
56
|
expect(play.body).toContain("Create a launch page");
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
it("keeps raw play frontmatter on the shared SKILL parser contract", () => {
|
|
60
|
+
const play = parsePlayMarkdown(playMarkdown);
|
|
61
|
+
|
|
62
|
+
expect(play.frontmatter).toMatchObject({
|
|
63
|
+
kind: "play",
|
|
64
|
+
required_skills: ["content_store.write", "tool_protocol.call"],
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
59
68
|
it("rejects non-play SKILL.md documents with a fix suggestion", () => {
|
|
60
69
|
const markdown = playMarkdown.replace("kind: play", "kind: skill");
|
|
61
70
|
expect(() => parsePlayMarkdown(markdown)).toThrow(/kind: play/i);
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
3
4
|
import { CapabilityError } from "@nebutra/errors";
|
|
4
|
-
import {
|
|
5
|
+
import { type ParsedFrontmatter, parseSkillMarkdown, type SkillMeta } from "@nebutra/tool-registry";
|
|
5
6
|
|
|
6
7
|
export interface PlayDependency {
|
|
7
8
|
readonly play: string;
|
|
@@ -20,6 +21,7 @@ export interface PlayMeta extends SkillMeta {
|
|
|
20
21
|
export interface LoadedPlay {
|
|
21
22
|
readonly meta: PlayMeta;
|
|
22
23
|
readonly body: string;
|
|
24
|
+
readonly frontmatter: ParsedFrontmatter;
|
|
23
25
|
readonly requiredSkills: readonly string[];
|
|
24
26
|
readonly subAgents: readonly PlaySubagent[];
|
|
25
27
|
readonly dependsOnPlays: readonly PlayDependency[];
|
|
@@ -79,30 +81,12 @@ function normalizePlayName(name: string): string {
|
|
|
79
81
|
.replace(/^_+|_+$/g, "");
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
function playDebugPath(): string {
|
|
83
|
-
return join(process.cwd(), ".nebutra", "debug", "play-loader.jsonl");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
84
|
async function appendPlayDebug(entry: Record<string, unknown>): Promise<void> {
|
|
87
|
-
|
|
88
|
-
await mkdir(dirname(path), { recursive: true });
|
|
89
|
-
await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`, {
|
|
90
|
-
flag: "a",
|
|
91
|
-
});
|
|
85
|
+
await appendCapabilityDebug("play-loader", entry);
|
|
92
86
|
}
|
|
93
87
|
|
|
94
88
|
export async function readPlayDebug(limit = 10): Promise<unknown[]> {
|
|
95
|
-
|
|
96
|
-
const raw = await readFile(playDebugPath(), "utf8");
|
|
97
|
-
return raw
|
|
98
|
-
.trim()
|
|
99
|
-
.split("\n")
|
|
100
|
-
.filter(Boolean)
|
|
101
|
-
.slice(-limit)
|
|
102
|
-
.map((line) => JSON.parse(line) as unknown);
|
|
103
|
-
} catch {
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
89
|
+
return readCapabilityDebug("play-loader", { limit });
|
|
106
90
|
}
|
|
107
91
|
|
|
108
92
|
function parseSubagents(value: unknown): PlaySubagent[] {
|
|
@@ -128,7 +112,7 @@ function parsePlayDependencies(value: unknown): PlayDependency[] {
|
|
|
128
112
|
|
|
129
113
|
export function parsePlayMarkdown(markdown: string): LoadedPlay {
|
|
130
114
|
const loaded = parseSkillMarkdown(markdown);
|
|
131
|
-
const { frontmatter } =
|
|
115
|
+
const { frontmatter } = loaded;
|
|
132
116
|
if (frontmatter.kind !== "play") {
|
|
133
117
|
throw new CapabilityError("play-loader", "Play SKILL.md must set kind: play", {
|
|
134
118
|
suggestion: "Set `kind: play` in frontmatter or keep this document in tool-registry only.",
|
|
@@ -140,6 +124,7 @@ export function parsePlayMarkdown(markdown: string): LoadedPlay {
|
|
|
140
124
|
return {
|
|
141
125
|
meta: { ...loaded.meta, kind: "play" },
|
|
142
126
|
body: loaded.body,
|
|
127
|
+
frontmatter,
|
|
143
128
|
requiredSkills: asStringArray(frontmatter.required_skills),
|
|
144
129
|
subAgents: parseSubagents(frontmatter.sub_agents),
|
|
145
130
|
dependsOnPlays: parsePlayDependencies(frontmatter.depends_on_plays),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { parseSkillFrontmatter, parseSkillMarkdown, type SkillMeta } from \"@nebutra/tool-registry\";\n\nexport interface PlayDependency {\n readonly play: string;\n readonly outputs: readonly string[];\n}\n\nexport interface PlaySubagent {\n readonly role: string;\n readonly allowedSkills: readonly string[];\n}\n\nexport interface PlayMeta extends SkillMeta {\n readonly kind: \"play\";\n}\n\nexport interface LoadedPlay {\n readonly meta: PlayMeta;\n readonly body: string;\n readonly requiredSkills: readonly string[];\n readonly subAgents: readonly PlaySubagent[];\n readonly dependsOnPlays: readonly PlayDependency[];\n}\n\nexport interface PlayNode {\n readonly name: string;\n readonly dependsOn: readonly string[];\n}\n\nexport interface PlayTestReport {\n readonly name: string;\n readonly ok: boolean;\n readonly checks: readonly string[];\n readonly suggestion?: string;\n}\n\nexport interface PlayLoaderDoctorReport {\n readonly ok: boolean;\n readonly plays: number;\n readonly root: string;\n readonly suggestion?: string;\n}\n\nasync function walk(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n const files: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) files.push(...(await walk(full)));\n else files.push(full);\n }\n return files;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : [];\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction normalizePlayName(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\nfunction playDebugPath(): string {\n return join(process.cwd(), \".nebutra\", \"debug\", \"play-loader.jsonl\");\n}\n\nasync function appendPlayDebug(entry: Record<string, unknown>): Promise<void> {\n const path = playDebugPath();\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\\n`, {\n flag: \"a\",\n });\n}\n\nexport async function readPlayDebug(limit = 10): Promise<unknown[]> {\n try {\n const raw = await readFile(playDebugPath(), \"utf8\");\n return raw\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .slice(-limit)\n .map((line) => JSON.parse(line) as unknown);\n } catch {\n return [];\n }\n}\n\nfunction parseSubagents(value: unknown): PlaySubagent[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlaySubagent[] => {\n const record = asRecord(item);\n const role = asString(record?.role);\n if (!record || !role) return [];\n return [{ role, allowedSkills: asStringArray(record.allowed_skills) }];\n });\n}\n\nfunction parsePlayDependencies(value: unknown): PlayDependency[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlayDependency[] => {\n if (typeof item === \"string\") return [{ play: item, outputs: [] }];\n const record = asRecord(item);\n const play = asString(record?.play);\n if (!record || !play) return [];\n return [{ play, outputs: asStringArray(record.outputs) }];\n });\n}\n\nexport function parsePlayMarkdown(markdown: string): LoadedPlay {\n const loaded = parseSkillMarkdown(markdown);\n const { frontmatter } = parseSkillFrontmatter(markdown);\n if (frontmatter.kind !== \"play\") {\n throw new CapabilityError(\"play-loader\", \"Play SKILL.md must set kind: play\", {\n suggestion: \"Set `kind: play` in frontmatter or keep this document in tool-registry only.\",\n statusCode: 400,\n metadata: { name: loaded.meta.name, kind: frontmatter.kind },\n });\n }\n\n return {\n meta: { ...loaded.meta, kind: \"play\" },\n body: loaded.body,\n requiredSkills: asStringArray(frontmatter.required_skills),\n subAgents: parseSubagents(frontmatter.sub_agents),\n dependsOnPlays: parsePlayDependencies(frontmatter.depends_on_plays),\n };\n}\n\nexport function resolvePlayChain(nodes: readonly PlayNode[]): readonly string[] {\n const byName = new Map(nodes.map((node) => [node.name, node]));\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const ordered: string[] = [];\n\n const visit = (name: string): void => {\n if (visited.has(name)) return;\n if (visiting.has(name)) {\n throw new CapabilityError(\"play-loader\", \"Play dependency cycle detected\", {\n suggestion: \"Remove the cycle or merge mutually dependent plays into one declaration.\",\n statusCode: 400,\n metadata: { name },\n });\n }\n visiting.add(name);\n const node = byName.get(name);\n for (const dependency of node?.dependsOn ?? []) visit(dependency);\n visiting.delete(name);\n visited.add(name);\n ordered.push(name);\n };\n\n for (const node of nodes) visit(node.name);\n return ordered;\n}\n\nexport class PlayLoader {\n readonly #root: string;\n readonly #plays = new Map<string, { readonly meta: PlayMeta; readonly path: string }>();\n\n private constructor(root: string) {\n this.#root = root;\n }\n\n static async open(root: string): Promise<PlayLoader> {\n const loader = new PlayLoader(root);\n await mkdir(loader.playsRoot(), { recursive: true });\n await loader.reload();\n return loader;\n }\n\n playsRoot(): string {\n return join(this.#root, \"plays\");\n }\n\n async writePlay(name: string, markdown: string): Promise<void> {\n const normalized = normalizePlayName(name);\n const parsed = parsePlayMarkdown(markdown);\n if (parsed.meta.name !== normalized) {\n throw new CapabilityError(\"play-loader\", \"Play name does not match path\", {\n suggestion: \"Use the same lowercase codename in the path and SKILL.md frontmatter.\",\n metadata: { pathName: normalized, playName: parsed.meta.name },\n statusCode: 400,\n });\n }\n const path = join(this.playsRoot(), normalized, \"SKILL.md\");\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, markdown, \"utf8\");\n await this.reload();\n await appendPlayDebug({ type: \"write\", play: normalized });\n }\n\n async newPlay(name: string): Promise<LoadedPlay> {\n const normalized = normalizePlayName(name);\n const markdown = [\n \"---\",\n `name: ${normalized}`,\n \"kind: play\",\n \"version: 1.0.0\",\n `description: ${normalized.replaceAll(\"_\", \" \")} play`,\n \"required_skills: []\",\n \"sub_agents: []\",\n \"depends_on_plays: []\",\n \"---\",\n \"\",\n \"## What this play does\",\n \"\",\n \"Describe the user story and expected output.\",\n ].join(\"\\n\");\n await this.writePlay(normalized, markdown);\n return this.load(normalized);\n }\n\n async reload(): Promise<void> {\n this.#plays.clear();\n for (const file of await walk(this.playsRoot())) {\n if (!file.endsWith(\"SKILL.md\")) continue;\n const raw = await readFile(file, \"utf8\");\n const play = parsePlayMarkdown(raw);\n this.#plays.set(play.meta.name, { meta: play.meta, path: relative(this.#root, file) });\n }\n await appendPlayDebug({ type: \"reload\", plays: this.#plays.size });\n }\n\n async list(): Promise<PlayMeta[]> {\n return [...this.#plays.values()]\n .map((entry) => entry.meta)\n .sort((left, right) => left.name.localeCompare(right.name));\n }\n\n async load(name: string): Promise<LoadedPlay> {\n const play = this.#plays.get(normalizePlayName(name));\n if (!play) {\n throw new CapabilityError(\"play-loader\", \"Play not found\", {\n suggestion: \"Run `pnpm play:list` or create it with `pnpm play:new <name>`.\",\n metadata: { name },\n statusCode: 404,\n });\n }\n const loaded = parsePlayMarkdown(await readFile(join(this.#root, play.path), \"utf8\"));\n await appendPlayDebug({ type: \"load\", play: loaded.meta.name });\n return loaded;\n }\n\n async install(source: string): Promise<LoadedPlay> {\n if (!source.startsWith(\"file:\")) {\n throw new CapabilityError(\"play-loader\", \"Only file installs are enabled locally\", {\n suggestion:\n \"Download remote plays through a reviewed distribution workflow, then install with file:<path>.\",\n metadata: { source },\n statusCode: 400,\n });\n }\n const raw = await readFile(source.slice(\"file:\".length), \"utf8\");\n const play = parsePlayMarkdown(raw);\n await this.writePlay(play.meta.name, raw);\n return this.load(play.meta.name);\n }\n\n async test(name: string): Promise<PlayTestReport> {\n try {\n const play = await this.load(name);\n return {\n name: play.meta.name,\n ok: play.body.length > 0,\n checks: [\"frontmatter\", \"kind:play\", \"body\", \"dependency-dag\"],\n ...(!play.body ? { suggestion: \"Add a markdown body after the frontmatter.\" } : {}),\n };\n } catch (error) {\n return {\n name,\n ok: false,\n checks: [],\n suggestion:\n error instanceof CapabilityError\n ? \"Create the play with `pnpm play:new <name>` or fix its SKILL.md metadata.\"\n : \"Run `pnpm play:list` and verify the play directory exists.\",\n };\n }\n }\n\n async doctor(): Promise<PlayLoaderDoctorReport> {\n const plays = this.#plays.size;\n return {\n ok: plays > 0,\n plays,\n root: this.#root,\n ...(plays === 0 ? { suggestion: \"Create a play with `pnpm play:new <name>`.\" } : {}),\n };\n }\n}\n"],"mappings":"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","names":[]}
|