@agentyx/core 0.5.0 → 0.7.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/dist/index.d.mts +317 -30
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +569 -87
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/schema/agentyx.schema.json +31 -0
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { access, readFile, readdir, realpath } from "node:fs/promises";
|
|
3
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { constants, readFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
//#region src/errors.ts
|
|
@@ -97,6 +97,30 @@ var LocalSkillDirectoryError = class extends AgentyxError {
|
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
99
|
//#endregion
|
|
100
|
+
//#region src/hook/schema.ts
|
|
101
|
+
const HOOK_ACTIVATION_LEVELS = ["default", "optional"];
|
|
102
|
+
const hookActivationLevelSchema = z.enum(HOOK_ACTIVATION_LEVELS);
|
|
103
|
+
const HOOK_EVENTS = ["SessionStart"];
|
|
104
|
+
const hookEventSchema = z.enum(HOOK_EVENTS);
|
|
105
|
+
const hookNameSchema = z.string().min(1, "Hook names must be non-empty strings.").regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Hook names must be lowercase kebab-case, for example \"session-doctor-bootstrap\".");
|
|
106
|
+
const hookDefinitionSchema = z.strictObject({
|
|
107
|
+
name: hookNameSchema.describe("Unique hook identifier."),
|
|
108
|
+
description: z.string().trim().min(1, "Hook descriptions must be non-empty strings."),
|
|
109
|
+
event: hookEventSchema.describe("The provider lifecycle event this hook runs on."),
|
|
110
|
+
command: z.string().trim().min(1, "Hook command must be a non-empty string."),
|
|
111
|
+
args: z.array(z.string()).default([])
|
|
112
|
+
});
|
|
113
|
+
const hookReferenceSchema = z.union([hookNameSchema, z.strictObject({
|
|
114
|
+
name: hookNameSchema.describe("Hook identifier."),
|
|
115
|
+
activation: hookActivationLevelSchema.default("default")
|
|
116
|
+
})]).transform((value) => typeof value === "string" ? {
|
|
117
|
+
name: value,
|
|
118
|
+
activation: "default"
|
|
119
|
+
} : value).pipe(z.strictObject({
|
|
120
|
+
name: hookNameSchema.describe("Hook identifier."),
|
|
121
|
+
activation: hookActivationLevelSchema.describe("Whether this hook is active by default or explicit opt-in.")
|
|
122
|
+
}));
|
|
123
|
+
//#endregion
|
|
100
124
|
//#region src/mcp/schema.ts
|
|
101
125
|
const MCP_CAPABILITY_LEVELS = ["default", "optional"];
|
|
102
126
|
const mcpCapabilityLevelSchema = z.enum(MCP_CAPABILITY_LEVELS);
|
|
@@ -106,13 +130,15 @@ const MCP_CONTEXT_COSTS = [
|
|
|
106
130
|
"high"
|
|
107
131
|
];
|
|
108
132
|
const mcpContextCostSchema = z.enum(MCP_CONTEXT_COSTS);
|
|
133
|
+
const mcpRuntimeSchema = z.enum(["local", "may-download"]);
|
|
109
134
|
const mcpServerNameSchema = z.string().min(1, "MCP server names must be non-empty strings.").regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "MCP server names must be lowercase kebab-case, for example \"context7\".");
|
|
110
135
|
const mcpEnvReferenceSchema = z.strictObject({ fromEnv: z.string().trim().min(1, "Environment variable references must be non-empty strings.").regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "Environment variable names must be valid shell names.") });
|
|
111
136
|
const commonMcpServerDefinitionSchema = z.strictObject({
|
|
112
137
|
name: mcpServerNameSchema.describe("Unique MCP server identifier."),
|
|
113
138
|
description: z.string().trim().min(1, "MCP server descriptions must be non-empty strings."),
|
|
114
139
|
transport: z.enum(["stdio", "http"]),
|
|
115
|
-
contextCost: mcpContextCostSchema.describe("Qualitative context/tool-schema overhead classification; not token accounting.").optional()
|
|
140
|
+
contextCost: mcpContextCostSchema.describe("Qualitative context/tool-schema overhead classification; not token accounting.").optional(),
|
|
141
|
+
runtime: mcpRuntimeSchema.describe("Whether launching this server can fetch a package on first use; see MCP_RUNTIMES.").optional()
|
|
116
142
|
});
|
|
117
143
|
const explicitMcpServerReferenceSchema = z.strictObject({
|
|
118
144
|
name: mcpServerNameSchema.describe("MCP server identifier."),
|
|
@@ -204,7 +230,8 @@ const packDefinitionSchema = z.strictObject({
|
|
|
204
230
|
description: z.string().min(1, "Pack descriptions must be non-empty when provided.").describe("Short human-readable summary of the pack.").optional(),
|
|
205
231
|
skills: z.array(skillNameSchema).describe("Skills this pack contributes, in declaration order.").default([]),
|
|
206
232
|
mcpServers: z.array(mcpServerReferenceSchema).describe("MCP servers this pack contributes, in declaration order.").default([]),
|
|
207
|
-
tools: z.array(toolReferenceSchema).describe("Local tools this pack contributes, in declaration order.").default([])
|
|
233
|
+
tools: z.array(toolReferenceSchema).describe("Local tools this pack contributes, in declaration order.").default([]),
|
|
234
|
+
hooks: z.array(hookReferenceSchema).describe("Provider lifecycle hooks this pack contributes, in declaration order.").default([])
|
|
208
235
|
});
|
|
209
236
|
//#endregion
|
|
210
237
|
//#region src/source/schema.ts
|
|
@@ -434,6 +461,10 @@ const builtInPacks = [
|
|
|
434
461
|
tools: [{
|
|
435
462
|
name: "rtk",
|
|
436
463
|
activation: "optional"
|
|
464
|
+
}],
|
|
465
|
+
hooks: [{
|
|
466
|
+
name: "session-doctor-bootstrap",
|
|
467
|
+
activation: "default"
|
|
437
468
|
}]
|
|
438
469
|
},
|
|
439
470
|
{
|
|
@@ -641,14 +672,14 @@ var InvalidSkillError = class extends AgentyxError {
|
|
|
641
672
|
origin;
|
|
642
673
|
reason;
|
|
643
674
|
constructor(origin, reason, options) {
|
|
644
|
-
const detail = typeof reason === "string" ? reason : formatIssues$
|
|
675
|
+
const detail = typeof reason === "string" ? reason : formatIssues$2(reason);
|
|
645
676
|
super("invalid_skill", `Invalid skill in ${origin}: ${detail}`, options);
|
|
646
677
|
this.name = "InvalidSkillError";
|
|
647
678
|
this.origin = origin;
|
|
648
679
|
this.reason = detail;
|
|
649
680
|
}
|
|
650
681
|
};
|
|
651
|
-
function formatIssues$
|
|
682
|
+
function formatIssues$2(error) {
|
|
652
683
|
return error.issues.map((issue) => {
|
|
653
684
|
const path = issue.path.map(String).join(".");
|
|
654
685
|
return path === "" ? issue.message : `${path}: ${issue.message}`;
|
|
@@ -969,13 +1000,20 @@ async function loadLocalSkillSources(projectDir, directories) {
|
|
|
969
1000
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
970
1001
|
if (!entry.isDirectory()) continue;
|
|
971
1002
|
const filePath = join(rootRealPath, entry.name, "SKILL.md");
|
|
972
|
-
let
|
|
1003
|
+
let realFilePath;
|
|
973
1004
|
try {
|
|
974
|
-
|
|
1005
|
+
realFilePath = await realpath(filePath);
|
|
975
1006
|
} catch (cause) {
|
|
976
1007
|
if (cause instanceof Error && cause.code === "ENOENT") continue;
|
|
977
1008
|
throw localDirectoryError(directory, `cannot read ${entry.name}/SKILL.md`, cause);
|
|
978
1009
|
}
|
|
1010
|
+
if (!isInside$1(realFilePath, rootRealPath)) throw new LocalSkillDirectoryError(directory, `${entry.name}/SKILL.md resolves outside the skill directory`);
|
|
1011
|
+
let markdown;
|
|
1012
|
+
try {
|
|
1013
|
+
markdown = await readFile(realFilePath, "utf8");
|
|
1014
|
+
} catch (cause) {
|
|
1015
|
+
throw localDirectoryError(directory, `cannot read ${entry.name}/SKILL.md`, cause);
|
|
1016
|
+
}
|
|
979
1017
|
const skill = parseSkillMarkdown(markdown, filePath);
|
|
980
1018
|
if (skill.name !== entry.name) throw new LocalSkillDirectoryError(directory, `${entry.name}/SKILL.md declares the name "${skill.name}"`);
|
|
981
1019
|
sources.push({
|
|
@@ -994,6 +1032,149 @@ function localDirectoryError(directory, reason, cause) {
|
|
|
994
1032
|
return new LocalSkillDirectoryError(directory, reason, cause instanceof Error ? { cause } : void 0);
|
|
995
1033
|
}
|
|
996
1034
|
//#endregion
|
|
1035
|
+
//#region src/hook/errors.ts
|
|
1036
|
+
var UnknownHookError = class extends AgentyxError {
|
|
1037
|
+
hookName;
|
|
1038
|
+
requiredBy;
|
|
1039
|
+
knownHooks;
|
|
1040
|
+
constructor(hookName, requiredBy, knownHooks) {
|
|
1041
|
+
const origin = requiredBy === void 0 ? "" : ` (required by "${requiredBy}")`;
|
|
1042
|
+
const known = knownHooks.length > 0 ? [...knownHooks].sort().join(", ") : "none";
|
|
1043
|
+
super("unknown_hook", `Unknown hook "${hookName}"${origin}. Known hooks: ${known}.`);
|
|
1044
|
+
this.name = "UnknownHookError";
|
|
1045
|
+
this.hookName = hookName;
|
|
1046
|
+
this.requiredBy = requiredBy;
|
|
1047
|
+
this.knownHooks = knownHooks;
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
var DuplicateHookError = class extends AgentyxError {
|
|
1051
|
+
hookName;
|
|
1052
|
+
constructor(hookName) {
|
|
1053
|
+
super("duplicate_hook", `Duplicate hook definition: "${hookName}".`);
|
|
1054
|
+
this.name = "DuplicateHookError";
|
|
1055
|
+
this.hookName = hookName;
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
var InvalidHookError = class extends AgentyxError {
|
|
1059
|
+
origin;
|
|
1060
|
+
reason;
|
|
1061
|
+
constructor(origin, reason, options) {
|
|
1062
|
+
const detail = typeof reason === "string" ? reason : formatIssues$1(reason);
|
|
1063
|
+
super("invalid_hook", `Invalid hook in ${origin}: ${detail}`, options);
|
|
1064
|
+
this.name = "InvalidHookError";
|
|
1065
|
+
this.origin = origin;
|
|
1066
|
+
this.reason = detail;
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
function formatIssues$1(error) {
|
|
1070
|
+
return error.issues.map((issue) => {
|
|
1071
|
+
const path = issue.path.map(String).join(".");
|
|
1072
|
+
return path === "" ? issue.message : `${path}: ${issue.message}`;
|
|
1073
|
+
}).join("; ");
|
|
1074
|
+
}
|
|
1075
|
+
//#endregion
|
|
1076
|
+
//#region src/hook/registry.ts
|
|
1077
|
+
function createHookRegistry(sources) {
|
|
1078
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const source of sources) {
|
|
1080
|
+
const name = hookNameSchema.safeParse(source.name);
|
|
1081
|
+
if (!name.success) throw new InvalidHookError(`Hook source "${source.name}"`, name.error);
|
|
1082
|
+
if (byName.has(name.data)) throw new DuplicateHookError(name.data);
|
|
1083
|
+
byName.set(name.data, source);
|
|
1084
|
+
}
|
|
1085
|
+
const names = [...byName.keys()];
|
|
1086
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
1087
|
+
const get = (name) => {
|
|
1088
|
+
const cached = loaded.get(name);
|
|
1089
|
+
if (cached !== void 0) return cached;
|
|
1090
|
+
const source = byName.get(name);
|
|
1091
|
+
if (source === void 0) throw new UnknownHookError(name, void 0, names);
|
|
1092
|
+
const origin = `Hook "${name}"`;
|
|
1093
|
+
const hook = hookDefinitionSchema.safeParse(source.load());
|
|
1094
|
+
if (!hook.success) throw new InvalidHookError(origin, hook.error);
|
|
1095
|
+
if (hook.data.name !== name) throw new InvalidHookError(origin, `it declares the name "${hook.data.name}"`);
|
|
1096
|
+
loaded.set(name, hook.data);
|
|
1097
|
+
return hook.data;
|
|
1098
|
+
};
|
|
1099
|
+
return {
|
|
1100
|
+
names,
|
|
1101
|
+
has: (name) => byName.has(name),
|
|
1102
|
+
listMetadata: () => names.map((name) => {
|
|
1103
|
+
const hook = get(name);
|
|
1104
|
+
return {
|
|
1105
|
+
name: hook.name,
|
|
1106
|
+
description: hook.description,
|
|
1107
|
+
event: hook.event
|
|
1108
|
+
};
|
|
1109
|
+
}),
|
|
1110
|
+
get
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
const builtInHookRegistry = createHookRegistry([{
|
|
1114
|
+
name: "session-doctor-bootstrap",
|
|
1115
|
+
load: () => ({
|
|
1116
|
+
name: "session-doctor-bootstrap",
|
|
1117
|
+
description: "Reports Agentyx project health at session start; silent when healthy.",
|
|
1118
|
+
event: "SessionStart",
|
|
1119
|
+
command: "npx",
|
|
1120
|
+
args: [
|
|
1121
|
+
"--no-install",
|
|
1122
|
+
"agentyx",
|
|
1123
|
+
"doctor",
|
|
1124
|
+
"--hook"
|
|
1125
|
+
]
|
|
1126
|
+
})
|
|
1127
|
+
}]);
|
|
1128
|
+
const builtInHookNames = builtInHookRegistry.names;
|
|
1129
|
+
//#endregion
|
|
1130
|
+
//#region src/pack/resolver.ts
|
|
1131
|
+
/**
|
|
1132
|
+
* Resolves selected packs without hidden inheritance.
|
|
1133
|
+
*
|
|
1134
|
+
* The result preserves configured order, removes duplicates by first
|
|
1135
|
+
* occurrence, and validates every requested pack.
|
|
1136
|
+
*
|
|
1137
|
+
* @throws {UnknownPackError} when a requested pack is missing.
|
|
1138
|
+
*/
|
|
1139
|
+
function resolvePacks(requestedPacks, registry = builtInPackRegistry) {
|
|
1140
|
+
const resolved = [];
|
|
1141
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1142
|
+
for (const name of requestedPacks) {
|
|
1143
|
+
if (seen.has(name)) continue;
|
|
1144
|
+
if (!registry.has(name)) throw new UnknownPackError(name, void 0, [...registry.keys()]);
|
|
1145
|
+
seen.add(name);
|
|
1146
|
+
resolved.push(name);
|
|
1147
|
+
}
|
|
1148
|
+
return resolved;
|
|
1149
|
+
}
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/hook/resolver.ts
|
|
1152
|
+
function collectPackHookReferences(resolvedPacks, packRegistry, hookRegistry) {
|
|
1153
|
+
const hooks = [];
|
|
1154
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1155
|
+
for (const packName of resolvedPacks) for (const hook of packRegistry.get(packName)?.hooks ?? []) {
|
|
1156
|
+
const hookName = hook.name;
|
|
1157
|
+
if (seen.has(hookName)) continue;
|
|
1158
|
+
if (!hookRegistry.has(hookName)) throw new UnknownHookError(hookName, packName, hookRegistry.names);
|
|
1159
|
+
seen.add(hookName);
|
|
1160
|
+
hooks.push(hook);
|
|
1161
|
+
}
|
|
1162
|
+
return hooks;
|
|
1163
|
+
}
|
|
1164
|
+
function filterEffectiveHooks(hooks, enabledCapabilities) {
|
|
1165
|
+
const enabled = new Set(enabledCapabilities);
|
|
1166
|
+
return hooks.filter((hook) => hook.activation === "default" || enabled.has(hook.name)).map((hook) => hook.name);
|
|
1167
|
+
}
|
|
1168
|
+
function collectPackHooks(resolvedPacks, packRegistry, hookRegistry, enabledCapabilities = []) {
|
|
1169
|
+
return filterEffectiveHooks(collectPackHookReferences(resolvedPacks, packRegistry, hookRegistry), enabledCapabilities);
|
|
1170
|
+
}
|
|
1171
|
+
function resolvePackHookReferences(requestedPacks, packRegistry = builtInPackRegistry, hookRegistry = builtInHookRegistry) {
|
|
1172
|
+
return collectPackHookReferences(resolvePacks(requestedPacks, packRegistry), packRegistry, hookRegistry);
|
|
1173
|
+
}
|
|
1174
|
+
function resolvePackHooks(requestedPacks, packRegistry = builtInPackRegistry, hookRegistry = builtInHookRegistry, enabledCapabilities = []) {
|
|
1175
|
+
return collectPackHooks(resolvePacks(requestedPacks, packRegistry), packRegistry, hookRegistry, enabledCapabilities);
|
|
1176
|
+
}
|
|
1177
|
+
//#endregion
|
|
997
1178
|
//#region src/mcp/errors.ts
|
|
998
1179
|
var UnknownMcpServerError = class extends AgentyxError {
|
|
999
1180
|
serverName;
|
|
@@ -1067,7 +1248,8 @@ function createMcpServerRegistry(sources) {
|
|
|
1067
1248
|
name: server.name,
|
|
1068
1249
|
description: server.description,
|
|
1069
1250
|
transport: server.transport,
|
|
1070
|
-
contextCost: server.contextCost
|
|
1251
|
+
contextCost: server.contextCost,
|
|
1252
|
+
runtime: server.runtime
|
|
1071
1253
|
};
|
|
1072
1254
|
}),
|
|
1073
1255
|
get
|
|
@@ -1081,6 +1263,7 @@ const builtInMcpServerRegistry = createMcpServerRegistry([
|
|
|
1081
1263
|
description: "Fetch up-to-date library documentation from Context7.",
|
|
1082
1264
|
transport: "http",
|
|
1083
1265
|
contextCost: "medium",
|
|
1266
|
+
runtime: "local",
|
|
1084
1267
|
url: "https://mcp.context7.com/mcp"
|
|
1085
1268
|
})
|
|
1086
1269
|
},
|
|
@@ -1091,8 +1274,9 @@ const builtInMcpServerRegistry = createMcpServerRegistry([
|
|
|
1091
1274
|
description: "Automate and inspect browsers through Playwright MCP.",
|
|
1092
1275
|
transport: "stdio",
|
|
1093
1276
|
contextCost: "high",
|
|
1277
|
+
runtime: "may-download",
|
|
1094
1278
|
command: "npx",
|
|
1095
|
-
args: ["@playwright/mcp@
|
|
1279
|
+
args: ["@playwright/mcp@0.0.81"]
|
|
1096
1280
|
})
|
|
1097
1281
|
},
|
|
1098
1282
|
{
|
|
@@ -1102,6 +1286,7 @@ const builtInMcpServerRegistry = createMcpServerRegistry([
|
|
|
1102
1286
|
description: "Structural code-intelligence MCP backed by a persistent code knowledge graph.",
|
|
1103
1287
|
transport: "stdio",
|
|
1104
1288
|
contextCost: "high",
|
|
1289
|
+
runtime: "local",
|
|
1105
1290
|
command: "codebase-memory-mcp"
|
|
1106
1291
|
})
|
|
1107
1292
|
},
|
|
@@ -1112,6 +1297,7 @@ const builtInMcpServerRegistry = createMcpServerRegistry([
|
|
|
1112
1297
|
description: "Read repositories, issues, pull requests and workflow runs on GitHub.",
|
|
1113
1298
|
transport: "http",
|
|
1114
1299
|
contextCost: "high",
|
|
1300
|
+
runtime: "local",
|
|
1115
1301
|
url: "https://api.githubcopilot.com/mcp/"
|
|
1116
1302
|
})
|
|
1117
1303
|
},
|
|
@@ -1122,6 +1308,7 @@ const builtInMcpServerRegistry = createMcpServerRegistry([
|
|
|
1122
1308
|
description: "Inspect production issues, events and stack traces recorded by Sentry.",
|
|
1123
1309
|
transport: "http",
|
|
1124
1310
|
contextCost: "medium",
|
|
1311
|
+
runtime: "local",
|
|
1125
1312
|
url: "https://mcp.sentry.dev/mcp"
|
|
1126
1313
|
})
|
|
1127
1314
|
},
|
|
@@ -1132,8 +1319,9 @@ const builtInMcpServerRegistry = createMcpServerRegistry([
|
|
|
1132
1319
|
description: "Record performance traces and inspect pages through Chrome DevTools.",
|
|
1133
1320
|
transport: "stdio",
|
|
1134
1321
|
contextCost: "high",
|
|
1322
|
+
runtime: "may-download",
|
|
1135
1323
|
command: "npx",
|
|
1136
|
-
args: ["-y", "chrome-devtools-mcp@
|
|
1324
|
+
args: ["-y", "chrome-devtools-mcp@1.9.0"]
|
|
1137
1325
|
})
|
|
1138
1326
|
},
|
|
1139
1327
|
{
|
|
@@ -1143,35 +1331,15 @@ const builtInMcpServerRegistry = createMcpServerRegistry([
|
|
|
1143
1331
|
description: "Inspect and query Supabase project schemas, tables and logs.",
|
|
1144
1332
|
transport: "stdio",
|
|
1145
1333
|
contextCost: "high",
|
|
1334
|
+
runtime: "may-download",
|
|
1146
1335
|
command: "npx",
|
|
1147
|
-
args: ["-y", "@supabase/mcp-server-supabase@
|
|
1336
|
+
args: ["-y", "@supabase/mcp-server-supabase@0.13.0"],
|
|
1148
1337
|
env: { SUPABASE_ACCESS_TOKEN: { fromEnv: "SUPABASE_ACCESS_TOKEN" } }
|
|
1149
1338
|
})
|
|
1150
1339
|
}
|
|
1151
1340
|
]);
|
|
1152
1341
|
const builtInMcpServerNames = builtInMcpServerRegistry.names;
|
|
1153
1342
|
//#endregion
|
|
1154
|
-
//#region src/pack/resolver.ts
|
|
1155
|
-
/**
|
|
1156
|
-
* Resolves selected packs without hidden inheritance.
|
|
1157
|
-
*
|
|
1158
|
-
* The result preserves configured order, removes duplicates by first
|
|
1159
|
-
* occurrence, and validates every requested pack.
|
|
1160
|
-
*
|
|
1161
|
-
* @throws {UnknownPackError} when a requested pack is missing.
|
|
1162
|
-
*/
|
|
1163
|
-
function resolvePacks(requestedPacks, registry = builtInPackRegistry) {
|
|
1164
|
-
const resolved = [];
|
|
1165
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1166
|
-
for (const name of requestedPacks) {
|
|
1167
|
-
if (seen.has(name)) continue;
|
|
1168
|
-
if (!registry.has(name)) throw new UnknownPackError(name, void 0, [...registry.keys()]);
|
|
1169
|
-
seen.add(name);
|
|
1170
|
-
resolved.push(name);
|
|
1171
|
-
}
|
|
1172
|
-
return resolved;
|
|
1173
|
-
}
|
|
1174
|
-
//#endregion
|
|
1175
1343
|
//#region src/mcp/resolver.ts
|
|
1176
1344
|
function collectPackMcpServerReferences(resolvedPacks, packRegistry, mcpRegistry) {
|
|
1177
1345
|
const servers = [];
|
|
@@ -1347,12 +1515,17 @@ function resolvePackTools(requestedPacks, packRegistry = builtInPackRegistry, to
|
|
|
1347
1515
|
* @throws {UnknownPackError} when a configured pack is missing.
|
|
1348
1516
|
* @throws {UnknownSkillError} when a pack references an unknown skill.
|
|
1349
1517
|
*/
|
|
1350
|
-
function resolveAgentyxConfig(config, registry = builtInPackRegistry, skillRegistry = builtInSkillRegistry, mcpRegistry = builtInMcpServerRegistry, toolRegistry = builtInToolRegistry) {
|
|
1518
|
+
function resolveAgentyxConfig(config, registry = builtInPackRegistry, skillRegistry = builtInSkillRegistry, mcpRegistry = builtInMcpServerRegistry, toolRegistry = builtInToolRegistry, hookRegistry = builtInHookRegistry) {
|
|
1351
1519
|
const requestedPacks = [...config.packs];
|
|
1352
1520
|
const resolvedPacks = resolvePacks(requestedPacks, registry);
|
|
1353
1521
|
const declaredMcpServers = collectPackMcpServerReferences(resolvedPacks, registry, mcpRegistry);
|
|
1354
1522
|
const declaredTools = collectPackToolReferences(resolvedPacks, registry, toolRegistry);
|
|
1355
|
-
const
|
|
1523
|
+
const declaredHooks = collectPackHookReferences(resolvedPacks, registry, hookRegistry);
|
|
1524
|
+
const knownOptionalCapabilities = [
|
|
1525
|
+
...declaredMcpServers.filter((server) => server.activation === "optional").map((server) => server.name),
|
|
1526
|
+
...declaredTools.filter((tool) => tool.activation === "optional").map((tool) => tool.name),
|
|
1527
|
+
...declaredHooks.filter((hook) => hook.activation === "optional").map((hook) => hook.name)
|
|
1528
|
+
];
|
|
1356
1529
|
for (const capability of config.enable) if (!knownOptionalCapabilities.includes(capability)) throw new UnknownEnabledCapabilityError(capability, knownOptionalCapabilities);
|
|
1357
1530
|
return {
|
|
1358
1531
|
requestedPacks,
|
|
@@ -1362,6 +1535,8 @@ function resolveAgentyxConfig(config, registry = builtInPackRegistry, skillRegis
|
|
|
1362
1535
|
mcpServers: filterEffectiveMcpServers(declaredMcpServers, config.enable),
|
|
1363
1536
|
declaredTools,
|
|
1364
1537
|
tools: filterEffectiveTools(declaredTools, config.enable),
|
|
1538
|
+
declaredHooks,
|
|
1539
|
+
hooks: filterEffectiveHooks(declaredHooks, config.enable),
|
|
1365
1540
|
enabled: [...config.enable],
|
|
1366
1541
|
targets: [...config.targets]
|
|
1367
1542
|
};
|
|
@@ -1440,7 +1615,26 @@ const mcpManifestEntrySchema = z.strictObject({
|
|
|
1440
1615
|
hash: contentHashSchema,
|
|
1441
1616
|
created: z.boolean()
|
|
1442
1617
|
});
|
|
1443
|
-
|
|
1618
|
+
/**
|
|
1619
|
+
* A provider hook configuration file Agentyx contributed entries to.
|
|
1620
|
+
*
|
|
1621
|
+
* Shares the `mcp` entry's shape and rationale: the file is shared with the
|
|
1622
|
+
* user, so the entry records the hook names Agentyx added rather than
|
|
1623
|
+
* claiming the whole document.
|
|
1624
|
+
*/
|
|
1625
|
+
const hookManifestEntrySchema = z.strictObject({
|
|
1626
|
+
kind: z.literal("hook"),
|
|
1627
|
+
path: managedPathSchema,
|
|
1628
|
+
hooks: z.array(z.string().min(1, "Hook names must be non-empty strings.")),
|
|
1629
|
+
targets: targetsSchema,
|
|
1630
|
+
hash: contentHashSchema,
|
|
1631
|
+
created: z.boolean()
|
|
1632
|
+
});
|
|
1633
|
+
const installManifestEntrySchema = z.discriminatedUnion("kind", [
|
|
1634
|
+
skillManifestEntrySchema,
|
|
1635
|
+
mcpManifestEntrySchema,
|
|
1636
|
+
hookManifestEntrySchema
|
|
1637
|
+
]);
|
|
1444
1638
|
/** The `.agentyx.lock.json` record of everything Agentyx installed into a project. */
|
|
1445
1639
|
const installManifestSchema = z.strictObject({
|
|
1446
1640
|
version: z.literal(1),
|
|
@@ -1517,13 +1711,21 @@ async function loadInstallManifest(projectPath = process.cwd()) {
|
|
|
1517
1711
|
* touches the filesystem.
|
|
1518
1712
|
*/
|
|
1519
1713
|
function formatInstallManifest(manifest) {
|
|
1520
|
-
const entries = [...manifest.entries].map((entry) =>
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1714
|
+
const entries = [...manifest.entries].map((entry) => {
|
|
1715
|
+
if (entry.kind === "skill") return {
|
|
1716
|
+
...entry,
|
|
1717
|
+
targets: sorted(entry.targets)
|
|
1718
|
+
};
|
|
1719
|
+
if (entry.kind === "mcp") return {
|
|
1720
|
+
...entry,
|
|
1721
|
+
targets: sorted(entry.targets),
|
|
1722
|
+
servers: sorted(entry.servers)
|
|
1723
|
+
};
|
|
1724
|
+
return {
|
|
1725
|
+
...entry,
|
|
1726
|
+
targets: sorted(entry.targets),
|
|
1727
|
+
hooks: sorted(entry.hooks)
|
|
1728
|
+
};
|
|
1527
1729
|
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
1528
1730
|
return `${JSON.stringify({
|
|
1529
1731
|
version: manifest.version,
|
|
@@ -1549,13 +1751,92 @@ function getCoreStatus() {
|
|
|
1549
1751
|
return "ready";
|
|
1550
1752
|
}
|
|
1551
1753
|
//#endregion
|
|
1552
|
-
//#region src/project/
|
|
1754
|
+
//#region src/project/executable.ts
|
|
1755
|
+
/** Whether `command` resolves to an executable file, via `PATH` unless it already contains a `/`. */
|
|
1756
|
+
async function isExecutableOnPath(command) {
|
|
1757
|
+
if (command.includes("/")) return isFileExecutable(command);
|
|
1758
|
+
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
1759
|
+
if (directory.length === 0) continue;
|
|
1760
|
+
if (await isFileExecutable(`${directory}/${command}`)) return true;
|
|
1761
|
+
}
|
|
1762
|
+
return false;
|
|
1763
|
+
}
|
|
1764
|
+
async function isFileExecutable(path) {
|
|
1765
|
+
try {
|
|
1766
|
+
await access(path, constants.X_OK);
|
|
1767
|
+
return true;
|
|
1768
|
+
} catch {
|
|
1769
|
+
return false;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
//#endregion
|
|
1773
|
+
//#region src/project/signals.ts
|
|
1553
1774
|
const PACKAGE_MANAGERS = [
|
|
1554
1775
|
"pnpm",
|
|
1555
1776
|
"npm",
|
|
1556
1777
|
"yarn",
|
|
1557
1778
|
"bun"
|
|
1558
1779
|
];
|
|
1780
|
+
/** Package name patterns matched exactly, or as a `@scope/*` prefix. */
|
|
1781
|
+
const TEST_FRAMEWORK_PATTERNS = [
|
|
1782
|
+
"vitest",
|
|
1783
|
+
"jest",
|
|
1784
|
+
"@jest/*",
|
|
1785
|
+
"@playwright/test",
|
|
1786
|
+
"cypress",
|
|
1787
|
+
"karma"
|
|
1788
|
+
];
|
|
1789
|
+
const BROWSER_TESTING_PATTERNS = ["@playwright/test", "cypress"];
|
|
1790
|
+
const OBSERVABILITY_PATTERNS = ["@sentry/*", "@opentelemetry/*"];
|
|
1791
|
+
const DATA_TOOLING_PATTERNS = [
|
|
1792
|
+
"@supabase/*",
|
|
1793
|
+
"drizzle-orm",
|
|
1794
|
+
"prisma",
|
|
1795
|
+
"@prisma/client",
|
|
1796
|
+
"typeorm",
|
|
1797
|
+
"sequelize"
|
|
1798
|
+
];
|
|
1799
|
+
const ACCESSIBILITY_PATTERNS = [
|
|
1800
|
+
"axe-core",
|
|
1801
|
+
"@axe-core/*",
|
|
1802
|
+
"pa11y"
|
|
1803
|
+
];
|
|
1804
|
+
const DEPENDENCY_FIELDS = [
|
|
1805
|
+
"dependencies",
|
|
1806
|
+
"devDependencies",
|
|
1807
|
+
"peerDependencies",
|
|
1808
|
+
"optionalDependencies"
|
|
1809
|
+
];
|
|
1810
|
+
const CONTAINER_IGNORED = /* @__PURE__ */ new Set([
|
|
1811
|
+
"node_modules",
|
|
1812
|
+
".git",
|
|
1813
|
+
"dist",
|
|
1814
|
+
"build",
|
|
1815
|
+
"coverage"
|
|
1816
|
+
]);
|
|
1817
|
+
/** Extensions counted as source files by the bounded repository-size scan. */
|
|
1818
|
+
const SOURCE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1819
|
+
".ts",
|
|
1820
|
+
".tsx",
|
|
1821
|
+
".js",
|
|
1822
|
+
".jsx",
|
|
1823
|
+
".mjs",
|
|
1824
|
+
".cjs",
|
|
1825
|
+
".vue",
|
|
1826
|
+
".svelte",
|
|
1827
|
+
".py",
|
|
1828
|
+
".go",
|
|
1829
|
+
".rs",
|
|
1830
|
+
".java",
|
|
1831
|
+
".kt",
|
|
1832
|
+
".rb",
|
|
1833
|
+
".php",
|
|
1834
|
+
".css",
|
|
1835
|
+
".scss",
|
|
1836
|
+
".html"
|
|
1837
|
+
]);
|
|
1838
|
+
/** Maximum directory entries visited by `scanRepositorySize`, regardless of match. */
|
|
1839
|
+
const REPOSITORY_SCAN_ENTRY_LIMIT = 5e3;
|
|
1559
1840
|
const lockfileManagers = /* @__PURE__ */ new Map([
|
|
1560
1841
|
["pnpm-lock.yaml", "pnpm"],
|
|
1561
1842
|
["package-lock.json", "npm"],
|
|
@@ -1563,52 +1844,141 @@ const lockfileManagers = /* @__PURE__ */ new Map([
|
|
|
1563
1844
|
["bun.lock", "bun"],
|
|
1564
1845
|
["bun.lockb", "bun"]
|
|
1565
1846
|
]);
|
|
1566
|
-
|
|
1847
|
+
/** Gathers every project signal with a bounded, cheap set of reads. */
|
|
1848
|
+
async function collectProjectSignals(projectDir) {
|
|
1567
1849
|
const packageJsonPath = join(projectDir, "package.json");
|
|
1568
|
-
const
|
|
1569
|
-
const
|
|
1570
|
-
const
|
|
1850
|
+
const packageJsonResult = await readPackageJson(packageJsonPath);
|
|
1851
|
+
const packageJson = packageJsonResult.data;
|
|
1852
|
+
const [packageManager, containers, ci, monorepo, repositorySize, tsconfig, rtkAvailable] = await Promise.all([
|
|
1853
|
+
detectPackageManager(projectDir, packageJson),
|
|
1854
|
+
detectContainerFiles(projectDir),
|
|
1855
|
+
detectCiFiles(projectDir),
|
|
1856
|
+
detectMonorepo(projectDir, packageJson),
|
|
1857
|
+
scanRepositorySize(projectDir),
|
|
1858
|
+
exists(join(projectDir, "tsconfig.json")),
|
|
1859
|
+
isExecutableOnPath(builtInToolRegistry.get("rtk").command)
|
|
1860
|
+
]);
|
|
1571
1861
|
return {
|
|
1572
1862
|
projectDir,
|
|
1573
1863
|
packageJson: {
|
|
1574
|
-
present:
|
|
1575
|
-
valid:
|
|
1864
|
+
present: packageJsonResult.present,
|
|
1865
|
+
valid: packageJsonResult.valid,
|
|
1576
1866
|
path: packageJsonPath,
|
|
1577
|
-
name: getString(packageJson
|
|
1578
|
-
packageManager: getString(packageJson
|
|
1579
|
-
error:
|
|
1867
|
+
name: getString(packageJson?.name),
|
|
1868
|
+
packageManager: getString(packageJson?.packageManager),
|
|
1869
|
+
error: packageJsonResult.error
|
|
1580
1870
|
},
|
|
1581
1871
|
packageManager,
|
|
1582
|
-
|
|
1583
|
-
|
|
1872
|
+
typescript: {
|
|
1873
|
+
dependency: findDependency(packageJson, "typescript"),
|
|
1874
|
+
tsconfig
|
|
1875
|
+
},
|
|
1876
|
+
angular: findDependency(packageJson, "@angular/core"),
|
|
1877
|
+
testFrameworks: findAnyDependency(packageJson, TEST_FRAMEWORK_PATTERNS),
|
|
1878
|
+
browserTesting: findAnyDependency(packageJson, BROWSER_TESTING_PATTERNS),
|
|
1879
|
+
observability: findAnyDependency(packageJson, OBSERVABILITY_PATTERNS),
|
|
1880
|
+
dataTooling: findAnyDependency(packageJson, DATA_TOOLING_PATTERNS),
|
|
1881
|
+
accessibilityTooling: findAnyDependency(packageJson, ACCESSIBILITY_PATTERNS),
|
|
1882
|
+
containers,
|
|
1883
|
+
ci,
|
|
1884
|
+
monorepo,
|
|
1885
|
+
repositorySize,
|
|
1886
|
+
localTools: { rtkAvailable }
|
|
1584
1887
|
};
|
|
1585
1888
|
}
|
|
1586
|
-
|
|
1889
|
+
/**
|
|
1890
|
+
* Counts source-like files under a bounded traversal, ignoring dependency, VCS and build output
|
|
1891
|
+
* directories. Stops after visiting `entryLimit` directory entries — cheap even on a monorepo — and
|
|
1892
|
+
* reports `capped` so callers never mistake a partial scan for a complete one.
|
|
1893
|
+
*/
|
|
1894
|
+
async function scanRepositorySize(projectDir, options = {}) {
|
|
1895
|
+
const entryLimit = options.entryLimit ?? 5e3;
|
|
1896
|
+
const directories = [projectDir];
|
|
1897
|
+
let filesScanned = 0;
|
|
1898
|
+
let entriesVisited = 0;
|
|
1899
|
+
let capped = false;
|
|
1900
|
+
while (directories.length > 0 && !capped) {
|
|
1901
|
+
const directory = directories.shift();
|
|
1902
|
+
if (directory === void 0) break;
|
|
1903
|
+
let entries;
|
|
1904
|
+
try {
|
|
1905
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
1906
|
+
} catch {
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
for (const entry of entries) {
|
|
1910
|
+
if (entriesVisited >= entryLimit) {
|
|
1911
|
+
capped = true;
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
entriesVisited += 1;
|
|
1915
|
+
if (entry.isDirectory()) {
|
|
1916
|
+
if (entry.name.startsWith(".") || CONTAINER_IGNORED.has(entry.name)) continue;
|
|
1917
|
+
directories.push(join(directory, entry.name));
|
|
1918
|
+
} else if (entry.isFile() && SOURCE_FILE_EXTENSIONS.has(extname(entry.name))) filesScanned += 1;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1587
1921
|
return {
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
targets: [...input.targets]
|
|
1922
|
+
filesScanned,
|
|
1923
|
+
capped
|
|
1591
1924
|
};
|
|
1592
1925
|
}
|
|
1593
|
-
function
|
|
1594
|
-
|
|
1926
|
+
function findDependency(packageJson, pattern) {
|
|
1927
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
1928
|
+
const value = packageJson?.[field];
|
|
1929
|
+
if (!isRecord(value)) continue;
|
|
1930
|
+
if (pattern.endsWith("/*")) {
|
|
1931
|
+
const prefix = pattern.slice(0, -1);
|
|
1932
|
+
const name = Object.keys(value).find((key) => key.startsWith(prefix));
|
|
1933
|
+
if (name !== void 0) return {
|
|
1934
|
+
dependency: name,
|
|
1935
|
+
field
|
|
1936
|
+
};
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
if (pattern in value) return {
|
|
1940
|
+
dependency: pattern,
|
|
1941
|
+
field
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1595
1944
|
}
|
|
1596
|
-
function
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1945
|
+
function findAnyDependency(packageJson, patterns) {
|
|
1946
|
+
const matches = [];
|
|
1947
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1948
|
+
for (const pattern of patterns) {
|
|
1949
|
+
const match = findDependency(packageJson, pattern);
|
|
1950
|
+
if (match !== void 0 && !seen.has(match.dependency)) {
|
|
1951
|
+
seen.add(match.dependency);
|
|
1952
|
+
matches.push(match);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return matches;
|
|
1956
|
+
}
|
|
1957
|
+
async function detectContainerFiles(projectDir) {
|
|
1958
|
+
const [dockerfile, dockerCompose, compose] = await Promise.all([
|
|
1959
|
+
exists(join(projectDir, "Dockerfile")),
|
|
1960
|
+
exists(join(projectDir, "docker-compose.yml")),
|
|
1961
|
+
exists(join(projectDir, "compose.yml"))
|
|
1962
|
+
]);
|
|
1963
|
+
return {
|
|
1964
|
+
dockerfile,
|
|
1965
|
+
compose: dockerCompose || compose
|
|
1966
|
+
};
|
|
1604
1967
|
}
|
|
1605
|
-
async function
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
if (
|
|
1611
|
-
|
|
1968
|
+
async function detectCiFiles(projectDir) {
|
|
1969
|
+
return { githubActions: await exists(join(projectDir, ".github", "workflows")) };
|
|
1970
|
+
}
|
|
1971
|
+
async function detectMonorepo(projectDir, packageJson) {
|
|
1972
|
+
const markers = [];
|
|
1973
|
+
if (await exists(join(projectDir, "pnpm-workspace.yaml"))) markers.push("pnpm-workspace.yaml");
|
|
1974
|
+
if (await exists(join(projectDir, "turbo.json"))) markers.push("turbo.json");
|
|
1975
|
+
if (await exists(join(projectDir, "nx.json"))) markers.push("nx.json");
|
|
1976
|
+
const workspaces = packageJson?.workspaces;
|
|
1977
|
+
if (Array.isArray(workspaces) || isRecord(workspaces)) markers.push("package.json workspaces");
|
|
1978
|
+
return {
|
|
1979
|
+
detected: markers.length > 0,
|
|
1980
|
+
markers
|
|
1981
|
+
};
|
|
1612
1982
|
}
|
|
1613
1983
|
async function detectPackageManager(projectDir, packageJson) {
|
|
1614
1984
|
const lockfiles = [];
|
|
@@ -1671,16 +2041,6 @@ async function readPackageJson(path) {
|
|
|
1671
2041
|
};
|
|
1672
2042
|
}
|
|
1673
2043
|
}
|
|
1674
|
-
function collectDependencies(packageJson) {
|
|
1675
|
-
const dependencies = /* @__PURE__ */ new Set();
|
|
1676
|
-
for (const field of [
|
|
1677
|
-
packageJson?.dependencies,
|
|
1678
|
-
packageJson?.devDependencies,
|
|
1679
|
-
packageJson?.peerDependencies,
|
|
1680
|
-
packageJson?.optionalDependencies
|
|
1681
|
-
]) if (isRecord(field)) for (const name of Object.keys(field)) dependencies.add(name);
|
|
1682
|
-
return dependencies;
|
|
1683
|
-
}
|
|
1684
2044
|
function parsePackageManagerName(value) {
|
|
1685
2045
|
const name = value?.split("@")[0];
|
|
1686
2046
|
return PACKAGE_MANAGERS.includes(name) ? name : void 0;
|
|
@@ -1704,6 +2064,128 @@ function isNotFound(cause) {
|
|
|
1704
2064
|
return cause instanceof Error && cause.code === "ENOENT";
|
|
1705
2065
|
}
|
|
1706
2066
|
//#endregion
|
|
2067
|
+
//#region src/project/detector.ts
|
|
2068
|
+
/**
|
|
2069
|
+
* Factual project detection only. It gathers `ProjectSignals` and never decides which packs or
|
|
2070
|
+
* capabilities fit — that policy lives in `recommendCapabilities`, kept separate so each half stays
|
|
2071
|
+
* independently testable.
|
|
2072
|
+
*/
|
|
2073
|
+
async function detectProject(projectDir) {
|
|
2074
|
+
const signals = await collectProjectSignals(projectDir);
|
|
2075
|
+
return {
|
|
2076
|
+
projectDir,
|
|
2077
|
+
packageJson: signals.packageJson,
|
|
2078
|
+
packageManager: signals.packageManager,
|
|
2079
|
+
signals
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
function buildAgentyxConfig(input) {
|
|
2083
|
+
return {
|
|
2084
|
+
packs: [...input.packs],
|
|
2085
|
+
enable: [...input.enable ?? []],
|
|
2086
|
+
targets: [...input.targets]
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
function formatAgentyxConfig(config) {
|
|
2090
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
2091
|
+
}
|
|
2092
|
+
//#endregion
|
|
2093
|
+
//#region src/project/recommend.ts
|
|
2094
|
+
const RECOMMENDATION_CONFIDENCES = ["high", "medium"];
|
|
2095
|
+
/**
|
|
2096
|
+
* Heuristic thresholds for the `codebase-memory` capability suggestion, in scanned source files.
|
|
2097
|
+
* These are approximate signals, not exact token accounting, and a monorepo is treated as already
|
|
2098
|
+
* complex enough to lower the bar.
|
|
2099
|
+
*/
|
|
2100
|
+
const CODEBASE_MEMORY_FILE_THRESHOLD = 2e3;
|
|
2101
|
+
const CODEBASE_MEMORY_MONOREPO_FILE_THRESHOLD = 800;
|
|
2102
|
+
/**
|
|
2103
|
+
* Applies conservative, deterministic policy on top of `ProjectSignals`. This is the only place
|
|
2104
|
+
* that turns an observed fact into a product suggestion — `ProjectSignals` stays fact-only so it
|
|
2105
|
+
* can be tested independently of pack names or capability policy.
|
|
2106
|
+
*
|
|
2107
|
+
* Never enables anything and never touches the filesystem: the caller decides what, if anything,
|
|
2108
|
+
* to do with the result.
|
|
2109
|
+
*/
|
|
2110
|
+
function recommendCapabilities(signals, options = {}) {
|
|
2111
|
+
const packs = recommendPacks(signals);
|
|
2112
|
+
const configuredPacks = options.configuredPacks ?? [];
|
|
2113
|
+
return {
|
|
2114
|
+
packs,
|
|
2115
|
+
capabilities: recommendOptionalCapabilities(signals, /* @__PURE__ */ new Set([...packs.map((pack) => pack.name), ...configuredPacks]), configuredPacks)
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
function recommendPacks(signals) {
|
|
2119
|
+
const packs = [];
|
|
2120
|
+
if (signals.packageJson.present) packs.push(packRecommendation("technical", "high", ["package.json detected — baseline engineering practices apply to any software project."]));
|
|
2121
|
+
const typescriptReasons = [...signals.typescript.dependency !== void 0 ? [dependencyReason("TypeScript", signals.typescript.dependency)] : [], ...signals.typescript.tsconfig ? ["tsconfig.json detected."] : []];
|
|
2122
|
+
if (typescriptReasons.length > 0) packs.push(packRecommendation("typescript", "high", typescriptReasons));
|
|
2123
|
+
if (signals.angular !== void 0) packs.push(packRecommendation("angular", "high", [dependencyReason("@angular/core", signals.angular)]));
|
|
2124
|
+
const testingMatches = dedupeMatches([...signals.testFrameworks, ...signals.browserTesting]);
|
|
2125
|
+
if (testingMatches.length > 0) packs.push(packRecommendation("testing", "high", testingMatches.map((match) => dependencyReason(match.dependency, match))));
|
|
2126
|
+
const devopsReasons = [
|
|
2127
|
+
...signals.containers.dockerfile ? ["Dockerfile detected."] : [],
|
|
2128
|
+
...signals.containers.compose ? ["Container compose file detected."] : [],
|
|
2129
|
+
...signals.ci.githubActions ? ["GitHub Actions workflow detected (.github/workflows)."] : []
|
|
2130
|
+
];
|
|
2131
|
+
if (devopsReasons.length > 0) packs.push(packRecommendation("devops", "high", devopsReasons));
|
|
2132
|
+
if (signals.observability.length > 0) packs.push(packRecommendation("observability", "high", signals.observability.map((match) => dependencyReason(match.dependency, match))));
|
|
2133
|
+
if (signals.dataTooling.length > 0) packs.push(packRecommendation("data", "high", signals.dataTooling.map((match) => dependencyReason(match.dependency, match))));
|
|
2134
|
+
if (signals.accessibilityTooling.length > 0) packs.push(packRecommendation("accessibility", "high", signals.accessibilityTooling.map((match) => dependencyReason(match.dependency, match))));
|
|
2135
|
+
return packs;
|
|
2136
|
+
}
|
|
2137
|
+
function recommendOptionalCapabilities(signals, activePacks, configuredPacks) {
|
|
2138
|
+
const capabilities = [];
|
|
2139
|
+
const playwrightMatch = signals.browserTesting.find((match) => match.dependency === "@playwright/test");
|
|
2140
|
+
if (activePacks.has("testing") && playwrightMatch !== void 0) capabilities.push(capabilityRecommendation("playwright", "high", ["Playwright is already part of this project; browser MCP may be useful for interactive browser work."]));
|
|
2141
|
+
if (signals.observability.find((match) => match.dependency.startsWith("@sentry/")) !== void 0) capabilities.push(capabilityRecommendation("sentry", "high", ["Sentry is already configured as a dependency."]));
|
|
2142
|
+
if (signals.dataTooling.find((match) => match.dependency.startsWith("@supabase/")) !== void 0) capabilities.push(capabilityRecommendation("supabase", "high", ["Supabase is already configured as a dependency."]));
|
|
2143
|
+
if (configuredPacks.includes("efficiency") && signals.localTools.rtkAvailable) capabilities.push(capabilityRecommendation("rtk", "high", ["RTK is already installed locally."]));
|
|
2144
|
+
const sizeThreshold = signals.monorepo.detected ? 800 : CODEBASE_MEMORY_FILE_THRESHOLD;
|
|
2145
|
+
if (signals.repositorySize.filesScanned >= sizeThreshold || signals.repositorySize.capped) capabilities.push(capabilityRecommendation("codebase-memory", "medium", [repositorySizeReason(signals)]));
|
|
2146
|
+
return capabilities;
|
|
2147
|
+
}
|
|
2148
|
+
function repositorySizeReason(signals) {
|
|
2149
|
+
const { filesScanned, capped } = signals.repositorySize;
|
|
2150
|
+
const scope = signals.monorepo.detected ? " across this monorepo" : "";
|
|
2151
|
+
return `Heuristic: found ${capped ? `at least ${filesScanned} source files (the scan was capped before finishing)` : `${filesScanned} source files`}${scope}. A persistent codebase-memory MCP may reduce repeated full-tree reads on a repository this size — this is an approximate signal, not an exact token measurement.`;
|
|
2152
|
+
}
|
|
2153
|
+
function packRecommendation(name, confidence, reasons) {
|
|
2154
|
+
return {
|
|
2155
|
+
kind: "pack",
|
|
2156
|
+
name,
|
|
2157
|
+
confidence,
|
|
2158
|
+
reasons
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
/** Provenance comes from the pack registry itself — never a second, hand-maintained capability map. */
|
|
2162
|
+
function capabilityRecommendation(name, confidence, reasons) {
|
|
2163
|
+
return {
|
|
2164
|
+
kind: "capability",
|
|
2165
|
+
name,
|
|
2166
|
+
confidence,
|
|
2167
|
+
reasons,
|
|
2168
|
+
contributedBy: packsDeclaringOptionalCapability(name)
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
function packsDeclaringOptionalCapability(name) {
|
|
2172
|
+
const packs = [];
|
|
2173
|
+
for (const pack of builtInPackRegistry.values()) if (pack.mcpServers.some((server) => server.name === name && server.activation === "optional") || pack.tools.some((tool) => tool.name === name && tool.activation === "optional") || pack.hooks.some((hook) => hook.name === name && hook.activation === "optional")) packs.push(pack.name);
|
|
2174
|
+
return packs;
|
|
2175
|
+
}
|
|
2176
|
+
function dependencyReason(label, match) {
|
|
2177
|
+
return label === match.dependency ? `Detected ${match.dependency} in ${match.field}.` : `Detected ${label} (${match.dependency}) in ${match.field}.`;
|
|
2178
|
+
}
|
|
2179
|
+
function dedupeMatches(matches) {
|
|
2180
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2181
|
+
const result = [];
|
|
2182
|
+
for (const match of matches) if (!seen.has(match.dependency)) {
|
|
2183
|
+
seen.add(match.dependency);
|
|
2184
|
+
result.push(match);
|
|
2185
|
+
}
|
|
2186
|
+
return result;
|
|
2187
|
+
}
|
|
2188
|
+
//#endregion
|
|
1707
2189
|
//#region src/source/inspection.ts
|
|
1708
2190
|
async function inspectTrustedSource(projectPath, reference) {
|
|
1709
2191
|
const definition = getTrustedSourceDefinition(reference);
|
|
@@ -1792,6 +2274,6 @@ function trustedSourceLoadError(sourceName, reason, cause) {
|
|
|
1792
2274
|
return new TrustedSourceLoadError(sourceName, reason, cause instanceof Error ? { cause } : void 0);
|
|
1793
2275
|
}
|
|
1794
2276
|
//#endregion
|
|
1795
|
-
export { AGENTYX_CONFIG_FILENAME, AGENTYX_CONFIG_SCHEMA_ID, AGENTYX_MANIFEST_FILENAME, AgentyxConfigNotFoundError, AgentyxConfigParseError, AgentyxConfigValidationError, AgentyxError, AgentyxManifestParseError, AgentyxManifestValidationError, CircularPackDependencyError, DuplicateMcpServerError, DuplicatePackError, DuplicateSkillError, DuplicateToolError, DuplicateTrustedSourceError, INSTALL_MANIFEST_VERSION, InvalidMcpServerError, InvalidSkillError, InvalidToolError, LocalSkillDirectoryError, MCP_CAPABILITY_LEVELS, MCP_CONTEXT_COSTS, PACKAGE_MANAGERS, PACK_CATEGORIES, TOOL_ACTIVATION_LEVELS, TrustedSourceLoadError, UnknownEnabledCapabilityError, UnknownMcpServerError, UnknownPackError, UnknownSkillError, UnknownToolError, UnknownTrustedSourceError, agentyxConfigPath, agentyxConfigSchema, agentyxCoreName, agentyxManifestPath, agentyxTargetSchema, buildAgentyxConfig, buildAgentyxConfigJsonSchema, builtInMcpServerNames, builtInMcpServerRegistry, builtInPackRegistry, builtInPacks, builtInSkillNames, builtInSkillRegistry, builtInToolNames, builtInToolRegistry, codexPluginManifestSchema, collectPackMcpServerReferences, collectPackMcpServers, collectPackToolReferences, createMcpServerRegistry, createPackRegistry, createSkillRegistry, createToolRegistry, createTrustedSourceRegistry, detectProject, emptyInstallManifest, enabledCapabilityNameSchema, filterEffectiveMcpServers, filterEffectiveTools, formatAgentyxConfig, formatInstallManifest, formatSkillMarkdown, getCoreStatus, getTrustedSourceDefinition, hashContent, inspectTrustedSource, installManifestEntrySchema, installManifestSchema, knownTrustedSourceRegistry, knownTrustedSources, loadAgentyxConfig, loadAgentyxProject, loadInstallManifest, manifestEntriesByPath, mcpCapabilityLevelSchema, mcpContextCostSchema, mcpEnvReferenceSchema, mcpManifestEntrySchema, mcpServerDefinitionSchema, mcpServerReferenceSchema, packCategorySchema, packDefinitionSchema, packNameSchema, parseAgentyxConfig, parseInstallManifest, parseSkillMarkdown, resolveAgentyxConfig, resolvePackMcpServerReferences, resolvePackMcpServers, resolvePackSkills, resolvePackToolReferences, resolvePackTools, resolvePacks, skillDefinitionSchema, skillDirectorySchema, skillManifestEntrySchema, toolActivationLevelSchema, toolDefinitionSchema, toolNameSchema, toolReferenceSchema, trustedSourceDefinitionSchema, trustedSourceNameSchema, trustedSourcePathSchema, trustedSourceReferenceSchema, trustedSourceSkillSummarySchema };
|
|
2277
|
+
export { AGENTYX_CONFIG_FILENAME, AGENTYX_CONFIG_SCHEMA_ID, AGENTYX_MANIFEST_FILENAME, AgentyxConfigNotFoundError, AgentyxConfigParseError, AgentyxConfigValidationError, AgentyxError, AgentyxManifestParseError, AgentyxManifestValidationError, CODEBASE_MEMORY_FILE_THRESHOLD, CODEBASE_MEMORY_MONOREPO_FILE_THRESHOLD, CircularPackDependencyError, DuplicateHookError, DuplicateMcpServerError, DuplicatePackError, DuplicateSkillError, DuplicateToolError, DuplicateTrustedSourceError, HOOK_ACTIVATION_LEVELS, HOOK_EVENTS, INSTALL_MANIFEST_VERSION, InvalidHookError, InvalidMcpServerError, InvalidSkillError, InvalidToolError, LocalSkillDirectoryError, MCP_CAPABILITY_LEVELS, MCP_CONTEXT_COSTS, PACKAGE_MANAGERS, PACK_CATEGORIES, RECOMMENDATION_CONFIDENCES, REPOSITORY_SCAN_ENTRY_LIMIT, TOOL_ACTIVATION_LEVELS, TrustedSourceLoadError, UnknownEnabledCapabilityError, UnknownHookError, UnknownMcpServerError, UnknownPackError, UnknownSkillError, UnknownToolError, UnknownTrustedSourceError, agentyxConfigPath, agentyxConfigSchema, agentyxCoreName, agentyxManifestPath, agentyxTargetSchema, buildAgentyxConfig, buildAgentyxConfigJsonSchema, builtInHookNames, builtInHookRegistry, builtInMcpServerNames, builtInMcpServerRegistry, builtInPackRegistry, builtInPacks, builtInSkillNames, builtInSkillRegistry, builtInToolNames, builtInToolRegistry, codexPluginManifestSchema, collectPackHookReferences, collectPackHooks, collectPackMcpServerReferences, collectPackMcpServers, collectPackToolReferences, collectProjectSignals, createHookRegistry, createMcpServerRegistry, createPackRegistry, createSkillRegistry, createToolRegistry, createTrustedSourceRegistry, detectProject, emptyInstallManifest, enabledCapabilityNameSchema, filterEffectiveHooks, filterEffectiveMcpServers, filterEffectiveTools, formatAgentyxConfig, formatInstallManifest, formatSkillMarkdown, getCoreStatus, getTrustedSourceDefinition, hashContent, hookActivationLevelSchema, hookDefinitionSchema, hookEventSchema, hookManifestEntrySchema, hookNameSchema, hookReferenceSchema, inspectTrustedSource, installManifestEntrySchema, installManifestSchema, isExecutableOnPath, knownTrustedSourceRegistry, knownTrustedSources, loadAgentyxConfig, loadAgentyxProject, loadInstallManifest, manifestEntriesByPath, mcpCapabilityLevelSchema, mcpContextCostSchema, mcpEnvReferenceSchema, mcpManifestEntrySchema, mcpServerDefinitionSchema, mcpServerReferenceSchema, packCategorySchema, packDefinitionSchema, packNameSchema, parseAgentyxConfig, parseInstallManifest, parseSkillMarkdown, recommendCapabilities, resolveAgentyxConfig, resolvePackHookReferences, resolvePackHooks, resolvePackMcpServerReferences, resolvePackMcpServers, resolvePackSkills, resolvePackToolReferences, resolvePackTools, resolvePacks, scanRepositorySize, skillDefinitionSchema, skillDirectorySchema, skillManifestEntrySchema, toolActivationLevelSchema, toolDefinitionSchema, toolNameSchema, toolReferenceSchema, trustedSourceDefinitionSchema, trustedSourceNameSchema, trustedSourcePathSchema, trustedSourceReferenceSchema, trustedSourceSkillSummarySchema };
|
|
1796
2278
|
|
|
1797
2279
|
//# sourceMappingURL=index.mjs.map
|