@kuznai/inception-engine 0.18.0 → 0.19.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
@@ -27,7 +27,7 @@ inception-engine reads a manifest file (`inception.json`) from the target direct
27
27
 
28
28
  Managed skills overwrite their previous version. If a target exists but was not created by inception-engine, deployment refuses to replace it. On POSIX systems, symlinks mean updates to the source repo are reflected immediately.
29
29
 
30
- Before executing, the deploy command runs preflight analysis on instruction files: it warns when the same agent will have both global and repo `agentRules` active simultaneously, when the same source file is deployed to both scopes (duplicate-content risk), and when `agentRules` or `agentDefinitions` source files exceed 50 KB (context-budget risk). Warnings are printed but do not block deployment.
30
+ Before executing, the deploy command runs preflight analysis on instruction files: it validates that `agentRules` and `agentDefinitions` for targets requiring specific structure (like `github-copilot` and `antigravity`) include valid YAML frontmatter with `name` and `description` fields. For `github-copilot`, it further ensures either `tools` or `instructions` are defined; for `antigravity`, it validates the shape of any `mcp-servers` or `mcpServers` defined in the frontmatter. It also warns when the same agent will have multiple `agentRules` scopes active simultaneously, when the same source file is deployed to multiple scopes (duplicate-content risk), when `agentRules` or `agentDefinitions` source files exceed 50 KB (context-budget risk), and when GitHub Copilot appears to be running under enterprise-managed policy that may override local configuration. Warnings are printed but do not block deployment; structural validation failures block deployment for the affected targets.
31
31
 
32
32
  ## Agent Compatibility Matrix
33
33
 
@@ -51,11 +51,11 @@ 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, gemini-cli, codex, antigravity, opencode; github-copilot repo-scoped surfaces are warned and skipped | claude-code, gemini-cli, codex, antigravity, opencode |
54
- | Global/Repo Rules Files | All agents via `scope: "global"` (home-dir) or `scope: "repo"` (project-root); antigravity always uses repo-local `.agents/rules/`; github-copilot reads CLAUDE.md natively (deploy via claude-code) | All 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` and has no separate rules deployment target | All supported agents |
55
55
  | Permissions / Approval Config | claude-code (`~/.claude/settings.json`), codex (`~/.codex/config.toml`); other agents are warned and skipped | claude-code, codex |
56
56
  | Agent Definitions | claude-code (`{repo}/.claude/agents/{name}.md`), gemini-cli (`{repo}/.gemini/agents/{name}.md`), antigravity (`{repo}/.agents/rules/{name}.md`), opencode (`{repo}/.opencode/agents/{name}.md`), github-copilot (`{repo}/.github/agents/{name}.agent.md`); codex is warned and skipped | All supported agents |
57
57
  | `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`); emits hints for `files/` and `configs/` directories | N/A |
58
- | Instruction preflight analysis | Emits `precedence` warnings when an agent has both global and repo `agentRules` active simultaneously (stacking advisory) or the same source file deployed to both scopes (duplicate-content warning); emits `budget` warnings when `agentRules` or `agentDefinitions` source files exceed 50 KB | N/A |
58
+ | Instruction preflight analysis | 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 | N/A |
59
59
 
60
60
  Features that depend on agent-specific config surfaces are intentionally conservative: if a target path or schema is not implemented with enough confidence, inception-engine warns and skips it rather than guessing.
61
61
 
@@ -173,22 +173,23 @@ Each **agentRules** entry deploys a Markdown instruction file to an agent's supp
173
173
  - **name** - Unique identifier (same format as skill names)
174
174
  - **path** - Relative path to the source Markdown file within the repo; supported rules adapters require a `.md` or `.markdown` source path
175
175
  - **agents** - Array of agent IDs to deploy this file to
176
- - **scope** - `"global"` (default) or `"repo"`. Controls which instruction surface is targeted:
177
- - `"global"` — deploys to the agent's home-directory instruction file (e.g., `~/.claude/CLAUDE.md` for `claude-code`)
178
- - `"repo"` — deploys to the project-root instruction file inside the deployed repository (e.g., `{repo}/CLAUDE.md` for `claude-code`)
176
+ - **scope** - `"global"` (default), `"repo"`, or `"workspace"`. Controls which instruction surface is targeted:
177
+ - `"global"` — deploys to the agent's home-directory instruction file when that surface is supported (e.g., `~/.claude/CLAUDE.md` for `claude-code`)
178
+ - `"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`)
179
+ - `"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
179
180
 
180
- Instruction rule deployment is supported for all agents. The target path depends on the agent and the `scope`:
181
+ Instruction rule deployment is supported for implemented global, repo, and workspace surfaces. The target path depends on the agent and the `scope`:
181
182
 
182
- | Agent | `scope: "global"` | `scope: "repo"` |
183
- |---|---|---|
184
- | `claude-code` | `~/.claude/CLAUDE.md` | `{repo}/CLAUDE.md` |
185
- | `codex` | `~/.codex/AGENTS.md` | `{repo}/AGENTS.md` |
186
- | `gemini-cli` | `~/.gemini/GEMINI.md` | `{repo}/GEMINI.md` |
187
- | `antigravity` | `{repo}/.agents/rules/{name}.md` | `{repo}/.agents/rules/{name}.md` |
188
- | `opencode` | `~/.config/opencode/AGENTS.md` | `{repo}/AGENTS.md` |
189
- | `github-copilot` | unsupported reads `CLAUDE.md` natively | unsupported deploy via `claude-code` |
183
+ | Agent | `scope: "global"` | `scope: "repo"` | `scope: "workspace"` |
184
+ |---|---|---|---|
185
+ | `claude-code` | `~/.claude/CLAUDE.md` | `{repo}/CLAUDE.md` | `{workspace}/CLAUDE.md` |
186
+ | `codex` | `~/.codex/AGENTS.md` | `{repo}/AGENTS.md` | `{workspace}/AGENTS.md` |
187
+ | `gemini-cli` | `~/.gemini/GEMINI.md` | `{repo}/GEMINI.md` | `{workspace}/GEMINI.md` |
188
+ | `antigravity` | `~/.gemini/GEMINI.md` | `{repo}/GEMINI.md` | unsupported; warns and skips |
189
+ | `opencode` | `~/.config/opencode/AGENTS.md` | `{repo}/AGENTS.md` | unsupported; warns and skips |
190
+ | `github-copilot` | unsupported / Claude-first | deploy via `claude-code` | unsupported; deploy via `claude-code` with `scope: "workspace"` |
190
191
 
191
- For `antigravity`, both scopes target the same repo-local surface (`{repo}/.agents/rules/{name}.md`) since Antigravity has no global home-directory instruction file. For `github-copilot`, no separate deployment is needed for either scope — target it via the `claude-code` agentRules entry and it reaches Copilot automatically. Revert removes the deployed rules file.
192
+ 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.
192
193
 
193
194
  Each **permissions** entry deploys execution and safety-oriented configuration to an agent's permission or approval surface:
194
195
 
@@ -275,8 +276,8 @@ Current `init` behavior:
275
276
  - Uses the directory name as the manifest skill name
276
277
  - Applies either the `--agents` list or all currently known agent IDs
277
278
  - Refuses to overwrite an existing `inception.json` unless `--force` is provided
278
- - Supports `--dry-run` so you can inspect the generated manifest before writing it
279
- - Discovers agent-rules Markdown files in the root and conventional subdirectories (`rules/`, `instructions/`, `.github/`, `.agents/rules/`), 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 agents whose agentRules surface is unsupported
279
+ - Supports `--plan` so you can inspect the generated manifest before writing it
280
+ - 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 agents whose agentRules surface is unsupported
280
281
  - Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
281
282
  - Reads `files-manifest.json` from the repo root (if present) and generates `files` entries; invalid entries are warned and skipped
282
283
  - Reads `configs-manifest.json` from the repo root (if present) and generates `configs` entries; invalid entries are warned and skipped
@@ -366,13 +367,15 @@ inception-engine init <directory> [options]
366
367
 
367
368
  | Option | Description |
368
369
  |---|---|
369
- | `--dry-run` | Show what would be done without making changes |
370
+ | `--plan` | Show what would be done without making changes |
370
371
  | `--agents <list>` | Comma-separated list of agent IDs to target (overrides deploy detection; restricts revert) |
371
372
  | `--force` | `init` only; overwrite an existing `inception.json` |
372
373
  | `--verbose` | Show detailed output including file paths |
373
374
  | `--debug` | Show full error stack traces |
374
375
  | `--help` | Show help message |
375
376
 
377
+ With `--plan`, deploy and revert print a grouped action preview by agent. Each planned change includes the source path when applicable, the resolved target path, and action-specific details such as JSON/TOML patch payloads or emitted frontmatter content.
378
+
376
379
  ### Examples
377
380
 
378
381
  ```bash
@@ -380,7 +383,7 @@ inception-engine init <directory> [options]
380
383
  npx @kuznai/inception-engine ./my-skills-repo
381
384
 
382
385
  # Preview what would be deployed
383
- npx @kuznai/inception-engine ./my-skills-repo --dry-run
386
+ npx @kuznai/inception-engine ./my-skills-repo --plan
384
387
 
385
388
  # Deploy only to Claude Code and Codex
386
389
  npx @kuznai/inception-engine ./my-skills-repo --agents claude-code,codex
@@ -389,13 +392,13 @@ npx @kuznai/inception-engine ./my-skills-repo --agents claude-code,codex
389
392
  npx @kuznai/inception-engine revert ./my-skills-repo
390
393
 
391
394
  # Preview what would be removed
392
- npx @kuznai/inception-engine revert ./my-skills-repo --dry-run
395
+ npx @kuznai/inception-engine revert ./my-skills-repo --plan
393
396
 
394
397
  # Generate a starter manifest from discovered skill folders
395
398
  npx @kuznai/inception-engine init ./my-skills-repo
396
399
 
397
400
  # Preview the generated manifest without writing it
398
- npx @kuznai/inception-engine init ./my-skills-repo --dry-run
401
+ npx @kuznai/inception-engine init ./my-skills-repo --plan
399
402
  ```
400
403
 
401
404
  ## Sample Skills
@@ -405,8 +408,8 @@ The `limbo/` directory contains exceptional sample skills for testing purposes o
405
408
  Try them out:
406
409
 
407
410
  ```bash
408
- npx @kuznai/inception-engine init limbo --dry-run
409
- npx @kuznai/inception-engine limbo --dry-run
411
+ npx @kuznai/inception-engine init limbo --plan
412
+ npx @kuznai/inception-engine limbo --plan
410
413
  ```
411
414
 
412
415
  ## Agent Detection
@@ -60,6 +60,14 @@ export const AGENT_REGISTRY = [
60
60
  windows: ["{repo}", ".claude", "agents", "{name}.md"],
61
61
  },
62
62
  },
63
+ agentRulesWorkspaceSupport: {
64
+ status: "supported",
65
+ schemaLabel: "workspace-local CLAUDE.md",
66
+ path: {
67
+ posix: ["{workspace}", "CLAUDE.md"],
68
+ windows: ["{workspace}", "CLAUDE.md"],
69
+ },
70
+ },
63
71
  },
64
72
  {
65
73
  id: "codex",
@@ -117,6 +125,14 @@ export const AGENT_REGISTRY = [
117
125
  schemaLabel: "dedicated agent definition directory",
118
126
  reason: "OpenAI Codex does not expose a documented dedicated per-agent definition directory surface separate from AGENTS.md and config.toml — use agentRules to deploy persona instructions instead",
119
127
  },
128
+ agentRulesWorkspaceSupport: {
129
+ status: "supported",
130
+ schemaLabel: "workspace-local AGENTS.md",
131
+ path: {
132
+ posix: ["{workspace}", "AGENTS.md"],
133
+ windows: ["{workspace}", "AGENTS.md"],
134
+ },
135
+ },
120
136
  },
121
137
  {
122
138
  id: "gemini-cli",
@@ -175,6 +191,14 @@ export const AGENT_REGISTRY = [
175
191
  windows: ["{repo}", ".gemini", "agents", "{name}.md"],
176
192
  },
177
193
  },
194
+ agentRulesWorkspaceSupport: {
195
+ status: "supported",
196
+ schemaLabel: "workspace-local GEMINI.md",
197
+ path: {
198
+ posix: ["{workspace}", "GEMINI.md"],
199
+ windows: ["{workspace}", "GEMINI.md"],
200
+ },
201
+ },
178
202
  },
179
203
  {
180
204
  id: "antigravity",
@@ -203,20 +227,25 @@ export const AGENT_REGISTRY = [
203
227
  windows: ["{repo}", ".agents", "rules", "{name}.md"],
204
228
  },
205
229
  },
230
+ // Antigravity shares its instruction surfaces with gemini-cli (both target
231
+ // the same GEMINI.md paths). surfaceKind: shared-via drives deduplication
232
+ // when both agents appear in the same agentRules entry.
206
233
  agentRulesSupport: {
207
234
  status: "supported",
208
- schemaLabel: "repo-local Markdown rules file",
235
+ surfaceKind: { kind: "shared-via", via: "gemini-cli" },
236
+ schemaLabel: "global Gemini blueprint instructions file",
209
237
  path: {
210
- posix: ["{repo}", ".agents", "rules", "{name}.md"],
211
- windows: ["{repo}", ".agents", "rules", "{name}.md"],
238
+ posix: ["{home}", ".gemini", "GEMINI.md"],
239
+ windows: ["{home}", ".gemini", "GEMINI.md"],
212
240
  },
213
241
  },
214
242
  agentRulesRepoSupport: {
215
243
  status: "supported",
216
- schemaLabel: "repo-local Markdown rules file",
244
+ surfaceKind: { kind: "shared-via", via: "gemini-cli" },
245
+ schemaLabel: "repo-local GEMINI.md",
217
246
  path: {
218
- posix: ["{repo}", ".agents", "rules", "{name}.md"],
219
- windows: ["{repo}", ".agents", "rules", "{name}.md"],
247
+ posix: ["{repo}", "GEMINI.md"],
248
+ windows: ["{repo}", "GEMINI.md"],
220
249
  },
221
250
  },
222
251
  permissionsSupport: {
@@ -232,6 +261,12 @@ export const AGENT_REGISTRY = [
232
261
  windows: ["{repo}", ".agents", "rules", "{name}.md"],
233
262
  },
234
263
  },
264
+ agentRulesWorkspaceSupport: {
265
+ status: "unsupported",
266
+ schemaLabel: "workspace-local instruction surface",
267
+ reason: "Antigravity is natively repo-local and does not expose a separate workspace-local instruction surface",
268
+ },
269
+ instructionFrontmatterRequired: true,
235
270
  },
236
271
  {
237
272
  id: "opencode",
@@ -290,6 +325,11 @@ export const AGENT_REGISTRY = [
290
325
  windows: ["{repo}", ".opencode", "agents", "{name}.md"],
291
326
  },
292
327
  },
328
+ agentRulesWorkspaceSupport: {
329
+ status: "unsupported",
330
+ schemaLabel: "workspace-local AGENTS.md",
331
+ reason: "OpenCode does not expose a documented workspace-local instruction surface distinct from repo-local AGENTS.md",
332
+ },
293
333
  },
294
334
  {
295
335
  id: "github-copilot",
@@ -298,6 +338,7 @@ export const AGENT_REGISTRY = [
298
338
  // from `.claude/skills/` (the same path used by claude-code). Deploying
299
339
  // via the `claude-code` skills target automatically covers Copilot — no
300
340
  // separate `~/.copilot/skills/` path is needed or maintained.
341
+ skillsSurfaceKind: { kind: "shared-via", via: "claude-code" },
301
342
  detectPaths: {
302
343
  posix: ["{home}", ".copilot"],
303
344
  windows: ["{home}", ".copilot"],
@@ -314,15 +355,49 @@ export const AGENT_REGISTRY = [
314
355
  plannedSurface: "devcontainer (.devcontainer/devcontainer.json) and agent-frontmatter (.github/agents/*.agent.md)",
315
356
  reason: "GitHub Copilot MCP support will be implemented via repo-scoped devcontainer features and agent-frontmatter mappings — surfaces that are genuinely Copilot-specific and not covered by other agent targets",
316
357
  },
358
+ // GitHub Copilot reads Claude-native instruction files (CLAUDE.md) without
359
+ // a separate deploy action. These surfaces are marked shared-via claude-code
360
+ // with requiresPrimary: true so deploy skips emitting a separate action
361
+ // when claude-code is also targeted, and emits a guidance warning when it
362
+ // is not (copilot cannot write to these surfaces independently).
317
363
  agentRulesSupport: {
318
- status: "unsupported",
364
+ status: "supported",
365
+ surfaceKind: {
366
+ kind: "shared-via",
367
+ via: "claude-code",
368
+ requiresPrimary: true,
369
+ },
319
370
  schemaLabel: "Claude-native shared instructions",
320
- reason: 'GitHub Copilot reads CLAUDE.md natively, so deploy via the "claude-code" agentRules target instead of a separate rules surface',
371
+ path: {
372
+ posix: ["{home}", ".claude", "CLAUDE.md"],
373
+ windows: ["{home}", ".claude", "CLAUDE.md"],
374
+ },
321
375
  },
322
376
  agentRulesRepoSupport: {
323
- status: "unsupported",
377
+ status: "supported",
378
+ surfaceKind: {
379
+ kind: "shared-via",
380
+ via: "claude-code",
381
+ requiresPrimary: true,
382
+ },
324
383
  schemaLabel: "repo-local CLAUDE.md",
325
- reason: 'GitHub Copilot reads CLAUDE.md natively, so deploy via the "claude-code" agentRules target with scope: "repo" instead of a separate rules surface',
384
+ path: {
385
+ posix: ["{repo}", "CLAUDE.md"],
386
+ windows: ["{repo}", "CLAUDE.md"],
387
+ },
388
+ },
389
+ agentRulesWorkspaceSupport: {
390
+ status: "supported",
391
+ surfaceKind: {
392
+ kind: "shared-via",
393
+ via: "claude-code",
394
+ requiresPrimary: true,
395
+ },
396
+ schemaLabel: "workspace-local CLAUDE.md",
397
+ path: {
398
+ posix: ["{workspace}", "CLAUDE.md"],
399
+ windows: ["{workspace}", "CLAUDE.md"],
400
+ },
326
401
  },
327
402
  permissionsSupport: {
328
403
  status: "unsupported",
@@ -338,6 +413,8 @@ export const AGENT_REGISTRY = [
338
413
  },
339
414
  },
340
415
  policyNote: "Organization policies may override locally deployed configuration. Verify with your GitHub org admin if deployed skills or rules are not active.",
416
+ instructionFrontmatterRequired: true,
417
+ enterprisePolicyDetection: true,
341
418
  },
342
419
  ];
343
420
  export const AGENT_REGISTRY_BY_ID = Object.fromEntries(AGENT_REGISTRY.map((a) => [a.id, a]));
@@ -16,5 +16,5 @@ export interface AgentDefinitionsAdapterResult {
16
16
  * Target paths use the `{repo}` placeholder so definitions land in the
17
17
  * repository being deployed, not the user's home directory.
18
18
  */
19
- export declare function compileAgentDefinitionActions(entry: AgentDefinitionEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string): Promise<AgentDefinitionsAdapterResult>;
20
- export declare function compileAgentDefinitionReverts(entry: AgentDefinitionEntry, agentFilter: AgentId[] | null, home: string, repo?: string): FileWriteRevertAction[];
19
+ export declare function compileAgentDefinitionActions(entry: AgentDefinitionEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<AgentDefinitionsAdapterResult>;
20
+ export declare function compileAgentDefinitionReverts(entry: AgentDefinitionEntry, agentFilter: AgentId[] | null, home: string, repo?: string, workspace?: string): FileWriteRevertAction[];
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
3
3
  import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
4
- import { validateAgentRuleMarkdownPath, validateSourceFile, validateSourcePath, } from "../validation.js";
4
+ import { validateAgentRuleMarkdownPath, validateInstructionFileRequirements, validateSourceFile, validateSourcePath, } from "../validation.js";
5
5
  /**
6
6
  * Compiles deploy actions for agentDefinitions manifest entries.
7
7
  *
@@ -14,7 +14,7 @@ import { validateAgentRuleMarkdownPath, validateSourceFile, validateSourcePath,
14
14
  * Target paths use the `{repo}` placeholder so definitions land in the
15
15
  * repository being deployed, not the user's home directory.
16
16
  */
17
- export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
17
+ export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
18
18
  const actions = [];
19
19
  const warnings = [];
20
20
  const platform = getPlatformKey();
@@ -43,7 +43,7 @@ export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSo
43
43
  supportedTargets.push({
44
44
  agentId,
45
45
  confidence: agent.provenance.agentDefinitions ?? "provisional",
46
- target: resolvePlaceholders(support.path[platform], entry.name, home, repo),
46
+ target: resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace),
47
47
  });
48
48
  }
49
49
  if (supportedTargets.length === 0) {
@@ -55,6 +55,7 @@ export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSo
55
55
  await validateSourceFile(source, entry.path);
56
56
  for (const target of supportedTargets) {
57
57
  validateAgentRuleMarkdownPath(entry.path, target.agentId);
58
+ await validateInstructionFileRequirements(source, entry.path, target.agentId);
58
59
  actions.push({
59
60
  kind: "file-write",
60
61
  skill: entry.name,
@@ -66,7 +67,7 @@ export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSo
66
67
  }
67
68
  return { actions, warnings };
68
69
  }
69
- export function compileAgentDefinitionReverts(entry, agentFilter, home, repo) {
70
+ export function compileAgentDefinitionReverts(entry, agentFilter, home, repo, workspace) {
70
71
  const actions = [];
71
72
  const platform = getPlatformKey();
72
73
  for (const agentId of entry.agents) {
@@ -78,7 +79,7 @@ export function compileAgentDefinitionReverts(entry, agentFilter, home, repo) {
78
79
  support.status === "unsupported" ||
79
80
  support.status === "planned")
80
81
  continue;
81
- const target = resolvePlaceholders(support.path[platform], entry.name, home, repo);
82
+ const target = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
82
83
  actions.push({
83
84
  kind: "file-write",
84
85
  skill: entry.name,
@@ -10,4 +10,4 @@ export interface AdapterResult {
10
10
  actions: AdapterAction[];
11
11
  warnings: PlanWarning[];
12
12
  }
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[]): Promise<AdapterResult>;
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>;
@@ -3,16 +3,16 @@ import { compileMcpServerActions, compileMcpServerReverts } from "./mcp.js";
3
3
  import { compilePermissionsActions, compilePermissionsReverts, } from "./permissions.js";
4
4
  import { compileAgentRuleActions, compileAgentRuleReverts } from "./rules.js";
5
5
  export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
6
- export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, agentDefinitions) {
6
+ export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, agentDefinitions, workspace) {
7
7
  const actions = [];
8
8
  const warnings = [];
9
9
  for (const entry of mcpServers) {
10
- const r = compileMcpServerActions(entry, detectedAgents, home, repo);
10
+ const r = compileMcpServerActions(entry, detectedAgents, home, repo, workspace);
11
11
  actions.push(...r.actions);
12
12
  warnings.push(...r.warnings);
13
13
  }
14
14
  for (const entry of agentRules) {
15
- const r = await compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo);
15
+ const r = await compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace);
16
16
  actions.push(...r.actions);
17
17
  warnings.push(...r.warnings);
18
18
  }
@@ -22,7 +22,7 @@ export async function compileAdapterActions(mcpServers, agentRules, permissions,
22
22
  warnings.push(...r.warnings);
23
23
  }
24
24
  for (const entry of agentDefinitions ?? []) {
25
- const r = await compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo);
25
+ const r = await compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace);
26
26
  actions.push(...r.actions);
27
27
  warnings.push(...r.warnings);
28
28
  }
@@ -4,5 +4,5 @@ export interface McpAdapterResult {
4
4
  actions: Array<ConfigPatchDeployAction | TomlPatchDeployAction | FrontmatterEmitDeployAction>;
5
5
  warnings: PlanWarning[];
6
6
  }
7
- export declare function compileMcpServerActions(entry: McpServerEntry, detectedAgents: AgentId[], home: string, repo?: string): McpAdapterResult;
8
- export declare function compileMcpServerReverts(entry: McpServerEntry, agentFilter: AgentId[] | null, home: string, repo?: string): Array<ConfigPatchRevertAction | TomlPatchRevertAction | FrontmatterEmitRevertAction>;
7
+ export declare function compileMcpServerActions(entry: McpServerEntry, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): McpAdapterResult;
8
+ export declare function compileMcpServerReverts(entry: McpServerEntry, agentFilter: AgentId[] | null, home: string, repo?: string, workspace?: string): Array<ConfigPatchRevertAction | TomlPatchRevertAction | FrontmatterEmitRevertAction>;
@@ -46,7 +46,7 @@ function buildMcpDeployAction(entry, agentId, resolvedTarget, confidence, mcpPat
46
46
  confidence,
47
47
  };
48
48
  }
49
- export function compileMcpServerActions(entry, detectedAgents, home, repo) {
49
+ export function compileMcpServerActions(entry, detectedAgents, home, repo, workspace) {
50
50
  const actions = [];
51
51
  const warnings = [];
52
52
  const platform = getPlatformKey();
@@ -70,7 +70,7 @@ export function compileMcpServerActions(entry, detectedAgents, home, repo) {
70
70
  continue;
71
71
  }
72
72
  validateMcpServerConfigShape(entry.config, entry.name, agentId);
73
- const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo);
73
+ const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
74
74
  const resolvedTarget = path.resolve(rawTarget);
75
75
  const confidence = agent.provenance.mcpConfig ?? "provisional";
76
76
  const mcpPatchKey = support.mcpPatchKey ?? "mcpServers";
@@ -78,7 +78,7 @@ export function compileMcpServerActions(entry, detectedAgents, home, repo) {
78
78
  }
79
79
  return { actions, warnings };
80
80
  }
81
- export function compileMcpServerReverts(entry, agentFilter, home, repo) {
81
+ export function compileMcpServerReverts(entry, agentFilter, home, repo, workspace) {
82
82
  const actions = [];
83
83
  const platform = getPlatformKey();
84
84
  for (const agentId of entry.agents) {
@@ -90,7 +90,7 @@ export function compileMcpServerReverts(entry, agentFilter, home, repo) {
90
90
  support.status === "unsupported" ||
91
91
  support.status === "planned")
92
92
  continue;
93
- const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo);
93
+ const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
94
94
  const target = path.resolve(rawTarget);
95
95
  if (isMarkdownTarget(target)) {
96
96
  actions.push({
@@ -4,5 +4,5 @@ export interface RulesAdapterResult {
4
4
  actions: FileWriteDeployAction[];
5
5
  warnings: PlanWarning[];
6
6
  }
7
- export declare function compileAgentRuleActions(entry: AgentRuleEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string): Promise<RulesAdapterResult>;
8
- export declare function compileAgentRuleReverts(entry: AgentRuleEntry, agentFilter: AgentId[] | null, home: string, repo?: string): FileWriteRevertAction[];
7
+ export declare function compileAgentRuleActions(entry: AgentRuleEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<RulesAdapterResult>;
8
+ export declare function compileAgentRuleReverts(entry: AgentRuleEntry, agentFilter: AgentId[] | null, home: string, repo?: string, workspace?: string): FileWriteRevertAction[];
@@ -1,20 +1,25 @@
1
1
  import path from "node:path";
2
2
  import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
3
3
  import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
4
- import { validateAgentRuleMarkdownPath, validateSourceFile, validateSourcePath, } from "../validation.js";
4
+ import { validateAgentRuleMarkdownPath, validateInstructionFileRequirements, validateSourceFile, validateSourcePath, } from "../validation.js";
5
5
  function resolveRulesSupport(agentId, scope) {
6
6
  const agent = AGENT_REGISTRY_BY_ID[agentId];
7
7
  if (scope === "repo") {
8
8
  return agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport;
9
9
  }
10
+ if (scope === "workspace") {
11
+ return (agent?.agentRulesWorkspaceSupport ??
12
+ agent?.agentRulesRepoSupport ??
13
+ agent?.agentRulesSupport);
14
+ }
10
15
  return agent?.agentRulesSupport;
11
16
  }
12
- function resolveAgentTarget(agentId, entry, home, repo, platform) {
17
+ function resolveAgentTarget(agentId, entry, home, repo, platform, workspace, allTargetAgentIds) {
13
18
  const support = resolveRulesSupport(agentId, entry.scope);
14
19
  if (!support || support.status === "unsupported") {
15
20
  return {
16
21
  kind: "confidence",
17
- message: `agentRules: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported instruction schema"} and ${support?.status === "unsupported" ? support.reason : "does not expose a supported rules adapter"} — skipping "${entry.name}"`,
22
+ message: `agentRules: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported instruction schema"} (unsupported) and ${support?.status === "unsupported" ? support.reason : "does not expose a supported rules adapter"} — skipping "${entry.name}"`,
18
23
  };
19
24
  }
20
25
  if (support.status === "planned") {
@@ -23,20 +28,37 @@ function resolveAgentTarget(agentId, entry, home, repo, platform) {
23
28
  message: `agentRules: agent "${agentId}" rules support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
24
29
  };
25
30
  }
31
+ // shared-via: when requiresPrimary is set and the primary agent is absent
32
+ // from the target list, emit a guidance warning instead of deploying.
33
+ if (support.surfaceKind?.kind === "shared-via" &&
34
+ support.surfaceKind.requiresPrimary &&
35
+ allTargetAgentIds &&
36
+ !allTargetAgentIds.includes(support.surfaceKind.via)) {
37
+ return {
38
+ kind: "confidence",
39
+ message: `agentRules: agent "${agentId}" reads this surface via "${support.surfaceKind.via}" — add "${support.surfaceKind.via}" to the entry's agents list to deploy to this surface, or deploy via the "${support.surfaceKind.via}" agentRules target instead`,
40
+ };
41
+ }
26
42
  if (entry.scope === "repo" && !repo) {
27
43
  return {
28
44
  kind: "confidence",
29
45
  message: `agentRules: scope "repo" requires a repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
30
46
  };
31
47
  }
48
+ if (entry.scope === "workspace" && !workspace && !repo) {
49
+ return {
50
+ kind: "confidence",
51
+ message: `agentRules: scope "workspace" requires a workspace or repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
52
+ };
53
+ }
32
54
  const agent = AGENT_REGISTRY_BY_ID[agentId];
33
55
  return {
34
56
  agentId,
35
57
  confidence: agent?.provenance.agentRules ?? "provisional",
36
- target: resolvePlaceholders(support.path[platform], entry.name, home, repo),
58
+ target: resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace),
37
59
  };
38
60
  }
39
- export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
61
+ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
40
62
  const actions = [];
41
63
  const warnings = [];
42
64
  const platform = getPlatformKey();
@@ -46,7 +68,7 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
46
68
  return { actions, warnings };
47
69
  }
48
70
  for (const agentId of targetAgents) {
49
- const result = resolveAgentTarget(agentId, entry, home, repo, platform);
71
+ const result = resolveAgentTarget(agentId, entry, home, repo, platform, workspace, targetAgents);
50
72
  if ("kind" in result) {
51
73
  warnings.push(result);
52
74
  }
@@ -57,13 +79,34 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
57
79
  if (supportedTargets.length === 0) {
58
80
  return { actions, warnings };
59
81
  }
82
+ // Deduplicate: when a shared-via rider's primary is also in supportedTargets,
83
+ // skip the rider — the primary agent writes the shared surface. As a fallback,
84
+ // also dedup by resolved path so no target file is written twice.
85
+ const seenTargetPaths = new Set();
86
+ const dedupedTargets = [];
87
+ for (const t of supportedTargets) {
88
+ const sup = resolveRulesSupport(t.agentId, entry.scope);
89
+ if (sup?.status === "supported" &&
90
+ sup.surfaceKind?.kind === "shared-via" &&
91
+ supportedTargets.some((o) => o.agentId ===
92
+ sup.surfaceKind.via &&
93
+ o.target === t.target)) {
94
+ // Primary agent is present and writes the same target — skip rider.
95
+ continue;
96
+ }
97
+ if (!seenTargetPaths.has(t.target)) {
98
+ seenTargetPaths.add(t.target);
99
+ dedupedTargets.push(t);
100
+ }
101
+ }
60
102
  // Validate the shared source file only when at least one target uses the
61
103
  // current rules adapter surface.
62
104
  const source = path.resolve(sourceDir, entry.path);
63
105
  await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
64
106
  await validateSourceFile(source, entry.path);
65
- for (const target of supportedTargets) {
107
+ for (const target of dedupedTargets) {
66
108
  validateAgentRuleMarkdownPath(entry.path, target.agentId);
109
+ await validateInstructionFileRequirements(source, entry.path, target.agentId);
67
110
  actions.push({
68
111
  kind: "file-write",
69
112
  skill: entry.name,
@@ -75,23 +118,27 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
75
118
  }
76
119
  return { actions, warnings };
77
120
  }
78
- export function compileAgentRuleReverts(entry, agentFilter, home, repo) {
121
+ export function compileAgentRuleReverts(entry, agentFilter, home, repo, workspace) {
79
122
  const actions = [];
80
123
  const platform = getPlatformKey();
124
+ const seenRevertTargets = new Set();
81
125
  for (const agentId of entry.agents) {
82
126
  if (agentFilter && !agentFilter.includes(agentId))
83
127
  continue;
84
- const agent = AGENT_REGISTRY_BY_ID[agentId];
85
- const support = entry.scope === "repo"
86
- ? (agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport)
87
- : agent?.agentRulesSupport;
128
+ const support = resolveRulesSupport(agentId, entry.scope);
88
129
  if (!support ||
89
130
  support.status === "unsupported" ||
90
- support.status === "planned")
131
+ support.status === "planned") {
91
132
  continue;
133
+ }
92
134
  if (entry.scope === "repo" && !repo)
93
135
  continue;
94
- const target = resolvePlaceholders(support.path[platform], entry.name, home, repo);
136
+ if (entry.scope === "workspace" && !workspace && !repo)
137
+ continue;
138
+ const target = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
139
+ if (seenRevertTargets.has(target))
140
+ continue;
141
+ seenRevertTargets.add(target);
95
142
  actions.push({
96
143
  kind: "file-write",
97
144
  skill: entry.name,
@@ -1,6 +1,6 @@
1
1
  import type { AgentId, DeployAction, Manifest, PlannedChange, PlanWarning, SkillDirDeployAction } from "../types.ts";
2
2
  import { type RegistryPersistence } from "./ownership.ts";
3
- export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string): Promise<{
3
+ export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<{
4
4
  actions: DeployAction[];
5
5
  warnings: PlanWarning[];
6
6
  }>;