@kuznai/inception-engine 0.21.0 → 0.23.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.
@@ -2,6 +2,37 @@ import path from "node:path";
2
2
  import { planCapabilityForDeploy, resolveCapabilitySurface, } from "../capabilities.js";
3
3
  import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
4
4
  import { validateAgentRuleMarkdownPath, validateInstructionFileRequirements, validateSourceFile, validateSourcePath, } from "../validation.js";
5
+ function requiresRepoPath(scope) {
6
+ return (scope === "repo" || scope === "copilot-repo" || scope === "copilot-scoped");
7
+ }
8
+ function deduplicateTargets(supportedTargets, scope) {
9
+ const seenTargetPaths = new Set();
10
+ const dedupedTargets = [];
11
+ for (const t of supportedTargets) {
12
+ const surface = resolveCapabilitySurface(t.agentId, "agentRules", scope);
13
+ if (surface.surfaceKind === "shared-via" &&
14
+ surface.sharedVia &&
15
+ supportedTargets.some((o) => o.agentId === surface.sharedVia && o.target === t.target)) {
16
+ continue;
17
+ }
18
+ if (!seenTargetPaths.has(t.target)) {
19
+ seenTargetPaths.add(t.target);
20
+ dedupedTargets.push(t);
21
+ }
22
+ }
23
+ return dedupedTargets;
24
+ }
25
+ function injectTargetDir(pathSegments, targetDir) {
26
+ if (!targetDir)
27
+ return pathSegments;
28
+ const placeholderIndex = pathSegments.findIndex((seg) => seg === "{repo}" || seg === "{workspace}");
29
+ if (placeholderIndex === -1)
30
+ return pathSegments;
31
+ const dirSegments = targetDir.split(/[\\/]+/).filter(Boolean);
32
+ const result = [...pathSegments];
33
+ result.splice(placeholderIndex + 1, 0, ...dirSegments);
34
+ return result;
35
+ }
5
36
  function resolveAgentTarget(agentId, entry, home, repo, platform, workspace, allTargetAgentIds) {
6
37
  const plan = planCapabilityForDeploy({
7
38
  agentId,
@@ -28,10 +59,17 @@ function resolveAgentTarget(agentId, entry, home, repo, platform, workspace, all
28
59
  message: `agentRules: scope "workspace" requires a workspace or repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
29
60
  };
30
61
  }
62
+ if ((entry.scope === "copilot-repo" || entry.scope === "copilot-scoped") &&
63
+ !repo) {
64
+ return {
65
+ kind: "confidence",
66
+ message: `agentRules: scope "${entry.scope}" requires a repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
67
+ };
68
+ }
31
69
  return {
32
70
  agentId,
33
71
  confidence: plan.confidence,
34
- target: resolvePlaceholders(support?.path[platform] ?? [], entry.name, home, repo, workspace),
72
+ target: resolvePlaceholders(injectTargetDir(support?.path[platform] ?? [], entry.targetDir), entry.name, home, repo, workspace),
35
73
  };
36
74
  }
37
75
  export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
@@ -61,29 +99,22 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
61
99
  // Deduplicate: when a shared-via rider's primary is also in supportedTargets,
62
100
  // skip the rider — the primary agent writes the shared surface. As a fallback,
63
101
  // also dedup by resolved path so no target file is written twice.
64
- const seenTargetPaths = new Set();
65
- const dedupedTargets = [];
66
- for (const t of supportedTargets) {
67
- const surface = resolveCapabilitySurface(t.agentId, "agentRules", entry.scope);
68
- if (surface.surfaceKind === "shared-via" &&
69
- surface.sharedVia &&
70
- supportedTargets.some((o) => o.agentId === surface.sharedVia && o.target === t.target)) {
71
- // Primary agent is present and writes the same target — skip rider.
72
- continue;
73
- }
74
- if (!seenTargetPaths.has(t.target)) {
75
- seenTargetPaths.add(t.target);
76
- dedupedTargets.push(t);
77
- }
78
- }
102
+ const dedupedTargets = deduplicateTargets(supportedTargets, entry.scope);
79
103
  // Validate the shared source file only when at least one target uses the
80
104
  // current rules adapter surface.
81
105
  const source = path.resolve(sourceDir, entry.path);
82
106
  await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
83
107
  await validateSourceFile(source, entry.path);
108
+ // Native Copilot instruction files (.github/copilot-instructions.md and
109
+ // .github/instructions/*.instructions.md) are plain markdown and do not
110
+ // require agent-definition-style frontmatter (tools/instructions keys).
111
+ // Skip the instructionFrontmatterRequired check for these scopes.
112
+ const skipFrontmatterValidation = entry.scope === "copilot-repo" || entry.scope === "copilot-scoped";
84
113
  for (const target of dedupedTargets) {
85
114
  validateAgentRuleMarkdownPath(entry.path, target.agentId);
86
- await validateInstructionFileRequirements(source, entry.path, target.agentId);
115
+ if (!skipFrontmatterValidation) {
116
+ await validateInstructionFileRequirements(source, entry.path, target.agentId);
117
+ }
87
118
  actions.push({
88
119
  kind: "file-write",
89
120
  skill: entry.name,
@@ -107,11 +138,11 @@ export function compileAgentRuleReverts(entry, agentFilter, home, repo, workspac
107
138
  if (surface.supportStatus !== "supported" || !support) {
108
139
  continue;
109
140
  }
110
- if (entry.scope === "repo" && !repo)
141
+ if (requiresRepoPath(entry.scope) && !repo)
111
142
  continue;
112
143
  if (entry.scope === "workspace" && !workspace && !repo)
113
144
  continue;
114
- const target = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
145
+ const target = resolvePlaceholders(injectTargetDir(support.path[platform], entry.targetDir), entry.name, home, repo, workspace);
115
146
  if (seenRevertTargets.has(target))
116
147
  continue;
117
148
  seenRevertTargets.add(target);
@@ -1,5 +1,5 @@
1
1
  import type { AgentId, AgentSurfaceSupport, CapabilityKind, Confidence, PlanWarning, SupportedAgentSurface } from "../types.ts";
2
- type CapabilityScope = "global" | "repo" | "workspace";
2
+ type CapabilityScope = "global" | "repo" | "workspace" | "devcontainer" | "copilot-repo" | "copilot-scoped";
3
3
  export interface ResolvedCapabilitySurface {
4
4
  agentId: AgentId;
5
5
  capability: CapabilityKind;
@@ -1,6 +1,12 @@
1
1
  import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
2
2
  function resolveAgentRulesSupport(agentId, scope) {
3
3
  const agent = AGENT_REGISTRY_BY_ID[agentId];
4
+ if (scope === "copilot-repo") {
5
+ return agent?.agentRulesCopilotRepoSupport;
6
+ }
7
+ if (scope === "copilot-scoped") {
8
+ return agent?.agentRulesCopilotScopedSupport;
9
+ }
4
10
  if (scope === "repo") {
5
11
  return agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport;
6
12
  }
@@ -19,6 +25,11 @@ function resolveMcpSupport(agentId, scope) {
19
25
  if (scope === "workspace") {
20
26
  return (agent?.mcpWorkspaceSupport ?? agent?.mcpRepoSupport ?? agent?.mcpSupport);
21
27
  }
28
+ if (scope === "devcontainer") {
29
+ return (agent?.mcpDevcontainerSupport ??
30
+ agent?.mcpRepoSupport ??
31
+ agent?.mcpSupport);
32
+ }
22
33
  return agent?.mcpSupport;
23
34
  }
24
35
  function resolveAgentDefinitionsSupport(agentId, scope) {
@@ -45,6 +56,10 @@ function capabilityLabel(capability) {
45
56
  return "permissions";
46
57
  case "agentDefinitions":
47
58
  return "agentDefinitions";
59
+ case "hooks":
60
+ return "hooks";
61
+ case "executionConfigs":
62
+ return "executionConfigs";
48
63
  }
49
64
  }
50
65
  function capabilitySurfaceLabel(capability) {
@@ -59,6 +74,10 @@ function capabilitySurfaceLabel(capability) {
59
74
  return "permissions";
60
75
  case "agentDefinitions":
61
76
  return "agent-definitions";
77
+ case "hooks":
78
+ return "hooks";
79
+ case "executionConfigs":
80
+ return "execution-config";
62
81
  }
63
82
  }
64
83
  function resolveSkillsCapability(agentId) {
@@ -110,9 +129,21 @@ function getSurfaceRecordAndConfidence(agentId, capability, scope) {
110
129
  confidence: agent.provenance.permissions,
111
130
  };
112
131
  }
132
+ if (capability === "agentDefinitions") {
133
+ return {
134
+ supportRecord: resolveAgentDefinitionsSupport(agentId, scope),
135
+ confidence: agent.provenance.agentDefinitions,
136
+ };
137
+ }
138
+ if (capability === "hooks") {
139
+ return {
140
+ supportRecord: agent.hooksSupport,
141
+ confidence: agent.provenance.hooks,
142
+ };
143
+ }
113
144
  return {
114
- supportRecord: resolveAgentDefinitionsSupport(agentId, scope),
115
- confidence: agent.provenance.agentDefinitions,
145
+ supportRecord: agent.executionConfigSupport,
146
+ confidence: agent.provenance.executionConfig,
116
147
  };
117
148
  }
118
149
  function resolveUnsupportedSurface(agentId, capability, supportRecord, confidence) {
@@ -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 ?? [], manifest.executionConfigs ?? []);
284
284
  actions.push(...adapterResult.actions);
285
285
  const warnings = [
286
286
  ...skillPlan.warnings,
@@ -32,9 +32,10 @@ const AGENT_RULES_FILE_PATTERNS = (() => {
32
32
  fileNames: [filename, filename.replace(".md", "-instructions.md")],
33
33
  agents,
34
34
  })),
35
- // Convention mapping: copilot-instructions.md claude-code. Copilot reads
36
- // CLAUDE.md natively; this filename is a well-known convention that cannot
37
- // be derived from any agent path template.
35
+ // Convention mapping for copilot-instructions.md found outside .github/:
36
+ // map to claude-code since Copilot reads CLAUDE.md natively. Files at
37
+ // .github/copilot-instructions.md are handled separately as a native
38
+ // Copilot surface via the copilot-repo scope.
38
39
  {
39
40
  fileNames: ["copilot-instructions.md"],
40
41
  agents: ["claude-code"],
@@ -183,18 +184,73 @@ async function findAgentRulesCandidates(baseDir, skillDirRelPaths) {
183
184
  for (const subdir of AGENT_RULES_SUBDIRS) {
184
185
  await scanDirForMarkdown(path.join(baseDir, subdir), baseDir, skillDirRelPaths, seen, candidates);
185
186
  }
187
+ // Promote .github/copilot-instructions.md to the native copilot-repo scope.
188
+ // The general scanDirForMarkdown pass above picks it up via AGENT_RULES_SUBDIRS
189
+ // (".github"), so we just patch the candidate that was already added.
190
+ const copilotRepoRelPath = ".github/copilot-instructions.md";
191
+ const copilotRepoCandidate = candidates.find((c) => c.relPath === copilotRepoRelPath);
192
+ if (copilotRepoCandidate) {
193
+ copilotRepoCandidate.defaultAgents = ["github-copilot"];
194
+ copilotRepoCandidate.scope = "copilot-repo";
195
+ }
196
+ // Discover .github/instructions/*.instructions.md as copilot-scoped entries.
197
+ // These are not covered by the general scan (it only looks for .md/.markdown
198
+ // and the stem derivation would lose the .instructions suffix).
199
+ const instructionsDir = path.join(baseDir, ".github", "instructions");
200
+ let instrEntries;
201
+ try {
202
+ instrEntries = await readdir(instructionsDir, {
203
+ withFileTypes: true,
204
+ encoding: "utf-8",
205
+ });
206
+ }
207
+ catch {
208
+ instrEntries = [];
209
+ }
210
+ for (const entry of instrEntries) {
211
+ if (!entry.isFile())
212
+ continue;
213
+ const lower = entry.name.toLowerCase();
214
+ if (!lower.endsWith(".instructions.md"))
215
+ continue;
216
+ const absPath = path.join(instructionsDir, entry.name);
217
+ const relPath = path.relative(baseDir, absPath).split(path.sep).join("/");
218
+ if (seen.has(relPath) || isInsideSkillDir(relPath, skillDirRelPaths))
219
+ continue;
220
+ // Derive name by stripping ".instructions.md" suffix
221
+ const baseStem = entry.name.slice(0, -".instructions.md".length);
222
+ const rawName = baseStem.toLowerCase().replace(/[^a-zA-Z0-9._-]/g, "-");
223
+ if (!SAFE_NAME_RE.test(rawName)) {
224
+ logger.warn("init", `Skipping "${relPath}": could not derive a valid agentRules name`);
225
+ continue;
226
+ }
227
+ seen.add(relPath);
228
+ candidates.push({
229
+ relPath,
230
+ name: rawName,
231
+ defaultAgents: ["github-copilot"],
232
+ scope: "copilot-scoped",
233
+ });
234
+ }
186
235
  return candidates;
187
236
  }
188
- function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
237
+ function buildAgentRules(candidates, activeAgents, allAgents, skillNamesSeen) {
189
238
  const rules = [];
190
239
  const namesSeen = new Set(skillNamesSeen);
191
- for (const { relPath, name: rawName } of candidates) {
192
- const fileName = path.basename(relPath);
193
- const defaultAgents = defaultAgentsForFile(fileName, activeAgents);
194
- // Intersect with active agents; fall back to full active list if empty
195
- const intersection = defaultAgents.filter((a) => activeAgents.includes(a));
196
- const agents = intersection.length > 0 ? intersection : activeAgents;
197
- // Skip if no capable agents remain (e.g. --agents github-copilot only)
240
+ for (const { relPath, name: rawName, defaultAgents: presetAgents, scope: presetScope, } of candidates) {
241
+ let agents;
242
+ if (presetAgents.length > 0) {
243
+ // Candidates with preset agents (e.g. copilot-repo, copilot-scoped) use
244
+ // those agents directly, intersected with the full agents list.
245
+ agents = presetAgents.filter((a) => allAgents.includes(a));
246
+ }
247
+ else {
248
+ // Derive from filename pattern and intersect with active (capable) agents.
249
+ const defaults = defaultAgentsForFile(path.basename(relPath), activeAgents);
250
+ const ix = defaults.filter((a) => activeAgents.includes(a));
251
+ agents = ix.length > 0 ? ix : activeAgents;
252
+ }
253
+ // Skip if excluded by --agents or no capable agents remain
198
254
  if (agents.length === 0)
199
255
  continue;
200
256
  // Resolve name collision with skill names
@@ -211,7 +267,7 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
211
267
  }
212
268
  }
213
269
  namesSeen.add(name);
214
- rules.push({ name, path: relPath, agents, scope: "global" });
270
+ rules.push({ name, path: relPath, agents, scope: presetScope ?? "global" });
215
271
  }
216
272
  return rules;
217
273
  }
@@ -269,16 +325,24 @@ function agentsForDefinitionSubdir(subdir) {
269
325
  */
270
326
  async function isSkippedMcpFile(subdir, absPath, relPath) {
271
327
  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;
328
+ const supports = [
329
+ agent.mcpSupport,
330
+ agent.mcpRepoSupport,
331
+ agent.mcpWorkspaceSupport,
332
+ ];
333
+ for (const support of supports) {
334
+ if (support?.status !== "supported")
335
+ continue;
336
+ const tmpl = support.path.posix;
337
+ const repoIdx = tmpl.indexOf("{repo}");
338
+ const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
339
+ if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
340
+ continue;
341
+ const prefix = tmpl.slice(repoIdx + 1, nameIdx).join("/");
342
+ if (prefix === subdir)
343
+ return true;
344
+ }
345
+ return false;
282
346
  });
283
347
  if (!hasMcpSurfaceHere)
284
348
  return false;
@@ -568,7 +632,7 @@ export async function runInit(options) {
568
632
  const skillNamesSeen = new Set(skills.map((s) => s.name));
569
633
  const skillDirRelPaths = new Set(found.map((f) => f.relPath));
570
634
  const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
571
- const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, skillNamesSeen);
635
+ const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, agents, skillNamesSeen);
572
636
  const allNamesSeen = new Set([
573
637
  ...skillNamesSeen,
574
638
  ...agentRules.map((r) => r.name),
@@ -118,12 +118,37 @@ function detectMultipleActiveInstructionScopes(agentId, rulesForAgent) {
118
118
  },
119
119
  ];
120
120
  }
121
+ /**
122
+ * Warns when github-copilot has both a shared-via CLAUDE.md agentRules entry
123
+ * (scope: "repo" or "global") AND a native Copilot instruction entry
124
+ * (scope: "copilot-repo" or "copilot-scoped"). GitHub Copilot merges all
125
+ * active instruction sources at runtime, so duplicate or conflicting rules
126
+ * across these surfaces may cause unexpected agent behavior.
127
+ */
128
+ function detectCopilotInstructionPrecedence(rulesForAgent) {
129
+ const hasSharedVia = (rulesForAgent ?? []).some((e) => e.scope === "repo" || e.scope === "global");
130
+ const hasNative = (rulesForAgent ?? []).some((e) => e.scope === "copilot-repo" || e.scope === "copilot-scoped");
131
+ if (!(hasSharedVia && hasNative))
132
+ return [];
133
+ return [
134
+ {
135
+ kind: "precedence",
136
+ message: `Agent "github-copilot" will load both a CLAUDE.md-shared instruction file` +
137
+ ` and a native Copilot instruction file (.github/copilot-instructions.md or` +
138
+ ` .github/instructions/). GitHub Copilot merges all active instruction` +
139
+ ` sources - ensure content is non-conflicting and does not duplicate rules.`,
140
+ },
141
+ ];
142
+ }
121
143
  function detectInstructionPrecedence(detectedAgents, manifest) {
122
144
  const warnings = [];
123
145
  for (const agentId of detectedAgents) {
124
146
  const rulesForAgent = (manifest.agentRules ?? []).filter((e) => e.agents.includes(agentId));
125
147
  warnings.push(...detectScopeOverlaps(agentId, rulesForAgent));
126
148
  warnings.push(...detectMultipleActiveInstructionScopes(agentId, rulesForAgent));
149
+ if (agentId === "github-copilot") {
150
+ warnings.push(...detectCopilotInstructionPrecedence(rulesForAgent));
151
+ }
127
152
  }
128
153
  return warnings;
129
154
  }
@@ -239,6 +264,9 @@ function collectManifestCapabilityWarnings(manifest, detectedAgents) {
239
264
  for (const entry of manifest.agentDefinitions ?? []) {
240
265
  collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "agentDefinitions", entry.name, entry.scope);
241
266
  }
267
+ for (const entry of manifest.executionConfigs ?? []) {
268
+ collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "executionConfigs", entry.name);
269
+ }
242
270
  return acc.warnings;
243
271
  }
244
272
  function detectCapabilityPlanningWarnings(manifest, detectedAgents) {
@@ -2,7 +2,7 @@ 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
4
  import * as frontmatterAdapter from "./adapters/frontmatter.js";
5
- import { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
5
+ import { compileAgentDefinitionReverts, compileAgentRuleReverts, compileExecutionConfigReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
6
6
  import { revertTomlMcpPatch } from "./adapters/toml.js";
7
7
  import { applyUndoPatch } from "./merge-patch.js";
8
8
  import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
@@ -75,6 +75,8 @@ export function planRevert(manifest, detectedAgents, home, repo) {
75
75
  ...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home, repo)),
76
76
  ...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home, repo)),
77
77
  ...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, detectedAgents, home)),
78
+ ...(manifest.hooks ?? []).flatMap((e) => compileHookReverts(e, detectedAgents, home)),
79
+ ...(manifest.executionConfigs ?? []).flatMap((e) => compileExecutionConfigReverts(e, detectedAgents, home)),
78
80
  ...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, detectedAgents, home, repo)),
79
81
  ];
80
82
  }
@@ -86,6 +88,8 @@ export function planRevertAll(manifest, home, repo) {
86
88
  ...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home, repo)),
87
89
  ...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home, repo)),
88
90
  ...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, null, home)),
91
+ ...(manifest.hooks ?? []).flatMap((e) => compileHookReverts(e, null, home)),
92
+ ...(manifest.executionConfigs ?? []).flatMap((e) => compileExecutionConfigReverts(e, null, home)),
89
93
  ...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, null, home, repo)),
90
94
  ];
91
95
  }
@@ -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,63 @@ export function validatePermissionsConfigShape(config, entryName, agentId) {
163
163
  validateOpenCodePermissions(config, entryName);
164
164
  }
165
165
  }
166
+ function validateClaudeHookCommand(cmd, path) {
167
+ if (typeof cmd !== "object" || cmd === null || Array.isArray(cmd)) {
168
+ throw new UserError("DEPLOY_FAILED", `hooks entry ${path} must be an object`);
169
+ }
170
+ const cmdObj = cmd;
171
+ if (cmdObj.type !== "command") {
172
+ throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.type must be "command"`);
173
+ }
174
+ if (typeof cmdObj.command !== "string") {
175
+ throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.command must be a string`);
176
+ }
177
+ }
178
+ function validateClaudeHookMatcher(matcher, path) {
179
+ if (typeof matcher !== "object" ||
180
+ matcher === null ||
181
+ Array.isArray(matcher)) {
182
+ throw new UserError("DEPLOY_FAILED", `hooks entry ${path} must be an object`);
183
+ }
184
+ const matcherObj = matcher;
185
+ if (matcherObj.matcher !== undefined &&
186
+ typeof matcherObj.matcher !== "string") {
187
+ throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.matcher must be a string when present`);
188
+ }
189
+ const matcherHooks = matcherObj.hooks;
190
+ if (!Array.isArray(matcherHooks)) {
191
+ throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.hooks must be an array`);
192
+ }
193
+ for (const [cmdIdx, cmd] of matcherHooks.entries()) {
194
+ validateClaudeHookCommand(cmd, `${path}.hooks[${cmdIdx}]`);
195
+ }
196
+ }
197
+ function validateClaudeCodeHooks(config, entryName) {
198
+ const unknownKeys = Object.keys(config).filter((k) => k !== "hooks");
199
+ if (unknownKeys.length > 0) {
200
+ throw new UserError("DEPLOY_FAILED", `hooks entry "${entryName}" for agent "claude-code" contains unrecognized keys: ${unknownKeys.join(", ")}. Only "hooks" is allowed.`);
201
+ }
202
+ const hooks = config.hooks;
203
+ if (hooks === undefined)
204
+ return;
205
+ if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) {
206
+ throw new UserError("DEPLOY_FAILED", `hooks entry "${entryName}" for agent "claude-code" must define "hooks" as an object`);
207
+ }
208
+ const hooksObj = hooks;
209
+ for (const [eventName, matchers] of Object.entries(hooksObj)) {
210
+ if (!Array.isArray(matchers)) {
211
+ throw new UserError("DEPLOY_FAILED", `hooks entry "${entryName}" for agent "claude-code": "hooks.${eventName}" must be an array`);
212
+ }
213
+ for (const [idx, matcher] of matchers.entries()) {
214
+ validateClaudeHookMatcher(matcher, `"${entryName}" for agent "claude-code": "hooks.${eventName}[${idx}]"`);
215
+ }
216
+ }
217
+ }
218
+ export function validateHookConfigShape(config, entryName, agentId) {
219
+ if (agentId === "claude-code") {
220
+ validateClaudeCodeHooks(config, entryName);
221
+ }
222
+ }
166
223
  export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
167
224
  const extension = path.extname(manifestPath).toLowerCase();
168
225
  if (extension !== ".md" && extension !== ".markdown") {
@@ -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<{
@@ -80,7 +81,10 @@ export declare const AgentRuleEntrySchema: z.ZodObject<{
80
81
  global: "global";
81
82
  repo: "repo";
82
83
  workspace: "workspace";
84
+ "copilot-repo": "copilot-repo";
85
+ "copilot-scoped": "copilot-scoped";
83
86
  }>>;
87
+ targetDir: z.ZodOptional<z.ZodString>;
84
88
  }, z.core.$strip>;
85
89
  export declare const PermissionsEntrySchema: z.ZodObject<{
86
90
  name: z.ZodString;
@@ -94,6 +98,18 @@ export declare const PermissionsEntrySchema: z.ZodObject<{
94
98
  }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
95
99
  config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
96
100
  }, z.core.$strip>;
101
+ export declare const ExecutionConfigEntrySchema: z.ZodObject<{
102
+ name: z.ZodString;
103
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
104
+ "claude-code": "claude-code";
105
+ codex: "codex";
106
+ "gemini-cli": "gemini-cli";
107
+ antigravity: "antigravity";
108
+ opencode: "opencode";
109
+ "github-copilot": "github-copilot";
110
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
111
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
112
+ }, z.core.$strip>;
97
113
  export declare const AgentDefinitionEntrySchema: z.ZodObject<{
98
114
  name: z.ZodString;
99
115
  agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
@@ -111,6 +127,18 @@ export declare const AgentDefinitionEntrySchema: z.ZodObject<{
111
127
  workspace: "workspace";
112
128
  }>>;
113
129
  }, z.core.$strip>;
130
+ export declare const HookEntrySchema: z.ZodObject<{
131
+ name: z.ZodString;
132
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
133
+ "claude-code": "claude-code";
134
+ codex: "codex";
135
+ "gemini-cli": "gemini-cli";
136
+ antigravity: "antigravity";
137
+ opencode: "opencode";
138
+ "github-copilot": "github-copilot";
139
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
140
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
141
+ }, z.core.$strip>;
114
142
  export declare const ManifestSchema: z.ZodObject<{
115
143
  skills: z.ZodArray<z.ZodObject<{
116
144
  name: z.ZodString;
@@ -165,6 +193,7 @@ export declare const ManifestSchema: z.ZodObject<{
165
193
  global: "global";
166
194
  repo: "repo";
167
195
  workspace: "workspace";
196
+ devcontainer: "devcontainer";
168
197
  }>>;
169
198
  }, z.core.$strip>>>;
170
199
  agentRules: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -182,7 +211,10 @@ export declare const ManifestSchema: z.ZodObject<{
182
211
  global: "global";
183
212
  repo: "repo";
184
213
  workspace: "workspace";
214
+ "copilot-repo": "copilot-repo";
215
+ "copilot-scoped": "copilot-scoped";
185
216
  }>>;
217
+ targetDir: z.ZodOptional<z.ZodString>;
186
218
  }, z.core.$strip>>>;
187
219
  permissions: z.ZodDefault<z.ZodArray<z.ZodObject<{
188
220
  name: z.ZodString;
@@ -213,6 +245,30 @@ export declare const ManifestSchema: z.ZodObject<{
213
245
  workspace: "workspace";
214
246
  }>>;
215
247
  }, z.core.$strip>>>;
248
+ hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
249
+ name: z.ZodString;
250
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
251
+ "claude-code": "claude-code";
252
+ codex: "codex";
253
+ "gemini-cli": "gemini-cli";
254
+ antigravity: "antigravity";
255
+ opencode: "opencode";
256
+ "github-copilot": "github-copilot";
257
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
258
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
259
+ }, z.core.$strip>>>;
260
+ executionConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
261
+ name: z.ZodString;
262
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
263
+ "claude-code": "claude-code";
264
+ codex: "codex";
265
+ "gemini-cli": "gemini-cli";
266
+ antigravity: "antigravity";
267
+ opencode: "opencode";
268
+ "github-copilot": "github-copilot";
269
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
270
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
271
+ }, z.core.$strip>>>;
216
272
  }, z.core.$strip>;
217
273
  export type SkillEntry = z.infer<typeof SkillEntrySchema>;
218
274
  export type FileEntry = z.infer<typeof FileEntrySchema>;
@@ -221,6 +277,8 @@ export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
221
277
  export type AgentRuleEntry = z.infer<typeof AgentRuleEntrySchema>;
222
278
  export type PermissionsEntry = z.infer<typeof PermissionsEntrySchema>;
223
279
  export type AgentDefinitionEntry = z.infer<typeof AgentDefinitionEntrySchema>;
280
+ export type HookEntry = z.infer<typeof HookEntrySchema>;
281
+ export type ExecutionConfigEntry = z.infer<typeof ExecutionConfigEntrySchema>;
224
282
  export type Manifest = z.infer<typeof ManifestSchema>;
225
283
  export declare const AgentListSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>, z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
226
284
  "claude-code": "claude-code";