@kuznai/inception-engine 0.20.0 → 0.22.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.
@@ -4,7 +4,9 @@ import path from "node:path";
4
4
  import { AGENT_REGISTRY, AGENT_REGISTRY_BY_ID } from "../config/agents.js";
5
5
  import { UserError } from "../errors.js";
6
6
  import { logger } from "../logger.js";
7
+ import * as frontmatterAdapter from "./adapters/frontmatter.js";
7
8
  import { compileAdapterActions } from "./adapters/index.js";
9
+ import { applyTomlMcpPatch } from "./adapters/toml.js";
8
10
  import { planCapabilityForDeploy } from "./capabilities.js";
9
11
  import { applyMergePatch, computeUndoPatch, isPlainObject, } from "./merge-patch.js";
10
12
  import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
@@ -105,7 +107,7 @@ function checkPairAgentRuleAmbiguities(manifest, primary, rider) {
105
107
  JSON.stringify(primarySupport.path) === JSON.stringify(riderSupport.path)) {
106
108
  warnings.push({
107
109
  kind: "ambiguity",
108
- message: `Both "${primary}" and "${rider}" are listed in agentRules entry "${entry.name}". Both target the same surface listing both is redundant but harmless; deduplication ensures only one write action is emitted.`,
110
+ message: `Both "${primary}" and "${rider}" are listed in agentRules entry "${entry.name}". Both target the same surface - listing both is redundant but harmless; deduplication ensures only one write action is emitted.`,
109
111
  });
110
112
  }
111
113
  }
@@ -122,7 +124,7 @@ function checkPairMcpAmbiguities(manifest, primary, rider) {
122
124
  continue;
123
125
  warnings.push({
124
126
  kind: "ambiguity",
125
- message: `Both "${primary}" and "${rider}" are listed in mcpServers entry "${entry.name}". "${primary}" writes to a shared MCP surface verify that deploying to both does not produce conflicting MCP server behavior.`,
127
+ message: `Both "${primary}" and "${rider}" are listed in mcpServers entry "${entry.name}". "${primary}" writes to a shared MCP surface - verify that deploying to both does not produce conflicting MCP server behavior.`,
126
128
  });
127
129
  }
128
130
  return warnings;
@@ -134,7 +136,7 @@ function checkPairAgentDefinitionAmbiguities(manifest, primary, rider) {
134
136
  continue;
135
137
  warnings.push({
136
138
  kind: "ambiguity",
137
- message: `Both "${primary}" and "${rider}" are listed in agentDefinitions entry "${entry.name}". They write to distinct surfaces verify that this behavioral divergence is intended.`,
139
+ message: `Both "${primary}" and "${rider}" are listed in agentDefinitions entry "${entry.name}". They write to distinct surfaces - verify that this behavioral divergence is intended.`,
138
140
  });
139
141
  }
140
142
  return warnings;
@@ -162,7 +164,7 @@ function checkAntigravityPathCollisions(manifest) {
162
164
  if (defNames.has(entry.name)) {
163
165
  warnings.push({
164
166
  kind: "collision",
165
- message: `agentDefinitions entry "${entry.name}" and mcpServers entry "${entry.name}" for agent "antigravity" both resolve to {repo}/.agents/rules/${entry.name}.md one will silently overwrite the other; use different names or remove one entry`,
167
+ message: `agentDefinitions entry "${entry.name}" and mcpServers entry "${entry.name}" for agent "antigravity" both resolve to {repo}/.agents/rules/${entry.name}.md - one will silently overwrite the other; use different names or remove one entry`,
166
168
  });
167
169
  }
168
170
  }
@@ -212,17 +214,15 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
212
214
  });
213
215
  }
214
216
  }
215
- for (const skill of manifest.skills) {
216
- await planSkillEntry(skill);
217
- }
217
+ await Promise.all(manifest.skills.map(planSkillEntry));
218
218
  return { actions, warnings };
219
219
  }
220
220
  async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
221
221
  const actions = [];
222
- for (const fileEntry of manifest.files ?? []) {
222
+ await Promise.all((manifest.files ?? []).map(async (fileEntry) => {
223
223
  const targetAgents = fileEntry.agents.filter((agentId) => detectedAgents.includes(agentId));
224
224
  if (targetAgents.length === 0)
225
- continue;
225
+ return;
226
226
  const source = path.resolve(sourceDir, fileEntry.path);
227
227
  await validateSourcePath(source, fileEntry.path, resolvedSourceDir, realRoot);
228
228
  await validateSourceFile(source, fileEntry.path);
@@ -236,10 +236,10 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
236
236
  agent: agentId,
237
237
  source,
238
238
  target: resolveTargetTemplate(fileEntry.target, home, repo, workspace),
239
- confidence: agent.provenance.skills,
239
+ confidence: "implementation-only",
240
240
  });
241
241
  }
242
- }
242
+ }));
243
243
  return actions;
244
244
  }
245
245
  function planConfigPatchActions(manifest, detectedAgents, home, repo, workspace) {
@@ -280,7 +280,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo
280
280
  ...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, workspace)),
281
281
  ...planConfigPatchActions(manifest, detectedAgents, home, repoDir, workspace),
282
282
  ];
283
- const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace);
283
+ const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace, manifest.hooks ?? []);
284
284
  actions.push(...adapterResult.actions);
285
285
  const warnings = [
286
286
  ...skillPlan.warnings,
@@ -370,7 +370,7 @@ async function backupManagedFileWriteTarget(action, home, deps) {
370
370
  agent: action.agent,
371
371
  }, deps.registry);
372
372
  if (!isOwned) {
373
- throw new Error(`Target "${action.target}" exists but is not managed by inception-engine refusing to overwrite`);
373
+ throw new Error(`Target "${action.target}" exists but is not managed by inception-engine - refusing to overwrite`);
374
374
  }
375
375
  const backupPath = `${action.target}.inception-backup`;
376
376
  await (deps.fileOps ?? defaultDeployFileOps).rm(backupPath, {
@@ -528,7 +528,7 @@ async function deployConfigPatch(action, dryRun, verbose, home, planned, deps) {
528
528
  if (existingEntry &&
529
529
  (existingEntry.skill !== action.skill ||
530
530
  existingEntry.agent !== action.agent)) {
531
- throw new Error(`Config "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" refusing to double-patch`);
531
+ throw new Error(`Config "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" - refusing to double-patch`);
532
532
  }
533
533
  const original = await readJsonConfigFile(action.target);
534
534
  const undoPatch = computeUndoPatch(original, patch);
@@ -576,7 +576,7 @@ async function deployTomlPatch(action, dryRun, verbose, home, planned, deps) {
576
576
  try {
577
577
  const { previousValue } = await (deps.registry
578
578
  ? Promise.resolve({ previousValue: null })
579
- : (await import("./adapters/toml.js")).applyTomlMcpPatch(action.target, action.skill, action.config));
579
+ : applyTomlMcpPatch(action.target, action.skill, action.config));
580
580
  // Note: To truly support custom deps here we'd need to refactor toml adapter to accept deps.
581
581
  // For now we assume standard adapter for TOML.
582
582
  await registerDeployment(home, action.target, {
@@ -622,9 +622,8 @@ async function deployFrontmatterEmit(action, dryRun, verbose, home, planned, dep
622
622
  (existingEntry.kind !== "frontmatter-emit" ||
623
623
  existingEntry.skill !== action.skill ||
624
624
  existingEntry.agent !== action.agent)) {
625
- throw new Error(`Frontmatter target "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" refusing to double-patch`);
625
+ throw new Error(`Frontmatter target "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" - refusing to double-patch`);
626
626
  }
627
- const frontmatterAdapter = await import("./adapters/frontmatter.js");
628
627
  const existing = await frontmatterAdapter.readFrontmatterDocumentFile(action.target);
629
628
  const undoPatch = computeUndoPatch(existing.attributes, action.frontmatter);
630
629
  const patchedFrontmatter = applyMergePatch(existing.attributes, action.frontmatter);
@@ -712,7 +711,7 @@ async function assertTargetAbsent(targetPath) {
712
711
  catch (err) {
713
712
  if (err instanceof Error && err.message.startsWith("Target path appeared"))
714
713
  throw err;
715
- // ENOENT is expected target should not exist after backup
714
+ // ENOENT is expected - target should not exist after backup
716
715
  }
717
716
  }
718
717
  async function createDeployTarget(action, home, deps) {
@@ -775,7 +774,7 @@ async function backupExisting(targetPath, verbose, home, expected, deps) {
775
774
  skill: expected.skill,
776
775
  agent: expected.agent,
777
776
  }, deps.registry))) {
778
- throw new Error(`Target "${targetPath}" exists but is not managed by inception-engine refusing to overwrite`);
777
+ throw new Error(`Target "${targetPath}" exists but is not managed by inception-engine - refusing to overwrite`);
779
778
  }
780
779
  const backupPath = `${targetPath}.inception-backup`;
781
780
  if (verbose) {
@@ -8,13 +8,8 @@ const defaultExecFn = async (cmd, args) => {
8
8
  await execFileAsync(cmd, args);
9
9
  };
10
10
  export async function detectInstalledAgents(home) {
11
- const detected = [];
12
- for (const agent of AGENT_REGISTRY) {
13
- if (await isAgentInstalled(agent, home)) {
14
- detected.push(agent.id);
15
- }
16
- }
17
- return detected;
11
+ const results = await Promise.all(AGENT_REGISTRY.map((agent) => isAgentInstalled(agent, home)));
12
+ return AGENT_REGISTRY.filter((_, i) => results[i]).map((a) => a.id);
18
13
  }
19
14
  async function isAgentInstalled(agent, home) {
20
15
  const detectPath = resolveAgentDetectPath(agent, home);
@@ -269,16 +269,24 @@ function agentsForDefinitionSubdir(subdir) {
269
269
  */
270
270
  async function isSkippedMcpFile(subdir, absPath, relPath) {
271
271
  const hasMcpSurfaceHere = AGENT_REGISTRY.some((agent) => {
272
- const support = agent.mcpSupport;
273
- if (support?.status !== "supported")
274
- return false;
275
- const tmpl = support.path.posix;
276
- const repoIdx = tmpl.indexOf("{repo}");
277
- const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
278
- if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
279
- return false;
280
- const prefix = tmpl.slice(repoIdx + 1, nameIdx).join("/");
281
- return prefix === subdir;
272
+ const supports = [
273
+ agent.mcpSupport,
274
+ agent.mcpRepoSupport,
275
+ agent.mcpWorkspaceSupport,
276
+ ];
277
+ for (const support of supports) {
278
+ if (support?.status !== "supported")
279
+ continue;
280
+ const tmpl = support.path.posix;
281
+ const repoIdx = tmpl.indexOf("{repo}");
282
+ const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
283
+ if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
284
+ continue;
285
+ const prefix = tmpl.slice(repoIdx + 1, nameIdx).join("/");
286
+ if (prefix === subdir)
287
+ return true;
288
+ }
289
+ return false;
282
290
  });
283
291
  if (!hasMcpSurfaceHere)
284
292
  return false;
@@ -521,7 +529,8 @@ function logPathAgentEntries(label, entries) {
521
529
  return;
522
530
  logger.detail(`${label}:`);
523
531
  for (const e of entries) {
524
- logger.detail(` ${e.name} → ${e.path} [${e.agents.join(", ")}]`);
532
+ const entryAgents = e.agents ?? [];
533
+ logger.detail(` ${e.name} → ${e.path} [${entryAgents.join(", ")}]`);
525
534
  }
526
535
  }
527
536
  function logVerboseManifest(skills, agentRules, mcpServers, files, configs, agentDefinitions) {
@@ -155,12 +155,22 @@ async function detectInstructionBudgetRisk(detectedAgents, manifest, sourceDir)
155
155
  // will produce the proper UserError during action compilation.
156
156
  }
157
157
  }
158
+ const entries = [];
158
159
  for (const entry of manifest.agentRules ?? []) {
159
- await checkEntry(entry.path, "agentRules", entry.agents);
160
+ entries.push({
161
+ path: entry.path,
162
+ label: "agentRules",
163
+ agents: entry.agents,
164
+ });
160
165
  }
161
166
  for (const entry of manifest.agentDefinitions ?? []) {
162
- await checkEntry(entry.path, "agentDefinitions", entry.agents);
167
+ entries.push({
168
+ path: entry.path,
169
+ label: "agentDefinitions",
170
+ agents: entry.agents,
171
+ });
163
172
  }
173
+ await Promise.all(entries.map((e) => checkEntry(e.path, e.label, e.agents)));
164
174
  return warnings;
165
175
  }
166
176
  function pushCapabilityWarning(acc, kind, message) {
@@ -169,6 +179,21 @@ function pushCapabilityWarning(acc, kind, message) {
169
179
  acc.seen.add(`${kind}:${message}`);
170
180
  acc.warnings.push({ kind, message });
171
181
  }
182
+ function collectPlannedSurfaceWarnings(acc, agentId, capability) {
183
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
184
+ if (!agent?.unsupportedSurfaces ||
185
+ (capability !== "mcpServers" && capability !== "permissions")) {
186
+ return;
187
+ }
188
+ for (const surface of agent.unsupportedSurfaces) {
189
+ if (surface.status === "planned") {
190
+ pushCapabilityWarning(acc, "info", `Agent "${agentId}": ${surface.reason ?? surface.plannedSurface}`);
191
+ }
192
+ else if (surface.status === "unsupported") {
193
+ pushCapabilityWarning(acc, "config-authority", `Agent "${agentId}": ${surface.reason ?? surface.schemaLabel}`);
194
+ }
195
+ }
196
+ }
172
197
  function collectCapabilityWarningsForAgent(acc, agentId, capability, entryName, targetAgents, scope) {
173
198
  const plan = planCapabilityForDeploy({
174
199
  agentId,
@@ -181,24 +206,13 @@ function collectCapabilityWarningsForAgent(acc, agentId, capability, entryName,
181
206
  pushCapabilityWarning(acc, "info", plan.warning.message);
182
207
  return;
183
208
  }
184
- // Evaluate undocumented/planned surfaces warning
185
- const agent = AGENT_REGISTRY_BY_ID[agentId];
186
- if (agent?.unsupportedSurfaces) {
187
- for (const surface of agent.unsupportedSurfaces) {
188
- if (surface.status === "planned") {
189
- pushCapabilityWarning(acc, "info", `Agent "${agentId}": ${surface.reason ?? surface.plannedSurface}`);
190
- }
191
- else if (surface.status === "unsupported") {
192
- pushCapabilityWarning(acc, "config-authority", `Agent "${agentId}": ${surface.reason ?? surface.schemaLabel}`);
193
- }
209
+ if (capability !== "skills") {
210
+ const confidence = describeCapabilityConfidence(agentId, capability, scope);
211
+ if (confidence.message) {
212
+ pushCapabilityWarning(acc, "config-authority", confidence.message);
194
213
  }
195
214
  }
196
- if (capability === "skills")
197
- return;
198
- const confidence = describeCapabilityConfidence(agentId, capability, scope);
199
- if (confidence.message) {
200
- pushCapabilityWarning(acc, "config-authority", confidence.message);
201
- }
215
+ collectPlannedSurfaceWarnings(acc, agentId, capability);
202
216
  }
203
217
  function collectCapabilityWarningsForTargets(acc, targetAgents, capability, entryName, scope) {
204
218
  for (const agentId of targetAgents) {
@@ -252,7 +266,9 @@ async function collectAgentWarnings(agentId, manifest, home) {
252
266
  });
253
267
  }
254
268
  else if (agent.policyNote &&
255
- !agent.policyNote.includes("Organization policies may override")) {
269
+ !agent.policyNote
270
+ .toLowerCase()
271
+ .includes("organization policies may override")) {
256
272
  warnings.push({
257
273
  kind: "policy",
258
274
  message: `Agent "${agentId}": ${agent.policyNote}`,
@@ -1,6 +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 * as frontmatterAdapter from "./adapters/frontmatter.js";
4
5
  import { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
5
6
  import { revertTomlMcpPatch } from "./adapters/toml.js";
6
7
  import { applyUndoPatch } from "./merge-patch.js";
@@ -177,7 +178,6 @@ function planFrontmatterRevert(action, frontmatterEntry, planned) {
177
178
  return { outcome: "ok" };
178
179
  }
179
180
  async function applyFrontmatterRevert(action, frontmatterEntry) {
180
- const frontmatterAdapter = await import("./adapters/frontmatter.js");
181
181
  const current = await frontmatterAdapter.readFrontmatterDocumentFile(action.target);
182
182
  const restoredFrontmatter = applyUndoPatch(current.attributes, frontmatterEntry.undoPatch ?? {});
183
183
  const shouldDeleteFile = frontmatterEntry.created === true &&
@@ -4,6 +4,7 @@ export declare function validateSourcePath(source: string, skillPath: string, re
4
4
  export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
5
5
  export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
6
6
  export declare function validatePermissionsConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
7
+ export declare function validateHookConfigShape(_config: Record<string, unknown>, _entryName: string, _agentId: string): void;
7
8
  export declare function validateAgentRuleMarkdownPath(manifestPath: string, agentId: string): void;
8
9
  export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<{
9
10
  attributes: Record<string, unknown>;
@@ -163,6 +163,10 @@ export function validatePermissionsConfigShape(config, entryName, agentId) {
163
163
  validateOpenCodePermissions(config, entryName);
164
164
  }
165
165
  }
166
+ export function validateHookConfigShape(_config, _entryName, _agentId) {
167
+ // Placeholder for agent-specific hook validation logic.
168
+ // Currently allows any record as a hook payload.
169
+ }
166
170
  export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
167
171
  const extension = path.extname(manifestPath).toLowerCase();
168
172
  if (extension !== ".md" && extension !== ".markdown") {
@@ -218,6 +222,17 @@ function validateGithubCopilotRequirements(attributes, manifestPath) {
218
222
  if (!(hasTools || hasInstructions)) {
219
223
  throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" for agent "github-copilot" must define "tools" or "instructions" in frontmatter`);
220
224
  }
225
+ if (hasTools) {
226
+ const tools = attributes.tools;
227
+ if (!Array.isArray(tools)) {
228
+ throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" for agent "github-copilot" has a "tools" field that must be an array`);
229
+ }
230
+ for (const tool of tools) {
231
+ if (typeof tool !== "string") {
232
+ throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" for agent "github-copilot" has a "tools" entry that must be a string`);
233
+ }
234
+ }
235
+ }
221
236
  }
222
237
  function validateAntigravityRequirements(attributes, manifestPath) {
223
238
  const mcpServers = attributes["mcp-servers"] ?? attributes.mcpServers;
@@ -1,5 +1,7 @@
1
1
  export type ErrorCode = "INVALID_ARGS" | "MANIFEST_INVALID" | "DEPLOY_FAILED" | "RESOLVE_FAILED";
2
2
  export declare class UserError extends Error {
3
3
  readonly code: ErrorCode;
4
- constructor(code: ErrorCode, message: string);
4
+ constructor(code: ErrorCode, message: string, options?: {
5
+ cause?: unknown;
6
+ });
5
7
  }
@@ -1,7 +1,7 @@
1
1
  export class UserError extends Error {
2
2
  code;
3
- constructor(code, message) {
4
- super(message);
3
+ constructor(code, message, options) {
4
+ super(message, options);
5
5
  this.name = "UserError";
6
6
  this.code = code;
7
7
  }
@@ -63,6 +63,7 @@ export declare const McpServerEntrySchema: z.ZodObject<{
63
63
  global: "global";
64
64
  repo: "repo";
65
65
  workspace: "workspace";
66
+ devcontainer: "devcontainer";
66
67
  }>>;
67
68
  }, z.core.$strip>;
68
69
  export declare const AgentRuleEntrySchema: z.ZodObject<{
@@ -111,6 +112,18 @@ export declare const AgentDefinitionEntrySchema: z.ZodObject<{
111
112
  workspace: "workspace";
112
113
  }>>;
113
114
  }, z.core.$strip>;
115
+ export declare const HookEntrySchema: z.ZodObject<{
116
+ name: z.ZodString;
117
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
118
+ "claude-code": "claude-code";
119
+ codex: "codex";
120
+ "gemini-cli": "gemini-cli";
121
+ antigravity: "antigravity";
122
+ opencode: "opencode";
123
+ "github-copilot": "github-copilot";
124
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
125
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
126
+ }, z.core.$strip>;
114
127
  export declare const ManifestSchema: z.ZodObject<{
115
128
  skills: z.ZodArray<z.ZodObject<{
116
129
  name: z.ZodString;
@@ -165,6 +178,7 @@ export declare const ManifestSchema: z.ZodObject<{
165
178
  global: "global";
166
179
  repo: "repo";
167
180
  workspace: "workspace";
181
+ devcontainer: "devcontainer";
168
182
  }>>;
169
183
  }, z.core.$strip>>>;
170
184
  agentRules: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -213,6 +227,18 @@ export declare const ManifestSchema: z.ZodObject<{
213
227
  workspace: "workspace";
214
228
  }>>;
215
229
  }, z.core.$strip>>>;
230
+ hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
231
+ name: z.ZodString;
232
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
233
+ "claude-code": "claude-code";
234
+ codex: "codex";
235
+ "gemini-cli": "gemini-cli";
236
+ antigravity: "antigravity";
237
+ opencode: "opencode";
238
+ "github-copilot": "github-copilot";
239
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
240
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
241
+ }, z.core.$strip>>>;
216
242
  }, z.core.$strip>;
217
243
  export type SkillEntry = z.infer<typeof SkillEntrySchema>;
218
244
  export type FileEntry = z.infer<typeof FileEntrySchema>;
@@ -221,6 +247,7 @@ export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
221
247
  export type AgentRuleEntry = z.infer<typeof AgentRuleEntrySchema>;
222
248
  export type PermissionsEntry = z.infer<typeof PermissionsEntrySchema>;
223
249
  export type AgentDefinitionEntry = z.infer<typeof AgentDefinitionEntrySchema>;
250
+ export type HookEntry = z.infer<typeof HookEntrySchema>;
224
251
  export type Manifest = z.infer<typeof ManifestSchema>;
225
252
  export declare const AgentListSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>, z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
226
253
  "claude-code": "claude-code";
@@ -88,7 +88,9 @@ export const McpServerEntrySchema = z.object({
88
88
  // config files for agents that support them (e.g. GitHub Copilot's
89
89
  // .vscode/mcp.json). For agents without scope-specific surfaces the
90
90
  // adapter falls back to the global surface or emits a warning.
91
- scope: z.enum(["global", "repo", "workspace"]).default("global"),
91
+ scope: z
92
+ .enum(["global", "repo", "workspace", "devcontainer"])
93
+ .default("global"),
92
94
  });
93
95
  export const AgentRuleEntrySchema = z.object({
94
96
  name: nameField,
@@ -124,6 +126,13 @@ export const AgentDefinitionEntrySchema = z.object({
124
126
  // within the deployed repository (default).
125
127
  scope: z.enum(["global", "repo", "workspace"]).default("repo"),
126
128
  });
129
+ export const HookEntrySchema = z.object({
130
+ name: nameField,
131
+ agents: agentsField,
132
+ // Raw hook config payload validated per agent by the hooks adapter.
133
+ // Supports lifecycle-binding hooks (e.g. pre-exec, post-exec) for various agents.
134
+ config: z.record(z.string(), z.unknown()),
135
+ });
127
136
  export const ManifestSchema = z.object({
128
137
  skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
129
138
  const seen = new Set();
@@ -144,6 +153,7 @@ export const ManifestSchema = z.object({
144
153
  agentRules: z.array(AgentRuleEntrySchema).default([]),
145
154
  permissions: z.array(PermissionsEntrySchema).default([]),
146
155
  agentDefinitions: z.array(AgentDefinitionEntrySchema).default([]),
156
+ hooks: z.array(HookEntrySchema).optional(),
147
157
  });
148
158
  // Parses the --agents CLI flag: comma-separated agent IDs → AgentId[]
149
159
  export const AgentListSchema = z
@@ -1,11 +1,11 @@
1
1
  import type { AgentId } from "./schemas/manifest.ts";
2
- export type { AgentDefinitionEntry, AgentId, ConfigEntry, FileEntry, Manifest, SkillEntry, } from "./schemas/manifest.ts";
2
+ export type { AgentDefinitionEntry, AgentId, ConfigEntry, FileEntry, HookEntry, Manifest, SkillEntry, } from "./schemas/manifest.ts";
3
3
  export interface AgentPaths {
4
4
  posix: string[];
5
5
  windows: string[];
6
6
  }
7
7
  export type Confidence = "documented" | "implementation-only" | "provisional";
8
- export type CapabilityKind = "skills" | "mcpServers" | "agentRules" | "permissions" | "agentDefinitions";
8
+ export type CapabilityKind = "skills" | "mcpServers" | "agentRules" | "permissions" | "hooks" | "agentDefinitions";
9
9
  export interface SupportedAgentSurface {
10
10
  status: "supported";
11
11
  /**
@@ -67,6 +67,7 @@ export interface AgentProvenance {
67
67
  mcpConfig?: Confidence;
68
68
  agentRules?: Confidence;
69
69
  permissions?: Confidence;
70
+ hooks?: Confidence;
70
71
  agentDefinitions?: Confidence;
71
72
  }
72
73
  export interface AgentConfig {
@@ -97,11 +98,16 @@ export interface AgentConfig {
97
98
  agentRulesRepoSupport?: AgentSurfaceSupport;
98
99
  agentRulesWorkspaceSupport?: AgentSurfaceSupport;
99
100
  permissionsSupport?: AgentSurfaceSupport;
101
+ hooksSupport?: AgentSurfaceSupport;
102
+ hooksRepoSupport?: AgentSurfaceSupport;
103
+ hooksWorkspaceSupport?: AgentSurfaceSupport;
100
104
  agentDefinitionsSupport?: AgentSurfaceSupport;
101
105
  agentDefinitionsRepoSupport?: AgentSurfaceSupport;
102
106
  agentDefinitionsWorkspaceSupport?: AgentSurfaceSupport;
103
107
  agentDefinitionsTomlSupport?: AgentSurfaceSupport;
104
108
  agentDefinitionsTomlRepoSupport?: AgentSurfaceSupport;
109
+ mcpDevcontainerSupport?: AgentSurfaceSupport;
110
+ mcpAgentFrontmatterSupport?: AgentSurfaceSupport;
105
111
  policyNote?: string;
106
112
  /**
107
113
  * A list of documented surfaces that are currently not safe to support directly.