@kuznai/inception-engine 0.21.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.
package/README.md CHANGED
@@ -50,9 +50,10 @@ Before executing, the deploy command runs preflight analysis on instruction file
50
50
  | Skills (SKILL.md) | All agents via manifest and CLI | All agents |
51
51
  | File write | All agents via manifest and CLI | All agents |
52
52
  | Config patch (JSON merge) | All agents via manifest and CLI | All agents |
53
- | MCP Servers | claude-code (`scope: "global"` → `~/.claude.json`; `scope: "repo"` → `{repo}/.claude/mcp.json`; `scope: "workspace"` → `{workspace}/.claude/mcp.json`), gemini-cli, codex, antigravity, opencode; github-copilot with `scope: "repo"` deploys to `{repo}/.vscode/mcp.json` and `scope: "workspace"` deploys to `{workspace}/.vscode/mcp.json`; github-copilot with `scope: "global"` (default) is unsupported and warns | claude-code, gemini-cli, codex, antigravity, opencode, github-copilot |
53
+ | MCP Servers | claude-code (`scope: "global"` → `~/.claude.json`; `scope: "repo"` → `{repo}/.claude/mcp.json`; `scope: "workspace"` → `{workspace}/.claude/mcp.json`), gemini-cli, codex, antigravity (`scope: "global"` → `~/.gemini/antigravity/mcp_config.json`; `scope: "repo"` → `{repo}/.agents/rules/{name}.md`), opencode; github-copilot with `scope: "repo"` deploys to `{repo}/.vscode/mcp.json` and `scope: "workspace"` deploys to `{workspace}/.vscode/mcp.json`; github-copilot with `scope: "global"` (default) is unsupported and warns | claude-code, gemini-cli, codex, antigravity, opencode, github-copilot |
54
54
  | Global/Repo/Workspace Rules Files | `scope: "global"` and `scope: "repo"` are supported on the implemented agent surfaces; `scope: "workspace"` is supported for `claude-code`, `codex`, and `gemini-cli`; `github-copilot` reads Claude-native rules via `claude-code` and has no separate rules deployment target | All supported agents |
55
55
  | Permissions / Approval Config | claude-code (`~/.claude/settings.json`), codex (`~/.codex/config.toml`), opencode (`~/.config/opencode/opencode.json` on POSIX, `%APPDATA%\\opencode\\opencode.json` on Windows); other agents are warned and skipped | claude-code, codex, opencode |
56
+ | Execution Hooks | claude-code (`~/.claude/settings.json`), github-copilot (`planned`); other agents are warned and skipped | claude-code |
56
57
  | Agent Definitions | claude-code (`{repo}/.claude/agents/{name}.md`), gemini-cli (`{repo}/.gemini/agents/{name}.md` or `.toml`, plus `scope: "global"` to `~/.gemini/agents/{name}.md` or `.toml`), antigravity (`{repo}/.agents/rules/{name}.md`), opencode (`{repo}/.opencode/agents/{name}.md`, plus `scope: "global"` to the user config dir), github-copilot (`{repo}/.github/copilot/agents/{name}.md`, with migration from legacy `.github/agents/{name}.agent.md`); codex is warned and skipped | All supported agents |
57
58
  | `init` manifest generation | Scans `SKILL.md` directories (`skills`), `.md` files with Claude-first agent mapping (`agentRules`), `mcp-servers.json` (`mcpServers`), and agent-definition Markdown files (`agentDefinitions`); shared surfaces default to the primary deploy target instead of shared riders; emits hints for `files/` and `configs/` directories | N/A |
58
59
  | Instruction preflight analysis | Emits capability warnings for implementation-only, planned, unsupported, and shared-through surfaces used by the manifest; emits `precedence` warnings when an agent has multiple `agentRules` scopes active simultaneously or the same source file is deployed to multiple scopes; emits `budget` warnings when `agentRules` or `agentDefinitions` source files exceed 50 KB; emits GitHub Copilot enterprise-policy warnings when local configuration may be overridden; emits a `config-authority` warning when Gemini CLI's `settings.json` contains an `instructionFilename` override that differs from the deploy target | N/A |
@@ -135,6 +136,17 @@ Create an `inception.json` file at the root of your skills directory:
135
136
  "path": "agents/code-reviewer.md",
136
137
  "agents": ["claude-code", "opencode", "github-copilot"]
137
138
  }
139
+ ],
140
+ "hooks": [
141
+ {
142
+ "name": "pre-exec-check",
143
+ "agents": ["claude-code"],
144
+ "config": {
145
+ "hooks": {
146
+ "pre_exec": "scripts/check-env.sh"
147
+ }
148
+ }
149
+ }
138
150
  ]
139
151
  }
140
152
  ```
@@ -166,17 +178,18 @@ Each **mcpServer** entry registers an MCP server into the agent's config file by
166
178
  - **name** - Unique identifier (same format as skill names); used as the server's key in the config
167
179
  - **agents** - Array of agent IDs to register this server with
168
180
  - **config** - Raw server descriptor object. For the currently supported JSON-backed adapters, inception-engine requires at least one non-empty `command` or `url` field, validates `args` as an array of strings when present, and validates `env` as a string-to-string object when present. Additional keys are passed through verbatim.
169
- - **scope** - `"global"` (default), `"repo"`, or `"workspace"`. For most agents this field is ignored (they have only a single user-level config path). For `github-copilot`, `scope` selects the target file:
181
+ - **scope** - `"global"` (default), `"repo"`, `"workspace"`, or `"devcontainer"`. For most agents this field is ignored (they have only a single user-level config path). For `github-copilot`, `scope` selects the target file:
170
182
  - `"global"` — unsupported; emits a warning and is skipped
171
183
  - `"repo"` — deploys to `{repo}/.vscode/mcp.json` under the `servers` key
172
184
  - `"workspace"` — deploys to `{workspace}/.vscode/mcp.json` under the `servers` key
185
+ - `"devcontainer"` — deploys to `{repo}/.devcontainer/devcontainer.json` under the `customizations.vscode.mcp.servers` key
173
186
 
174
187
  GitHub Copilot's `.vscode/mcp.json` uses `servers` (not `mcpServers`) as the top-level key and optionally accepts a `type` field (`"stdio"` | `"sse"` | `"http"`) in the server descriptor, which is passed through verbatim.
175
188
 
176
189
  MCP server registration is supported for all agents. Inception-engine automatically uses the correct adapter for each agent's configuration schema:
177
- - **JSON (Merge Patch)**: `claude-code` (`~/.claude.json` for `scope: "global"`; `{repo}/.claude/mcp.json` for `scope: "repo"`; `{workspace}/.claude/mcp.json` for `scope: "workspace"`), `gemini-cli` (`~/.gemini/settings.json`), `opencode` (`~/.config/opencode/opencode.json` using the custom `"mcp"` key), and `github-copilot` (`{repo}/.vscode/mcp.json` or `{workspace}/.vscode/mcp.json` using the `"servers"` key).
190
+ - **JSON (Merge Patch)**: `claude-code` (`~/.claude.json` for `scope: "global"`; `{repo}/.claude/mcp.json` for `scope: "repo"`; `{workspace}/.claude/mcp.json` for `scope: "workspace"`), `gemini-cli` (`~/.gemini/settings.json`), `antigravity` (`~/.gemini/antigravity/mcp_config.json` for `scope: "global"`), `opencode` (`~/.config/opencode/opencode.json` using the custom `"mcp"` key), and `github-copilot` (`{repo}/.vscode/mcp.json`, `{workspace}/.vscode/mcp.json`, or `{repo}/.devcontainer/devcontainer.json`).
178
191
  - **TOML (Patch)**: `codex` (`~/.codex/config.toml`).
179
- - **Markdown Frontmatter (Emit)**: `antigravity` (repo-local `.agents/rules/{name}.md` files).
192
+ - **Markdown Frontmatter (Emit)**: `antigravity` (`{repo}/.agents/rules/{name}.md` for `scope: "repo"`).
180
193
 
181
194
  For Markdown frontmatter targets, inception-engine now records patch-level provenance for the emitted frontmatter block. Deploy merges only the owned frontmatter keys, preserves unrelated frontmatter and Markdown body content, and `revert` removes only the engine-owned keys instead of deleting the whole file unless inception-engine originally created an otherwise-empty file.
182
195
 
@@ -248,6 +261,31 @@ Valid `approval_policy` values are `"auto"`, `"manual"`, `"suggest"`, and `"on-f
248
261
 
249
262
  Revert restores the previous config values using the undo patch recorded at deploy time.
250
263
 
264
+ Each **hooks** entry deploys lifecycle-binding configurations to an agent's execution hook surface:
265
+
266
+ - **name** - Unique identifier (same format as skill names)
267
+ - **agents** - Array of agent IDs to deploy this entry to
268
+ - **config** - Hook configuration payload; shape is validated per agent (see below)
269
+
270
+ Hook deployment is currently supported for `claude-code`. For `github-copilot`, it is marked as `planned`. Other agents emit a warning and are skipped.
271
+
272
+ For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the `hooks` key is accepted:
273
+
274
+ ```json
275
+ {
276
+ "name": "pre-exec-check",
277
+ "agents": ["claude-code"],
278
+ "config": {
279
+ "hooks": {
280
+ "pre_exec": "scripts/check-env.sh",
281
+ "post_exec": "scripts/notify.sh"
282
+ }
283
+ }
284
+ }
285
+ ```
286
+
287
+ Revert restores the previous config values using the undo patch recorded at deploy time.
288
+
251
289
  Each **agentDefinitions** entry deploys an agent-definition file to the agent-definition directory of each targeted agent. `scope: "repo"` is the default, and some agents also support `scope: "global"`:
252
290
 
253
291
  - **name** - Unique identifier (same format as skill names); used as the definition filename
@@ -18,6 +18,7 @@ export const AGENT_REGISTRY = [
18
18
  mcpConfig: "documented",
19
19
  agentRules: "documented",
20
20
  permissions: "documented",
21
+ hooks: "documented",
21
22
  agentDefinitions: "documented",
22
23
  },
23
24
  mcpSupport: {
@@ -68,6 +69,14 @@ export const AGENT_REGISTRY = [
68
69
  windows: ["{home}", ".claude", "settings.json"],
69
70
  },
70
71
  },
72
+ hooksSupport: {
73
+ status: "supported",
74
+ schemaLabel: "JSON hooks config",
75
+ path: {
76
+ posix: ["{home}", ".claude", "settings.json"],
77
+ windows: ["{home}", ".claude", "settings.json"],
78
+ },
79
+ },
71
80
  agentDefinitionsRepoSupport: {
72
81
  status: "supported",
73
82
  schemaLabel: "repo-local agent definition Markdown file",
@@ -103,6 +112,7 @@ export const AGENT_REGISTRY = [
103
112
  detectBinary: "documented",
104
113
  agentRules: "documented",
105
114
  permissions: "documented",
115
+ hooks: "provisional",
106
116
  },
107
117
  mcpSupport: {
108
118
  status: "supported",
@@ -136,6 +146,11 @@ export const AGENT_REGISTRY = [
136
146
  windows: ["{home}", ".codex", "config.toml"],
137
147
  },
138
148
  },
149
+ hooksSupport: {
150
+ status: "unsupported",
151
+ schemaLabel: "global hooks surface",
152
+ reason: "OpenAI Codex does not expose a documented lifecycle-hook config surface in config.toml",
153
+ },
139
154
  agentDefinitionsRepoSupport: {
140
155
  status: "unsupported",
141
156
  schemaLabel: "dedicated agent definition directory",
@@ -168,6 +183,7 @@ export const AGENT_REGISTRY = [
168
183
  detectBinary: "documented",
169
184
  mcpConfig: "documented",
170
185
  agentRules: "documented",
186
+ hooks: "provisional",
171
187
  agentDefinitions: "documented",
172
188
  },
173
189
  mcpSupport: {
@@ -202,6 +218,12 @@ export const AGENT_REGISTRY = [
202
218
  schemaLabel: "global permissions surface",
203
219
  reason: "Gemini CLI does not expose a documented global per-user permission or approval config surface",
204
220
  },
221
+ hooksSupport: {
222
+ status: "planned",
223
+ schemaLabel: "global hooks surface",
224
+ plannedSurface: "settings.json hooks field",
225
+ reason: "Gemini CLI execution hooks are emerging and not yet settled",
226
+ },
205
227
  agentDefinitionsSupport: {
206
228
  status: "supported",
207
229
  schemaLabel: "global agent definition Markdown file",
@@ -263,6 +285,14 @@ export const AGENT_REGISTRY = [
263
285
  agentDefinitions: "documented",
264
286
  },
265
287
  mcpSupport: {
288
+ status: "supported",
289
+ schemaLabel: "global mcp_config.json",
290
+ path: {
291
+ posix: ["{home}", ".gemini", "antigravity", "mcp_config.json"],
292
+ windows: ["{home}", ".gemini", "antigravity", "mcp_config.json"],
293
+ },
294
+ },
295
+ mcpRepoSupport: {
266
296
  status: "supported",
267
297
  schemaLabel: "repo-local frontmatter-emit MCP rules",
268
298
  path: {
@@ -296,6 +326,11 @@ export const AGENT_REGISTRY = [
296
326
  schemaLabel: "global permissions surface",
297
327
  reason: "Antigravity does not expose a documented global per-user permission or approval config surface",
298
328
  },
329
+ hooksSupport: {
330
+ status: "unsupported",
331
+ schemaLabel: "global hooks surface",
332
+ reason: "Antigravity does not expose a documented global per-user hook config surface",
333
+ },
299
334
  agentDefinitionsRepoSupport: {
300
335
  status: "supported",
301
336
  schemaLabel: "repo-local agent definition Markdown file",
@@ -330,6 +365,7 @@ export const AGENT_REGISTRY = [
330
365
  agentRules: "documented",
331
366
  agentDefinitions: "documented",
332
367
  permissions: "documented",
368
+ hooks: "provisional",
333
369
  },
334
370
  mcpSupport: {
335
371
  status: "supported",
@@ -364,6 +400,11 @@ export const AGENT_REGISTRY = [
364
400
  windows: ["{appdata}", "opencode", "opencode.json"],
365
401
  },
366
402
  },
403
+ hooksSupport: {
404
+ status: "unsupported",
405
+ schemaLabel: "global hooks surface",
406
+ reason: "OpenCode does not expose a documented lifecycle-hook surface",
407
+ },
367
408
  agentDefinitionsSupport: {
368
409
  status: "supported",
369
410
  schemaLabel: "global agent definition Markdown file",
@@ -404,6 +445,7 @@ export const AGENT_REGISTRY = [
404
445
  detectBinary: "documented",
405
446
  agentDefinitions: "documented",
406
447
  mcpConfig: "documented",
448
+ hooks: "provisional",
407
449
  },
408
450
  mcpSupport: {
409
451
  status: "unsupported",
@@ -473,16 +515,25 @@ export const AGENT_REGISTRY = [
473
515
  },
474
516
  },
475
517
  mcpDevcontainerSupport: {
476
- status: "planned",
518
+ status: "supported",
477
519
  schemaLabel: "devcontainer.json MCP surface",
478
- plannedSurface: "devcontainer.json customizations.vscode.mcp.servers",
479
- reason: 'devcontainer.json MCP support is planned — use scope: "repo" or scope: "workspace" to target .vscode/mcp.json in the meantime',
520
+ mcpPatchKey: "customizations.vscode.mcp.servers",
521
+ path: {
522
+ posix: ["{repo}", ".devcontainer", "devcontainer.json"],
523
+ windows: ["{repo}", ".devcontainer", "devcontainer.json"],
524
+ },
480
525
  },
481
526
  permissionsSupport: {
482
527
  status: "unsupported",
483
528
  schemaLabel: "global permissions surface",
484
529
  reason: "GitHub Copilot permissions are managed via organization policy, not a deployable per-user config surface",
485
530
  },
531
+ hooksSupport: {
532
+ status: "planned",
533
+ schemaLabel: "binary execution hooks",
534
+ plannedSurface: "agent frontmatter pre_exec/post_exec hooks",
535
+ reason: "GitHub Copilot binary hook support is planned and requires further validation of the stdin/stdout contract",
536
+ },
486
537
  agentDefinitionsRepoSupport: {
487
538
  status: "supported",
488
539
  schemaLabel: "repo-local agent definition Markdown file",
@@ -504,14 +555,7 @@ export const AGENT_REGISTRY = [
504
555
  },
505
556
  },
506
557
  policyNote: "Organization policies may override locally deployed configuration. Verify with your GitHub org admin if deployed skills or rules are not active.",
507
- unsupportedSurfaces: [
508
- {
509
- status: "planned",
510
- schemaLabel: "devcontainer.json MCP surface",
511
- plannedSurface: "devcontainer.json customizations.vscode.mcp.servers",
512
- reason: 'devcontainer.json MCP support is planned — use scope: "repo" or scope: "workspace" to target .vscode/mcp.json in the meantime',
513
- },
514
- ],
558
+ unsupportedSurfaces: [],
515
559
  instructionFrontmatterRequired: true,
516
560
  enterprisePolicyDetection: true,
517
561
  },
@@ -0,0 +1,8 @@
1
+ import type { HookEntry } from "../../schemas/manifest.ts";
2
+ import type { AgentId, ConfigPatchDeployAction, ConfigPatchRevertAction, PlanWarning, TomlPatchDeployAction, TomlPatchRevertAction } from "../../types.ts";
3
+ export interface HooksAdapterResult {
4
+ actions: Array<ConfigPatchDeployAction | TomlPatchDeployAction>;
5
+ warnings: PlanWarning[];
6
+ }
7
+ export declare function compileHookActions(entry: HookEntry, detectedAgents: AgentId[], home: string): HooksAdapterResult;
8
+ export declare function compileHookReverts(entry: HookEntry, agentFilter: AgentId[] | null, home: string): Array<ConfigPatchRevertAction | TomlPatchRevertAction>;
@@ -0,0 +1,86 @@
1
+ import path from "node:path";
2
+ import { planCapabilityForDeploy, resolveCapabilitySurface, } from "../capabilities.js";
3
+ import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
4
+ import { validateHookConfigShape } from "../validation.js";
5
+ function isTomlTarget(targetPath) {
6
+ return path.extname(targetPath).toLowerCase() === ".toml";
7
+ }
8
+ export function compileHookActions(entry, detectedAgents, home) {
9
+ const actions = [];
10
+ const warnings = [];
11
+ const platform = getPlatformKey();
12
+ for (const agentId of entry.agents) {
13
+ if (!detectedAgents.includes(agentId))
14
+ continue;
15
+ const plan = planCapabilityForDeploy({
16
+ agentId,
17
+ capability: "hooks",
18
+ entryName: entry.name,
19
+ targetAgentIds: entry.agents,
20
+ });
21
+ if (plan.outcome === "warn") {
22
+ warnings.push(plan.warning);
23
+ continue;
24
+ }
25
+ if (plan.outcome === "native" || plan.outcome === "redundant")
26
+ continue;
27
+ const support = resolveCapabilitySurface(agentId, "hooks").support;
28
+ if (!support)
29
+ continue;
30
+ validateHookConfigShape(entry.config, entry.name, agentId);
31
+ const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home);
32
+ const resolvedTarget = path.resolve(rawTarget);
33
+ const confidence = plan.confidence ?? "provisional";
34
+ if (isTomlTarget(resolvedTarget)) {
35
+ actions.push({
36
+ kind: "toml-patch",
37
+ skill: entry.name,
38
+ agent: agentId,
39
+ target: resolvedTarget,
40
+ config: entry.config,
41
+ confidence,
42
+ });
43
+ }
44
+ else {
45
+ actions.push({
46
+ kind: "config-patch",
47
+ skill: entry.name,
48
+ agent: agentId,
49
+ target: resolvedTarget,
50
+ patch: entry.config,
51
+ confidence,
52
+ });
53
+ }
54
+ }
55
+ return { actions, warnings };
56
+ }
57
+ export function compileHookReverts(entry, agentFilter, home) {
58
+ const actions = [];
59
+ const platform = getPlatformKey();
60
+ for (const agentId of entry.agents) {
61
+ if (agentFilter && !agentFilter.includes(agentId))
62
+ continue;
63
+ const support = resolveCapabilitySurface(agentId, "hooks").support;
64
+ if (!support)
65
+ continue;
66
+ const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home);
67
+ const target = path.resolve(rawTarget);
68
+ if (isTomlTarget(target)) {
69
+ actions.push({
70
+ kind: "toml-patch",
71
+ skill: entry.name,
72
+ agent: agentId,
73
+ target,
74
+ });
75
+ }
76
+ else {
77
+ actions.push({
78
+ kind: "config-patch",
79
+ skill: entry.name,
80
+ agent: agentId,
81
+ target,
82
+ });
83
+ }
84
+ }
85
+ return actions;
86
+ }
@@ -1,13 +1,14 @@
1
- import type { AgentDefinitionEntry, AgentRuleEntry, McpServerEntry, PermissionsEntry } from "../../schemas/manifest.ts";
1
+ import type { AgentDefinitionEntry, AgentRuleEntry, HookEntry, McpServerEntry, PermissionsEntry } from "../../schemas/manifest.ts";
2
2
  import type { AgentId, ConfigPatchDeployAction, FileWriteDeployAction, FrontmatterEmitDeployAction, PlanWarning, TomlPatchDeployAction } from "../../types.ts";
3
3
  import { compileAgentDefinitionReverts } from "./agent-definitions.ts";
4
+ import { compileHookReverts } from "./hooks.ts";
4
5
  import { compileMcpServerReverts } from "./mcp.ts";
5
6
  import { compilePermissionsReverts } from "./permissions.ts";
6
7
  import { compileAgentRuleReverts } from "./rules.ts";
7
- export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
8
+ export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, };
8
9
  export type AdapterAction = ConfigPatchDeployAction | FileWriteDeployAction | TomlPatchDeployAction | FrontmatterEmitDeployAction;
9
10
  export interface AdapterResult {
10
11
  actions: AdapterAction[];
11
12
  warnings: PlanWarning[];
12
13
  }
13
- export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], permissions: PermissionsEntry[], sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string, agentDefinitions?: AgentDefinitionEntry[], workspace?: string): Promise<AdapterResult>;
14
+ export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], permissions: PermissionsEntry[], sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string, agentDefinitions?: AgentDefinitionEntry[], workspace?: string, hooks?: HookEntry[]): Promise<AdapterResult>;
@@ -1,8 +1,9 @@
1
1
  import { compileAgentDefinitionActions, compileAgentDefinitionReverts, } from "./agent-definitions.js";
2
+ import { compileHookActions, compileHookReverts } from "./hooks.js";
2
3
  import { compileMcpServerActions, compileMcpServerReverts } from "./mcp.js";
3
4
  import { compilePermissionsActions, compilePermissionsReverts, } from "./permissions.js";
4
5
  import { compileAgentRuleActions, compileAgentRuleReverts } from "./rules.js";
5
- export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
6
+ export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, };
6
7
  async function compileAll(entries, fn) {
7
8
  const results = await Promise.all(entries.map(fn));
8
9
  return {
@@ -10,16 +11,17 @@ async function compileAll(entries, fn) {
10
11
  warnings: results.flatMap((r) => r.warnings),
11
12
  };
12
13
  }
13
- export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, agentDefinitions, workspace) {
14
+ export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, agentDefinitions, workspace, hooks = []) {
14
15
  const actions = [];
15
16
  const warnings = [];
16
- const [mcp, rules, perms, defs] = await Promise.all([
17
+ const [mcp, rules, perms, defs, hookResults] = await Promise.all([
17
18
  compileAll(mcpServers, (entry) => compileMcpServerActions(entry, detectedAgents, home, repo, workspace)),
18
19
  compileAll(agentRules, (entry) => compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace)),
19
20
  compileAll(permissions, (entry) => compilePermissionsActions(entry, detectedAgents, home)),
20
21
  compileAll(agentDefinitions ?? [], (entry) => compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace)),
22
+ compileAll(hooks, (entry) => compileHookActions(entry, detectedAgents, home)),
21
23
  ]);
22
- actions.push(...mcp.actions, ...rules.actions, ...perms.actions, ...defs.actions);
23
- warnings.push(...mcp.warnings, ...rules.warnings, ...perms.warnings, ...defs.warnings);
24
+ actions.push(...mcp.actions, ...rules.actions, ...perms.actions, ...defs.actions, ...hookResults.actions);
25
+ warnings.push(...mcp.warnings, ...rules.warnings, ...perms.warnings, ...defs.warnings, ...hookResults.warnings);
24
26
  return { actions, warnings };
25
27
  }
@@ -16,6 +16,20 @@ function isMarkdownTarget(targetPath) {
16
16
  const ext = path.extname(targetPath).toLowerCase();
17
17
  return ext === ".md" || ext === ".markdown";
18
18
  }
19
+ /**
20
+ * Builds a nested object from a dot-separated key path.
21
+ * e.g. ("a.b", {c: 1}) -> {a: {b: {c: 1}}}
22
+ */
23
+ function buildNestedPatch(keyPath, leafValue) {
24
+ const parts = keyPath.split(".");
25
+ let current = {
26
+ [parts[parts.length - 1]]: leafValue,
27
+ };
28
+ for (let i = parts.length - 2; i >= 0; i--) {
29
+ current = { [parts[i]]: current };
30
+ }
31
+ return current;
32
+ }
19
33
  function buildMcpDeployAction(entry, agentId, resolvedTarget, confidence, mcpPatchKey) {
20
34
  if (isMarkdownTarget(resolvedTarget)) {
21
35
  return {
@@ -42,7 +56,7 @@ function buildMcpDeployAction(entry, agentId, resolvedTarget, confidence, mcpPat
42
56
  skill: entry.name,
43
57
  agent: agentId,
44
58
  target: resolvedTarget,
45
- patch: { [mcpPatchKey]: { [entry.name]: entry.config } },
59
+ patch: buildNestedPatch(mcpPatchKey, { [entry.name]: entry.config }),
46
60
  confidence,
47
61
  };
48
62
  }
@@ -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";
3
3
  export interface ResolvedCapabilitySurface {
4
4
  agentId: AgentId;
5
5
  capability: CapabilityKind;
@@ -19,6 +19,11 @@ function resolveMcpSupport(agentId, scope) {
19
19
  if (scope === "workspace") {
20
20
  return (agent?.mcpWorkspaceSupport ?? agent?.mcpRepoSupport ?? agent?.mcpSupport);
21
21
  }
22
+ if (scope === "devcontainer") {
23
+ return (agent?.mcpDevcontainerSupport ??
24
+ agent?.mcpRepoSupport ??
25
+ agent?.mcpSupport);
26
+ }
22
27
  return agent?.mcpSupport;
23
28
  }
24
29
  function resolveAgentDefinitionsSupport(agentId, scope) {
@@ -45,6 +50,8 @@ function capabilityLabel(capability) {
45
50
  return "permissions";
46
51
  case "agentDefinitions":
47
52
  return "agentDefinitions";
53
+ case "hooks":
54
+ return "hooks";
48
55
  }
49
56
  }
50
57
  function capabilitySurfaceLabel(capability) {
@@ -59,6 +66,8 @@ function capabilitySurfaceLabel(capability) {
59
66
  return "permissions";
60
67
  case "agentDefinitions":
61
68
  return "agent-definitions";
69
+ case "hooks":
70
+ return "hooks";
62
71
  }
63
72
  }
64
73
  function resolveSkillsCapability(agentId) {
@@ -110,9 +119,15 @@ function getSurfaceRecordAndConfidence(agentId, capability, scope) {
110
119
  confidence: agent.provenance.permissions,
111
120
  };
112
121
  }
122
+ if (capability === "agentDefinitions") {
123
+ return {
124
+ supportRecord: resolveAgentDefinitionsSupport(agentId, scope),
125
+ confidence: agent.provenance.agentDefinitions,
126
+ };
127
+ }
113
128
  return {
114
- supportRecord: resolveAgentDefinitionsSupport(agentId, scope),
115
- confidence: agent.provenance.agentDefinitions,
129
+ supportRecord: agent.hooksSupport,
130
+ confidence: agent.provenance.hooks,
116
131
  };
117
132
  }
118
133
  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 ?? []);
284
284
  actions.push(...adapterResult.actions);
285
285
  const warnings = [
286
286
  ...skillPlan.warnings,
@@ -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;
@@ -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") {
@@ -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,6 +98,9 @@ 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;
@@ -137,13 +137,13 @@ describe("compileMcpServerActions", () => {
137
137
  servers: { "my-mcp": { command: "s" } },
138
138
  });
139
139
  });
140
- it("returns a frontmatter-emit action for antigravity MCP", () => {
140
+ it("returns a frontmatter-emit action for antigravity MCP with scope: repo", () => {
141
141
  const home = "/home/test";
142
142
  const { actions, warnings } = compileMcpServerActions({
143
143
  name: "my-mcp",
144
144
  agents: ["antigravity"],
145
145
  config: { command: "s" },
146
- scope: "global",
146
+ scope: "repo",
147
147
  }, ["antigravity"], home, "/repo/test");
148
148
  assert.equal(actions.length, 1);
149
149
  assert.equal(warnings.length, 0);
@@ -151,6 +151,20 @@ describe("compileMcpServerActions", () => {
151
151
  assert.equal(actions[0]?.agent, "antigravity");
152
152
  assertPathEndsWith(actions[0]?.target ?? "", ".agents/rules/my-mcp.md", `expected target to end with .agents/rules/my-mcp.md, got ${actions[0]?.target}`);
153
153
  });
154
+ it("returns a config-patch action for antigravity MCP with scope: global", () => {
155
+ const home = "/home/test";
156
+ const { actions, warnings } = compileMcpServerActions({
157
+ name: "my-mcp",
158
+ agents: ["antigravity"],
159
+ config: { command: "s" },
160
+ scope: "global",
161
+ }, ["antigravity"], home);
162
+ assert.equal(actions.length, 1);
163
+ assert.equal(warnings.length, 0);
164
+ assert.equal(actions[0]?.kind, "config-patch");
165
+ assert.equal(actions[0]?.agent, "antigravity");
166
+ assertPathEndsWith(actions[0]?.target ?? "", ".gemini/antigravity/mcp_config.json", `expected target to end with .gemini/antigravity/mcp_config.json, got ${actions[0]?.target}`);
167
+ });
154
168
  it("throws when a supported MCP target is missing both command and url", () => {
155
169
  assert.throws(() => compileMcpServerActions({
156
170
  name: "my-mcp",
@@ -30,6 +30,7 @@ const testManifest = {
30
30
  agentRules: [],
31
31
  permissions: [],
32
32
  agentDefinitions: [],
33
+ hooks: [],
33
34
  };
34
35
  describe("planDeploy", () => {
35
36
  it("creates actions for detected agents only", async () => {
@@ -647,8 +648,9 @@ describe("planDeploy", () => {
647
648
  assert.ok(geminiAction, "expected a gemini-cli action");
648
649
  assert.match(geminiAction.target, /[\\/]\.gemini[\\/]settings\.json$/);
649
650
  assert.ok(antigravityAction, "expected an antigravity action");
650
- // Antigravity's target for mcpServer is .agents/rules/{name}.md
651
- assert.match(antigravityAction.target, /[\\/]\.agents[\\/]rules[\\/]my-mcp\.md$/);
651
+ // Antigravity's target for global mcpServer is .gemini/antigravity/mcp_config.json
652
+ assert.equal(antigravityAction.kind, "config-patch");
653
+ assert.match(antigravityAction.target, /[\\/]\.gemini[\\/]antigravity[\\/]mcp_config\.json$/);
652
654
  }
653
655
  finally {
654
656
  await rm(sourceDir, { recursive: true });
@@ -802,7 +804,7 @@ describe("planDeploy", () => {
802
804
  name: "my-tool",
803
805
  agents: ["antigravity"],
804
806
  config: { command: "npx", args: ["-y", "my-tool"] },
805
- scope: "global",
807
+ scope: "repo",
806
808
  },
807
809
  ],
808
810
  agentRules: [],
@@ -125,24 +125,22 @@ describe("runPreflight", () => {
125
125
  assert.equal(implementationOnlyWarning, undefined, `expected no implementation-only warning, got: ${implementationOnlyWarning?.message}`);
126
126
  });
127
127
  });
128
- describe("github-copilot planned surfaces", () => {
129
- it("emits a planned surface info notice for devcontainer MCP when Copilot is detected and MCP is targeted", async () => {
128
+ describe("github-copilot devcontainer support", () => {
129
+ it("emits no capability warning for devcontainer MCP when Copilot is detected and devcontainer scope is targeted", async () => {
130
130
  const manifestWithMcp = {
131
131
  ...emptyManifest,
132
132
  mcpServers: [
133
133
  {
134
134
  name: "test-mcp",
135
- scope: "repo",
135
+ scope: "devcontainer",
136
136
  agents: ["github-copilot"],
137
137
  config: { command: "node", args: ["server.js"] },
138
138
  },
139
139
  ],
140
140
  };
141
141
  const warnings = await runPreflight(baseOptions, manifestWithMcp, "/home/test", ["github-copilot"]);
142
- const plannedWarning = warnings.find((w) => w.kind === "info" &&
143
- /devcontainer\.json MCP support is planned/.test(w.message));
144
- assert.ok(plannedWarning, "expected a planned surface warning for Copilot devcontainer when MCP is targeted");
145
- assert.match(plannedWarning.message, /planned/);
142
+ const capabilityWarning = warnings.find((w) => w.kind === "info" && /mcpServers/.test(w.message));
143
+ assert.equal(capabilityWarning, undefined, `expected no MCP capability warning for devcontainer scope, got: ${capabilityWarning?.message}`);
146
144
  });
147
145
  });
148
146
  describe("instruction precedence warnings", () => {
@@ -46,6 +46,7 @@ describe("planRevertAll", () => {
46
46
  agentRules: [],
47
47
  permissions: [],
48
48
  agentDefinitions: [],
49
+ hooks: [],
49
50
  };
50
51
  const actions = planRevertAll(multiAgentManifest, "/home/test");
51
52
  assert.equal(actions.length, 3);
@@ -75,6 +76,7 @@ describe("planRevertAll", () => {
75
76
  agentRules: [],
76
77
  permissions: [],
77
78
  agentDefinitions: [],
79
+ hooks: [],
78
80
  };
79
81
  const actions = planRevertAll(manifest, "/home/test");
80
82
  assert.equal(actions.length, 5);
@@ -712,6 +714,7 @@ describe("planRevert — mcpServers and agentRules", () => {
712
714
  agentRules: [],
713
715
  permissions: [],
714
716
  agentDefinitions: [],
717
+ hooks: [],
715
718
  };
716
719
  const actions = planRevert(manifest, ["claude-code"], home);
717
720
  assert.equal(actions.length, 1);
@@ -735,6 +738,7 @@ describe("planRevert — mcpServers and agentRules", () => {
735
738
  agentRules: [],
736
739
  permissions: [],
737
740
  agentDefinitions: [],
741
+ hooks: [],
738
742
  };
739
743
  const actions = planRevert(manifest, ["codex"], home);
740
744
  assert.equal(actions.length, 0);
@@ -755,6 +759,7 @@ describe("planRevert — mcpServers and agentRules", () => {
755
759
  ],
756
760
  permissions: [],
757
761
  agentDefinitions: [],
762
+ hooks: [],
758
763
  };
759
764
  const actions = planRevert(manifest, ["claude-code"], home);
760
765
  assert.equal(actions.length, 1);
@@ -778,6 +783,7 @@ describe("planRevert — mcpServers and agentRules", () => {
778
783
  ],
779
784
  permissions: [],
780
785
  agentDefinitions: [],
786
+ hooks: [],
781
787
  };
782
788
  const actions = planRevert(manifest, ["codex"], home);
783
789
  assert.equal(actions.length, 0);
@@ -801,6 +807,7 @@ describe("planRevertAll — mcpServers and agentRules", () => {
801
807
  agentRules: [],
802
808
  permissions: [],
803
809
  agentDefinitions: [],
810
+ hooks: [],
804
811
  };
805
812
  const actions = planRevertAll(manifest, home);
806
813
  assert.equal(actions.length, 2);
@@ -824,6 +831,7 @@ describe("planRevertAll — mcpServers and agentRules", () => {
824
831
  ],
825
832
  permissions: [],
826
833
  agentDefinitions: [],
834
+ hooks: [],
827
835
  };
828
836
  const actions = planRevertAll(manifest, home);
829
837
  assert.equal(actions.length, 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuznai/inception-engine",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Deploy AI agent skills from a git repo to user home directories",
5
5
  "license": "MIT",
6
6
  "author": "Damian Piątkowski",