@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.
- package/README.md +45 -58
- package/dist/{agent-tool.d.ts → agents.d.ts} +4 -1
- package/dist/agents.d.ts.map +1 -0
- package/dist/{agent-tool.js → agents.js} +1 -1
- package/dist/agents.js.map +1 -0
- package/dist/api/extensions.d.ts +3 -0
- package/dist/api/extensions.d.ts.map +1 -0
- package/dist/api/extensions.js +2 -0
- package/dist/api/extensions.js.map +1 -0
- package/dist/api/mcp.d.ts +4 -0
- package/dist/api/mcp.d.ts.map +1 -0
- package/dist/api/mcp.js +2 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/session.d.ts +3 -0
- package/dist/api/session.d.ts.map +1 -0
- package/dist/api/session.js +2 -0
- package/dist/api/session.js.map +1 -0
- package/dist/child-agent.d.ts +5 -4
- package/dist/child-agent.d.ts.map +1 -1
- package/dist/child-agent.js +26 -117
- package/dist/child-agent.js.map +1 -1
- package/dist/child-session.d.ts +23 -0
- package/dist/child-session.d.ts.map +1 -0
- package/dist/child-session.js +48 -0
- package/dist/child-session.js.map +1 -0
- package/dist/extensions.d.ts +29 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +119 -0
- package/dist/extensions.js.map +1 -0
- package/dist/index.d.ts +4 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -13
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +38 -5
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +92 -41
- package/dist/inspect.js.map +1 -1
- package/dist/interactions.d.ts +18 -2
- package/dist/interactions.d.ts.map +1 -1
- package/dist/interactions.js +41 -10
- package/dist/interactions.js.map +1 -1
- package/dist/mcp-catalog.d.ts +1 -0
- package/dist/mcp-catalog.d.ts.map +1 -1
- package/dist/mcp-catalog.js +14 -6
- package/dist/mcp-catalog.js.map +1 -1
- package/dist/mcp-chunks/auth-command-ZLGYIEUY.js +273 -0
- package/dist/mcp-chunks/call-arguments-LU3W6D3K.js +13 -0
- package/dist/mcp-chunks/call-command-LKTKSYTH.js +612 -0
- package/dist/mcp-chunks/chunk-2U4BSDC4.js +3951 -0
- package/dist/mcp-chunks/chunk-2XL7MG7R.js +272 -0
- package/dist/mcp-chunks/chunk-3BJA27PY.js +2321 -0
- package/dist/mcp-chunks/chunk-4SQKWYFF.js +15 -0
- package/dist/mcp-chunks/chunk-5QOSCSQB.js +290 -0
- package/dist/mcp-chunks/chunk-6W5QFASN.js +77 -0
- package/dist/mcp-chunks/chunk-6XCZGU3G.js +1004 -0
- package/dist/mcp-chunks/chunk-7FHEBT5G.js +149 -0
- package/dist/mcp-chunks/chunk-7KA2VJVZ.js +114 -0
- package/dist/mcp-chunks/chunk-7UDVSOOF.js +138 -0
- package/dist/mcp-chunks/chunk-BNV3TPE4.js +66 -0
- package/dist/mcp-chunks/chunk-BTLAWHTA.js +129 -0
- package/dist/mcp-chunks/chunk-BV56DXPW.js +13 -0
- package/dist/mcp-chunks/chunk-BWHIA4PC.js +90 -0
- package/dist/mcp-chunks/chunk-BZIQOWE6.js +267 -0
- package/dist/mcp-chunks/chunk-D3MRAZ6H.js +16140 -0
- package/dist/mcp-chunks/chunk-DHBE7UBH.js +750 -0
- package/dist/mcp-chunks/chunk-E22DYOW5.js +251 -0
- package/dist/mcp-chunks/chunk-ECIVB7P4.js +139 -0
- package/dist/mcp-chunks/chunk-HKRJ6O36.js +1076 -0
- package/dist/mcp-chunks/chunk-IABRYROW.js +44 -0
- package/dist/mcp-chunks/chunk-J6LYIZXN.js +45 -0
- package/dist/mcp-chunks/chunk-L7NTQVYN.js +161 -0
- package/dist/mcp-chunks/chunk-LEZSACDE.js +141 -0
- package/dist/mcp-chunks/chunk-LKIBAYKU.js +208 -0
- package/dist/mcp-chunks/chunk-LNH5LAJD.js +152 -0
- package/dist/mcp-chunks/chunk-LQQEYD76.js +124 -0
- package/dist/mcp-chunks/chunk-LRKZP7DJ.js +47 -0
- package/dist/mcp-chunks/chunk-LS7PMWSR.js +109 -0
- package/dist/mcp-chunks/chunk-M3TPODXI.js +95 -0
- package/dist/mcp-chunks/chunk-MZ2NARG7.js +400 -0
- package/dist/mcp-chunks/chunk-N46NEFAF.js +45 -0
- package/dist/mcp-chunks/chunk-NNOEGEUZ.js +17273 -0
- package/dist/mcp-chunks/chunk-NRAJX5E4.js +69 -0
- package/dist/mcp-chunks/chunk-P3PSC54P.js +869 -0
- package/dist/mcp-chunks/chunk-PIIYJR44.js +6341 -0
- package/dist/mcp-chunks/chunk-PWYFKGKG.js +385 -0
- package/dist/mcp-chunks/chunk-SJCCBNZ4.js +45 -0
- package/dist/mcp-chunks/chunk-TATVT5IT.js +194 -0
- package/dist/mcp-chunks/chunk-TEUKFFQL.js +29 -0
- package/dist/mcp-chunks/chunk-UKYNQGNE.js +150 -0
- package/dist/mcp-chunks/chunk-UNKB4SBV.js +37 -0
- package/dist/mcp-chunks/chunk-WD6BOD24.js +138 -0
- package/dist/mcp-chunks/chunk-WQJZMCJN.js +73 -0
- package/dist/mcp-chunks/chunk-XANPGTLI.js +364 -0
- package/dist/mcp-chunks/chunk-XQK2JSW4.js +35 -0
- package/dist/mcp-chunks/chunk-YHQUTU6L.js +571 -0
- package/dist/mcp-chunks/chunk-YVZKMV5H.js +57 -0
- package/dist/mcp-chunks/cli-UV4QJZZM.js +929 -0
- package/dist/mcp-chunks/client-S7RQUG5U.js +13 -0
- package/dist/mcp-chunks/config-command-NLAGKID5.js +1101 -0
- package/dist/mcp-chunks/daemon-command-U242Z6VY.js +26 -0
- package/dist/mcp-chunks/dist-LK3SHAMX.js +6478 -0
- package/dist/mcp-chunks/emit-ts-command-VBXVXO6V.js +386 -0
- package/dist/mcp-chunks/generate-cli-runner-6CAZZA3P.js +1426 -0
- package/dist/mcp-chunks/inspect-cli-command-3ZORTOR7.js +111 -0
- package/dist/mcp-chunks/launch-YKRXHXJI.js +10 -0
- package/dist/mcp-chunks/lifecycle-XVKRHPH3.js +16 -0
- package/dist/mcp-chunks/list-command-2MEGGNFQ.js +4190 -0
- package/dist/mcp-chunks/output-utils-NKESLAJ6.js +12 -0
- package/dist/mcp-chunks/prompt-B1Yc1NPt-VGOTWJMN.js +918 -0
- package/dist/mcp-chunks/record-command-YXHTTTT5.js +17 -0
- package/dist/mcp-chunks/replay-command-25HJ6AQU.js +85 -0
- package/dist/mcp-chunks/resource-command-OO4NYF5C.js +97 -0
- package/dist/mcp-chunks/result-utils-MOHHLNM7.js +15 -0
- package/dist/mcp-chunks/runtime-5WJ3G25Y.js +33 -0
- package/dist/mcp-chunks/runtime-wrapper-RPWDIDBF.js +10 -0
- package/dist/mcp-chunks/serve-command-DHXJZMMN.js +3302 -0
- package/dist/mcp-chunks/timeouts-W2XWCCXR.js +18 -0
- package/dist/mcp-chunks/vault-command-PIYIUEZL.js +154 -0
- package/dist/mcp-daemon-client.d.ts +6 -1
- package/dist/mcp-daemon-client.d.ts.map +1 -1
- package/dist/mcp-daemon-client.js +119 -2
- package/dist/mcp-daemon-client.js.map +1 -1
- package/dist/mcp-daemon-protocol.d.ts +18 -1
- package/dist/mcp-daemon-protocol.d.ts.map +1 -1
- package/dist/mcp-daemon-protocol.js +31 -0
- package/dist/mcp-daemon-protocol.js.map +1 -1
- package/dist/mcp-daemon.js +169 -76602
- package/dist/mcp-daemon.js.map +1 -1
- package/dist/mcp-policy.d.ts +11 -0
- package/dist/mcp-policy.d.ts.map +1 -0
- package/dist/mcp-policy.js +27 -0
- package/dist/mcp-policy.js.map +1 -0
- package/dist/mcp-run-worker.js +38 -76540
- package/dist/mcp-tools.d.ts +39 -0
- package/dist/mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools.js +493 -0
- package/dist/mcp-tools.js.map +1 -0
- package/dist/mcp.d.ts +21 -9
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +101 -31
- package/dist/mcp.js.map +1 -1
- package/dist/model-binding.d.ts +39 -0
- package/dist/model-binding.d.ts.map +1 -0
- package/dist/model-binding.js +103 -0
- package/dist/model-binding.js.map +1 -0
- package/dist/pi-extension.d.ts +2 -22
- package/dist/pi-extension.d.ts.map +1 -1
- package/dist/pi-extension.js +296 -71
- package/dist/pi-extension.js.map +1 -1
- package/dist/recipe/resolve.d.ts +43 -20
- package/dist/recipe/resolve.d.ts.map +1 -1
- package/dist/recipe/resolve.js +215 -70
- package/dist/recipe/resolve.js.map +1 -1
- package/dist/recipe-agent.d.ts +24 -27
- package/dist/recipe-agent.d.ts.map +1 -1
- package/dist/recipe-agent.js +278 -230
- package/dist/recipe-agent.js.map +1 -1
- package/dist/recipe-check.d.ts +17 -0
- package/dist/recipe-check.d.ts.map +1 -0
- package/dist/recipe-check.js +258 -0
- package/dist/recipe-check.js.map +1 -0
- package/dist/recipe-extensions.d.ts.map +1 -1
- package/dist/recipe-extensions.js +0 -3
- package/dist/recipe-extensions.js.map +1 -1
- package/dist/recipe-model.d.ts +3 -1
- package/dist/recipe-model.d.ts.map +1 -1
- package/dist/recipe-model.js +15 -12
- package/dist/recipe-model.js.map +1 -1
- package/dist/recipe-package.d.ts +6 -28
- package/dist/recipe-package.d.ts.map +1 -1
- package/dist/recipe-package.js +248 -191
- package/dist/recipe-package.js.map +1 -1
- package/dist/recipe-skills.d.ts.map +1 -1
- package/dist/recipe-skills.js +42 -29
- package/dist/recipe-skills.js.map +1 -1
- package/dist/run-controller.d.ts +10 -9
- package/dist/run-controller.d.ts.map +1 -1
- package/dist/run-controller.js +28 -21
- package/dist/run-controller.js.map +1 -1
- package/dist/session.d.ts +40 -53
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +312 -173
- package/dist/session.js.map +1 -1
- package/dist/test-utils.d.ts +7 -7
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +32 -15
- package/dist/test-utils.js.map +1 -1
- package/docs/agent-composition.md +41 -35
- package/docs/host-api.md +201 -0
- package/docs/index.md +8 -14
- package/docs/interactions.md +12 -11
- package/docs/mcp-auth.md +6 -6
- package/docs/mcp-configuration.md +72 -24
- package/docs/pi-extension.md +39 -11
- package/docs/recipe-flow.md +9 -9
- package/docs/recipe-format.md +90 -41
- package/package.json +23 -17
- package/vendor/mcp-client/darwin-arm64/mcp-client +0 -0
- package/vendor/mcp-client/darwin-x64/mcp-client +0 -0
- package/vendor/mcp-client/linux-arm64/mcp-client +0 -0
- package/vendor/recipe-check/darwin-arm64/recipe-check +0 -0
- package/vendor/recipe-check/darwin-x64/recipe-check +0 -0
- package/vendor/recipe-check/linux-arm64/recipe-check +0 -0
- package/vendor/recipe-check/linux-x64/recipe-check +0 -0
- package/vendor/recipe-check/linux-x64-musl/recipe-check +0 -0
- package/vendor/recipe-check/win32-x64/recipe-check.exe +0 -0
- package/dist/agent-tool.d.ts.map +0 -1
- package/dist/agent-tool.js.map +0 -1
- package/dist/run.d.ts +0 -28
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -84
- package/dist/run.js.map +0 -1
- package/dist/testing.d.ts +0 -25
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -96
- package/dist/testing.js.map +0 -1
- package/docs/deployment-configuration.md +0 -65
- package/docs/migration.md +0 -70
- package/docs/python-bindings-release.md +0 -47
- package/docs/recipe-evals.md +0 -70
- package/docs/recipe-judges.md +0 -152
- package/docs/runtime-library.md +0 -171
package/dist/recipe-package.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
71
|
-
|
|
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":"
|
|
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"}
|
package/dist/recipe-package.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
337
|
-
|
|
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
|
-
|
|
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
|
|
413
|
+
export function resolvePiPackageMcpManifestPaths(pkg) {
|
|
371
414
|
const globs = pkg.mcp.manifests;
|
|
372
|
-
const resolved =
|
|
373
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
453
|
+
for (const match of matches.sort()) {
|
|
454
|
+
assertResourceTreeContained(pkg.path, match, "mcp manifest");
|
|
455
|
+
add(match);
|
|
456
|
+
}
|
|
406
457
|
}
|
|
407
|
-
return
|
|
458
|
+
return resolved;
|
|
408
459
|
}
|
|
409
|
-
export function resolvePiPackageResourcePaths(pkg, key
|
|
460
|
+
export function resolvePiPackageResourcePaths(pkg, key) {
|
|
410
461
|
const globs = pkg.resources[key];
|
|
411
|
-
const resolved =
|
|
412
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
491
|
+
for (const match of matches.sort()) {
|
|
492
|
+
assertResourceTreeContained(pkg.path, match, `${key} resource`);
|
|
493
|
+
add(match);
|
|
494
|
+
}
|
|
436
495
|
}
|
|
437
|
-
return
|
|
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("
|
|
517
|
+
findings.push(finding("package.name_missing", "Package is missing name"));
|
|
461
518
|
}
|
|
462
519
|
if (!piPackageManifestPath(pkg.path)) {
|
|
463
|
-
findings.push(finding("
|
|
520
|
+
findings.push(finding("package.manifest_missing", "Package is missing package.json with a pi manifest", pkg.name));
|
|
464
521
|
}
|
|
465
|
-
if (pkg.
|
|
522
|
+
if (!pkg.resourceDeclarations?.agents &&
|
|
523
|
+
pkg.resources.agents.length === 0 &&
|
|
466
524
|
!existsSync(join(pkg.path, "agents"))) {
|
|
467
|
-
findings.push(finding("
|
|
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("
|
|
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.
|
|
532
|
+
valid: findings.length === 0,
|
|
476
533
|
findings,
|
|
477
534
|
};
|
|
478
535
|
}
|