@kuznai/inception-engine 0.22.0 → 0.24.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.
Files changed (43) hide show
  1. package/README.md +50 -9
  2. package/dist/src/config/agents.js +39 -0
  3. package/dist/src/core/adapters/execution-config.d.ts +14 -0
  4. package/dist/src/core/adapters/execution-config.js +60 -0
  5. package/dist/src/core/adapters/index.d.ts +4 -3
  6. package/dist/src/core/adapters/index.js +7 -5
  7. package/dist/src/core/adapters/rules.js +50 -19
  8. package/dist/src/core/capabilities.d.ts +1 -1
  9. package/dist/src/core/capabilities.js +18 -2
  10. package/dist/src/core/deploy.js +66 -2
  11. package/dist/src/core/init.js +69 -13
  12. package/dist/src/core/ownership.d.ts +1 -0
  13. package/dist/src/core/ownership.js +34 -4
  14. package/dist/src/core/preflight.js +28 -0
  15. package/dist/src/core/revert.js +86 -18
  16. package/dist/src/core/validation.d.ts +1 -1
  17. package/dist/src/core/validation.js +56 -3
  18. package/dist/src/schemas/manifest.d.ts +31 -0
  19. package/dist/src/schemas/manifest.js +39 -3
  20. package/dist/src/types.d.ts +6 -2
  21. package/dist/test/helpers/path.d.ts +2 -2
  22. package/dist/test/helpers/path.js +3 -3
  23. package/dist/test/os/posix/deploy.test.js +395 -0
  24. package/dist/test/os/posix/revert.test.js +188 -0
  25. package/dist/test/os/windows/deploy.test.d.ts +1 -0
  26. package/dist/test/os/windows/deploy.test.js +238 -0
  27. package/dist/test/unit/adapters.test.js +402 -2
  28. package/dist/test/unit/capabilities.test.js +44 -1
  29. package/dist/test/unit/cli.test.js +162 -0
  30. package/dist/test/unit/deploy.test.js +88 -647
  31. package/dist/test/unit/formatters.test.d.ts +1 -0
  32. package/dist/test/unit/formatters.test.js +74 -0
  33. package/dist/test/unit/init-fixture.test.js +85 -1
  34. package/dist/test/unit/manifest.test.js +72 -0
  35. package/dist/test/unit/ownership.test.js +61 -4
  36. package/dist/test/unit/preflight.test.js +145 -0
  37. package/dist/test/unit/resolve.test.js +67 -1
  38. package/dist/test/unit/revert.test.js +81 -182
  39. package/package.json +1 -1
  40. package/dist/test/os/windows/agentRules-integration.test.js +0 -245
  41. package/dist/test/os/windows/revert-integration.test.js +0 -72
  42. /package/dist/test/os/{windows/agentRules-integration.test.d.ts → posix/deploy.test.d.ts} +0 -0
  43. /package/dist/test/os/{windows/revert-integration.test.d.ts → posix/revert.test.d.ts} +0 -0
package/README.md CHANGED
@@ -51,9 +51,10 @@ Before executing, the deploy command runs preflight analysis on instruction file
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
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
- | 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 |
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` (shared-via) and also supports native `scope: "copilot-repo"` (`{repo}/.github/copilot-instructions.md`) and `scope: "copilot-scoped"` (`{repo}/.github/instructions/{name}.instructions.md`) | 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
56
  | Execution Hooks | claude-code (`~/.claude/settings.json`), github-copilot (`planned`); other agents are warned and skipped | claude-code |
57
+ | Execution / Safety Config | gemini-cli (`~/.gemini/settings.json`); other agents are warned and skipped | gemini-cli |
57
58
  | 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 |
58
59
  | `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 |
59
60
  | 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 |
@@ -143,14 +144,35 @@ Create an `inception.json` file at the root of your skills directory:
143
144
  "agents": ["claude-code"],
144
145
  "config": {
145
146
  "hooks": {
146
- "pre_exec": "scripts/check-env.sh"
147
+ "PreToolUse": [
148
+ {
149
+ "matcher": "Bash",
150
+ "hooks": [{ "type": "command", "command": "scripts/check-env.sh" }]
151
+ }
152
+ ],
153
+ "Stop": [
154
+ {
155
+ "hooks": [{ "type": "command", "command": "scripts/notify.sh" }]
156
+ }
157
+ ]
147
158
  }
148
159
  }
149
160
  }
161
+ ],
162
+ "executionConfigs": [
163
+ {
164
+ "name": "gemini-safety",
165
+ "agents": ["gemini-cli"],
166
+ "config": {
167
+ "safeMode": true
168
+ }
169
+ }
150
170
  ]
151
171
  }
152
172
  ```
153
173
 
174
+ `files` and `configs` are intentionally narrower than the raw target-template syntax might suggest. Arbitrary project-local targets under `{repo}` and `{workspace}` are allowed, but user-profile targets under `{home}`, `{appdata}`, and `{xdg_config}` must resolve to documented agent-owned global surfaces such as `~/.claude/settings.json` or `~/.codex/AGENTS.md`. Manifests cannot target inception-engine's own `~/.inception-engine/` state directory.
175
+
154
176
  Each **skill** entry has:
155
177
 
156
178
  - **name** - Unique identifier using letters, digits, dots, underscores, or hyphens; must not start with a dot
@@ -202,10 +224,12 @@ Each **agentRules** entry deploys a Markdown instruction file to an agent's supp
202
224
  - **name** - Unique identifier (same format as skill names)
203
225
  - **path** - Relative path to the source Markdown file within the repo; supported rules adapters require a `.md` or `.markdown` source path
204
226
  - **agents** - Array of agent IDs to deploy this file to
205
- - **scope** - `"global"` (default), `"repo"`, or `"workspace"`. Controls which instruction surface is targeted:
227
+ - **scope** - `"global"` (default), `"repo"`, `"workspace"`, `"copilot-repo"`, or `"copilot-scoped"`. Controls which instruction surface is targeted:
206
228
  - `"global"` — deploys to the agent's home-directory instruction file when that surface is supported (e.g., `~/.claude/CLAUDE.md` for `claude-code`)
207
229
  - `"repo"` — deploys to the project-root instruction file inside the deployed repository when that surface is supported (e.g., `{repo}/CLAUDE.md` for `claude-code`)
208
230
  - `"workspace"` — deploys to the workspace-root instruction file when an agent exposes one (e.g., `{workspace}/CLAUDE.md` for `claude-code`); when unsupported, deployment is skipped with a warning
231
+ - `"copilot-repo"` — deploys to GitHub Copilot's native repo-level instruction file at `{repo}/.github/copilot-instructions.md` (`github-copilot` only; other agents are warned and skipped)
232
+ - `"copilot-scoped"` — deploys to `{repo}/.github/instructions/{name}.instructions.md` where `{name}` is the manifest entry name (`github-copilot` only; other agents are warned and skipped)
209
233
 
210
234
  Instruction rule deployment is supported for implemented global, repo, and workspace surfaces. The target path depends on the agent and the `scope`:
211
235
 
@@ -218,7 +242,13 @@ Instruction rule deployment is supported for implemented global, repo, and works
218
242
  | `opencode` | `~/.config/opencode/AGENTS.md` | `{repo}/AGENTS.md` | unsupported; warns and skips |
219
243
  | `github-copilot` | unsupported / Claude-first | deploy via `claude-code` | unsupported; deploy via `claude-code` with `scope: "workspace"` |
220
244
 
221
- For `antigravity`, `agentRules` now targets the shared GEMINI.md surface (`~/.gemini/GEMINI.md` for `global`, `{repo}/GEMINI.md` for `repo`) — the same paths used by `gemini-cli`. When both agents appear in the same entry, deduplication ensures only one write action is emitted. `workspace` scope is not supported and is skipped with a warning. For `github-copilot`, no separate deployment is needed — target it via the `claude-code` agentRules entry and it reaches Copilot automatically. Revert removes the deployed rules file.
245
+ GitHub Copilot also exposes two native instruction surfaces via dedicated scope values:
246
+
247
+ | Agent | `scope: "copilot-repo"` | `scope: "copilot-scoped"` |
248
+ |---|---|---|
249
+ | `github-copilot` | `{repo}/.github/copilot-instructions.md` | `{repo}/.github/instructions/{name}.instructions.md` |
250
+
251
+ For `antigravity`, `agentRules` now targets the shared GEMINI.md surface (`~/.gemini/GEMINI.md` for `global`, `{repo}/GEMINI.md` for `repo`) — the same paths used by `gemini-cli`. When both agents appear in the same entry, deduplication ensures only one write action is emitted. `workspace` scope is not supported and is skipped with a warning. For `github-copilot`, the Claude-first path (targeting via `claude-code` agentRules) remains the recommended approach for shared rules; the `copilot-repo` and `copilot-scoped` scopes are for Copilot-specific instructions that should not be shared with other agents. When both are present, preflight emits a precedence warning. Revert removes the deployed rules file.
222
252
 
223
253
  Each **permissions** entry deploys execution and safety-oriented configuration to an agent's permission or approval surface:
224
254
 
@@ -269,7 +299,7 @@ Each **hooks** entry deploys lifecycle-binding configurations to an agent's exec
269
299
 
270
300
  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
301
 
272
- For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the `hooks` key is accepted:
302
+ For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the `hooks` key is accepted. Event names follow Claude Code's settings.json hooks surface (e.g. `PreToolUse`, `PostToolUse`, `Notification`, `Stop`, `SubagentStop`). Each event maps to an array of hook matchers; each matcher has an optional `matcher` string and a required `hooks` array of `{ type: "command", command: string }` objects:
273
303
 
274
304
  ```json
275
305
  {
@@ -277,8 +307,17 @@ For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the
277
307
  "agents": ["claude-code"],
278
308
  "config": {
279
309
  "hooks": {
280
- "pre_exec": "scripts/check-env.sh",
281
- "post_exec": "scripts/notify.sh"
310
+ "PreToolUse": [
311
+ {
312
+ "matcher": "Bash",
313
+ "hooks": [{ "type": "command", "command": "scripts/check-env.sh" }]
314
+ }
315
+ ],
316
+ "Stop": [
317
+ {
318
+ "hooks": [{ "type": "command", "command": "scripts/notify.sh" }]
319
+ }
320
+ ]
282
321
  }
283
322
  }
284
323
  }
@@ -341,7 +380,7 @@ Current `init` behavior:
341
380
  - Applies either the `--agents` list or all currently known agent IDs
342
381
  - Refuses to overwrite an existing `inception.json` unless `--force` is provided
343
382
  - Supports `--plan` so you can inspect the generated manifest before writing it
344
- - Discovers agent-rules Markdown files in the root and conventional subdirectories (`rules/`, `instructions/`, `.github/`), mapping them to agents using Claude-first portability conventions: `copilot-instructions.md` maps to `claude-code` (Copilot reads `CLAUDE.md` natively), and the fallback for unrecognized files excludes unsupported agents plus shared-surface riders that should default to their primary deploy target
383
+ - Discovers agent-rules Markdown files in the root and conventional subdirectories (`rules/`, `instructions/`, `.github/`), mapping them to agents using Claude-first portability conventions: `copilot-instructions.md` found outside `.github/` maps to `claude-code` (Copilot reads `CLAUDE.md` natively), `.github/copilot-instructions.md` maps to `github-copilot` with `scope: "copilot-repo"`, and `*.instructions.md` files in `.github/instructions/` map to `github-copilot` with `scope: "copilot-scoped"`; the fallback for unrecognized files excludes unsupported agents plus shared-surface riders that should default to their primary deploy target
345
384
  - Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
346
385
  - Reads `files-manifest.json` from the repo root (if present) and generates `files` entries; invalid entries are warned and skipped
347
386
  - Reads `configs-manifest.json` from the repo root (if present) and generates `configs` entries; invalid entries are warned and skipped
@@ -505,9 +544,11 @@ inception-engine maintains a centralized deployment registry at `~/.inception-en
505
544
 
506
545
  - **Strong binding**: Each registry entry binds a specific target path to its skill, agent, and action kind. For `skill-dir` and `file-write`, ownership checks also require the recorded `source` to match before an existing target is treated as managed. For `config-patch` and `frontmatter-emit`, overwrite protection is keyed by target path, kind, skill, and agent; the stored `patch` and `undoPatch` are used for patch-level revert bookkeeping rather than deploy-time identity checks.
507
546
 
547
+ - **Reserved state**: Manifest-driven deploy and revert actions are not allowed to touch `~/.inception-engine/`, and the registry loader refuses symlinked registry files or directories.
548
+
508
549
  - **Atomic redeploy**: When overwriting an existing managed `skill-dir` target, the engine renames the old target to a backup, creates the new deployment, and only removes the backup on success. If the new deployment fails, the backup is restored. `file-write` and `config-patch` deployments write directly to the target without this backup/rollback model.
509
550
 
510
- - **Cross-platform**: The registry uses the same resolved home directory as the rest of the tool, including sudo scenarios on POSIX and elevated PowerShell on Windows.
551
+ - **Cross-platform**: The registry uses the same resolved home directory as the rest of the tool, including sudo scenarios on POSIX and elevated PowerShell on Windows. On POSIX, inception-engine keeps the state directory at `0700` and the registry file at `0600`.
511
552
 
512
553
  ## Running with Privilege Escalation
513
554
 
@@ -185,6 +185,7 @@ export const AGENT_REGISTRY = [
185
185
  agentRules: "documented",
186
186
  hooks: "provisional",
187
187
  agentDefinitions: "documented",
188
+ executionConfig: "provisional",
188
189
  },
189
190
  mcpSupport: {
190
191
  status: "supported",
@@ -256,6 +257,14 @@ export const AGENT_REGISTRY = [
256
257
  windows: ["{repo}", ".gemini", "agents", "{name}.toml"],
257
258
  },
258
259
  },
260
+ executionConfigSupport: {
261
+ status: "supported",
262
+ schemaLabel: "Settings execution config",
263
+ path: {
264
+ posix: ["{home}", ".gemini", "settings.json"],
265
+ windows: ["{home}", ".gemini", "settings.json"],
266
+ },
267
+ },
259
268
  agentRulesWorkspaceSupport: {
260
269
  status: "supported",
261
270
  schemaLabel: "workspace-local GEMINI.md",
@@ -443,6 +452,7 @@ export const AGENT_REGISTRY = [
443
452
  provenance: {
444
453
  detectPaths: "documented",
445
454
  detectBinary: "documented",
455
+ agentRules: "documented",
446
456
  agentDefinitions: "documented",
447
457
  mcpConfig: "documented",
448
458
  hooks: "provisional",
@@ -514,6 +524,35 @@ export const AGENT_REGISTRY = [
514
524
  windows: ["{workspace}", "CLAUDE.md"],
515
525
  },
516
526
  },
527
+ // GitHub Copilot native repo-level instruction file. Not shared via
528
+ // CLAUDE.md; this is Copilot's own .github/copilot-instructions.md surface
529
+ // deployed directly without the shared-via deduplication path.
530
+ agentRulesCopilotRepoSupport: {
531
+ status: "supported",
532
+ surfaceKind: { kind: "agent-specific" },
533
+ schemaLabel: "repo-local .github/copilot-instructions.md",
534
+ path: {
535
+ posix: ["{repo}", ".github", "copilot-instructions.md"],
536
+ windows: ["{repo}", ".github", "copilot-instructions.md"],
537
+ },
538
+ },
539
+ // GitHub Copilot scoped instruction files. Each entry deploys to
540
+ // .github/instructions/{name}.instructions.md where {name} comes from
541
+ // the manifest entry name field.
542
+ agentRulesCopilotScopedSupport: {
543
+ status: "supported",
544
+ surfaceKind: { kind: "agent-specific" },
545
+ schemaLabel: "scoped .github/instructions/{name}.instructions.md",
546
+ path: {
547
+ posix: ["{repo}", ".github", "instructions", "{name}.instructions.md"],
548
+ windows: [
549
+ "{repo}",
550
+ ".github",
551
+ "instructions",
552
+ "{name}.instructions.md",
553
+ ],
554
+ },
555
+ },
517
556
  mcpDevcontainerSupport: {
518
557
  status: "supported",
519
558
  schemaLabel: "devcontainer.json MCP surface",
@@ -0,0 +1,14 @@
1
+ import type { ExecutionConfigEntry } from "../../schemas/manifest.ts";
2
+ import type { AgentId, ConfigPatchDeployAction, ConfigPatchRevertAction, PlanWarning } from "../../types.ts";
3
+ export interface ExecutionConfigAdapterResult {
4
+ actions: ConfigPatchDeployAction[];
5
+ warnings: PlanWarning[];
6
+ }
7
+ /**
8
+ * Compiles deploy actions for executionConfigs manifest entries.
9
+ *
10
+ * Execution configuration settings (like Gemini CLI's safeMode) are patched
11
+ * directly into the agent's primary settings file using config-patch actions.
12
+ */
13
+ export declare function compileExecutionConfigActions(entry: ExecutionConfigEntry, detectedAgents: AgentId[], home: string): ExecutionConfigAdapterResult;
14
+ export declare function compileExecutionConfigReverts(entry: ExecutionConfigEntry, agentFilter: AgentId[] | null, home: string): ConfigPatchRevertAction[];
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+ import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
3
+ import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
4
+ /**
5
+ * Compiles deploy actions for executionConfigs manifest entries.
6
+ *
7
+ * Execution configuration settings (like Gemini CLI's safeMode) are patched
8
+ * directly into the agent's primary settings file using config-patch actions.
9
+ */
10
+ export function compileExecutionConfigActions(entry, detectedAgents, home) {
11
+ const actions = [];
12
+ const warnings = [];
13
+ const platform = getPlatformKey();
14
+ for (const agentId of entry.agents) {
15
+ if (!detectedAgents.includes(agentId))
16
+ continue;
17
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
18
+ if (!agent?.executionConfigSupport ||
19
+ agent.executionConfigSupport.status !== "supported") {
20
+ warnings.push({
21
+ kind: "confidence",
22
+ message: `Agent "${agentId}" does not support modeled execution configuration surfaces — skipping "${entry.name}"`,
23
+ });
24
+ continue;
25
+ }
26
+ const rawTarget = resolvePlaceholders(agent.executionConfigSupport.path[platform], entry.name, home);
27
+ const target = path.resolve(rawTarget);
28
+ actions.push({
29
+ kind: "config-patch",
30
+ skill: entry.name,
31
+ agent: agentId,
32
+ target,
33
+ patch: entry.config,
34
+ confidence: "provisional",
35
+ });
36
+ }
37
+ return { actions, warnings };
38
+ }
39
+ export function compileExecutionConfigReverts(entry, agentFilter, home) {
40
+ const actions = [];
41
+ const platform = getPlatformKey();
42
+ for (const agentId of entry.agents) {
43
+ if (agentFilter && !agentFilter.includes(agentId))
44
+ continue;
45
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
46
+ if (!agent?.executionConfigSupport ||
47
+ agent.executionConfigSupport.status !== "supported") {
48
+ continue;
49
+ }
50
+ const rawTarget = resolvePlaceholders(agent.executionConfigSupport.path[platform], entry.name, home);
51
+ const target = path.resolve(rawTarget);
52
+ actions.push({
53
+ kind: "config-patch",
54
+ skill: entry.name,
55
+ agent: agentId,
56
+ target,
57
+ });
58
+ }
59
+ return actions;
60
+ }
@@ -1,14 +1,15 @@
1
- import type { AgentDefinitionEntry, AgentRuleEntry, HookEntry, McpServerEntry, PermissionsEntry } from "../../schemas/manifest.ts";
1
+ import type { AgentDefinitionEntry, AgentRuleEntry, ExecutionConfigEntry, 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 { compileExecutionConfigReverts } from "./execution-config.ts";
4
5
  import { compileHookReverts } from "./hooks.ts";
5
6
  import { compileMcpServerReverts } from "./mcp.ts";
6
7
  import { compilePermissionsReverts } from "./permissions.ts";
7
8
  import { compileAgentRuleReverts } from "./rules.ts";
8
- export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, };
9
+ export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileExecutionConfigReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, };
9
10
  export type AdapterAction = ConfigPatchDeployAction | FileWriteDeployAction | TomlPatchDeployAction | FrontmatterEmitDeployAction;
10
11
  export interface AdapterResult {
11
12
  actions: AdapterAction[];
12
13
  warnings: PlanWarning[];
13
14
  }
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>;
15
+ 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[], executionConfigs?: ExecutionConfigEntry[]): Promise<AdapterResult>;
@@ -1,9 +1,10 @@
1
1
  import { compileAgentDefinitionActions, compileAgentDefinitionReverts, } from "./agent-definitions.js";
2
+ import { compileExecutionConfigActions, compileExecutionConfigReverts, } from "./execution-config.js";
2
3
  import { compileHookActions, compileHookReverts } from "./hooks.js";
3
4
  import { compileMcpServerActions, compileMcpServerReverts } from "./mcp.js";
4
5
  import { compilePermissionsActions, compilePermissionsReverts, } from "./permissions.js";
5
6
  import { compileAgentRuleActions, compileAgentRuleReverts } from "./rules.js";
6
- export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, };
7
+ export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileExecutionConfigReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, };
7
8
  async function compileAll(entries, fn) {
8
9
  const results = await Promise.all(entries.map(fn));
9
10
  return {
@@ -11,17 +12,18 @@ async function compileAll(entries, fn) {
11
12
  warnings: results.flatMap((r) => r.warnings),
12
13
  };
13
14
  }
14
- export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, agentDefinitions, workspace, hooks = []) {
15
+ export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, agentDefinitions, workspace, hooks = [], executionConfigs = []) {
15
16
  const actions = [];
16
17
  const warnings = [];
17
- const [mcp, rules, perms, defs, hookResults] = await Promise.all([
18
+ const [mcp, rules, perms, defs, hookResults, execResults] = await Promise.all([
18
19
  compileAll(mcpServers, (entry) => compileMcpServerActions(entry, detectedAgents, home, repo, workspace)),
19
20
  compileAll(agentRules, (entry) => compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace)),
20
21
  compileAll(permissions, (entry) => compilePermissionsActions(entry, detectedAgents, home)),
21
22
  compileAll(agentDefinitions ?? [], (entry) => compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace)),
22
23
  compileAll(hooks, (entry) => compileHookActions(entry, detectedAgents, home)),
24
+ compileAll(executionConfigs, (entry) => compileExecutionConfigActions(entry, detectedAgents, home)),
23
25
  ]);
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);
26
+ actions.push(...mcp.actions, ...rules.actions, ...perms.actions, ...defs.actions, ...hookResults.actions, ...execResults.actions);
27
+ warnings.push(...mcp.warnings, ...rules.warnings, ...perms.warnings, ...defs.warnings, ...hookResults.warnings, ...execResults.warnings);
26
28
  return { actions, warnings };
27
29
  }
@@ -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" | "devcontainer";
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
  }
@@ -52,6 +58,8 @@ function capabilityLabel(capability) {
52
58
  return "agentDefinitions";
53
59
  case "hooks":
54
60
  return "hooks";
61
+ case "executionConfigs":
62
+ return "executionConfigs";
55
63
  }
56
64
  }
57
65
  function capabilitySurfaceLabel(capability) {
@@ -68,6 +76,8 @@ function capabilitySurfaceLabel(capability) {
68
76
  return "agent-definitions";
69
77
  case "hooks":
70
78
  return "hooks";
79
+ case "executionConfigs":
80
+ return "execution-config";
71
81
  }
72
82
  }
73
83
  function resolveSkillsCapability(agentId) {
@@ -125,9 +135,15 @@ function getSurfaceRecordAndConfidence(agentId, capability, scope) {
125
135
  confidence: agent.provenance.agentDefinitions,
126
136
  };
127
137
  }
138
+ if (capability === "hooks") {
139
+ return {
140
+ supportRecord: agent.hooksSupport,
141
+ confidence: agent.provenance.hooks,
142
+ };
143
+ }
128
144
  return {
129
- supportRecord: agent.hooksSupport,
130
- confidence: agent.provenance.hooks,
145
+ supportRecord: agent.executionConfigSupport,
146
+ confidence: agent.provenance.executionConfig,
131
147
  };
132
148
  }
133
149
  function resolveUnsupportedSurface(agentId, capability, supportRecord, confidence) {
@@ -9,7 +9,7 @@ import { compileAdapterActions } from "./adapters/index.js";
9
9
  import { applyTomlMcpPatch } from "./adapters/toml.js";
10
10
  import { planCapabilityForDeploy } from "./capabilities.js";
11
11
  import { applyMergePatch, computeUndoPatch, isPlainObject, } from "./merge-patch.js";
12
- import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
12
+ import { lookupDeployment, registryDirPath, registerDeployment, verifyDeployment, } from "./ownership.js";
13
13
  import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
14
14
  import { resolveTargetTemplate } from "./runtime-paths.js";
15
15
  import { sourceAccessError, validateSkillDefinitionFile, validateSourceFile, validateSourcePath, } from "./validation.js";
@@ -153,6 +153,62 @@ function detectAmbiguities(detectedAgents, manifest) {
153
153
  }
154
154
  return warnings;
155
155
  }
156
+ function normalizeTemplatePath(template) {
157
+ return template.replaceAll("\\", "/");
158
+ }
159
+ function isRepoScopedTemplate(template) {
160
+ return template.startsWith("{repo}") || template.startsWith("{workspace}");
161
+ }
162
+ function normalizePathForComparison(candidate) {
163
+ const normalized = path.normalize(candidate);
164
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
165
+ }
166
+ function isSameOrDescendantPath(candidate, root) {
167
+ const normalizedCandidate = normalizePathForComparison(candidate);
168
+ const normalizedRoot = normalizePathForComparison(root);
169
+ return (normalizedCandidate === normalizedRoot ||
170
+ normalizedCandidate.startsWith(normalizedRoot + path.sep));
171
+ }
172
+ function collectApprovedGlobalSurfaceTemplates(agentId) {
173
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
174
+ if (!agent)
175
+ return new Set();
176
+ const supports = [
177
+ agent.mcpSupport,
178
+ agent.agentRulesSupport,
179
+ agent.permissionsSupport,
180
+ agent.hooksSupport,
181
+ agent.executionConfigSupport,
182
+ ];
183
+ const approved = new Set();
184
+ for (const support of supports) {
185
+ if (!support || support.status !== "supported")
186
+ continue;
187
+ approved.add(normalizeTemplatePath(support.path.posix.join("/")));
188
+ approved.add(normalizeTemplatePath(support.path.windows.join("/")));
189
+ }
190
+ return approved;
191
+ }
192
+ function assertApprovedManagedTargetTemplate(template, targetAgents, kind) {
193
+ if (isRepoScopedTemplate(template))
194
+ return;
195
+ const normalizedTemplate = normalizeTemplatePath(template);
196
+ for (const agentId of targetAgents) {
197
+ const approvedTemplates = collectApprovedGlobalSurfaceTemplates(agentId);
198
+ if (approvedTemplates.has(normalizedTemplate)) {
199
+ return;
200
+ }
201
+ }
202
+ throw new UserError("DEPLOY_FAILED", `${kind} target "${template}" is not an approved managed surface. ` +
203
+ `Use {repo}/... or {workspace}/... for arbitrary project-local paths, ` +
204
+ `or target a documented agent-owned global config surface.`);
205
+ }
206
+ function assertTargetOutsideReservedEngineState(targetPath, home) {
207
+ const reservedDir = registryDirPath(home);
208
+ if (isSameOrDescendantPath(targetPath, reservedDir)) {
209
+ throw new UserError("DEPLOY_FAILED", `Target "${targetPath}" is inside inception-engine state directory "${reservedDir}" and cannot be managed by manifests`);
210
+ }
211
+ }
156
212
  function checkAntigravityPathCollisions(manifest) {
157
213
  const warnings = [];
158
214
  const defNames = new Set((manifest.agentDefinitions ?? [])
@@ -223,6 +279,7 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
223
279
  const targetAgents = fileEntry.agents.filter((agentId) => detectedAgents.includes(agentId));
224
280
  if (targetAgents.length === 0)
225
281
  return;
282
+ assertApprovedManagedTargetTemplate(fileEntry.target, targetAgents, "files");
226
283
  const source = path.resolve(sourceDir, fileEntry.path);
227
284
  await validateSourcePath(source, fileEntry.path, resolvedSourceDir, realRoot);
228
285
  await validateSourceFile(source, fileEntry.path);
@@ -245,6 +302,10 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
245
302
  function planConfigPatchActions(manifest, detectedAgents, home, repo, workspace) {
246
303
  const actions = [];
247
304
  for (const configEntry of manifest.configs ?? []) {
305
+ const targetAgents = configEntry.agents.filter((agentId) => detectedAgents.includes(agentId));
306
+ if (targetAgents.length === 0)
307
+ continue;
308
+ assertApprovedManagedTargetTemplate(configEntry.target, targetAgents, "configs");
248
309
  for (const agentId of configEntry.agents) {
249
310
  if (!detectedAgents.includes(agentId))
250
311
  continue;
@@ -280,7 +341,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo
280
341
  ...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, workspace)),
281
342
  ...planConfigPatchActions(manifest, detectedAgents, home, repoDir, workspace),
282
343
  ];
283
- const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace, manifest.hooks ?? []);
344
+ const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace, manifest.hooks ?? [], manifest.executionConfigs ?? []);
284
345
  actions.push(...adapterResult.actions);
285
346
  const warnings = [
286
347
  ...skillPlan.warnings,
@@ -289,6 +350,9 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo
289
350
  ...detectCollisions(actions),
290
351
  ...adapterResult.warnings,
291
352
  ];
353
+ for (const action of actions) {
354
+ assertTargetOutsideReservedEngineState(action.target, home);
355
+ }
292
356
  return { actions, warnings };
293
357
  }
294
358
  export async function executeDeploy(actions, dryRun, verbose, home, deps = {}) {