@kuznai/inception-engine 0.22.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.
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,10 +144,29 @@ 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
  ```
@@ -202,10 +222,12 @@ Each **agentRules** entry deploys a Markdown instruction file to an agent's supp
202
222
  - **name** - Unique identifier (same format as skill names)
203
223
  - **path** - Relative path to the source Markdown file within the repo; supported rules adapters require a `.md` or `.markdown` source path
204
224
  - **agents** - Array of agent IDs to deploy this file to
205
- - **scope** - `"global"` (default), `"repo"`, or `"workspace"`. Controls which instruction surface is targeted:
225
+ - **scope** - `"global"` (default), `"repo"`, `"workspace"`, `"copilot-repo"`, or `"copilot-scoped"`. Controls which instruction surface is targeted:
206
226
  - `"global"` — deploys to the agent's home-directory instruction file when that surface is supported (e.g., `~/.claude/CLAUDE.md` for `claude-code`)
207
227
  - `"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
228
  - `"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
229
+ - `"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)
230
+ - `"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
231
 
210
232
  Instruction rule deployment is supported for implemented global, repo, and workspace surfaces. The target path depends on the agent and the `scope`:
211
233
 
@@ -218,7 +240,13 @@ Instruction rule deployment is supported for implemented global, repo, and works
218
240
  | `opencode` | `~/.config/opencode/AGENTS.md` | `{repo}/AGENTS.md` | unsupported; warns and skips |
219
241
  | `github-copilot` | unsupported / Claude-first | deploy via `claude-code` | unsupported; deploy via `claude-code` with `scope: "workspace"` |
220
242
 
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.
243
+ GitHub Copilot also exposes two native instruction surfaces via dedicated scope values:
244
+
245
+ | Agent | `scope: "copilot-repo"` | `scope: "copilot-scoped"` |
246
+ |---|---|---|
247
+ | `github-copilot` | `{repo}/.github/copilot-instructions.md` | `{repo}/.github/instructions/{name}.instructions.md` |
248
+
249
+ 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
250
 
223
251
  Each **permissions** entry deploys execution and safety-oriented configuration to an agent's permission or approval surface:
224
252
 
@@ -269,7 +297,7 @@ Each **hooks** entry deploys lifecycle-binding configurations to an agent's exec
269
297
 
270
298
  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
299
 
272
- For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the `hooks` key is accepted:
300
+ 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
301
 
274
302
  ```json
275
303
  {
@@ -277,8 +305,17 @@ For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the
277
305
  "agents": ["claude-code"],
278
306
  "config": {
279
307
  "hooks": {
280
- "pre_exec": "scripts/check-env.sh",
281
- "post_exec": "scripts/notify.sh"
308
+ "PreToolUse": [
309
+ {
310
+ "matcher": "Bash",
311
+ "hooks": [{ "type": "command", "command": "scripts/check-env.sh" }]
312
+ }
313
+ ],
314
+ "Stop": [
315
+ {
316
+ "hooks": [{ "type": "command", "command": "scripts/notify.sh" }]
317
+ }
318
+ ]
282
319
  }
283
320
  }
284
321
  }
@@ -341,7 +378,7 @@ Current `init` behavior:
341
378
  - Applies either the `--agents` list or all currently known agent IDs
342
379
  - Refuses to overwrite an existing `inception.json` unless `--force` is provided
343
380
  - 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
381
+ - 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
382
  - Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
346
383
  - Reads `files-manifest.json` from the repo root (if present) and generates `files` entries; invalid entries are warned and skipped
347
384
  - Reads `configs-manifest.json` from the repo root (if present) and generates `configs` entries; invalid entries are warned and skipped
@@ -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) {
@@ -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, manifest.hooks ?? []);
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
  }
@@ -576,7 +632,7 @@ export async function runInit(options) {
576
632
  const skillNamesSeen = new Set(skills.map((s) => s.name));
577
633
  const skillDirRelPaths = new Set(found.map((f) => f.relPath));
578
634
  const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
579
- const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, skillNamesSeen);
635
+ const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, agents, skillNamesSeen);
580
636
  const allNamesSeen = new Set([
581
637
  ...skillNamesSeen,
582
638
  ...agentRules.map((r) => r.name),