@kuznai/inception-engine 0.15.0 → 0.17.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.
@@ -21,6 +21,13 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
21
21
  });
22
22
  continue;
23
23
  }
24
+ if (support.status === "planned") {
25
+ warnings.push({
26
+ kind: "confidence",
27
+ message: `agentRules: agent "${agentId}" rules support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
28
+ });
29
+ continue;
30
+ }
24
31
  supportedTargets.push({
25
32
  agentId,
26
33
  confidence: agent.provenance.agentRules ?? "provisional",
@@ -56,7 +63,9 @@ export function compileAgentRuleReverts(entry, agentFilter, home, repo) {
56
63
  continue;
57
64
  const agent = AGENT_REGISTRY_BY_ID[agentId];
58
65
  const support = agent?.agentRulesSupport;
59
- if (!support || support.status === "unsupported")
66
+ if (!support ||
67
+ support.status === "unsupported" ||
68
+ support.status === "planned")
60
69
  continue;
61
70
  const target = resolvePlaceholders(support.path[platform], entry.name, home, repo);
62
71
  actions.push({
@@ -91,7 +91,7 @@ function detectAmbiguities(detectedAgents, manifest) {
91
91
  if (bothAgents(entry.agents)) {
92
92
  warnings.push({
93
93
  kind: "ambiguity",
94
- message: `Both "gemini-cli" and "antigravity" are listed in agentRules entry "${entry.name}". They share a GEMINI.md-backed instruction shared surface — verify that deploying to both does not create conflicting behavior.`,
94
+ message: `Both "gemini-cli" and "antigravity" are listed in agentRules entry "${entry.name}". They write to distinct surfaces ("gemini-cli" → ~/.gemini/GEMINI.md, "antigravity" {repo}/.agents/rules/${entry.name}.md) from the same source file — verify that deploying to both produces the intended behavior on each agent.`,
95
95
  });
96
96
  }
97
97
  }
@@ -119,6 +119,8 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
119
119
  const agent = AGENT_REGISTRY_BY_ID[agentId];
120
120
  if (!agent)
121
121
  continue;
122
+ if (!agent.skills)
123
+ continue;
122
124
  actions.push({
123
125
  kind: "skill-dir",
124
126
  skill: skill.name,
@@ -126,7 +128,7 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
126
128
  source,
127
129
  target: resolveAgentSkillPath(agent, skill.name, home),
128
130
  method,
129
- confidence: agent.provenance.skills,
131
+ confidence: agent.provenance.skills ?? "provisional",
130
132
  });
131
133
  }
132
134
  }
@@ -192,7 +194,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
192
194
  ...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
193
195
  ...planConfigPatchActions(manifest, detectedAgents, home),
194
196
  ];
195
- const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
197
+ const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, undefined, manifest.agentDefinitions ?? []);
196
198
  actions.push(...adapterResult.actions);
197
199
  const warnings = [
198
200
  ...detectAmbiguities(detectedAgents, manifest),
package/dist/core/init.js CHANGED
@@ -2,7 +2,7 @@ import { access, readFile, readdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
4
4
  import { dryRunPrefix, logger } from "../logger.js";
5
- import { AGENT_IDS, McpServerEntrySchema } from "../schemas/manifest.js";
5
+ import { AGENT_IDS, AgentDefinitionEntrySchema, ConfigEntrySchema, FileEntrySchema, McpServerEntrySchema, } from "../schemas/manifest.js";
6
6
  const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
7
7
  // Ordered list: first match wins. Catch-all is applied at call site.
8
8
  const AGENT_RULES_FILE_PATTERNS = [
@@ -27,6 +27,16 @@ const AGENT_RULES_SUBDIRS = [
27
27
  ".github",
28
28
  ".agents/rules",
29
29
  ];
30
+ // Conventional subdirectories that contain agent definition files.
31
+ // These are the agent-specific directories that each agent scans for
32
+ // subagent/persona definitions at runtime.
33
+ const AGENT_DEFINITION_SUBDIRS = [
34
+ ".claude/agents",
35
+ ".gemini/agents",
36
+ ".agents/rules",
37
+ ".opencode/agents",
38
+ ".github/agents",
39
+ ];
30
40
  async function findSkillDirs(baseDir, dir, found) {
31
41
  let entries;
32
42
  try {
@@ -164,6 +174,117 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
164
174
  }
165
175
  return rules;
166
176
  }
177
+ /**
178
+ * Derives the name for an agent definition entry from its file name.
179
+ * For GitHub Copilot's `{name}.agent.md` naming convention, strips the
180
+ * `.agent` infix in addition to the `.md` extension.
181
+ */
182
+ function deriveAgentDefinitionName(relPath, fileName) {
183
+ const ext = path.extname(fileName).toLowerCase();
184
+ // Strip the extension to get the base name, then strip any trailing ".agent"
185
+ // suffix (GitHub Copilot convention: foo.agent.md → foo).
186
+ let baseName = path.basename(fileName, ext);
187
+ if (baseName.endsWith(".agent")) {
188
+ baseName = baseName.slice(0, -".agent".length);
189
+ }
190
+ const rawName = baseName.toLowerCase().replace(/[^a-zA-Z0-9._-]/g, "-");
191
+ if (!SAFE_NAME_RE.test(rawName)) {
192
+ logger.warn("init", `Skipping "${relPath}": could not derive a valid agentDefinitions name`);
193
+ return null;
194
+ }
195
+ return rawName;
196
+ }
197
+ /**
198
+ * Maps a known agent-definition subdirectory to the agent IDs that own it.
199
+ * Returns null when the subdir is not agent-specific (fall back to all agents
200
+ * that support agentDefinitions).
201
+ */
202
+ function agentsForDefinitionSubdir(subdir) {
203
+ switch (subdir) {
204
+ case ".claude/agents":
205
+ return ["claude-code"];
206
+ case ".gemini/agents":
207
+ return ["gemini-cli"];
208
+ case ".agents/rules":
209
+ return ["antigravity"];
210
+ case ".opencode/agents":
211
+ return ["opencode"];
212
+ case ".github/agents":
213
+ return ["github-copilot"];
214
+ default:
215
+ return null;
216
+ }
217
+ }
218
+ async function scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, agentRulesRelPaths, candidates) {
219
+ const suggestedAgents = agentsForDefinitionSubdir(subdir) ?? [];
220
+ const dir = path.join(baseDir, subdir);
221
+ let entries;
222
+ try {
223
+ entries = await readdir(dir, { withFileTypes: true, encoding: "utf-8" });
224
+ }
225
+ catch {
226
+ return;
227
+ }
228
+ for (const entry of entries) {
229
+ if (!entry.isFile())
230
+ continue;
231
+ const ext = path.extname(entry.name).toLowerCase();
232
+ if (ext !== ".md" && ext !== ".markdown")
233
+ continue;
234
+ const absPath = path.join(dir, entry.name);
235
+ const relPath = path.relative(baseDir, absPath).split(path.sep).join("/");
236
+ if (seen.has(relPath) ||
237
+ isInsideSkillDir(relPath, skillDirRelPaths) ||
238
+ agentRulesRelPaths.has(relPath))
239
+ continue;
240
+ const name = deriveAgentDefinitionName(relPath, entry.name);
241
+ if (name === null)
242
+ continue;
243
+ seen.add(relPath);
244
+ candidates.push({ relPath, name, suggestedAgents });
245
+ }
246
+ }
247
+ async function findAgentDefinitionCandidates(baseDir, skillDirRelPaths, agentRulesRelPaths) {
248
+ const candidates = [];
249
+ const seen = new Set();
250
+ for (const subdir of AGENT_DEFINITION_SUBDIRS) {
251
+ await scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, agentRulesRelPaths, candidates);
252
+ }
253
+ return candidates;
254
+ }
255
+ function buildAgentDefinitions(candidates, activeAgents, namesSeen) {
256
+ const definitions = [];
257
+ const localNamesSeen = new Set(namesSeen);
258
+ const definitionsCapableAgents = activeAgents.filter((id) => AGENT_REGISTRY_BY_ID[id].agentDefinitionsSupport?.status !==
259
+ "unsupported");
260
+ for (const { relPath, name: rawName, suggestedAgents } of candidates) {
261
+ // Use suggested agents (from the dir that was scanned), intersected with
262
+ // active agents that support agentDefinitions. Fall back to all capable
263
+ // agents if the intersection is empty.
264
+ const intersection = suggestedAgents.length > 0
265
+ ? suggestedAgents.filter((a) => definitionsCapableAgents.includes(a))
266
+ : [];
267
+ const agents = intersection.length > 0 ? intersection : definitionsCapableAgents;
268
+ if (agents.length === 0)
269
+ continue;
270
+ // Resolve name collision
271
+ let name = rawName;
272
+ if (localNamesSeen.has(name)) {
273
+ const candidate = `${name}-agent`;
274
+ if (SAFE_NAME_RE.test(candidate)) {
275
+ logger.warn("init", `agentDefinitions name "${name}" collides with an existing name; using "${candidate}"`);
276
+ name = candidate;
277
+ }
278
+ else {
279
+ logger.warn("init", `Skipping "${relPath}": name "${name}" collides and fallback is invalid`);
280
+ continue;
281
+ }
282
+ }
283
+ localNamesSeen.add(name);
284
+ definitions.push({ name, path: relPath, agents });
285
+ }
286
+ return definitions;
287
+ }
167
288
  async function loadMcpServers(baseDir) {
168
289
  const filePath = path.join(baseDir, "mcp-servers.json");
169
290
  let raw;
@@ -197,14 +318,115 @@ async function loadMcpServers(baseDir) {
197
318
  }
198
319
  return results;
199
320
  }
200
- async function emitDirectoryHints(baseDir) {
201
- for (const [dir, section] of [
202
- ["files", "files"],
203
- ["configs", "configs"],
321
+ async function loadFilesManifest(baseDir) {
322
+ const filePath = path.join(baseDir, "files-manifest.json");
323
+ let raw;
324
+ try {
325
+ raw = await readFile(filePath, "utf-8");
326
+ }
327
+ catch {
328
+ return [];
329
+ }
330
+ let parsed;
331
+ try {
332
+ parsed = JSON.parse(raw);
333
+ }
334
+ catch {
335
+ logger.warn("init", "files-manifest.json: invalid JSON, skipping");
336
+ return [];
337
+ }
338
+ if (!Array.isArray(parsed)) {
339
+ logger.warn("init", "files-manifest.json: expected a JSON array, skipping");
340
+ return [];
341
+ }
342
+ const results = [];
343
+ for (let i = 0; i < parsed.length; i++) {
344
+ const result = FileEntrySchema.safeParse(parsed[i]);
345
+ if (result.success) {
346
+ results.push(result.data);
347
+ }
348
+ else {
349
+ logger.warn("init", `files-manifest.json: entry[${i}] invalid, skipping`);
350
+ }
351
+ }
352
+ return results;
353
+ }
354
+ async function loadConfigsManifest(baseDir) {
355
+ const filePath = path.join(baseDir, "configs-manifest.json");
356
+ let raw;
357
+ try {
358
+ raw = await readFile(filePath, "utf-8");
359
+ }
360
+ catch {
361
+ return [];
362
+ }
363
+ let parsed;
364
+ try {
365
+ parsed = JSON.parse(raw);
366
+ }
367
+ catch {
368
+ logger.warn("init", "configs-manifest.json: invalid JSON, skipping");
369
+ return [];
370
+ }
371
+ if (!Array.isArray(parsed)) {
372
+ logger.warn("init", "configs-manifest.json: expected a JSON array, skipping");
373
+ return [];
374
+ }
375
+ const results = [];
376
+ for (let i = 0; i < parsed.length; i++) {
377
+ const result = ConfigEntrySchema.safeParse(parsed[i]);
378
+ if (result.success) {
379
+ results.push(result.data);
380
+ }
381
+ else {
382
+ logger.warn("init", `configs-manifest.json: entry[${i}] invalid, skipping`);
383
+ }
384
+ }
385
+ return results;
386
+ }
387
+ async function loadAgentDefinitionsManifest(baseDir) {
388
+ const filePath = path.join(baseDir, "agent-definitions-manifest.json");
389
+ let raw;
390
+ try {
391
+ raw = await readFile(filePath, "utf-8");
392
+ }
393
+ catch {
394
+ return [];
395
+ }
396
+ let parsed;
397
+ try {
398
+ parsed = JSON.parse(raw);
399
+ }
400
+ catch {
401
+ logger.warn("init", "agent-definitions-manifest.json: invalid JSON, skipping");
402
+ return [];
403
+ }
404
+ if (!Array.isArray(parsed)) {
405
+ logger.warn("init", "agent-definitions-manifest.json: expected a JSON array, skipping");
406
+ return [];
407
+ }
408
+ const results = [];
409
+ for (let i = 0; i < parsed.length; i++) {
410
+ const result = AgentDefinitionEntrySchema.safeParse(parsed[i]);
411
+ if (result.success) {
412
+ results.push(result.data);
413
+ }
414
+ else {
415
+ logger.warn("init", `agent-definitions-manifest.json: entry[${i}] invalid, skipping`);
416
+ }
417
+ }
418
+ return results;
419
+ }
420
+ async function emitDirectoryHints(baseDir, filesLoaded, configsLoaded) {
421
+ for (const [dir, section, loaded, sidecar] of [
422
+ ["files", "files", filesLoaded, "files-manifest.json"],
423
+ ["configs", "configs", configsLoaded, "configs-manifest.json"],
204
424
  ]) {
205
425
  try {
206
426
  await access(path.join(baseDir, dir));
207
- logger.info(`Detected ${dir}/ directory — add ${section} entries manually to the generated manifest with target paths.`);
427
+ if (loaded === 0) {
428
+ logger.info(`Detected ${dir}/ directory — create ${sidecar} at the repo root to have init populate ${section} entries automatically.`);
429
+ }
208
430
  }
209
431
  catch {
210
432
  // directory does not exist — silent
@@ -220,22 +442,38 @@ async function manifestExists(manifestPath) {
220
442
  return false;
221
443
  }
222
444
  }
223
- function logVerboseManifest(skills, agentRules, mcpServers) {
445
+ function logPathAgentEntries(label, entries) {
446
+ if (entries.length === 0)
447
+ return;
448
+ logger.detail(`${label}:`);
449
+ for (const e of entries) {
450
+ logger.detail(` ${e.name} → ${e.path} [${e.agents.join(", ")}]`);
451
+ }
452
+ }
453
+ function logVerboseManifest(skills, agentRules, mcpServers, files, configs, agentDefinitions) {
224
454
  for (const s of skills) {
225
455
  logger.detail(`${s.name} → ${s.path}`);
226
456
  }
227
- if (agentRules.length > 0) {
228
- logger.detail("agentRules:");
229
- for (const r of agentRules) {
230
- logger.detail(` ${r.name} → ${r.path} [${r.agents.join(", ")}]`);
231
- }
232
- }
457
+ logPathAgentEntries("agentRules", agentRules);
458
+ logPathAgentEntries("agentDefinitions", agentDefinitions);
233
459
  if (mcpServers.length > 0) {
234
460
  logger.detail("mcpServers:");
235
461
  for (const m of mcpServers) {
236
462
  logger.detail(` ${m.name} [${m.agents.join(", ")}]`);
237
463
  }
238
464
  }
465
+ if (files.length > 0) {
466
+ logger.detail("files:");
467
+ for (const f of files) {
468
+ logger.detail(` ${f.name} → ${f.target} [${f.agents.join(", ")}]`);
469
+ }
470
+ }
471
+ if (configs.length > 0) {
472
+ logger.detail("configs:");
473
+ for (const c of configs) {
474
+ logger.detail(` ${c.name} → ${c.target} [${c.agents.join(", ")}]`);
475
+ }
476
+ }
239
477
  }
240
478
  export async function runInit(options) {
241
479
  const { directory, dryRun, force, verbose } = options;
@@ -256,27 +494,51 @@ export async function runInit(options) {
256
494
  const skillDirRelPaths = new Set(found.map((f) => f.relPath));
257
495
  const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
258
496
  const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, skillNamesSeen);
497
+ const allNamesSeen = new Set([
498
+ ...skillNamesSeen,
499
+ ...agentRules.map((r) => r.name),
500
+ ]);
501
+ const agentRulesRelPaths = new Set(agentRules.map((r) => r.path));
502
+ const agentDefinitionCandidates = await findAgentDefinitionCandidates(directory, skillDirRelPaths, agentRulesRelPaths);
503
+ const discoveredDefinitions = buildAgentDefinitions(agentDefinitionCandidates, agents, allNamesSeen);
504
+ // Sidecar file overrides take precedence over auto-discovery
505
+ const sidecarDefinitions = await loadAgentDefinitionsManifest(directory);
506
+ const agentDefinitions = sidecarDefinitions.length > 0 ? sidecarDefinitions : discoveredDefinitions;
259
507
  const mcpServers = await loadMcpServers(directory);
508
+ const files = await loadFilesManifest(directory);
509
+ const configs = await loadConfigsManifest(directory);
260
510
  const manifest = {
261
511
  skills,
262
- files: [],
263
- configs: [],
512
+ files,
513
+ configs,
264
514
  mcpServers,
265
515
  agentRules,
516
+ agentDefinitions,
266
517
  };
267
518
  const json = `${JSON.stringify(manifest, null, 2)}\n`;
519
+ function summarize() {
520
+ const parts = [
521
+ `${skills.length} skill(s)`,
522
+ `${agentRules.length} agentRule(s)`,
523
+ `${agentDefinitions.length} agentDefinition(s)`,
524
+ `${mcpServers.length} mcpServer(s)`,
525
+ `${files.length} file(s)`,
526
+ `${configs.length} config(s)`,
527
+ ];
528
+ return parts.join(", ");
529
+ }
268
530
  if (dryRun) {
269
- logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${skills.length} skill(s), ${agentRules.length} agentRule(s), and ${mcpServers.length} mcpServer(s):`);
531
+ logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${summarize()}:`);
270
532
  logger.info("");
271
533
  logger.info(json);
272
- await emitDirectoryHints(directory);
534
+ await emitDirectoryHints(directory, files.length, configs.length);
273
535
  return 0;
274
536
  }
275
537
  await writeFile(manifestPath, json, "utf-8");
276
- logger.info(`Generated ${manifestPath} with ${skills.length} skill(s), ${agentRules.length} agentRule(s), and ${mcpServers.length} mcpServer(s).`);
538
+ logger.info(`Generated ${manifestPath} with ${summarize()}.`);
277
539
  if (verbose) {
278
- logVerboseManifest(skills, agentRules, mcpServers);
540
+ logVerboseManifest(skills, agentRules, mcpServers, files, configs, agentDefinitions);
279
541
  }
280
- await emitDirectoryHints(directory);
542
+ await emitDirectoryHints(directory, files.length, configs.length);
281
543
  return 0;
282
544
  }
@@ -85,6 +85,10 @@ export function getDeployMethod() {
85
85
  return process.platform === "win32" ? "copy" : "symlink";
86
86
  }
87
87
  export function resolveAgentSkillPathFor(agent, skillName, home, platform) {
88
+ if (!agent.skills) {
89
+ throw new Error(`Agent "${agent.id}" does not have a skills deployment path. ` +
90
+ `Deploy skills via another agent target that covers this agent natively (e.g. claude-code for github-copilot).`);
91
+ }
88
92
  return resolvePlaceholders(agent.skills[platform], skillName, home);
89
93
  }
90
94
  export function resolveAgentDetectPathFor(agent, home, platform) {
@@ -1,7 +1,7 @@
1
1
  import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
2
2
  import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
3
3
  import { logger } from "../logger.js";
4
- import { compileAgentRuleReverts, compileMcpServerReverts, } from "./adapters/index.js";
4
+ import { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
5
5
  import { revertTomlMcpPatch } from "./adapters/toml.js";
6
6
  import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
7
7
  import { resolveAgentSkillPath } from "./resolve.js";
@@ -15,6 +15,8 @@ function buildSkillDirReverts(manifest, home, agentFilter) {
15
15
  const agent = AGENT_REGISTRY_BY_ID[agentId];
16
16
  if (!agent)
17
17
  continue;
18
+ if (!agent.skills)
19
+ continue;
18
20
  actions.push({
19
21
  kind: "skill-dir",
20
22
  skill: skill.name,
@@ -70,6 +72,8 @@ export function planRevert(manifest, detectedAgents, home, repo) {
70
72
  ...buildConfigPatchReverts(manifest, home, detectedAgents),
71
73
  ...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home, repo)),
72
74
  ...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home, repo)),
75
+ ...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, detectedAgents, home)),
76
+ ...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, detectedAgents, home, repo)),
73
77
  ];
74
78
  }
75
79
  export function planRevertAll(manifest, home, repo) {
@@ -79,6 +83,8 @@ export function planRevertAll(manifest, home, repo) {
79
83
  ...buildConfigPatchReverts(manifest, home, null),
80
84
  ...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home, repo)),
81
85
  ...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home, repo)),
86
+ ...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, null, home)),
87
+ ...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, null, home, repo)),
82
88
  ];
83
89
  }
84
90
  function recordOutcome(result, action, counts, failed) {
@@ -2,5 +2,6 @@ export declare function sourceAccessError(err: unknown, sourcePath: string): str
2
2
  export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
3
3
  export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
4
4
  export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
5
+ export declare function validatePermissionsConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
5
6
  export declare function validateAgentRuleMarkdownPath(manifestPath: string, agentId: string): void;
6
7
  export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<void>;
@@ -96,6 +96,51 @@ export function validateMcpServerConfigShape(config, entryName, agentId) {
96
96
  throw new UserError("DEPLOY_FAILED", `mcpServers entry "${entryName}" for agent "${agentId}" must define "env" as an object of string values when present`);
97
97
  }
98
98
  }
99
+ const CODEX_APPROVAL_POLICY_VALUES = [
100
+ "auto",
101
+ "manual",
102
+ "suggest",
103
+ "on-failure",
104
+ ];
105
+ function rejectUnknownKeys(config, allowed, entryName, agentId) {
106
+ const unknownKeys = Object.keys(config).filter((k) => !allowed.includes(k));
107
+ if (unknownKeys.length > 0) {
108
+ throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "${agentId}" contains unrecognized keys: ${unknownKeys.join(", ")}. Only ${allowed.map((k) => `"${k}"`).join(", ")} is allowed.`);
109
+ }
110
+ }
111
+ function validateClaudeCodePermissions(config, entryName) {
112
+ rejectUnknownKeys(config, ["permissions"], entryName, "claude-code");
113
+ const perms = config.permissions;
114
+ if (perms === undefined)
115
+ return;
116
+ if (typeof perms !== "object" || perms === null || Array.isArray(perms)) {
117
+ throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "claude-code" must define "permissions" as an object`);
118
+ }
119
+ const permsObj = perms;
120
+ for (const key of ["allow", "deny"]) {
121
+ const val = permsObj[key];
122
+ if (val !== undefined &&
123
+ (!Array.isArray(val) || val.some((item) => typeof item !== "string"))) {
124
+ throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "claude-code" must define "permissions.${key}" as an array of strings when present`);
125
+ }
126
+ }
127
+ }
128
+ function validateCodexPermissions(config, entryName) {
129
+ rejectUnknownKeys(config, ["approval_policy"], entryName, "codex");
130
+ const policy = config.approval_policy;
131
+ if (policy !== undefined &&
132
+ !CODEX_APPROVAL_POLICY_VALUES.includes(policy)) {
133
+ throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "codex" must define "approval_policy" as one of: ${CODEX_APPROVAL_POLICY_VALUES.join(", ")}`);
134
+ }
135
+ }
136
+ export function validatePermissionsConfigShape(config, entryName, agentId) {
137
+ if (agentId === "claude-code") {
138
+ validateClaudeCodePermissions(config, entryName);
139
+ }
140
+ else if (agentId === "codex") {
141
+ validateCodexPermissions(config, entryName);
142
+ }
143
+ }
99
144
  export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
100
145
  const extension = path.extname(manifestPath).toLowerCase();
101
146
  if (extension !== ".md" && extension !== ".markdown") {
@@ -72,6 +72,30 @@ export declare const AgentRuleEntrySchema: z.ZodObject<{
72
72
  }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
73
73
  path: z.ZodString;
74
74
  }, z.core.$strip>;
75
+ export declare const PermissionsEntrySchema: z.ZodObject<{
76
+ name: z.ZodString;
77
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
78
+ "claude-code": "claude-code";
79
+ codex: "codex";
80
+ "gemini-cli": "gemini-cli";
81
+ antigravity: "antigravity";
82
+ opencode: "opencode";
83
+ "github-copilot": "github-copilot";
84
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
85
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
86
+ }, z.core.$strip>;
87
+ export declare const AgentDefinitionEntrySchema: z.ZodObject<{
88
+ name: z.ZodString;
89
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
90
+ "claude-code": "claude-code";
91
+ codex: "codex";
92
+ "gemini-cli": "gemini-cli";
93
+ antigravity: "antigravity";
94
+ opencode: "opencode";
95
+ "github-copilot": "github-copilot";
96
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
97
+ path: z.ZodString;
98
+ }, z.core.$strip>;
75
99
  export declare const ManifestSchema: z.ZodObject<{
76
100
  skills: z.ZodArray<z.ZodObject<{
77
101
  name: z.ZodString;
@@ -135,12 +159,38 @@ export declare const ManifestSchema: z.ZodObject<{
135
159
  }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
136
160
  path: z.ZodString;
137
161
  }, z.core.$strip>>>;
162
+ permissions: z.ZodDefault<z.ZodArray<z.ZodObject<{
163
+ name: z.ZodString;
164
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
165
+ "claude-code": "claude-code";
166
+ codex: "codex";
167
+ "gemini-cli": "gemini-cli";
168
+ antigravity: "antigravity";
169
+ opencode: "opencode";
170
+ "github-copilot": "github-copilot";
171
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
172
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
173
+ }, z.core.$strip>>>;
174
+ agentDefinitions: z.ZodDefault<z.ZodArray<z.ZodObject<{
175
+ name: z.ZodString;
176
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
177
+ "claude-code": "claude-code";
178
+ codex: "codex";
179
+ "gemini-cli": "gemini-cli";
180
+ antigravity: "antigravity";
181
+ opencode: "opencode";
182
+ "github-copilot": "github-copilot";
183
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
184
+ path: z.ZodString;
185
+ }, z.core.$strip>>>;
138
186
  }, z.core.$strip>;
139
187
  export type SkillEntry = z.infer<typeof SkillEntrySchema>;
140
188
  export type FileEntry = z.infer<typeof FileEntrySchema>;
141
189
  export type ConfigEntry = z.infer<typeof ConfigEntrySchema>;
142
190
  export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
143
191
  export type AgentRuleEntry = z.infer<typeof AgentRuleEntrySchema>;
192
+ export type PermissionsEntry = z.infer<typeof PermissionsEntrySchema>;
193
+ export type AgentDefinitionEntry = z.infer<typeof AgentDefinitionEntrySchema>;
144
194
  export type Manifest = z.infer<typeof ManifestSchema>;
145
195
  export declare const AgentListSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>, z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
146
196
  "claude-code": "claude-code";
@@ -90,6 +90,22 @@ export const AgentRuleEntrySchema = z.object({
90
90
  // Relative path to the rules/instruction file within the source bundle.
91
91
  path: sourcePathField,
92
92
  });
93
+ export const PermissionsEntrySchema = z.object({
94
+ name: nameField,
95
+ agents: agentsField,
96
+ // Raw permission config payload validated per agent by the permissions adapter.
97
+ // For claude-code: { permissions: { allow?: string[], deny?: string[] } }
98
+ // For codex: { approval_policy?: "auto" | "manual" | "suggest" | "on-failure" }
99
+ config: z.record(z.string(), z.unknown()),
100
+ });
101
+ export const AgentDefinitionEntrySchema = z.object({
102
+ name: nameField,
103
+ agents: agentsField,
104
+ // Relative path to the agent definition Markdown file within the source bundle.
105
+ // Must be a .md or .markdown file containing YAML frontmatter that describes
106
+ // the agent's persona, instructions, and any tool configuration.
107
+ path: sourcePathField,
108
+ });
93
109
  export const ManifestSchema = z.object({
94
110
  skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
95
111
  const seen = new Set();
@@ -108,6 +124,8 @@ export const ManifestSchema = z.object({
108
124
  configs: z.array(ConfigEntrySchema).default([]),
109
125
  mcpServers: z.array(McpServerEntrySchema).default([]),
110
126
  agentRules: z.array(AgentRuleEntrySchema).default([]),
127
+ permissions: z.array(PermissionsEntrySchema).default([]),
128
+ agentDefinitions: z.array(AgentDefinitionEntrySchema).default([]),
111
129
  });
112
130
  // Parses the --agents CLI flag: comma-separated agent IDs → AgentId[]
113
131
  export const AgentListSchema = z