@kuznai/inception-engine 0.16.0 → 0.18.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,6 +27,8 @@ 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.
31
+
30
32
  ## Agent Compatibility Matrix
31
33
 
32
34
  | Agent | ID | Skills | macOS | Linux | Windows |
@@ -49,9 +51,11 @@ Managed skills overwrite their previous version. If a target exists but was not
49
51
  | File write | All agents via manifest and CLI | All agents |
50
52
  | Config patch (JSON merge) | All agents via manifest and CLI | All agents |
51
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 |
52
- | Global/Repo Rules Files | All agents (antigravity uses repo-local `.agents/rules/`); github-copilot reads CLAUDE.md natively (deploy via claude-code) | All agents |
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 |
53
55
  | Permissions / Approval Config | claude-code (`~/.claude/settings.json`), codex (`~/.codex/config.toml`); other agents are warned and skipped | claude-code, codex |
54
- | `init` manifest generation | Scans `SKILL.md` directories (`skills`), `.md` files with Claude-first agent mapping (`agentRules`), and `mcp-servers.json` (`mcpServers`); emits hints for `files/` and `configs/` directories | N/A |
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
+ | `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 |
55
59
 
56
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.
57
61
 
@@ -97,7 +101,14 @@ Create an `inception.json` file at the root of your skills directory:
97
101
  {
98
102
  "name": "my-rules",
99
103
  "path": "rules/CLAUDE.md",
100
- "agents": ["claude-code"]
104
+ "agents": ["claude-code"],
105
+ "scope": "global"
106
+ },
107
+ {
108
+ "name": "project-rules",
109
+ "path": "rules/CLAUDE.md",
110
+ "agents": ["claude-code", "codex", "gemini-cli", "opencode"],
111
+ "scope": "repo"
101
112
  }
102
113
  ],
103
114
  "permissions": [
@@ -111,6 +122,13 @@ Create an `inception.json` file at the root of your skills directory:
111
122
  }
112
123
  }
113
124
  }
125
+ ],
126
+ "agentDefinitions": [
127
+ {
128
+ "name": "code-reviewer",
129
+ "path": "agents/code-reviewer.md",
130
+ "agents": ["claude-code", "opencode", "github-copilot"]
131
+ }
114
132
  ]
115
133
  }
116
134
  ```
@@ -150,13 +168,27 @@ MCP server registration is supported for all agents except GitHub Copilot. Incep
150
168
 
151
169
  Revert removes the registered server entry from the respective configuration file. GitHub Copilot, which uses repo-scoped MCP surfaces not yet implemented by inception-engine, continues to emit a schema-aware warning and is skipped.
152
170
 
153
- Each **agentRules** entry deploys a Markdown instruction file to an agent's supported global rules file location:
171
+ Each **agentRules** entry deploys a Markdown instruction file to an agent's supported instruction file location:
154
172
 
155
173
  - **name** - Unique identifier (same format as skill names)
156
- - **path** - Relative path to the source Markdown file within the repo; supported global rules adapters require a `.md` or `.markdown` source path
174
+ - **path** - Relative path to the source Markdown file within the repo; supported rules adapters require a `.md` or `.markdown` source path
157
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`)
179
+
180
+ Instruction rule deployment is supported for all agents. The target path depends on the agent and the `scope`:
181
+
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` |
158
190
 
159
- Instruction rule deployment is supported for all agents. For most agents, this targets a single global rules file (e.g., `~/.claude/CLAUDE.md` for `claude-code`, `~/.codex/AGENTS.md` for `codex`, `~/.gemini/GEMINI.md` for `gemini-cli`, and `~/.config/opencode/AGENTS.md` for `opencode`). For `antigravity`, inception-engine deploys to repo-local instruction surfaces at `{repo}/.agents/rules/{name}.md`. For `github-copilot`, no separate deployment is needed because Copilot reads `CLAUDE.md` natively — target it via the `claude-code` agentRules entry and it reaches Copilot automatically. Revert removes the deployed rules file.
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.
160
192
 
161
193
  Each **permissions** entry deploys execution and safety-oriented configuration to an agent's permission or approval surface:
162
194
 
@@ -199,6 +231,23 @@ Valid `approval_policy` values are `"auto"`, `"manual"`, `"suggest"`, and `"on-f
199
231
 
200
232
  Revert restores the previous config values using the undo patch recorded at deploy time.
201
233
 
234
+ Each **agentDefinitions** entry deploys a Markdown agent-definition file to the repo-local agent-definitions directory of each targeted agent:
235
+
236
+ - **name** - Unique identifier (same format as skill names); used as the definition filename
237
+ - **path** - Relative path to the source Markdown file within the repo; must be a `.md` or `.markdown` file
238
+ - **agents** - Array of agent IDs to deploy this definition to
239
+
240
+ Agent-definition deployment is supported for `claude-code`, `gemini-cli`, `antigravity`, `opencode`, and `github-copilot`. For `codex`, inception-engine emits a warning and skips the entry — use `agentRules` to deploy persona instructions to Codex instead.
241
+
242
+ All targets are repo-local (they land inside the repository being deployed, not the user's home directory):
243
+ - **claude-code**: `{repo}/.claude/agents/{name}.md`
244
+ - **gemini-cli**: `{repo}/.gemini/agents/{name}.md`
245
+ - **antigravity**: `{repo}/.agents/rules/{name}.md`
246
+ - **opencode**: `{repo}/.opencode/agents/{name}.md`
247
+ - **github-copilot**: `{repo}/.github/agents/{name}.agent.md` (note the `.agent.md` suffix)
248
+
249
+ Revert removes the deployed agent-definition file.
250
+
202
251
  ## Creating Skills
203
252
 
204
253
  Each skill is a directory containing at minimum a `SKILL.md` file with YAML frontmatter:
@@ -231,6 +280,7 @@ Current `init` behavior:
231
280
  - Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
232
281
  - Reads `files-manifest.json` from the repo root (if present) and generates `files` entries; invalid entries are warned and skipped
233
282
  - Reads `configs-manifest.json` from the repo root (if present) and generates `configs` entries; invalid entries are warned and skipped
283
+ - Reads `agent-definitions-manifest.json` from the repo root (if present) and generates `agentDefinitions` entries; if absent, auto-discovers agent-definition Markdown files from `.claude/agents/`, `.gemini/agents/`, `.agents/rules/`, `.opencode/agents/`, and `.github/agents/`; invalid entries are warned and skipped
234
284
  - Emits guidance to create a sidecar manifest when a `files/` or `configs/` directory is detected but the corresponding sidecar file is absent
235
285
 
236
286
  Current `init` limitations:
@@ -280,6 +330,22 @@ Place a `configs-manifest.json` file at the repo root to have `init` populate th
280
330
 
281
331
  Invalid entries are warned and skipped; the rest are written into the generated manifest verbatim.
282
332
 
333
+ Place an `agent-definitions-manifest.json` file at the repo root to have `init` populate the `agentDefinitions` section automatically. The file must be a JSON array of agent-definition entries using the same schema as the `agentDefinitions` field in `inception.json`:
334
+
335
+ ```json
336
+ [
337
+ {
338
+ "name": "code-reviewer",
339
+ "path": "agents/code-reviewer.md",
340
+ "agents": ["claude-code", "opencode", "github-copilot"]
341
+ }
342
+ ]
343
+ ```
344
+
345
+ If no `agent-definitions-manifest.json` is present, `init` auto-discovers agent-definition Markdown files from five conventional subdirectories: `.claude/agents/`, `.gemini/agents/`, `.agents/rules/`, `.opencode/agents/`, and `.github/agents/`. Each discovered file is mapped to the owning agent(s). GitHub Copilot files named `{name}.agent.md` have the `.agent` infix stripped to derive the manifest name (e.g., `foo.agent.md` → name `foo`).
346
+
347
+ Invalid entries are warned and skipped; the rest are written into the generated manifest verbatim.
348
+
283
349
  ## CLI Reference
284
350
 
285
351
  ```
@@ -18,6 +18,7 @@ export const AGENT_REGISTRY = [
18
18
  mcpConfig: "documented",
19
19
  agentRules: "documented",
20
20
  permissions: "documented",
21
+ agentDefinitions: "documented",
21
22
  },
22
23
  mcpSupport: {
23
24
  status: "supported",
@@ -35,6 +36,14 @@ export const AGENT_REGISTRY = [
35
36
  windows: ["{home}", ".claude", "CLAUDE.md"],
36
37
  },
37
38
  },
39
+ agentRulesRepoSupport: {
40
+ status: "supported",
41
+ schemaLabel: "repo-local CLAUDE.md",
42
+ path: {
43
+ posix: ["{repo}", "CLAUDE.md"],
44
+ windows: ["{repo}", "CLAUDE.md"],
45
+ },
46
+ },
38
47
  permissionsSupport: {
39
48
  status: "supported",
40
49
  schemaLabel: "JSON permissions config",
@@ -43,6 +52,14 @@ export const AGENT_REGISTRY = [
43
52
  windows: ["{home}", ".claude", "settings.json"],
44
53
  },
45
54
  },
55
+ agentDefinitionsSupport: {
56
+ status: "supported",
57
+ schemaLabel: "repo-local agent definition Markdown file",
58
+ path: {
59
+ posix: ["{repo}", ".claude", "agents", "{name}.md"],
60
+ windows: ["{repo}", ".claude", "agents", "{name}.md"],
61
+ },
62
+ },
46
63
  },
47
64
  {
48
65
  id: "codex",
@@ -79,6 +96,14 @@ export const AGENT_REGISTRY = [
79
96
  windows: ["{home}", ".codex", "AGENTS.md"],
80
97
  },
81
98
  },
99
+ agentRulesRepoSupport: {
100
+ status: "supported",
101
+ schemaLabel: "repo-local AGENTS.md",
102
+ path: {
103
+ posix: ["{repo}", "AGENTS.md"],
104
+ windows: ["{repo}", "AGENTS.md"],
105
+ },
106
+ },
82
107
  permissionsSupport: {
83
108
  status: "supported",
84
109
  schemaLabel: "TOML approval policy config",
@@ -87,6 +112,11 @@ export const AGENT_REGISTRY = [
87
112
  windows: ["{home}", ".codex", "config.toml"],
88
113
  },
89
114
  },
115
+ agentDefinitionsSupport: {
116
+ status: "unsupported",
117
+ schemaLabel: "dedicated agent definition directory",
118
+ 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
+ },
90
120
  },
91
121
  {
92
122
  id: "gemini-cli",
@@ -106,6 +136,7 @@ export const AGENT_REGISTRY = [
106
136
  detectBinary: "documented",
107
137
  mcpConfig: "documented",
108
138
  agentRules: "documented",
139
+ agentDefinitions: "implementation-only",
109
140
  },
110
141
  mcpSupport: {
111
142
  status: "supported",
@@ -123,11 +154,27 @@ export const AGENT_REGISTRY = [
123
154
  windows: ["{home}", ".gemini", "GEMINI.md"],
124
155
  },
125
156
  },
157
+ agentRulesRepoSupport: {
158
+ status: "supported",
159
+ schemaLabel: "repo-local GEMINI.md",
160
+ path: {
161
+ posix: ["{repo}", "GEMINI.md"],
162
+ windows: ["{repo}", "GEMINI.md"],
163
+ },
164
+ },
126
165
  permissionsSupport: {
127
166
  status: "unsupported",
128
167
  schemaLabel: "global permissions surface",
129
168
  reason: "Gemini CLI does not expose a documented global per-user permission or approval config surface",
130
169
  },
170
+ agentDefinitionsSupport: {
171
+ status: "supported",
172
+ schemaLabel: "repo-local agent definition Markdown file",
173
+ path: {
174
+ posix: ["{repo}", ".gemini", "agents", "{name}.md"],
175
+ windows: ["{repo}", ".gemini", "agents", "{name}.md"],
176
+ },
177
+ },
131
178
  },
132
179
  {
133
180
  id: "antigravity",
@@ -145,6 +192,8 @@ export const AGENT_REGISTRY = [
145
192
  skills: "implementation-only",
146
193
  detectPaths: "implementation-only",
147
194
  detectBinary: "provisional",
195
+ agentRules: "documented",
196
+ agentDefinitions: "documented",
148
197
  },
149
198
  mcpSupport: {
150
199
  status: "supported",
@@ -162,11 +211,27 @@ export const AGENT_REGISTRY = [
162
211
  windows: ["{repo}", ".agents", "rules", "{name}.md"],
163
212
  },
164
213
  },
214
+ agentRulesRepoSupport: {
215
+ status: "supported",
216
+ schemaLabel: "repo-local Markdown rules file",
217
+ path: {
218
+ posix: ["{repo}", ".agents", "rules", "{name}.md"],
219
+ windows: ["{repo}", ".agents", "rules", "{name}.md"],
220
+ },
221
+ },
165
222
  permissionsSupport: {
166
223
  status: "unsupported",
167
224
  schemaLabel: "global permissions surface",
168
225
  reason: "Antigravity does not expose a documented global per-user permission or approval config surface",
169
226
  },
227
+ agentDefinitionsSupport: {
228
+ status: "supported",
229
+ schemaLabel: "repo-local agent definition Markdown file",
230
+ path: {
231
+ posix: ["{repo}", ".agents", "rules", "{name}.md"],
232
+ windows: ["{repo}", ".agents", "rules", "{name}.md"],
233
+ },
234
+ },
170
235
  },
171
236
  {
172
237
  id: "opencode",
@@ -185,6 +250,7 @@ export const AGENT_REGISTRY = [
185
250
  detectPaths: "documented",
186
251
  detectBinary: "documented",
187
252
  agentRules: "documented",
253
+ agentDefinitions: "documented",
188
254
  },
189
255
  mcpSupport: {
190
256
  status: "supported",
@@ -203,45 +269,75 @@ export const AGENT_REGISTRY = [
203
269
  windows: ["{appdata}", "opencode", "AGENTS.md"],
204
270
  },
205
271
  },
272
+ agentRulesRepoSupport: {
273
+ status: "supported",
274
+ schemaLabel: "repo-local AGENTS.md",
275
+ path: {
276
+ posix: ["{repo}", "AGENTS.md"],
277
+ windows: ["{repo}", "AGENTS.md"],
278
+ },
279
+ },
206
280
  permissionsSupport: {
207
281
  status: "unsupported",
208
282
  schemaLabel: "global permissions surface",
209
283
  reason: "OpenCode does not expose a documented global per-user permission or approval config surface",
210
284
  },
285
+ agentDefinitionsSupport: {
286
+ status: "supported",
287
+ schemaLabel: "repo-local agent definition Markdown file",
288
+ path: {
289
+ posix: ["{repo}", ".opencode", "agents", "{name}.md"],
290
+ windows: ["{repo}", ".opencode", "agents", "{name}.md"],
291
+ },
292
+ },
211
293
  },
212
294
  {
213
295
  id: "github-copilot",
214
296
  displayName: "GitHub Copilot",
215
- skills: {
216
- posix: ["{home}", ".copilot", "skills", "{name}"],
217
- windows: ["{home}", ".copilot", "skills", "{name}"],
218
- },
297
+ // No `skills` field: GitHub Copilot natively executes Claude-style skills
298
+ // from `.claude/skills/` (the same path used by claude-code). Deploying
299
+ // via the `claude-code` skills target automatically covers Copilot — no
300
+ // separate `~/.copilot/skills/` path is needed or maintained.
219
301
  detectPaths: {
220
302
  posix: ["{home}", ".copilot"],
221
303
  windows: ["{home}", ".copilot"],
222
304
  },
223
305
  detectBinary: "github-copilot",
224
306
  provenance: {
225
- skills: "documented",
226
307
  detectPaths: "documented",
227
308
  detectBinary: "documented",
309
+ agentDefinitions: "documented",
228
310
  },
229
311
  mcpSupport: {
230
- status: "unsupported",
312
+ status: "planned",
231
313
  schemaLabel: "repo-scoped MCP surfaces",
232
- reason: "GitHub Copilot MCP support depends on repo-scoped files such as devcontainer or agent-frontmatter mappings, which are not translated here",
314
+ plannedSurface: "devcontainer (.devcontainer/devcontainer.json) and agent-frontmatter (.github/agents/*.agent.md)",
315
+ 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",
233
316
  },
234
317
  agentRulesSupport: {
235
318
  status: "unsupported",
236
319
  schemaLabel: "Claude-native shared instructions",
237
320
  reason: 'GitHub Copilot reads CLAUDE.md natively, so deploy via the "claude-code" agentRules target instead of a separate rules surface',
238
321
  },
322
+ agentRulesRepoSupport: {
323
+ status: "unsupported",
324
+ 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',
326
+ },
239
327
  permissionsSupport: {
240
328
  status: "unsupported",
241
329
  schemaLabel: "global permissions surface",
242
330
  reason: "GitHub Copilot permissions are managed via organization policy, not a deployable per-user config surface",
243
331
  },
244
- policyNote: "Organization policies may override locally deployed skills. Verify with your GitHub org admin if deployed skills are not active.",
332
+ agentDefinitionsSupport: {
333
+ status: "supported",
334
+ schemaLabel: "repo-local agent definition Markdown file",
335
+ path: {
336
+ posix: ["{repo}", ".github", "agents", "{name}.agent.md"],
337
+ windows: ["{repo}", ".github", "agents", "{name}.agent.md"],
338
+ },
339
+ },
340
+ policyNote: "Organization policies may override locally deployed configuration. Verify with your GitHub org admin if deployed skills or rules are not active.",
245
341
  },
246
342
  ];
247
343
  export const AGENT_REGISTRY_BY_ID = Object.fromEntries(AGENT_REGISTRY.map((a) => [a.id, a]));
@@ -42,6 +42,8 @@ function validateManifest(data, filePath) {
42
42
  if (issuePath.length === 1 &&
43
43
  (issuePath[0] === "mcpServers" ||
44
44
  issuePath[0] === "agentRules" ||
45
+ issuePath[0] === "agentDefinitions" ||
46
+ issuePath[0] === "permissions" ||
45
47
  issuePath[0] === "files" ||
46
48
  issuePath[0] === "configs")) {
47
49
  throw new UserError("MANIFEST_INVALID", `${filePath}: "${issuePath[0]}" must be an array`);
@@ -0,0 +1,20 @@
1
+ import type { AgentDefinitionEntry } from "../../schemas/manifest.ts";
2
+ import type { AgentId, FileWriteDeployAction, FileWriteRevertAction, PlanWarning } from "../../types.ts";
3
+ export interface AgentDefinitionsAdapterResult {
4
+ actions: FileWriteDeployAction[];
5
+ warnings: PlanWarning[];
6
+ }
7
+ /**
8
+ * Compiles deploy actions for agentDefinitions manifest entries.
9
+ *
10
+ * Agent definition files are Markdown files (typically with YAML frontmatter)
11
+ * that define custom agents or subagents for a given platform. Unlike
12
+ * agentRules (which target a single shared global file per agent), each
13
+ * definition entry produces a separate file named after the entry in the
14
+ * agent's dedicated agent-definitions directory (repo-local).
15
+ *
16
+ * Target paths use the `{repo}` placeholder so definitions land in the
17
+ * repository being deployed, not the user's home directory.
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[];
@@ -0,0 +1,90 @@
1
+ import path from "node:path";
2
+ import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
3
+ import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
4
+ import { validateAgentRuleMarkdownPath, validateSourceFile, validateSourcePath, } from "../validation.js";
5
+ /**
6
+ * Compiles deploy actions for agentDefinitions manifest entries.
7
+ *
8
+ * Agent definition files are Markdown files (typically with YAML frontmatter)
9
+ * that define custom agents or subagents for a given platform. Unlike
10
+ * agentRules (which target a single shared global file per agent), each
11
+ * definition entry produces a separate file named after the entry in the
12
+ * agent's dedicated agent-definitions directory (repo-local).
13
+ *
14
+ * Target paths use the `{repo}` placeholder so definitions land in the
15
+ * repository being deployed, not the user's home directory.
16
+ */
17
+ export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
18
+ const actions = [];
19
+ const warnings = [];
20
+ const platform = getPlatformKey();
21
+ const targetAgents = entry.agents.filter((agentId) => detectedAgents.includes(agentId));
22
+ const supportedTargets = [];
23
+ if (targetAgents.length === 0) {
24
+ return { actions, warnings };
25
+ }
26
+ for (const agentId of targetAgents) {
27
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
28
+ const support = agent?.agentDefinitionsSupport;
29
+ if (!support || support.status === "unsupported") {
30
+ warnings.push({
31
+ kind: "confidence",
32
+ message: `agentDefinitions: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported agent-definition schema"} and ${support?.status === "unsupported" ? support.reason : "does not expose a supported agent-definitions adapter"} — skipping "${entry.name}"`,
33
+ });
34
+ continue;
35
+ }
36
+ if (support.status === "planned") {
37
+ warnings.push({
38
+ kind: "confidence",
39
+ message: `agentDefinitions: agent "${agentId}" agent-definitions support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
40
+ });
41
+ continue;
42
+ }
43
+ supportedTargets.push({
44
+ agentId,
45
+ confidence: agent.provenance.agentDefinitions ?? "provisional",
46
+ target: resolvePlaceholders(support.path[platform], entry.name, home, repo),
47
+ });
48
+ }
49
+ if (supportedTargets.length === 0) {
50
+ return { actions, warnings };
51
+ }
52
+ // Validate the shared source file only when at least one target is active.
53
+ const source = path.resolve(sourceDir, entry.path);
54
+ await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
55
+ await validateSourceFile(source, entry.path);
56
+ for (const target of supportedTargets) {
57
+ validateAgentRuleMarkdownPath(entry.path, target.agentId);
58
+ actions.push({
59
+ kind: "file-write",
60
+ skill: entry.name,
61
+ agent: target.agentId,
62
+ source,
63
+ target: target.target,
64
+ confidence: target.confidence,
65
+ });
66
+ }
67
+ return { actions, warnings };
68
+ }
69
+ export function compileAgentDefinitionReverts(entry, agentFilter, home, repo) {
70
+ const actions = [];
71
+ const platform = getPlatformKey();
72
+ for (const agentId of entry.agents) {
73
+ if (agentFilter && !agentFilter.includes(agentId))
74
+ continue;
75
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
76
+ const support = agent?.agentDefinitionsSupport;
77
+ if (!support ||
78
+ support.status === "unsupported" ||
79
+ support.status === "planned")
80
+ continue;
81
+ const target = resolvePlaceholders(support.path[platform], entry.name, home, repo);
82
+ actions.push({
83
+ kind: "file-write",
84
+ skill: entry.name,
85
+ agent: agentId,
86
+ target,
87
+ });
88
+ }
89
+ return actions;
90
+ }
@@ -1,16 +1,28 @@
1
1
  /**
2
- * Serializes a flat (one-level) object to a YAML block suitable for use inside
3
- * `---` frontmatter delimiters.
2
+ * Splits a Markdown string into YAML frontmatter and the remaining body.
3
+ * Expects the file to start with --- delimiter.
4
+ */
5
+ export declare function splitFrontmatter(raw: string): {
6
+ frontmatter: string;
7
+ body: string;
8
+ };
9
+ /**
10
+ * Parses a Markdown string with YAML frontmatter.
11
+ */
12
+ export declare function parseFrontmatterDocument<T = Record<string, unknown>>(raw: string): {
13
+ attributes: T;
14
+ body: string;
15
+ };
16
+ /**
17
+ * Serializes an object to a YAML block.
4
18
  */
5
19
  export declare function serializeFrontmatter(data: Record<string, unknown>): string;
6
20
  /**
7
21
  * Builds the full content of a frontmatter-bearing Markdown file.
8
- * If `body` is provided it is appended after the closing delimiter.
9
22
  */
10
23
  export declare function buildFrontmatterDocument(frontmatter: Record<string, unknown>, body?: string): string;
11
24
  /**
12
- * Reads an existing `.md` file and parses its frontmatter (using the
13
- * `front-matter` package). Returns the parsed attributes and raw body.
25
+ * Reads an existing `.md` file and parses its frontmatter.
14
26
  * Returns `{ attributes: {}, body: "" }` if the file does not exist.
15
27
  */
16
28
  export declare function readFrontmatterFile(filePath: string): Promise<{
@@ -1,71 +1,52 @@
1
1
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
- import { createRequire } from "node:module";
3
2
  import path from "node:path";
4
- const _require = createRequire(import.meta.url);
5
- // front-matter is a CommonJS module whose export is the callable parse function.
6
- const parseFrontmatter = _require("front-matter");
7
- // ---------------------------------------------------------------------------
8
- // Hand-rolled flat-YAML serializer
9
- // Handles the shapes found in MCP server descriptors: strings, numbers,
10
- // booleans, string arrays, and one-level nested objects (e.g. "env").
11
- // ---------------------------------------------------------------------------
12
- function serializeScalar(value) {
13
- if (typeof value === "string") {
14
- const needsQuotes = /[:#[\]{},|>&*!'"@`]|^\s|\s$/.test(value);
15
- return needsQuotes
16
- ? `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
17
- : value;
3
+ import YAML from "yaml";
4
+ /**
5
+ * Splits a Markdown string into YAML frontmatter and the remaining body.
6
+ * Expects the file to start with --- delimiter.
7
+ */
8
+ export function splitFrontmatter(raw) {
9
+ const lines = raw.split(/\r?\n/);
10
+ if (lines[0]?.trim() !== "---") {
11
+ return { frontmatter: "", body: raw };
18
12
  }
19
- return String(value);
20
- }
21
- function serializeArray(key, arr, pad) {
22
- if (arr.length === 0)
23
- return `${pad}${key}: []`;
24
- const items = arr.map((item) => {
25
- const scalar = typeof item === "string" ||
26
- typeof item === "number" ||
27
- typeof item === "boolean"
28
- ? serializeScalar(item)
29
- : JSON.stringify(item);
30
- return `${pad} - ${scalar}`;
31
- });
32
- return [`${pad}${key}:`, ...items].join("\n");
33
- }
34
- function serializeObject(key, obj, pad) {
35
- const inner = serializeFrontmatterAtDepth(obj, `${pad} `);
36
- return inner ? `${pad}${key}:\n${inner}` : `${pad}${key}: {}`;
13
+ const closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
14
+ if (closingIndex === -1) {
15
+ return { frontmatter: "", body: raw };
16
+ }
17
+ // Preserve the actual lines for the body to avoid normalizing line endings if not needed
18
+ // but for frontmatter block, we join with \n for the YAML parser
19
+ const frontmatter = lines.slice(1, closingIndex).join("\n");
20
+ const body = lines.slice(closingIndex + 1).join("\n");
21
+ return { frontmatter, body };
37
22
  }
38
- function serializeFrontmatterAtDepth(data, pad) {
39
- const lines = [];
40
- for (const [key, value] of Object.entries(data)) {
41
- if (value === null || value === undefined)
42
- continue;
43
- if (Array.isArray(value)) {
44
- lines.push(serializeArray(key, value, pad));
45
- }
46
- else if (typeof value === "object") {
47
- lines.push(serializeObject(key, value, pad));
48
- }
49
- else {
50
- lines.push(`${pad}${key}: ${serializeScalar(value)}`);
51
- }
23
+ /**
24
+ * Parses a Markdown string with YAML frontmatter.
25
+ */
26
+ export function parseFrontmatterDocument(raw) {
27
+ const { frontmatter, body } = splitFrontmatter(raw);
28
+ if (!(frontmatter || raw.startsWith("---"))) {
29
+ return { attributes: {}, body };
52
30
  }
53
- return lines.join("\n");
31
+ const attributes = YAML.parse(frontmatter);
32
+ return { attributes: (attributes || {}), body };
54
33
  }
55
34
  /**
56
- * Serializes a flat (one-level) object to a YAML block suitable for use inside
57
- * `---` frontmatter delimiters.
35
+ * Serializes an object to a YAML block.
58
36
  */
59
37
  export function serializeFrontmatter(data) {
60
- return serializeFrontmatterAtDepth(data, "");
38
+ // Using a consistent indentation that matches standard YAML
39
+ return YAML.stringify(data, { indent: 2 }).trim();
61
40
  }
62
41
  /**
63
42
  * Builds the full content of a frontmatter-bearing Markdown file.
64
- * If `body` is provided it is appended after the closing delimiter.
65
43
  */
66
44
  export function buildFrontmatterDocument(frontmatter, body = "") {
67
45
  const serialized = serializeFrontmatter(frontmatter);
68
- return `---\n${serialized}\n---\n${body}`;
46
+ // Ensure exactly one newline after the closing delimiter before the body
47
+ const cleanBody = body.trimStart();
48
+ const separator = cleanBody ? "\n\n" : "\n";
49
+ return `---\n${serialized}\n---\n${separator}${cleanBody}`;
69
50
  }
70
51
  function createAtomicTempPath(targetPath) {
71
52
  return `${targetPath}.inception-tmp-${process.pid}-${Date.now()}-${Math.random()
@@ -73,8 +54,7 @@ function createAtomicTempPath(targetPath) {
73
54
  .slice(2)}`;
74
55
  }
75
56
  /**
76
- * Reads an existing `.md` file and parses its frontmatter (using the
77
- * `front-matter` package). Returns the parsed attributes and raw body.
57
+ * Reads an existing `.md` file and parses its frontmatter.
78
58
  * Returns `{ attributes: {}, body: "" }` if the file does not exist.
79
59
  */
80
60
  export async function readFrontmatterFile(filePath) {
@@ -88,8 +68,7 @@ export async function readFrontmatterFile(filePath) {
88
68
  return { attributes: {}, body: "" };
89
69
  throw err;
90
70
  }
91
- const parsed = parseFrontmatter(raw);
92
- return { attributes: parsed.attributes, body: parsed.body };
71
+ return parseFrontmatterDocument(raw);
93
72
  }
94
73
  /**
95
74
  * Atomically writes a Markdown file with the given frontmatter and optional