@kuznai/inception-engine 0.17.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,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 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
+
30
32
  ## Agent Compatibility Matrix
31
33
 
32
34
  | Agent | ID | Skills | macOS | Linux | Windows |
@@ -49,10 +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/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 |
53
55
  | Permissions / Approval Config | claude-code (`~/.claude/settings.json`), codex (`~/.codex/config.toml`); other agents are warned and skipped | claude-code, codex |
54
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 |
55
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 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 |
56
59
 
57
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.
58
61
 
@@ -98,7 +101,14 @@ Create an `inception.json` file at the root of your skills directory:
98
101
  {
99
102
  "name": "my-rules",
100
103
  "path": "rules/CLAUDE.md",
101
- "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"
102
112
  }
103
113
  ],
104
114
  "permissions": [
@@ -158,13 +168,28 @@ MCP server registration is supported for all agents except GitHub Copilot. Incep
158
168
 
159
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.
160
170
 
161
- 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:
162
172
 
163
173
  - **name** - Unique identifier (same format as skill names)
164
- - **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
165
175
  - **agents** - Array of agent IDs to deploy this file to
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
180
+
181
+ Instruction rule deployment is supported for implemented global, repo, and workspace surfaces. The target path depends on the agent and the `scope`:
166
182
 
167
- 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.
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"` |
191
+
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.
168
193
 
169
194
  Each **permissions** entry deploys execution and safety-oriented configuration to an agent's permission or approval surface:
170
195
 
@@ -251,8 +276,8 @@ Current `init` behavior:
251
276
  - Uses the directory name as the manifest skill name
252
277
  - Applies either the `--agents` list or all currently known agent IDs
253
278
  - Refuses to overwrite an existing `inception.json` unless `--force` is provided
254
- - Supports `--dry-run` so you can inspect the generated manifest before writing it
255
- - 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
256
281
  - Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
257
282
  - Reads `files-manifest.json` from the repo root (if present) and generates `files` entries; invalid entries are warned and skipped
258
283
  - Reads `configs-manifest.json` from the repo root (if present) and generates `configs` entries; invalid entries are warned and skipped
@@ -342,13 +367,15 @@ inception-engine init <directory> [options]
342
367
 
343
368
  | Option | Description |
344
369
  |---|---|
345
- | `--dry-run` | Show what would be done without making changes |
370
+ | `--plan` | Show what would be done without making changes |
346
371
  | `--agents <list>` | Comma-separated list of agent IDs to target (overrides deploy detection; restricts revert) |
347
372
  | `--force` | `init` only; overwrite an existing `inception.json` |
348
373
  | `--verbose` | Show detailed output including file paths |
349
374
  | `--debug` | Show full error stack traces |
350
375
  | `--help` | Show help message |
351
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
+
352
379
  ### Examples
353
380
 
354
381
  ```bash
@@ -356,7 +383,7 @@ inception-engine init <directory> [options]
356
383
  npx @kuznai/inception-engine ./my-skills-repo
357
384
 
358
385
  # Preview what would be deployed
359
- npx @kuznai/inception-engine ./my-skills-repo --dry-run
386
+ npx @kuznai/inception-engine ./my-skills-repo --plan
360
387
 
361
388
  # Deploy only to Claude Code and Codex
362
389
  npx @kuznai/inception-engine ./my-skills-repo --agents claude-code,codex
@@ -365,13 +392,13 @@ npx @kuznai/inception-engine ./my-skills-repo --agents claude-code,codex
365
392
  npx @kuznai/inception-engine revert ./my-skills-repo
366
393
 
367
394
  # Preview what would be removed
368
- npx @kuznai/inception-engine revert ./my-skills-repo --dry-run
395
+ npx @kuznai/inception-engine revert ./my-skills-repo --plan
369
396
 
370
397
  # Generate a starter manifest from discovered skill folders
371
398
  npx @kuznai/inception-engine init ./my-skills-repo
372
399
 
373
400
  # Preview the generated manifest without writing it
374
- npx @kuznai/inception-engine init ./my-skills-repo --dry-run
401
+ npx @kuznai/inception-engine init ./my-skills-repo --plan
375
402
  ```
376
403
 
377
404
  ## Sample Skills
@@ -381,8 +408,8 @@ The `limbo/` directory contains exceptional sample skills for testing purposes o
381
408
  Try them out:
382
409
 
383
410
  ```bash
384
- npx @kuznai/inception-engine init limbo --dry-run
385
- npx @kuznai/inception-engine limbo --dry-run
411
+ npx @kuznai/inception-engine init limbo --plan
412
+ npx @kuznai/inception-engine limbo --plan
386
413
  ```
387
414
 
388
415
  ## Agent Detection
@@ -36,6 +36,14 @@ export const AGENT_REGISTRY = [
36
36
  windows: ["{home}", ".claude", "CLAUDE.md"],
37
37
  },
38
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
+ },
39
47
  permissionsSupport: {
40
48
  status: "supported",
41
49
  schemaLabel: "JSON permissions config",
@@ -52,6 +60,14 @@ export const AGENT_REGISTRY = [
52
60
  windows: ["{repo}", ".claude", "agents", "{name}.md"],
53
61
  },
54
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
+ },
55
71
  },
56
72
  {
57
73
  id: "codex",
@@ -88,6 +104,14 @@ export const AGENT_REGISTRY = [
88
104
  windows: ["{home}", ".codex", "AGENTS.md"],
89
105
  },
90
106
  },
107
+ agentRulesRepoSupport: {
108
+ status: "supported",
109
+ schemaLabel: "repo-local AGENTS.md",
110
+ path: {
111
+ posix: ["{repo}", "AGENTS.md"],
112
+ windows: ["{repo}", "AGENTS.md"],
113
+ },
114
+ },
91
115
  permissionsSupport: {
92
116
  status: "supported",
93
117
  schemaLabel: "TOML approval policy config",
@@ -101,6 +125,14 @@ export const AGENT_REGISTRY = [
101
125
  schemaLabel: "dedicated agent definition directory",
102
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",
103
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
+ },
104
136
  },
105
137
  {
106
138
  id: "gemini-cli",
@@ -138,6 +170,14 @@ export const AGENT_REGISTRY = [
138
170
  windows: ["{home}", ".gemini", "GEMINI.md"],
139
171
  },
140
172
  },
173
+ agentRulesRepoSupport: {
174
+ status: "supported",
175
+ schemaLabel: "repo-local GEMINI.md",
176
+ path: {
177
+ posix: ["{repo}", "GEMINI.md"],
178
+ windows: ["{repo}", "GEMINI.md"],
179
+ },
180
+ },
141
181
  permissionsSupport: {
142
182
  status: "unsupported",
143
183
  schemaLabel: "global permissions surface",
@@ -151,6 +191,14 @@ export const AGENT_REGISTRY = [
151
191
  windows: ["{repo}", ".gemini", "agents", "{name}.md"],
152
192
  },
153
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
+ },
154
202
  },
155
203
  {
156
204
  id: "antigravity",
@@ -179,12 +227,25 @@ export const AGENT_REGISTRY = [
179
227
  windows: ["{repo}", ".agents", "rules", "{name}.md"],
180
228
  },
181
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.
182
233
  agentRulesSupport: {
183
234
  status: "supported",
184
- schemaLabel: "repo-local Markdown rules file",
235
+ surfaceKind: { kind: "shared-via", via: "gemini-cli" },
236
+ schemaLabel: "global Gemini blueprint instructions file",
185
237
  path: {
186
- posix: ["{repo}", ".agents", "rules", "{name}.md"],
187
- windows: ["{repo}", ".agents", "rules", "{name}.md"],
238
+ posix: ["{home}", ".gemini", "GEMINI.md"],
239
+ windows: ["{home}", ".gemini", "GEMINI.md"],
240
+ },
241
+ },
242
+ agentRulesRepoSupport: {
243
+ status: "supported",
244
+ surfaceKind: { kind: "shared-via", via: "gemini-cli" },
245
+ schemaLabel: "repo-local GEMINI.md",
246
+ path: {
247
+ posix: ["{repo}", "GEMINI.md"],
248
+ windows: ["{repo}", "GEMINI.md"],
188
249
  },
189
250
  },
190
251
  permissionsSupport: {
@@ -200,6 +261,12 @@ export const AGENT_REGISTRY = [
200
261
  windows: ["{repo}", ".agents", "rules", "{name}.md"],
201
262
  },
202
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,
203
270
  },
204
271
  {
205
272
  id: "opencode",
@@ -237,6 +304,14 @@ export const AGENT_REGISTRY = [
237
304
  windows: ["{appdata}", "opencode", "AGENTS.md"],
238
305
  },
239
306
  },
307
+ agentRulesRepoSupport: {
308
+ status: "supported",
309
+ schemaLabel: "repo-local AGENTS.md",
310
+ path: {
311
+ posix: ["{repo}", "AGENTS.md"],
312
+ windows: ["{repo}", "AGENTS.md"],
313
+ },
314
+ },
240
315
  permissionsSupport: {
241
316
  status: "unsupported",
242
317
  schemaLabel: "global permissions surface",
@@ -250,6 +325,11 @@ export const AGENT_REGISTRY = [
250
325
  windows: ["{repo}", ".opencode", "agents", "{name}.md"],
251
326
  },
252
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
+ },
253
333
  },
254
334
  {
255
335
  id: "github-copilot",
@@ -258,6 +338,7 @@ export const AGENT_REGISTRY = [
258
338
  // from `.claude/skills/` (the same path used by claude-code). Deploying
259
339
  // via the `claude-code` skills target automatically covers Copilot — no
260
340
  // separate `~/.copilot/skills/` path is needed or maintained.
341
+ skillsSurfaceKind: { kind: "shared-via", via: "claude-code" },
261
342
  detectPaths: {
262
343
  posix: ["{home}", ".copilot"],
263
344
  windows: ["{home}", ".copilot"],
@@ -274,10 +355,49 @@ export const AGENT_REGISTRY = [
274
355
  plannedSurface: "devcontainer (.devcontainer/devcontainer.json) and agent-frontmatter (.github/agents/*.agent.md)",
275
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",
276
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).
277
363
  agentRulesSupport: {
278
- status: "unsupported",
364
+ status: "supported",
365
+ surfaceKind: {
366
+ kind: "shared-via",
367
+ via: "claude-code",
368
+ requiresPrimary: true,
369
+ },
279
370
  schemaLabel: "Claude-native shared instructions",
280
- 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
+ },
375
+ },
376
+ agentRulesRepoSupport: {
377
+ status: "supported",
378
+ surfaceKind: {
379
+ kind: "shared-via",
380
+ via: "claude-code",
381
+ requiresPrimary: true,
382
+ },
383
+ schemaLabel: "repo-local CLAUDE.md",
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
+ },
281
401
  },
282
402
  permissionsSupport: {
283
403
  status: "unsupported",
@@ -293,6 +413,8 @@ export const AGENT_REGISTRY = [
293
413
  },
294
414
  },
295
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,
296
418
  },
297
419
  ];
298
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,
@@ -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
@@ -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>;