@introspection-ai/recipes 0.13.0 → 0.14.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 (222) hide show
  1. package/README.md +45 -58
  2. package/dist/{agent-tool.d.ts → agents.d.ts} +4 -1
  3. package/dist/agents.d.ts.map +1 -0
  4. package/dist/{agent-tool.js → agents.js} +1 -1
  5. package/dist/agents.js.map +1 -0
  6. package/dist/api/extensions.d.ts +3 -0
  7. package/dist/api/extensions.d.ts.map +1 -0
  8. package/dist/api/extensions.js +2 -0
  9. package/dist/api/extensions.js.map +1 -0
  10. package/dist/api/mcp.d.ts +4 -0
  11. package/dist/api/mcp.d.ts.map +1 -0
  12. package/dist/api/mcp.js +2 -0
  13. package/dist/api/mcp.js.map +1 -0
  14. package/dist/api/session.d.ts +3 -0
  15. package/dist/api/session.d.ts.map +1 -0
  16. package/dist/api/session.js +2 -0
  17. package/dist/api/session.js.map +1 -0
  18. package/dist/child-agent.d.ts +5 -4
  19. package/dist/child-agent.d.ts.map +1 -1
  20. package/dist/child-agent.js +26 -117
  21. package/dist/child-agent.js.map +1 -1
  22. package/dist/child-session.d.ts +23 -0
  23. package/dist/child-session.d.ts.map +1 -0
  24. package/dist/child-session.js +48 -0
  25. package/dist/child-session.js.map +1 -0
  26. package/dist/extensions.d.ts +29 -0
  27. package/dist/extensions.d.ts.map +1 -0
  28. package/dist/extensions.js +119 -0
  29. package/dist/extensions.js.map +1 -0
  30. package/dist/index.d.ts +4 -13
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -13
  33. package/dist/index.js.map +1 -1
  34. package/dist/inspect.d.ts +38 -5
  35. package/dist/inspect.d.ts.map +1 -1
  36. package/dist/inspect.js +92 -41
  37. package/dist/inspect.js.map +1 -1
  38. package/dist/interactions.d.ts +18 -2
  39. package/dist/interactions.d.ts.map +1 -1
  40. package/dist/interactions.js +41 -10
  41. package/dist/interactions.js.map +1 -1
  42. package/dist/mcp-catalog.d.ts +1 -0
  43. package/dist/mcp-catalog.d.ts.map +1 -1
  44. package/dist/mcp-catalog.js +14 -6
  45. package/dist/mcp-catalog.js.map +1 -1
  46. package/dist/mcp-chunks/auth-command-ZLGYIEUY.js +273 -0
  47. package/dist/mcp-chunks/call-arguments-LU3W6D3K.js +13 -0
  48. package/dist/mcp-chunks/call-command-LKTKSYTH.js +612 -0
  49. package/dist/mcp-chunks/chunk-2U4BSDC4.js +3951 -0
  50. package/dist/mcp-chunks/chunk-2XL7MG7R.js +272 -0
  51. package/dist/mcp-chunks/chunk-3BJA27PY.js +2321 -0
  52. package/dist/mcp-chunks/chunk-4SQKWYFF.js +15 -0
  53. package/dist/mcp-chunks/chunk-5QOSCSQB.js +290 -0
  54. package/dist/mcp-chunks/chunk-6W5QFASN.js +77 -0
  55. package/dist/mcp-chunks/chunk-6XCZGU3G.js +1004 -0
  56. package/dist/mcp-chunks/chunk-7FHEBT5G.js +149 -0
  57. package/dist/mcp-chunks/chunk-7KA2VJVZ.js +114 -0
  58. package/dist/mcp-chunks/chunk-7UDVSOOF.js +138 -0
  59. package/dist/mcp-chunks/chunk-BNV3TPE4.js +66 -0
  60. package/dist/mcp-chunks/chunk-BTLAWHTA.js +129 -0
  61. package/dist/mcp-chunks/chunk-BV56DXPW.js +13 -0
  62. package/dist/mcp-chunks/chunk-BWHIA4PC.js +90 -0
  63. package/dist/mcp-chunks/chunk-BZIQOWE6.js +267 -0
  64. package/dist/mcp-chunks/chunk-D3MRAZ6H.js +16140 -0
  65. package/dist/mcp-chunks/chunk-DHBE7UBH.js +750 -0
  66. package/dist/mcp-chunks/chunk-E22DYOW5.js +251 -0
  67. package/dist/mcp-chunks/chunk-ECIVB7P4.js +139 -0
  68. package/dist/mcp-chunks/chunk-HKRJ6O36.js +1076 -0
  69. package/dist/mcp-chunks/chunk-IABRYROW.js +44 -0
  70. package/dist/mcp-chunks/chunk-J6LYIZXN.js +45 -0
  71. package/dist/mcp-chunks/chunk-L7NTQVYN.js +161 -0
  72. package/dist/mcp-chunks/chunk-LEZSACDE.js +141 -0
  73. package/dist/mcp-chunks/chunk-LKIBAYKU.js +208 -0
  74. package/dist/mcp-chunks/chunk-LNH5LAJD.js +152 -0
  75. package/dist/mcp-chunks/chunk-LQQEYD76.js +124 -0
  76. package/dist/mcp-chunks/chunk-LRKZP7DJ.js +47 -0
  77. package/dist/mcp-chunks/chunk-LS7PMWSR.js +109 -0
  78. package/dist/mcp-chunks/chunk-M3TPODXI.js +95 -0
  79. package/dist/mcp-chunks/chunk-MZ2NARG7.js +400 -0
  80. package/dist/mcp-chunks/chunk-N46NEFAF.js +45 -0
  81. package/dist/mcp-chunks/chunk-NNOEGEUZ.js +17273 -0
  82. package/dist/mcp-chunks/chunk-NRAJX5E4.js +69 -0
  83. package/dist/mcp-chunks/chunk-P3PSC54P.js +869 -0
  84. package/dist/mcp-chunks/chunk-PIIYJR44.js +6341 -0
  85. package/dist/mcp-chunks/chunk-PWYFKGKG.js +385 -0
  86. package/dist/mcp-chunks/chunk-SJCCBNZ4.js +45 -0
  87. package/dist/mcp-chunks/chunk-TATVT5IT.js +194 -0
  88. package/dist/mcp-chunks/chunk-TEUKFFQL.js +29 -0
  89. package/dist/mcp-chunks/chunk-UKYNQGNE.js +150 -0
  90. package/dist/mcp-chunks/chunk-UNKB4SBV.js +37 -0
  91. package/dist/mcp-chunks/chunk-WD6BOD24.js +138 -0
  92. package/dist/mcp-chunks/chunk-WQJZMCJN.js +73 -0
  93. package/dist/mcp-chunks/chunk-XANPGTLI.js +364 -0
  94. package/dist/mcp-chunks/chunk-XQK2JSW4.js +35 -0
  95. package/dist/mcp-chunks/chunk-YHQUTU6L.js +571 -0
  96. package/dist/mcp-chunks/chunk-YVZKMV5H.js +57 -0
  97. package/dist/mcp-chunks/cli-UV4QJZZM.js +929 -0
  98. package/dist/mcp-chunks/client-S7RQUG5U.js +13 -0
  99. package/dist/mcp-chunks/config-command-NLAGKID5.js +1101 -0
  100. package/dist/mcp-chunks/daemon-command-U242Z6VY.js +26 -0
  101. package/dist/mcp-chunks/dist-LK3SHAMX.js +6478 -0
  102. package/dist/mcp-chunks/emit-ts-command-VBXVXO6V.js +386 -0
  103. package/dist/mcp-chunks/generate-cli-runner-6CAZZA3P.js +1426 -0
  104. package/dist/mcp-chunks/inspect-cli-command-3ZORTOR7.js +111 -0
  105. package/dist/mcp-chunks/launch-YKRXHXJI.js +10 -0
  106. package/dist/mcp-chunks/lifecycle-XVKRHPH3.js +16 -0
  107. package/dist/mcp-chunks/list-command-2MEGGNFQ.js +4190 -0
  108. package/dist/mcp-chunks/output-utils-NKESLAJ6.js +12 -0
  109. package/dist/mcp-chunks/prompt-B1Yc1NPt-VGOTWJMN.js +918 -0
  110. package/dist/mcp-chunks/record-command-YXHTTTT5.js +17 -0
  111. package/dist/mcp-chunks/replay-command-25HJ6AQU.js +85 -0
  112. package/dist/mcp-chunks/resource-command-OO4NYF5C.js +97 -0
  113. package/dist/mcp-chunks/result-utils-MOHHLNM7.js +15 -0
  114. package/dist/mcp-chunks/runtime-5WJ3G25Y.js +33 -0
  115. package/dist/mcp-chunks/runtime-wrapper-RPWDIDBF.js +10 -0
  116. package/dist/mcp-chunks/serve-command-DHXJZMMN.js +3302 -0
  117. package/dist/mcp-chunks/timeouts-W2XWCCXR.js +18 -0
  118. package/dist/mcp-chunks/vault-command-PIYIUEZL.js +154 -0
  119. package/dist/mcp-daemon-client.d.ts +6 -1
  120. package/dist/mcp-daemon-client.d.ts.map +1 -1
  121. package/dist/mcp-daemon-client.js +119 -2
  122. package/dist/mcp-daemon-client.js.map +1 -1
  123. package/dist/mcp-daemon-protocol.d.ts +18 -1
  124. package/dist/mcp-daemon-protocol.d.ts.map +1 -1
  125. package/dist/mcp-daemon-protocol.js +31 -0
  126. package/dist/mcp-daemon-protocol.js.map +1 -1
  127. package/dist/mcp-daemon.js +169 -76602
  128. package/dist/mcp-daemon.js.map +1 -1
  129. package/dist/mcp-policy.d.ts +11 -0
  130. package/dist/mcp-policy.d.ts.map +1 -0
  131. package/dist/mcp-policy.js +27 -0
  132. package/dist/mcp-policy.js.map +1 -0
  133. package/dist/mcp-run-worker.js +38 -76540
  134. package/dist/mcp-tools.d.ts +39 -0
  135. package/dist/mcp-tools.d.ts.map +1 -0
  136. package/dist/mcp-tools.js +493 -0
  137. package/dist/mcp-tools.js.map +1 -0
  138. package/dist/mcp.d.ts +21 -9
  139. package/dist/mcp.d.ts.map +1 -1
  140. package/dist/mcp.js +101 -31
  141. package/dist/mcp.js.map +1 -1
  142. package/dist/model-binding.d.ts +39 -0
  143. package/dist/model-binding.d.ts.map +1 -0
  144. package/dist/model-binding.js +103 -0
  145. package/dist/model-binding.js.map +1 -0
  146. package/dist/pi-extension.d.ts +2 -22
  147. package/dist/pi-extension.d.ts.map +1 -1
  148. package/dist/pi-extension.js +296 -71
  149. package/dist/pi-extension.js.map +1 -1
  150. package/dist/recipe/resolve.d.ts +43 -20
  151. package/dist/recipe/resolve.d.ts.map +1 -1
  152. package/dist/recipe/resolve.js +215 -70
  153. package/dist/recipe/resolve.js.map +1 -1
  154. package/dist/recipe-agent.d.ts +24 -27
  155. package/dist/recipe-agent.d.ts.map +1 -1
  156. package/dist/recipe-agent.js +278 -230
  157. package/dist/recipe-agent.js.map +1 -1
  158. package/dist/recipe-check.d.ts +17 -0
  159. package/dist/recipe-check.d.ts.map +1 -0
  160. package/dist/recipe-check.js +258 -0
  161. package/dist/recipe-check.js.map +1 -0
  162. package/dist/recipe-extensions.d.ts.map +1 -1
  163. package/dist/recipe-extensions.js +0 -3
  164. package/dist/recipe-extensions.js.map +1 -1
  165. package/dist/recipe-model.d.ts +3 -1
  166. package/dist/recipe-model.d.ts.map +1 -1
  167. package/dist/recipe-model.js +15 -12
  168. package/dist/recipe-model.js.map +1 -1
  169. package/dist/recipe-package.d.ts +6 -28
  170. package/dist/recipe-package.d.ts.map +1 -1
  171. package/dist/recipe-package.js +248 -191
  172. package/dist/recipe-package.js.map +1 -1
  173. package/dist/recipe-skills.d.ts.map +1 -1
  174. package/dist/recipe-skills.js +42 -29
  175. package/dist/recipe-skills.js.map +1 -1
  176. package/dist/run-controller.d.ts +10 -9
  177. package/dist/run-controller.d.ts.map +1 -1
  178. package/dist/run-controller.js +28 -21
  179. package/dist/run-controller.js.map +1 -1
  180. package/dist/session.d.ts +40 -53
  181. package/dist/session.d.ts.map +1 -1
  182. package/dist/session.js +312 -173
  183. package/dist/session.js.map +1 -1
  184. package/dist/test-utils.d.ts +7 -7
  185. package/dist/test-utils.d.ts.map +1 -1
  186. package/dist/test-utils.js +32 -15
  187. package/dist/test-utils.js.map +1 -1
  188. package/docs/agent-composition.md +41 -35
  189. package/docs/host-api.md +201 -0
  190. package/docs/index.md +8 -14
  191. package/docs/interactions.md +12 -11
  192. package/docs/mcp-auth.md +6 -6
  193. package/docs/mcp-configuration.md +72 -24
  194. package/docs/pi-extension.md +39 -11
  195. package/docs/recipe-flow.md +9 -9
  196. package/docs/recipe-format.md +90 -41
  197. package/package.json +23 -17
  198. package/vendor/mcp-client/darwin-arm64/mcp-client +0 -0
  199. package/vendor/mcp-client/darwin-x64/mcp-client +0 -0
  200. package/vendor/mcp-client/linux-arm64/mcp-client +0 -0
  201. package/vendor/recipe-check/darwin-arm64/recipe-check +0 -0
  202. package/vendor/recipe-check/darwin-x64/recipe-check +0 -0
  203. package/vendor/recipe-check/linux-arm64/recipe-check +0 -0
  204. package/vendor/recipe-check/linux-x64/recipe-check +0 -0
  205. package/vendor/recipe-check/linux-x64-musl/recipe-check +0 -0
  206. package/vendor/recipe-check/win32-x64/recipe-check.exe +0 -0
  207. package/dist/agent-tool.d.ts.map +0 -1
  208. package/dist/agent-tool.js.map +0 -1
  209. package/dist/run.d.ts +0 -28
  210. package/dist/run.d.ts.map +0 -1
  211. package/dist/run.js +0 -84
  212. package/dist/run.js.map +0 -1
  213. package/dist/testing.d.ts +0 -25
  214. package/dist/testing.d.ts.map +0 -1
  215. package/dist/testing.js +0 -96
  216. package/dist/testing.js.map +0 -1
  217. package/docs/deployment-configuration.md +0 -65
  218. package/docs/migration.md +0 -70
  219. package/docs/python-bindings-release.md +0 -47
  220. package/docs/recipe-evals.md +0 -70
  221. package/docs/recipe-judges.md +0 -152
  222. package/docs/runtime-library.md +0 -171
@@ -17,42 +17,25 @@ export interface RecipeMcpToolSelection {
17
17
  }
18
18
  /** Parse a selection while preserving whether its source shape was malformed. */
19
19
  export declare function parseRecipeMcpToolSelection(value: unknown): RecipeMcpToolSelection;
20
- /** Minimal runtime guard; recipe-check owns detailed authoring diagnostics. */
20
+ /** Minimal session guard; the Recipe checker owns authoring diagnostics. */
21
21
  export declare function isValidRecipeMcpToolSelection(selection: RecipeMcpToolSelection): boolean;
22
22
  export interface RecipePackageMcpConfig {
23
23
  manifests: string[];
24
24
  servers: RecipePackageMcpServer[];
25
25
  }
26
- export type RecipeEvalSuite = {
27
- name: string;
28
- type: "registry";
29
- dataset: string;
30
- version: string;
31
- } | {
32
- name: string;
33
- type: "git";
34
- repo: string;
35
- rev: string;
36
- dataset: string;
37
- };
38
- export interface RecipeEvalsConfig {
39
- suites: RecipeEvalSuite[];
40
- raw?: unknown;
41
- }
42
26
  export interface RecipePackageManifest {
43
27
  name: string;
44
28
  version: string;
45
29
  description?: string;
46
30
  path: string;
47
31
  resources: RecipePackageResources;
32
+ /** Whether each resource key was explicitly authored in package.json#pi. */
33
+ resourceDeclarations?: Record<keyof RecipePackageResources, boolean>;
48
34
  mcp: RecipePackageMcpConfig;
49
- evals: RecipeEvalsConfig;
50
35
  }
51
36
  export type PiPackageResources = RecipePackageResources;
52
37
  export type PiPackageManifest = RecipePackageManifest;
53
- export type RecipeValidationSeverity = "error" | "warning";
54
38
  export interface RecipeValidationFinding {
55
- severity: RecipeValidationSeverity;
56
39
  code: string;
57
40
  message: string;
58
41
  packageName?: string;
@@ -61,18 +44,13 @@ export interface RecipeValidationReport {
61
44
  valid: boolean;
62
45
  findings: RecipeValidationFinding[];
63
46
  }
47
+ export declare function assertRecipePathContained(packageDir: string, path: string, label: string): void;
64
48
  export declare class RecipePackageError extends Error {
65
49
  constructor(message: string);
66
50
  }
67
- export declare function parseEvalsConfig(value: unknown, present?: boolean): RecipeEvalsConfig;
68
- export declare function validateRecipeEvalsConfig(evals: RecipeEvalsConfig, packageName?: string): RecipeValidationReport;
69
51
  export declare function readPiPackageManifest(packageDir: string): RecipePackageManifest;
70
- export declare function resolvePiPackageMcpManifestPaths(pkg: RecipePackageManifest, opts?: {
71
- allowEmptyGlobMatches?: boolean;
72
- }): string[];
73
- export declare function resolvePiPackageResourcePaths(pkg: RecipePackageManifest, key: keyof RecipePackageResources, opts?: {
74
- allowEmptyGlobMatches?: boolean;
75
- }): string[];
52
+ export declare function resolvePiPackageMcpManifestPaths(pkg: RecipePackageManifest): string[];
53
+ export declare function resolvePiPackageResourcePaths(pkg: RecipePackageManifest, key: keyof RecipePackageResources): string[];
76
54
  export declare function defaultPiPackageResourcePaths(pkg: RecipePackageManifest, key: keyof RecipePackageResources): string[];
77
55
  export declare function packageResourcePaths(pkg: RecipePackageManifest, key: keyof RecipePackageResources): string[];
78
56
  export declare function validatePiPackageManifest(pkg: RecipePackageManifest): RecipeValidationReport;
@@ -1 +1 @@
1
- {"version":3,"file":"recipe-package.d.ts","sourceRoot":"","sources":["../src/recipe-package.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"recipe-package.d.ts","sourceRoot":"","sources":["../src/recipe-package.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,sBAAsB,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAQD,iFAAiF;AACjF,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,GACb,sBAAsB,CAoBxB;AAED,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,sBAAsB,GAChC,OAAO,CAWT;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,sBAAsB,CAAC;IAClC,4EAA4E;IAC5E,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,sBAAsB,EAAE,OAAO,CAAC,CAAC;IACrE,GAAG,EAAE,sBAAsB,CAAC;CAC7B;AAED,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AACxD,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEtD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,uBAAuB,EAAE,CAAC;CACrC;AAkRD,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,IAAI,CAaN;AAwID,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAwED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAmC/E;AAED,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,qBAAqB,GACzB,MAAM,EAAE,CAyDV;AAED,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,qBAAqB,EAC1B,GAAG,EAAE,MAAM,sBAAsB,GAChC,MAAM,EAAE,CA0CV;AAED,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,qBAAqB,EAC1B,GAAG,EAAE,MAAM,sBAAsB,GAChC,MAAM,EAAE,CAOV;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,qBAAqB,EAC1B,GAAG,EAAE,MAAM,sBAAsB,GAChC,MAAM,EAAE,CAKV;AAED,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,qBAAqB,GACzB,sBAAsB,CA+CxB"}
@@ -1,5 +1,5 @@
1
- import { existsSync, readdirSync, readFileSync } from "node:fs";
2
- import { isAbsolute, join, relative, resolve } from "node:path";
1
+ import { existsSync, realpathSync, readdirSync, readFileSync, statSync, } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
  const INVALID_MCP_TOOL_SELECTION = Symbol("invalidMcpToolSelection");
4
4
  /** Parse a selection while preserving whether its source shape was malformed. */
5
5
  export function parseRecipeMcpToolSelection(value) {
@@ -20,7 +20,7 @@ export function parseRecipeMcpToolSelection(value) {
20
20
  }
21
21
  return selection;
22
22
  }
23
- /** Minimal runtime guard; recipe-check owns detailed authoring diagnostics. */
23
+ /** Minimal session guard; the Recipe checker owns authoring diagnostics. */
24
24
  export function isValidRecipeMcpToolSelection(selection) {
25
25
  if (selection[INVALID_MCP_TOOL_SELECTION]) {
26
26
  return false;
@@ -39,6 +39,8 @@ const RESOURCE_KEYS = [
39
39
  "skills",
40
40
  "prompts",
41
41
  ];
42
+ const PI_KEYS = new Set([...RESOURCE_KEYS, "mcp"]);
43
+ const SOURCE_FINDINGS = Symbol("recipeSourceFindings");
42
44
  function stringArray(value) {
43
45
  return Array.isArray(value)
44
46
  ? value.filter((item) => typeof item === "string")
@@ -52,17 +54,119 @@ function emptyResources() {
52
54
  prompts: [],
53
55
  };
54
56
  }
57
+ function resourceDeclarations(pi) {
58
+ return Object.fromEntries(RESOURCE_KEYS.map((key) => [key, Object.hasOwn(pi, key)]));
59
+ }
60
+ function sourceShapeFindings(pi, packageName) {
61
+ const findings = [];
62
+ for (const key of Object.keys(pi)) {
63
+ if (!PI_KEYS.has(key)) {
64
+ findings.push(finding("pi.unknown_key", `package.json#pi contains unknown key '${key}'`, packageName));
65
+ }
66
+ }
67
+ for (const key of RESOURCE_KEYS) {
68
+ if (!Object.hasOwn(pi, key))
69
+ continue;
70
+ const value = pi[key];
71
+ if (!Array.isArray(value) ||
72
+ value.some((item) => typeof item !== "string" || item.trim().length === 0)) {
73
+ findings.push(finding(`pi.${key}_invalid`, `package.json#pi.${key} must be an array of non-empty strings`, packageName));
74
+ }
75
+ else if (new Set(value.map((item) => String(item).trim())).size !== value.length) {
76
+ findings.push(finding(`pi.${key}_duplicate`, `package.json#pi.${key} must not contain duplicate entries`, packageName));
77
+ }
78
+ }
79
+ findings.push(...mcpSourceShapeFindings(pi.mcp, packageName));
80
+ return findings;
81
+ }
82
+ function mcpSourceShapeFindings(value, packageName) {
83
+ if (value === undefined)
84
+ return [];
85
+ const invalid = (message) => finding("pi.mcp_invalid", message, packageName);
86
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
87
+ return [invalid("package.json#pi.mcp must be an object")];
88
+ }
89
+ const data = value;
90
+ const findings = [];
91
+ const unknown = Object.keys(data).filter((key) => !["manifests", "servers"].includes(key));
92
+ if (unknown.length > 0) {
93
+ findings.push(invalid(`package.json#pi.mcp contains unknown field(s): ${unknown.join(", ")}`));
94
+ }
95
+ const manifests = Array.isArray(data.manifests) ? data.manifests : [];
96
+ if (Object.hasOwn(data, "manifests") &&
97
+ (!Array.isArray(data.manifests) ||
98
+ data.manifests.some((item) => typeof item !== "string" || !item.trim()))) {
99
+ findings.push(invalid("package.json#pi.mcp manifests must be non-empty strings"));
100
+ }
101
+ if (manifests.every((item) => typeof item === "string") &&
102
+ new Set(manifests.map((item) => item.trim())).size !== manifests.length) {
103
+ findings.push(invalid("package.json#pi.mcp manifests must not contain duplicates"));
104
+ }
105
+ if (Object.hasOwn(data, "servers") &&
106
+ !Array.isArray(data.servers)) {
107
+ findings.push(invalid("package.json#pi.mcp.servers must be an array"));
108
+ return findings;
109
+ }
110
+ const ids = new Set();
111
+ const normalizedIds = new Set();
112
+ for (const [index, raw] of (Array.isArray(data.servers) ? data.servers : []).entries()) {
113
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
114
+ findings.push(invalid(`package.json#pi.mcp.servers[${index}] must be an object`));
115
+ continue;
116
+ }
117
+ const server = raw;
118
+ const serverUnknown = Object.keys(server).filter((key) => !["id", "required", "tools"].includes(key));
119
+ if (serverUnknown.length > 0) {
120
+ findings.push(invalid(`package.json#pi.mcp.servers[${index}] contains unknown field(s): ${serverUnknown.join(", ")}`));
121
+ }
122
+ const id = typeof server.id === "string" && server.id.trim()
123
+ ? server.id.trim()
124
+ : undefined;
125
+ if (!id) {
126
+ findings.push(invalid(`package.json#pi.mcp.servers[${index}].id must be non-empty`));
127
+ }
128
+ else if (ids.has(id)) {
129
+ findings.push(invalid(`package.json#pi.mcp contains duplicate server id '${id}'`));
130
+ }
131
+ else {
132
+ ids.add(id);
133
+ const normalized = id
134
+ .trim()
135
+ .toLowerCase()
136
+ .replace(/[^a-z0-9_-]+/g, "-")
137
+ .replace(/^-+|-+$/g, "") || "mcp";
138
+ if (normalizedIds.has(normalized)) {
139
+ findings.push(invalid(`package.json#pi.mcp server id '${id}' collides after normalization`));
140
+ }
141
+ normalizedIds.add(normalized);
142
+ }
143
+ if (Object.hasOwn(server, "required") &&
144
+ typeof server.required !== "boolean") {
145
+ findings.push(invalid(`package.json#pi.mcp.servers[${index}].required must be boolean`));
146
+ }
147
+ if (Object.hasOwn(server, "tools")) {
148
+ if (!server.tools ||
149
+ typeof server.tools !== "object" ||
150
+ Array.isArray(server.tools)) {
151
+ findings.push(invalid(`package.json#pi.mcp.servers[${index}].tools must be an object`));
152
+ }
153
+ else {
154
+ const tools = server.tools;
155
+ const toolsUnknown = Object.keys(tools).filter((key) => !["include", "exclude"].includes(key));
156
+ if (toolsUnknown.length > 0) {
157
+ findings.push(invalid(`package.json#pi.mcp.servers[${index}].tools contains unknown field(s): ${toolsUnknown.join(", ")}`));
158
+ }
159
+ }
160
+ }
161
+ }
162
+ return findings;
163
+ }
55
164
  function emptyMcpConfig() {
56
165
  return {
57
166
  manifests: [],
58
167
  servers: [],
59
168
  };
60
169
  }
61
- function emptyEvalsConfig() {
62
- return {
63
- suites: [],
64
- };
65
- }
66
170
  function normalizeResourcePath(path) {
67
171
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
68
172
  }
@@ -83,6 +187,32 @@ function assertPackageResourcePath(pkg, key, resource, resolved) {
83
187
  throw new RecipePackageError(`Recipe ${pkg.name} declares ${key} resource outside the package: ${resource}`);
84
188
  }
85
189
  }
190
+ export function assertRecipePathContained(packageDir, path, label) {
191
+ const root = realpathSync(packageDir);
192
+ const target = realpathSync(path);
193
+ const relativePath = relative(root, target);
194
+ if (relativePath === ".." ||
195
+ relativePath.startsWith(`..${sep}`) ||
196
+ isAbsolute(relativePath)) {
197
+ throw new RecipePackageError(`Recipe ${label} resolves outside the package: ${path}`);
198
+ }
199
+ }
200
+ function assertResourceTreeContained(packageDir, path, label, seen = new Set()) {
201
+ assertRecipePathContained(packageDir, path, label);
202
+ const realPath = realpathSync(path);
203
+ if (seen.has(realPath))
204
+ return;
205
+ seen.add(realPath);
206
+ if (!statSync(path).isDirectory())
207
+ return;
208
+ for (const entry of readdirSync(path, { withFileTypes: true })) {
209
+ const child = join(path, entry.name);
210
+ assertRecipePathContained(packageDir, child, label);
211
+ if (statSync(child).isDirectory()) {
212
+ assertResourceTreeContained(packageDir, child, label, seen);
213
+ }
214
+ }
215
+ }
86
216
  function hasGlob(value) {
87
217
  return /[*?[\]{}]/.test(value);
88
218
  }
@@ -119,23 +249,68 @@ function globToRegExp(glob) {
119
249
  pattern += "$";
120
250
  return new RegExp(pattern);
121
251
  }
122
- function listPackageEntries(root) {
252
+ const UNSCANNED_PACKAGE_DIRS = new Set(["node_modules", ".git"]);
253
+ function globScanRoot(glob) {
254
+ const segments = normalizeResourcePath(glob).split("/");
255
+ const staticSegments = [];
256
+ for (const segment of segments) {
257
+ if (hasGlob(segment))
258
+ break;
259
+ staticSegments.push(segment);
260
+ }
261
+ return staticSegments.join("/");
262
+ }
263
+ function listPackageEntries(root, globs) {
264
+ const scanRoots = new Set();
265
+ for (const glob of globs) {
266
+ if (!glob.trim() || !hasGlob(glob))
267
+ continue;
268
+ if (isAbsolute(glob) || hasTraversalSegment(glob)) {
269
+ throw new RecipePackageError(`Recipe resource glob resolves outside the package: ${glob}`);
270
+ }
271
+ scanRoots.add(globScanRoot(glob));
272
+ }
273
+ if (scanRoots.size === 0)
274
+ return [];
123
275
  const entries = [];
124
- function visit(dir, relativeDir = "") {
125
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
126
- const relative = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
276
+ const seen = new Set();
277
+ function visit(dir, relativeDir) {
278
+ let directoryEntries;
279
+ try {
280
+ directoryEntries = readdirSync(dir, { withFileTypes: true });
281
+ }
282
+ catch {
283
+ return;
284
+ }
285
+ for (const entry of directoryEntries) {
286
+ const relative = relativeDir
287
+ ? `${relativeDir}/${entry.name}`
288
+ : entry.name;
127
289
  const fullPath = join(dir, entry.name);
128
- entries.push(relative);
290
+ if (entry.isDirectory() &&
291
+ UNSCANNED_PACKAGE_DIRS.has(entry.name)) {
292
+ continue;
293
+ }
294
+ if (!seen.has(relative)) {
295
+ seen.add(relative);
296
+ entries.push(relative);
297
+ }
129
298
  if (entry.isDirectory())
130
299
  visit(fullPath, relative);
131
300
  }
132
301
  }
133
- visit(root);
302
+ const roots = scanRoots.has("") ? [""] : [...scanRoots];
303
+ for (const scanRoot of roots) {
304
+ if (scanRoot && !seen.has(scanRoot)) {
305
+ seen.add(scanRoot);
306
+ entries.push(scanRoot);
307
+ }
308
+ visit(scanRoot ? join(root, scanRoot) : root, scanRoot);
309
+ }
134
310
  return entries;
135
311
  }
136
- function finding(severity, code, message, packageName) {
312
+ function finding(code, message, packageName) {
137
313
  return {
138
- severity,
139
314
  code,
140
315
  message,
141
316
  packageName,
@@ -167,19 +342,10 @@ function stringValue(value) {
167
342
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
168
343
  }
169
344
  function parseMcpConfig(value) {
170
- if (typeof value === "string") {
171
- return { ...emptyMcpConfig(), manifests: stringArray([value]).map(normalizeResourcePath) };
172
- }
173
- if (Array.isArray(value)) {
174
- return { ...emptyMcpConfig(), manifests: stringArray(value).map(normalizeResourcePath) };
175
- }
176
345
  if (!value || typeof value !== "object" || Array.isArray(value))
177
346
  return emptyMcpConfig();
178
347
  const data = value;
179
- const manifests = [
180
- ...(stringValue(data.manifest) ? [stringValue(data.manifest)] : []),
181
- ...stringArray(data.manifests),
182
- ].map(normalizeResourcePath);
348
+ const manifests = stringArray(data.manifests).map(normalizeResourcePath);
183
349
  const seen = new Set();
184
350
  const servers = [];
185
351
  for (const raw of Array.isArray(data.servers) ? data.servers : []) {
@@ -199,142 +365,14 @@ function parseMcpConfig(value) {
199
365
  }
200
366
  return { manifests, servers };
201
367
  }
202
- function withRawEvalsConfig(config, raw) {
203
- Object.defineProperty(config, "raw", {
204
- value: raw,
205
- enumerable: false,
206
- configurable: false,
207
- });
208
- return config;
209
- }
210
- export function parseEvalsConfig(value, present = value !== undefined) {
211
- if (!present)
212
- return emptyEvalsConfig();
213
- const data = asRecord(value);
214
- const rawSuites = Array.isArray(data.suites) ? data.suites : [];
215
- const suites = [];
216
- for (const raw of rawSuites) {
217
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
218
- continue;
219
- const suite = raw;
220
- const name = stringValue(suite.name);
221
- const type = stringValue(suite.type);
222
- if (!name)
223
- continue;
224
- if (type === "registry") {
225
- const dataset = stringValue(suite.dataset);
226
- const version = stringValue(suite.version);
227
- if (!dataset || !version)
228
- continue;
229
- suites.push({ name, type, dataset, version });
230
- }
231
- else if (type === "git") {
232
- const repo = stringValue(suite.repo);
233
- const rev = stringValue(suite.rev);
234
- const dataset = stringValue(suite.dataset);
235
- if (!repo || !rev || !dataset)
236
- continue;
237
- suites.push({
238
- name,
239
- type,
240
- repo,
241
- rev,
242
- dataset,
243
- });
244
- }
245
- }
246
- return withRawEvalsConfig({ suites }, value);
247
- }
248
- function fixedRegistryTag(value) {
249
- const tag = value.trim();
250
- if (!tag || tag.toLowerCase() === "latest")
251
- return false;
252
- if (/\s/.test(tag) || /[\^~<>=*|]/.test(tag))
253
- return false;
254
- return !/(^|[._-])x($|[._-])/i.test(tag);
255
- }
256
- function fixedGitRev(value) {
257
- return /^[0-9a-fA-F]{7,40}$/.test(value);
258
- }
259
- export function validateRecipeEvalsConfig(evals, packageName) {
260
- const findings = [];
261
- const raw = evals.raw;
262
- const data = raw === undefined ? { suites: evals.suites } : asRecord(raw);
263
- const rawSuites = Array.isArray(data.suites) ? data.suites : [];
264
- if (raw !== undefined && (!raw || typeof raw !== "object" || Array.isArray(raw))) {
265
- findings.push(finding("error", "evals.suite_invalid", "pi.evals must be an object with a suites array", packageName));
266
- return { valid: false, findings };
267
- }
268
- if (data.suites !== undefined && !Array.isArray(data.suites)) {
269
- findings.push(finding("error", "evals.suite_invalid", "pi.evals.suites must be an array", packageName));
270
- return { valid: false, findings };
271
- }
272
- const seenNames = new Map();
273
- for (const [index, rawSuite] of rawSuites.entries()) {
274
- const label = `pi.evals.suites[${index}]`;
275
- if (!rawSuite || typeof rawSuite !== "object" || Array.isArray(rawSuite)) {
276
- findings.push(finding("error", "evals.suite_invalid", `${label} must be an object`, packageName));
277
- continue;
278
- }
279
- const suite = rawSuite;
280
- const name = stringValue(suite.name);
281
- const type = stringValue(suite.type);
282
- if (!name) {
283
- findings.push(finding("error", "evals.suite_invalid", `${label} must declare a non-empty name`, packageName));
284
- }
285
- else {
286
- const firstIndex = seenNames.get(name);
287
- if (firstIndex !== undefined) {
288
- findings.push(finding("error", "evals.name_duplicate", `${label} reuses suite name "${name}" already declared at pi.evals.suites[${firstIndex}]`, packageName));
289
- }
290
- else {
291
- seenNames.set(name, index);
292
- }
293
- }
294
- if (type !== "registry" && type !== "git") {
295
- findings.push(finding("error", "evals.suite_invalid", `${label} must use type "registry" or "git"`, packageName));
296
- continue;
297
- }
298
- if (type === "registry") {
299
- if (!stringValue(suite.dataset)) {
300
- findings.push(finding("error", "evals.suite_invalid", `${label} registry suite must declare dataset`, packageName));
301
- }
302
- const version = stringValue(suite.version);
303
- if (!version) {
304
- findings.push(finding("error", "evals.suite_invalid", `${label} registry suite must declare version`, packageName));
305
- }
306
- else if (!fixedRegistryTag(version)) {
307
- findings.push(finding("error", "evals.pin_mutable", `${label} registry version must be an explicit Harbor registry tag, not a mutable alias or range: ${version}`, packageName));
308
- }
309
- }
310
- else {
311
- if (!stringValue(suite.repo)) {
312
- findings.push(finding("error", "evals.suite_invalid", `${label} git suite must declare repo`, packageName));
313
- }
314
- const rev = stringValue(suite.rev);
315
- if (!rev) {
316
- findings.push(finding("error", "evals.suite_invalid", `${label} git suite must declare rev`, packageName));
317
- }
318
- else if (!fixedGitRev(rev)) {
319
- findings.push(finding("error", "evals.pin_mutable", `${label} git rev must be a 7-40 character hex commit SHA: ${rev}`, packageName));
320
- }
321
- if (!stringValue(suite.dataset)) {
322
- findings.push(finding("error", "evals.suite_invalid", `${label} git suite must declare dataset`, packageName));
323
- }
324
- }
325
- }
326
- return {
327
- valid: findings.every((item) => item.severity !== "error"),
328
- findings,
329
- };
330
- }
331
368
  function packageJsonPath(packageDir) {
332
369
  const packagePath = join(packageDir, "package.json");
333
370
  return existsSync(packagePath) ? packagePath : undefined;
334
371
  }
335
372
  function piPackageManifestBlock(raw) {
336
- const pi = asRecord(raw.pi);
337
- return Object.keys(pi).length > 0 ? pi : undefined;
373
+ return raw.pi && typeof raw.pi === "object" && !Array.isArray(raw.pi)
374
+ ? raw.pi
375
+ : undefined;
338
376
  }
339
377
  function piPackageManifestPath(packageDir) {
340
378
  const manifestPath = packageJsonPath(packageDir);
@@ -357,20 +395,32 @@ export function readPiPackageManifest(packageDir) {
357
395
  for (const key of RESOURCE_KEYS) {
358
396
  resources[key] = stringArray(pi[key]).map(normalizeResourcePath);
359
397
  }
360
- return {
398
+ const manifest = {
361
399
  name: stringValue(raw.name) ?? "",
362
400
  version: stringValue(raw.version) ?? "0.0.0",
363
401
  ...(stringValue(raw.description) ? { description: stringValue(raw.description) } : {}),
364
402
  path: packageDir,
365
403
  resources,
404
+ resourceDeclarations: resourceDeclarations(pi),
366
405
  mcp: parseMcpConfig(pi.mcp),
367
- evals: parseEvalsConfig(pi.evals, Object.prototype.hasOwnProperty.call(pi, "evals")),
368
406
  };
407
+ Object.defineProperty(manifest, SOURCE_FINDINGS, {
408
+ value: sourceShapeFindings(pi, manifest.name),
409
+ enumerable: false,
410
+ });
411
+ return manifest;
369
412
  }
370
- export function resolvePiPackageMcpManifestPaths(pkg, opts = {}) {
413
+ export function resolvePiPackageMcpManifestPaths(pkg) {
371
414
  const globs = pkg.mcp.manifests;
372
- const resolved = new Set();
373
- const entries = globs.some(hasGlob) ? listPackageEntries(pkg.path) : [];
415
+ const resolved = [];
416
+ const seen = new Set();
417
+ const add = (path) => {
418
+ if (seen.has(path))
419
+ return;
420
+ seen.add(path);
421
+ resolved.push(path);
422
+ };
423
+ const entries = listPackageEntries(pkg.path, globs);
374
424
  for (const glob of globs) {
375
425
  if (!glob.trim())
376
426
  continue;
@@ -389,7 +439,8 @@ export function resolvePiPackageMcpManifestPaths(pkg, opts = {}) {
389
439
  if (!existsSync(target)) {
390
440
  throw new RecipePackageError(`Recipe ${pkg.name} declares missing mcp manifest: ${glob}`);
391
441
  }
392
- resolved.add(target);
442
+ assertResourceTreeContained(pkg.path, target, "mcp manifest");
443
+ add(target);
393
444
  continue;
394
445
  }
395
446
  const matcher = globToRegExp(glob);
@@ -397,19 +448,26 @@ export function resolvePiPackageMcpManifestPaths(pkg, opts = {}) {
397
448
  .filter((entry) => matcher.test(normalizeResourcePath(entry)))
398
449
  .map((entry) => resolve(pkg.path, entry));
399
450
  if (matches.length === 0) {
400
- if (opts.allowEmptyGlobMatches)
401
- continue;
402
451
  throw new RecipePackageError(`Recipe ${pkg.name} declares mcp manifest glob with no matches: ${glob}`);
403
452
  }
404
- for (const match of matches)
405
- resolved.add(match);
453
+ for (const match of matches.sort()) {
454
+ assertResourceTreeContained(pkg.path, match, "mcp manifest");
455
+ add(match);
456
+ }
406
457
  }
407
- return [...resolved].sort();
458
+ return resolved;
408
459
  }
409
- export function resolvePiPackageResourcePaths(pkg, key, opts = {}) {
460
+ export function resolvePiPackageResourcePaths(pkg, key) {
410
461
  const globs = pkg.resources[key];
411
- const resolved = new Set();
412
- const entries = globs.some(hasGlob) ? listPackageEntries(pkg.path) : [];
462
+ const resolved = [];
463
+ const seen = new Set();
464
+ const add = (path) => {
465
+ if (seen.has(path))
466
+ return;
467
+ seen.add(path);
468
+ resolved.push(path);
469
+ };
470
+ const entries = listPackageEntries(pkg.path, globs);
413
471
  for (const glob of globs) {
414
472
  if (!glob.trim())
415
473
  continue;
@@ -419,7 +477,8 @@ export function resolvePiPackageResourcePaths(pkg, key, opts = {}) {
419
477
  if (!existsSync(direct)) {
420
478
  throw new RecipePackageError(`Recipe ${pkg.name} declares missing ${key} resource: ${glob}`);
421
479
  }
422
- resolved.add(direct);
480
+ assertResourceTreeContained(pkg.path, direct, `${key} resource`);
481
+ add(direct);
423
482
  continue;
424
483
  }
425
484
  const matcher = globToRegExp(glob);
@@ -427,14 +486,14 @@ export function resolvePiPackageResourcePaths(pkg, key, opts = {}) {
427
486
  .filter((entry) => matcher.test(normalizeResourcePath(entry)))
428
487
  .map((entry) => resolve(pkg.path, entry));
429
488
  if (matches.length === 0) {
430
- if (opts.allowEmptyGlobMatches)
431
- continue;
432
489
  throw new RecipePackageError(`Recipe ${pkg.name} declares ${key} glob with no matches: ${glob}`);
433
490
  }
434
- for (const match of matches)
435
- resolved.add(match);
491
+ for (const match of matches.sort()) {
492
+ assertResourceTreeContained(pkg.path, match, `${key} resource`);
493
+ add(match);
494
+ }
436
495
  }
437
- return [...resolved].sort();
496
+ return resolved;
438
497
  }
439
498
  export function defaultPiPackageResourcePaths(pkg, key) {
440
499
  const defaults = {
@@ -445,34 +504,32 @@ export function defaultPiPackageResourcePaths(pkg, key) {
445
504
  return (defaults[key] ?? []).filter((path) => existsSync(path));
446
505
  }
447
506
  export function packageResourcePaths(pkg, key) {
448
- if (pkg.resources[key].length > 0) {
449
- return resolvePiPackageResourcePaths(pkg, key, {
450
- allowEmptyGlobMatches: key === "extensions" ||
451
- key === "skills" ||
452
- key === "prompts",
453
- });
507
+ if (pkg.resourceDeclarations?.[key] || pkg.resources[key].length > 0) {
508
+ return resolvePiPackageResourcePaths(pkg, key);
454
509
  }
455
510
  return defaultPiPackageResourcePaths(pkg, key);
456
511
  }
457
512
  export function validatePiPackageManifest(pkg) {
458
- const findings = [];
513
+ const findings = [
514
+ ...(pkg[SOURCE_FINDINGS] ?? []),
515
+ ];
459
516
  if (!pkg.name.trim()) {
460
- findings.push(finding("error", "package.name_missing", "Package is missing name"));
517
+ findings.push(finding("package.name_missing", "Package is missing name"));
461
518
  }
462
519
  if (!piPackageManifestPath(pkg.path)) {
463
- findings.push(finding("error", "package.manifest_missing", "Package is missing package.json with a pi manifest", pkg.name));
520
+ findings.push(finding("package.manifest_missing", "Package is missing package.json with a pi manifest", pkg.name));
464
521
  }
465
- if (pkg.resources.agents.length === 0 &&
522
+ if (!pkg.resourceDeclarations?.agents &&
523
+ pkg.resources.agents.length === 0 &&
466
524
  !existsSync(join(pkg.path, "agents"))) {
467
- findings.push(finding("warning", "package.no_agents", "Package declares no agents and has no agents directory", pkg.name));
525
+ findings.push(finding("package.no_agents", "Package declares no agents and has no agents directory", pkg.name));
468
526
  }
469
527
  const invalidMcpServer = pkg.mcp.servers.find((server) => !isValidRecipeMcpToolSelection(server.tools));
470
528
  if (invalidMcpServer) {
471
- findings.push(finding("error", "pi.mcp_invalid", `MCP server "${invalidMcpServer.id}" has an invalid tool policy; run introspection check for details`, pkg.name));
529
+ findings.push(finding("pi.mcp_invalid", `MCP server "${invalidMcpServer.id}" has an invalid tool policy`, pkg.name));
472
530
  }
473
- findings.push(...validateRecipeEvalsConfig(pkg.evals, pkg.name).findings);
474
531
  return {
475
- valid: findings.every((item) => item.severity !== "error"),
532
+ valid: findings.length === 0,
476
533
  findings,
477
534
  };
478
535
  }