@kuznai/inception-engine 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +48 -22
  2. package/dist/{config → src/config}/agents.js +87 -14
  3. package/dist/{core → src/core}/adapters/agent-definitions.d.ts +0 -12
  4. package/dist/src/core/adapters/agent-definitions.js +162 -0
  5. package/dist/{core → src/core}/adapters/frontmatter.d.ts +10 -0
  6. package/dist/{core → src/core}/adapters/frontmatter.js +23 -6
  7. package/dist/{core → src/core}/adapters/mcp.js +17 -20
  8. package/dist/{core → src/core}/adapters/permissions.js +16 -20
  9. package/dist/{core → src/core}/adapters/rules.js +26 -50
  10. package/dist/src/core/capabilities.d.ts +47 -0
  11. package/dist/src/core/capabilities.js +280 -0
  12. package/dist/{core → src/core}/deploy.js +70 -41
  13. package/dist/{core → src/core}/init.js +18 -24
  14. package/dist/src/core/merge-patch.d.ts +4 -0
  15. package/dist/src/core/merge-patch.js +46 -0
  16. package/dist/{core → src/core}/ownership.js +13 -0
  17. package/dist/{core → src/core}/preflight.js +141 -29
  18. package/dist/{core → src/core}/revert.js +86 -26
  19. package/dist/{core → src/core}/validation.js +20 -0
  20. package/dist/{schemas → src/schemas}/manifest.d.ts +20 -0
  21. package/dist/{schemas → src/schemas}/manifest.js +11 -0
  22. package/dist/{schemas → src/schemas}/registry.d.ts +36 -0
  23. package/dist/{schemas → src/schemas}/registry.js +12 -4
  24. package/dist/{types.d.ts → src/types.d.ts} +13 -0
  25. package/dist/test/helpers/fs.d.ts +2 -0
  26. package/dist/test/helpers/fs.js +17 -0
  27. package/dist/test/helpers/path.d.ts +10 -0
  28. package/dist/test/helpers/path.js +15 -0
  29. package/dist/test/helpers/skill-dir-scenarios.d.ts +7 -0
  30. package/dist/test/helpers/skill-dir-scenarios.js +206 -0
  31. package/dist/test/helpers/skill-dir.d.ts +7 -0
  32. package/dist/test/helpers/skill-dir.js +46 -0
  33. package/dist/test/os/cross-platform/cross-platform.test.d.ts +1 -0
  34. package/dist/test/os/cross-platform/cross-platform.test.js +328 -0
  35. package/dist/test/os/posix/skill-dir.test.d.ts +1 -0
  36. package/dist/test/os/posix/skill-dir.test.js +19 -0
  37. package/dist/test/os/windows/revert-integration.test.d.ts +1 -0
  38. package/dist/test/os/windows/revert-integration.test.js +72 -0
  39. package/dist/test/os/windows/skill-dir.test.d.ts +1 -0
  40. package/dist/test/os/windows/skill-dir.test.js +19 -0
  41. package/dist/test/unit/adapters.test.d.ts +1 -0
  42. package/dist/test/unit/adapters.test.js +947 -0
  43. package/dist/test/unit/capabilities.test.d.ts +1 -0
  44. package/dist/test/unit/capabilities.test.js +71 -0
  45. package/dist/test/unit/cli.test.d.ts +1 -0
  46. package/dist/test/unit/cli.test.js +834 -0
  47. package/dist/test/unit/deploy.test.d.ts +1 -0
  48. package/dist/test/unit/deploy.test.js +2481 -0
  49. package/dist/test/unit/detect.test.d.ts +1 -0
  50. package/dist/test/unit/detect.test.js +192 -0
  51. package/dist/test/unit/enterprise.test.d.ts +1 -0
  52. package/dist/test/unit/enterprise.test.js +106 -0
  53. package/dist/test/unit/frontmatter.test.d.ts +1 -0
  54. package/dist/test/unit/frontmatter.test.js +102 -0
  55. package/dist/test/unit/gemini-north-star.test.d.ts +1 -0
  56. package/dist/test/unit/gemini-north-star.test.js +243 -0
  57. package/dist/test/unit/manifest.test.d.ts +1 -0
  58. package/dist/test/unit/manifest.test.js +737 -0
  59. package/dist/test/unit/migration-golden.test.d.ts +1 -0
  60. package/dist/test/unit/migration-golden.test.js +197 -0
  61. package/dist/test/unit/ownership.test.d.ts +1 -0
  62. package/dist/test/unit/ownership.test.js +587 -0
  63. package/dist/test/unit/preflight.test.d.ts +1 -0
  64. package/dist/test/unit/preflight.test.js +501 -0
  65. package/dist/test/unit/resolve.test.d.ts +1 -0
  66. package/dist/test/unit/resolve.test.js +119 -0
  67. package/dist/test/unit/revert.test.d.ts +1 -0
  68. package/dist/test/unit/revert.test.js +1004 -0
  69. package/dist/test/unit/toml.test.d.ts +1 -0
  70. package/dist/test/unit/toml.test.js +55 -0
  71. package/package.json +1 -1
  72. package/dist/core/adapters/agent-definitions.js +0 -91
  73. /package/dist/{config → src/config}/agents.d.ts +0 -0
  74. /package/dist/{config → src/config}/manifest.d.ts +0 -0
  75. /package/dist/{config → src/config}/manifest.js +0 -0
  76. /package/dist/{core → src/core}/adapters/index.d.ts +0 -0
  77. /package/dist/{core → src/core}/adapters/index.js +0 -0
  78. /package/dist/{core → src/core}/adapters/mcp.d.ts +0 -0
  79. /package/dist/{core → src/core}/adapters/permissions.d.ts +0 -0
  80. /package/dist/{core → src/core}/adapters/rules.d.ts +0 -0
  81. /package/dist/{core → src/core}/adapters/toml.d.ts +0 -0
  82. /package/dist/{core → src/core}/adapters/toml.js +0 -0
  83. /package/dist/{core → src/core}/deploy.d.ts +0 -0
  84. /package/dist/{core → src/core}/detect.d.ts +0 -0
  85. /package/dist/{core → src/core}/detect.js +0 -0
  86. /package/dist/{core → src/core}/init.d.ts +0 -0
  87. /package/dist/{core → src/core}/ownership.d.ts +0 -0
  88. /package/dist/{core → src/core}/preflight.d.ts +0 -0
  89. /package/dist/{core → src/core}/resolve.d.ts +0 -0
  90. /package/dist/{core → src/core}/resolve.js +0 -0
  91. /package/dist/{core → src/core}/revert.d.ts +0 -0
  92. /package/dist/{core → src/core}/runtime-paths.d.ts +0 -0
  93. /package/dist/{core → src/core}/runtime-paths.js +0 -0
  94. /package/dist/{core → src/core}/validation.d.ts +0 -0
  95. /package/dist/{errors.d.ts → src/errors.d.ts} +0 -0
  96. /package/dist/{errors.js → src/errors.js} +0 -0
  97. /package/dist/{formatters.d.ts → src/formatters.d.ts} +0 -0
  98. /package/dist/{formatters.js → src/formatters.js} +0 -0
  99. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  100. /package/dist/{index.js → src/index.js} +0 -0
  101. /package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
  102. /package/dist/{logger.js → src/logger.js} +0 -0
  103. /package/dist/{schemas → src/schemas}/errors.d.ts +0 -0
  104. /package/dist/{schemas → src/schemas}/errors.js +0 -0
  105. /package/dist/{types.js → src/types.js} +0 -0
package/README.md CHANGED
@@ -8,7 +8,7 @@ GitHub Copilot is no longer treated as a separate instruction or skill target in
8
8
 
9
9
  The broader portability layer is the roadmap direction, but this README focuses on what is working now.
10
10
 
11
- `init` is available as a bootstrap command. It scans for directories containing `SKILL.md`, discovers agent-rules Markdown files using the Claude-first portability conventions, and reads `mcp-servers.json` from the repo root to populate `mcpServers`. `files` and `configs` remain empty — `init` emits guidance when it detects a `files/` or `configs/` directory.
11
+ `init` is available as a bootstrap command. It scans for directories containing `SKILL.md`, discovers agent-rules Markdown files using the Claude-first portability conventions, and reads `mcp-servers.json` from the repo root to populate `mcpServers`. Shared surfaces now default to the primary deploy target in generated manifests: for example, `init` emits `claude-code` rather than `github-copilot` for shared Claude-native skills and rules, and emits `gemini-cli` rather than `antigravity` for shared `GEMINI.md` rules surfaces. `files` and `configs` remain empty — `init` emits guidance when it detects a `files/` or `configs/` directory.
12
12
 
13
13
  ## Quick Start
14
14
 
@@ -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 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.
30
+ Before executing, the deploy command runs preflight analysis on instruction files and capability planning inputs. 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 a manifest targets a surface that is implementation-only, planned, unsupported, or shared through another agent, 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
 
@@ -50,14 +50,14 @@ Before executing, the deploy command runs preflight analysis on instruction file
50
50
  | Skills (SKILL.md) | All agents via manifest and CLI | All agents |
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
- | 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 |
53
+ | MCP Servers | claude-code (`scope: "global"` → `~/.claude.json`; `scope: "repo"` → `{repo}/.claude/mcp.json`; `scope: "workspace"` → `{workspace}/.claude/mcp.json`), gemini-cli, codex, antigravity, opencode; github-copilot with `scope: "repo"` deploys to `{repo}/.vscode/mcp.json` and `scope: "workspace"` deploys to `{workspace}/.vscode/mcp.json`; github-copilot with `scope: "global"` (default) is unsupported and warns | claude-code, gemini-cli, codex, antigravity, opencode, github-copilot |
54
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
- | Permissions / Approval Config | claude-code (`~/.claude/settings.json`), codex (`~/.codex/config.toml`); other agents are warned and skipped | claude-code, codex |
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 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 |
55
+ | Permissions / Approval Config | claude-code (`~/.claude/settings.json`), codex (`~/.codex/config.toml`), opencode (`~/.config/opencode/opencode.json` on POSIX, `%APPDATA%\\opencode\\opencode.json` on Windows); other agents are warned and skipped | claude-code, codex, opencode |
56
+ | Agent Definitions | claude-code (`{repo}/.claude/agents/{name}.md`), gemini-cli (`{repo}/.gemini/agents/{name}.md` or `.toml`, plus `scope: "global"` to `~/.gemini/agents/{name}.md` or `.toml`), antigravity (`{repo}/.agents/rules/{name}.md`), opencode (`{repo}/.opencode/agents/{name}.md`, plus `scope: "global"` to the user config dir), github-copilot (`{repo}/.github/copilot/agents/{name}.md`, with migration from legacy `.github/agents/{name}.agent.md`); codex is warned and skipped | All supported agents |
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`); shared surfaces default to the primary deploy target instead of shared riders; emits hints for `files/` and `configs/` directories | N/A |
58
+ | Instruction preflight analysis | Emits capability warnings for implementation-only, planned, unsupported, and shared-through surfaces used by the manifest; emits `precedence` warnings when an agent has multiple `agentRules` scopes active simultaneously or the same source file is deployed to multiple scopes; emits `budget` warnings when `agentRules` or `agentDefinitions` source files exceed 50 KB; emits GitHub Copilot enterprise-policy warnings when local configuration may be overridden; emits a `config-authority` warning when Gemini CLI's `settings.json` contains an `instructionFilename` override that differs from the deploy target | N/A |
59
59
 
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.
60
+ Features that depend on agent-specific config surfaces are intentionally conservative: deploy and preflight now classify each surface through the same planner. If a target path or schema is implementation-only, planned, unsupported, or only shared through another agent, inception-engine warns and either routes through the primary surface or skips it rather than guessing.
61
61
 
62
62
  For GitHub Copilot specifically, the portability rule is Claude-first: if Copilot accepts the same Claude-native instruction or skill artifact, inception-engine should not add a separate Copilot deployment feature for it.
63
63
 
@@ -95,6 +95,12 @@ Create an `inception.json` file at the root of your skills directory:
95
95
  "name": "my-server",
96
96
  "agents": ["claude-code", "gemini-cli"],
97
97
  "config": { "command": "npx", "args": ["-y", "my-mcp-server"] }
98
+ },
99
+ {
100
+ "name": "my-server",
101
+ "agents": ["github-copilot"],
102
+ "scope": "repo",
103
+ "config": { "type": "stdio", "command": "npx", "args": ["-y", "my-mcp-server"] }
98
104
  }
99
105
  ],
100
106
  "agentRules": [
@@ -160,13 +166,23 @@ Each **mcpServer** entry registers an MCP server into the agent's config file by
160
166
  - **name** - Unique identifier (same format as skill names); used as the server's key in the config
161
167
  - **agents** - Array of agent IDs to register this server with
162
168
  - **config** - Raw server descriptor object. For the currently supported JSON-backed adapters, inception-engine requires at least one non-empty `command` or `url` field, validates `args` as an array of strings when present, and validates `env` as a string-to-string object when present. Additional keys are passed through verbatim.
169
+ - **scope** - `"global"` (default), `"repo"`, or `"workspace"`. For most agents this field is ignored (they have only a single user-level config path). For `github-copilot`, `scope` selects the target file:
170
+ - `"global"` — unsupported; emits a warning and is skipped
171
+ - `"repo"` — deploys to `{repo}/.vscode/mcp.json` under the `servers` key
172
+ - `"workspace"` — deploys to `{workspace}/.vscode/mcp.json` under the `servers` key
163
173
 
164
- MCP server registration is supported for all agents except GitHub Copilot. Inception-engine automatically uses the correct adapter for each agent's configuration schema:
165
- - **JSON (Merge Patch)**: `claude-code` (`~/.claude.json`), `gemini-cli` (`~/.gemini/settings.json`), and `opencode` (`~/.config/opencode/opencode.json` using the custom `"mcp"` key).
174
+ GitHub Copilot's `.vscode/mcp.json` uses `servers` (not `mcpServers`) as the top-level key and optionally accepts a `type` field (`"stdio"` | `"sse"` | `"http"`) in the server descriptor, which is passed through verbatim.
175
+
176
+ MCP server registration is supported for all agents. Inception-engine automatically uses the correct adapter for each agent's configuration schema:
177
+ - **JSON (Merge Patch)**: `claude-code` (`~/.claude.json` for `scope: "global"`; `{repo}/.claude/mcp.json` for `scope: "repo"`; `{workspace}/.claude/mcp.json` for `scope: "workspace"`), `gemini-cli` (`~/.gemini/settings.json`), `opencode` (`~/.config/opencode/opencode.json` using the custom `"mcp"` key), and `github-copilot` (`{repo}/.vscode/mcp.json` or `{workspace}/.vscode/mcp.json` using the `"servers"` key).
166
178
  - **TOML (Patch)**: `codex` (`~/.codex/config.toml`).
167
179
  - **Markdown Frontmatter (Emit)**: `antigravity` (repo-local `.agents/rules/{name}.md` files).
168
180
 
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.
181
+ For Markdown frontmatter targets, inception-engine now records patch-level provenance for the emitted frontmatter block. Deploy merges only the owned frontmatter keys, preserves unrelated frontmatter and Markdown body content, and `revert` removes only the engine-owned keys instead of deleting the whole file unless inception-engine originally created an otherwise-empty file.
182
+
183
+ Because Antigravity currently reuses `.agents/rules/{name}.md` for both MCP frontmatter emit and agent-definition files, planning now rejects a manifest when an `mcpServers` entry and an `agentDefinitions` entry would resolve to the same Antigravity target.
184
+
185
+ Revert removes the registered server entry from the respective configuration file or frontmatter block.
170
186
 
171
187
  Each **agentRules** entry deploys a Markdown instruction file to an agent's supported instruction file location:
172
188
 
@@ -232,20 +248,28 @@ Valid `approval_policy` values are `"auto"`, `"manual"`, `"suggest"`, and `"on-f
232
248
 
233
249
  Revert restores the previous config values using the undo patch recorded at deploy time.
234
250
 
235
- Each **agentDefinitions** entry deploys a Markdown agent-definition file to the repo-local agent-definitions directory of each targeted agent:
251
+ Each **agentDefinitions** entry deploys an agent-definition file to the agent-definition directory of each targeted agent. `scope: "repo"` is the default, and some agents also support `scope: "global"`:
236
252
 
237
253
  - **name** - Unique identifier (same format as skill names); used as the definition filename
238
- - **path** - Relative path to the source Markdown file within the repo; must be a `.md` or `.markdown` file
254
+ - **path** - Relative path to the source file within the repo; `.md` or `.markdown` for Markdown definitions, `.toml` for Gemini CLI TOML subagent definitions
239
255
  - **agents** - Array of agent IDs to deploy this definition to
240
256
 
241
- 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.
257
+ 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. Gemini CLI and OpenCode also support `scope: "global"` definitions; other supported agents are repo-local only.
258
+
259
+ Gemini CLI supports two definition formats: Markdown (`.md`) files deploy to `{scope}/.gemini/agents/{name}.md`, and TOML (`.toml`) subagent configuration files deploy to `{scope}/.gemini/agents/{name}.toml`. TOML files are deployed verbatim without frontmatter validation. Agents other than `gemini-cli` that do not have a TOML definition surface silently produce no action when a `.toml` source is specified.
242
260
 
243
- All targets are repo-local (they land inside the repository being deployed, not the user's home directory):
261
+ Repo-local targets:
244
262
  - **claude-code**: `{repo}/.claude/agents/{name}.md`
245
- - **gemini-cli**: `{repo}/.gemini/agents/{name}.md`
263
+ - **gemini-cli**: `{repo}/.gemini/agents/{name}.md` (Markdown) or `{repo}/.gemini/agents/{name}.toml` (TOML)
246
264
  - **antigravity**: `{repo}/.agents/rules/{name}.md`
247
265
  - **opencode**: `{repo}/.opencode/agents/{name}.md`
248
- - **github-copilot**: `{repo}/.github/agents/{name}.agent.md` (note the `.agent.md` suffix)
266
+ - **github-copilot**: `{repo}/.github/copilot/agents/{name}.md`
267
+
268
+ Global targets where supported:
269
+ - **gemini-cli**: `~/.gemini/agents/{name}.md` or `~/.gemini/agents/{name}.toml`
270
+ - **opencode**: `~/.config/opencode/agents/{name}.md` on POSIX, `%APPDATA%\\opencode\\agents\\{name}.md` on Windows
271
+
272
+ For GitHub Copilot, deploy also records migration from the legacy `{repo}/.github/agents/{name}.agent.md` path so older installs can be cleaned up safely.
249
273
 
250
274
  Revert removes the deployed agent-definition file.
251
275
 
@@ -277,11 +301,11 @@ Current `init` behavior:
277
301
  - Applies either the `--agents` list or all currently known agent IDs
278
302
  - Refuses to overwrite an existing `inception.json` unless `--force` is provided
279
303
  - 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
304
+ - Discovers agent-rules Markdown files in the root and conventional subdirectories (`rules/`, `instructions/`, `.github/`), mapping them to agents using Claude-first portability conventions: `copilot-instructions.md` maps to `claude-code` (Copilot reads `CLAUDE.md` natively), and the fallback for unrecognized files excludes unsupported agents plus shared-surface riders that should default to their primary deploy target
281
305
  - Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
282
306
  - Reads `files-manifest.json` from the repo root (if present) and generates `files` entries; invalid entries are warned and skipped
283
307
  - Reads `configs-manifest.json` from the repo root (if present) and generates `configs` entries; invalid entries are warned and skipped
284
- - 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
308
+ - 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/`, `.github/copilot/agents/`, and legacy `.github/agents/`; invalid entries are warned and skipped
285
309
  - Emits guidance to create a sidecar manifest when a `files/` or `configs/` directory is detected but the corresponding sidecar file is absent
286
310
 
287
311
  Current `init` limitations:
@@ -343,10 +367,12 @@ Place an `agent-definitions-manifest.json` file at the repo root to have `init`
343
367
  ]
344
368
  ```
345
369
 
346
- 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`).
370
+ If no `agent-definitions-manifest.json` is present, `init` auto-discovers agent-definition Markdown files from six conventional subdirectories: `.claude/agents/`, `.gemini/agents/`, `.agents/rules/`, `.opencode/agents/`, `.github/copilot/agents/`, and legacy `.github/agents/`. Each discovered file is mapped to the owning agent(s). GitHub Copilot files named `{name}.agent.md` in the legacy directory have the `.agent` infix stripped to derive the manifest name (e.g., `foo.agent.md` -> name `foo`).
347
371
 
348
372
  Invalid entries are warned and skipped; the rest are written into the generated manifest verbatim.
349
373
 
374
+ For shared surfaces, `init` prefers the primary deploy target instead of emitting both agents. That means generated `skills` entries exclude `github-copilot` in favor of `claude-code`, and generated shared `GEMINI.md` `agentRules` entries exclude `antigravity` in favor of `gemini-cli`. This keeps starter manifests minimal while preserving deploy behavior for explicitly authored shared-rider entries.
375
+
350
376
  ## CLI Reference
351
377
 
352
378
  ```
@@ -433,11 +459,11 @@ Revert targets all agents listed in the manifest by default (regardless of detec
433
459
 
434
460
  ### Ownership Tracking and Safe Revert
435
461
 
436
- inception-engine maintains a centralized deployment registry at `~/.inception-engine/registry.json`. Each deploy records the target path, skill name, agent ID, action-specific provenance (`source`/`method` for skill-dir, `source` for file-write, `patch`/`undoPatch` for config-patch), and timestamp. No files are written to the source repository.
462
+ inception-engine maintains a centralized deployment registry at `~/.inception-engine/registry.json`. Each deploy records the target path, skill name, agent ID, action-specific provenance (`source`/`method` for skill-dir, `source` for file-write, `patch`/`undoPatch` for config-patch, `patch`/`undoPatch` plus frontmatter-shape metadata for frontmatter-emit), and timestamp. Registry entries also carry a stable surface ID so future surface migrations can preserve ownership continuity. No files are written to the source repository.
437
463
 
438
464
  - **Registry-based ownership**: On revert, the registry is checked before removing any target. Only targets with a valid registry entry are removed. On redeploy, unmanaged targets are never replaced.
439
465
 
440
- - **Strong binding**: Each registry entry binds a specific target path to its skill, agent, and action kind. For `skill-dir` and `file-write`, ownership checks also require the recorded `source` to match before an existing target is treated as managed. For `config-patch`, overwrite protection is keyed by target path, kind, skill, and agent; the stored `patch` and `undoPatch` are used for revert bookkeeping rather than deploy-time identity checks.
466
+ - **Strong binding**: Each registry entry binds a specific target path to its skill, agent, and action kind. For `skill-dir` and `file-write`, ownership checks also require the recorded `source` to match before an existing target is treated as managed. For `config-patch` and `frontmatter-emit`, overwrite protection is keyed by target path, kind, skill, and agent; the stored `patch` and `undoPatch` are used for patch-level revert bookkeeping rather than deploy-time identity checks.
441
467
 
442
468
  - **Atomic redeploy**: When overwriting an existing managed `skill-dir` target, the engine renames the old target to a backup, creates the new deployment, and only removes the backup on success. If the new deployment fails, the backup is restored. `file-write` and `config-patch` deployments write directly to the target without this backup/rollback model.
443
469
 
@@ -28,6 +28,22 @@ export const AGENT_REGISTRY = [
28
28
  windows: ["{home}", ".claude.json"],
29
29
  },
30
30
  },
31
+ mcpRepoSupport: {
32
+ status: "supported",
33
+ schemaLabel: "repo-local .claude/mcp.json",
34
+ path: {
35
+ posix: ["{repo}", ".claude", "mcp.json"],
36
+ windows: ["{repo}", ".claude", "mcp.json"],
37
+ },
38
+ },
39
+ mcpWorkspaceSupport: {
40
+ status: "supported",
41
+ schemaLabel: "workspace-local .claude/mcp.json",
42
+ path: {
43
+ posix: ["{workspace}", ".claude", "mcp.json"],
44
+ windows: ["{workspace}", ".claude", "mcp.json"],
45
+ },
46
+ },
31
47
  agentRulesSupport: {
32
48
  status: "supported",
33
49
  schemaLabel: "global Markdown rules file",
@@ -52,7 +68,7 @@ export const AGENT_REGISTRY = [
52
68
  windows: ["{home}", ".claude", "settings.json"],
53
69
  },
54
70
  },
55
- agentDefinitionsSupport: {
71
+ agentDefinitionsRepoSupport: {
56
72
  status: "supported",
57
73
  schemaLabel: "repo-local agent definition Markdown file",
58
74
  path: {
@@ -120,7 +136,7 @@ export const AGENT_REGISTRY = [
120
136
  windows: ["{home}", ".codex", "config.toml"],
121
137
  },
122
138
  },
123
- agentDefinitionsSupport: {
139
+ agentDefinitionsRepoSupport: {
124
140
  status: "unsupported",
125
141
  schemaLabel: "dedicated agent definition directory",
126
142
  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",
@@ -152,7 +168,7 @@ export const AGENT_REGISTRY = [
152
168
  detectBinary: "documented",
153
169
  mcpConfig: "documented",
154
170
  agentRules: "documented",
155
- agentDefinitions: "implementation-only",
171
+ agentDefinitions: "documented",
156
172
  },
157
173
  mcpSupport: {
158
174
  status: "supported",
@@ -162,6 +178,9 @@ export const AGENT_REGISTRY = [
162
178
  windows: ["{home}", ".gemini", "settings.json"],
163
179
  },
164
180
  },
181
+ // NOTE: Gemini CLI also reads AGENTS.md as a fallback when GEMINI.md is absent.
182
+ // inception-engine intentionally targets GEMINI.md to avoid ownership collisions
183
+ // with codex and opencode, which also write AGENTS.md.
165
184
  agentRulesSupport: {
166
185
  status: "supported",
167
186
  schemaLabel: "global Markdown rules file",
@@ -184,6 +203,14 @@ export const AGENT_REGISTRY = [
184
203
  reason: "Gemini CLI does not expose a documented global per-user permission or approval config surface",
185
204
  },
186
205
  agentDefinitionsSupport: {
206
+ status: "supported",
207
+ schemaLabel: "global agent definition Markdown file",
208
+ path: {
209
+ posix: ["{home}", ".gemini", "agents", "{name}.md"],
210
+ windows: ["{home}", ".gemini", "agents", "{name}.md"],
211
+ },
212
+ },
213
+ agentDefinitionsRepoSupport: {
187
214
  status: "supported",
188
215
  schemaLabel: "repo-local agent definition Markdown file",
189
216
  path: {
@@ -191,6 +218,22 @@ export const AGENT_REGISTRY = [
191
218
  windows: ["{repo}", ".gemini", "agents", "{name}.md"],
192
219
  },
193
220
  },
221
+ agentDefinitionsTomlSupport: {
222
+ status: "supported",
223
+ schemaLabel: "global agent definition TOML file",
224
+ path: {
225
+ posix: ["{home}", ".gemini", "agents", "{name}.toml"],
226
+ windows: ["{home}", ".gemini", "agents", "{name}.toml"],
227
+ },
228
+ },
229
+ agentDefinitionsTomlRepoSupport: {
230
+ status: "supported",
231
+ schemaLabel: "repo-local agent definition TOML file",
232
+ path: {
233
+ posix: ["{repo}", ".gemini", "agents", "{name}.toml"],
234
+ windows: ["{repo}", ".gemini", "agents", "{name}.toml"],
235
+ },
236
+ },
194
237
  agentRulesWorkspaceSupport: {
195
238
  status: "supported",
196
239
  schemaLabel: "workspace-local GEMINI.md",
@@ -253,7 +296,7 @@ export const AGENT_REGISTRY = [
253
296
  schemaLabel: "global permissions surface",
254
297
  reason: "Antigravity does not expose a documented global per-user permission or approval config surface",
255
298
  },
256
- agentDefinitionsSupport: {
299
+ agentDefinitionsRepoSupport: {
257
300
  status: "supported",
258
301
  schemaLabel: "repo-local agent definition Markdown file",
259
302
  path: {
@@ -286,6 +329,7 @@ export const AGENT_REGISTRY = [
286
329
  detectBinary: "documented",
287
330
  agentRules: "documented",
288
331
  agentDefinitions: "documented",
332
+ permissions: "documented",
289
333
  },
290
334
  mcpSupport: {
291
335
  status: "supported",
@@ -313,11 +357,22 @@ export const AGENT_REGISTRY = [
313
357
  },
314
358
  },
315
359
  permissionsSupport: {
316
- status: "unsupported",
317
- schemaLabel: "global permissions surface",
318
- reason: "OpenCode does not expose a documented global per-user permission or approval config surface",
360
+ status: "supported",
361
+ schemaLabel: "opencode.json permissions config",
362
+ path: {
363
+ posix: ["{xdg_config}", "opencode", "opencode.json"],
364
+ windows: ["{appdata}", "opencode", "opencode.json"],
365
+ },
319
366
  },
320
367
  agentDefinitionsSupport: {
368
+ status: "supported",
369
+ schemaLabel: "global agent definition Markdown file",
370
+ path: {
371
+ posix: ["{xdg_config}", "opencode", "agents", "{name}.md"],
372
+ windows: ["{appdata}", "opencode", "agents", "{name}.md"],
373
+ },
374
+ },
375
+ agentDefinitionsRepoSupport: {
321
376
  status: "supported",
322
377
  schemaLabel: "repo-local agent definition Markdown file",
323
378
  path: {
@@ -348,12 +403,30 @@ export const AGENT_REGISTRY = [
348
403
  detectPaths: "documented",
349
404
  detectBinary: "documented",
350
405
  agentDefinitions: "documented",
406
+ mcpConfig: "documented",
351
407
  },
352
408
  mcpSupport: {
353
- status: "planned",
354
- schemaLabel: "repo-scoped MCP surfaces",
355
- plannedSurface: "devcontainer (.devcontainer/devcontainer.json) and agent-frontmatter (.github/agents/*.agent.md)",
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",
409
+ status: "unsupported",
410
+ schemaLabel: "global MCP config",
411
+ reason: 'GitHub Copilot has no user-level MCP config file; use scope: "repo" or scope: "workspace" to target .vscode/mcp.json instead',
412
+ },
413
+ mcpRepoSupport: {
414
+ status: "supported",
415
+ schemaLabel: "repo-local .vscode/mcp.json",
416
+ mcpPatchKey: "servers",
417
+ path: {
418
+ posix: ["{repo}", ".vscode", "mcp.json"],
419
+ windows: ["{repo}", ".vscode", "mcp.json"],
420
+ },
421
+ },
422
+ mcpWorkspaceSupport: {
423
+ status: "supported",
424
+ schemaLabel: "workspace-local .vscode/mcp.json",
425
+ mcpPatchKey: "servers",
426
+ path: {
427
+ posix: ["{workspace}", ".vscode", "mcp.json"],
428
+ windows: ["{workspace}", ".vscode", "mcp.json"],
429
+ },
357
430
  },
358
431
  // GitHub Copilot reads Claude-native instruction files (CLAUDE.md) without
359
432
  // a separate deploy action. These surfaces are marked shared-via claude-code
@@ -404,12 +477,12 @@ export const AGENT_REGISTRY = [
404
477
  schemaLabel: "global permissions surface",
405
478
  reason: "GitHub Copilot permissions are managed via organization policy, not a deployable per-user config surface",
406
479
  },
407
- agentDefinitionsSupport: {
480
+ agentDefinitionsRepoSupport: {
408
481
  status: "supported",
409
482
  schemaLabel: "repo-local agent definition Markdown file",
410
483
  path: {
411
- posix: ["{repo}", ".github", "agents", "{name}.agent.md"],
412
- windows: ["{repo}", ".github", "agents", "{name}.agent.md"],
484
+ posix: ["{repo}", ".github", "copilot", "agents", "{name}.md"],
485
+ windows: ["{repo}", ".github", "copilot", "agents", "{name}.md"],
413
486
  },
414
487
  },
415
488
  policyNote: "Organization policies may override locally deployed configuration. Verify with your GitHub org admin if deployed skills or rules are not active.",
@@ -4,17 +4,5 @@ export interface AgentDefinitionsAdapterResult {
4
4
  actions: FileWriteDeployAction[];
5
5
  warnings: PlanWarning[];
6
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
7
  export declare function compileAgentDefinitionActions(entry: AgentDefinitionEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<AgentDefinitionsAdapterResult>;
20
8
  export declare function compileAgentDefinitionReverts(entry: AgentDefinitionEntry, agentFilter: AgentId[] | null, home: string, repo?: string, workspace?: string): FileWriteRevertAction[];
@@ -0,0 +1,162 @@
1
+ import path from "node:path";
2
+ import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
3
+ import { planCapabilityForDeploy, resolveCapabilitySurface, } from "../capabilities.js";
4
+ import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
5
+ import { validateAgentRuleMarkdownPath, validateInstructionFileRequirements, validateSourceFile, validateSourcePath, } from "../validation.js";
6
+ /**
7
+ * Compiles deploy actions for agentDefinitions manifest entries.
8
+ *
9
+ * Agent definition files are Markdown (typically with YAML frontmatter) or
10
+ * TOML files that define custom agents or subagents for a given platform.
11
+ * Unlike agentRules (which target a single shared global file per agent),
12
+ * each definition entry produces a separate file named after the entry in
13
+ * the agent's dedicated agent-definitions directory.
14
+ *
15
+ * Target paths use the `{repo}` placeholder so definitions land in the
16
+ * repository being deployed, not the user's home directory.
17
+ *
18
+ * TOML source files (e.g. Gemini CLI `.gemini/agents/*.toml`) bypass
19
+ * Markdown-specific validation and are deployed verbatim.
20
+ */
21
+ /**
22
+ * Returns the TOML-specific surface for an agent (global or repo-local),
23
+ * or undefined when the agent has no TOML definition surface.
24
+ */
25
+ function resolveTomlDefinitionsSurface(agentId, scope) {
26
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
27
+ if (!agent)
28
+ return undefined;
29
+ const tomlSupport = scope === "repo"
30
+ ? (agent.agentDefinitionsTomlRepoSupport ??
31
+ agent.agentDefinitionsTomlSupport)
32
+ : agent.agentDefinitionsTomlSupport;
33
+ if (!tomlSupport || tomlSupport.status !== "supported")
34
+ return undefined;
35
+ return tomlSupport;
36
+ }
37
+ /** Returns a warning when the requested scope cannot be resolved, or null when it is fine. */
38
+ function scopeAvailabilityWarning(entry, agentId, repo, workspace) {
39
+ if (entry.scope === "repo" && !repo) {
40
+ return {
41
+ kind: "confidence",
42
+ message: `agentDefinitions: scope "repo" requires a repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
43
+ };
44
+ }
45
+ if (entry.scope === "workspace" && !workspace && !repo) {
46
+ return {
47
+ kind: "confidence",
48
+ message: `agentDefinitions: scope "workspace" requires a workspace or repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
49
+ };
50
+ }
51
+ return null;
52
+ }
53
+ function resolveSupportedTargets(entry, targetAgents, home, repo, workspace) {
54
+ const targets = [];
55
+ const warnings = [];
56
+ const platform = getPlatformKey();
57
+ const isToml = path.extname(entry.path).toLowerCase() === ".toml";
58
+ for (const agentId of targetAgents) {
59
+ const plan = planCapabilityForDeploy({
60
+ agentId,
61
+ capability: "agentDefinitions",
62
+ entryName: entry.name,
63
+ targetAgentIds: targetAgents,
64
+ scope: entry.scope,
65
+ });
66
+ if (plan.outcome === "warn") {
67
+ warnings.push(plan.warning);
68
+ continue;
69
+ }
70
+ if (plan.outcome === "native" || plan.outcome === "redundant")
71
+ continue;
72
+ // For TOML sources, use the agent's TOML-specific surface instead of the
73
+ // default Markdown surface. Agents without a TOML surface are skipped.
74
+ const support = isToml
75
+ ? resolveTomlDefinitionsSurface(agentId, entry.scope)
76
+ : resolveCapabilitySurface(agentId, "agentDefinitions", entry.scope)
77
+ .support;
78
+ if (!support)
79
+ continue;
80
+ const scopeWarning = scopeAvailabilityWarning(entry, agentId, repo, workspace);
81
+ if (scopeWarning) {
82
+ warnings.push(scopeWarning);
83
+ continue;
84
+ }
85
+ targets.push({
86
+ agentId,
87
+ confidence: plan.confidence ?? "provisional",
88
+ target: resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace),
89
+ });
90
+ }
91
+ return { targets, warnings };
92
+ }
93
+ async function createAgentDefinitionActions(entry, source, supportedTargets, home, repo, workspace) {
94
+ const actions = [];
95
+ const isToml = path.extname(entry.path).toLowerCase() === ".toml";
96
+ for (const target of supportedTargets) {
97
+ // TOML definition files have no YAML frontmatter — skip Markdown validation.
98
+ if (!isToml) {
99
+ validateAgentRuleMarkdownPath(entry.path, target.agentId);
100
+ await validateInstructionFileRequirements(source, entry.path, target.agentId);
101
+ }
102
+ let migratedFrom;
103
+ if (target.agentId === "github-copilot") {
104
+ migratedFrom = [
105
+ resolvePlaceholders(["{repo}", ".github", "agents", "{name}.agent.md"], entry.name, home, repo, workspace),
106
+ ];
107
+ }
108
+ actions.push({
109
+ kind: "file-write",
110
+ skill: entry.name,
111
+ agent: target.agentId,
112
+ source,
113
+ target: target.target,
114
+ confidence: target.confidence,
115
+ migratedFrom,
116
+ });
117
+ }
118
+ return actions;
119
+ }
120
+ export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
121
+ const targetAgents = entry.agents.filter((agentId) => detectedAgents.includes(agentId));
122
+ if (targetAgents.length === 0) {
123
+ return { actions: [], warnings: [] };
124
+ }
125
+ const { targets: supportedTargets, warnings } = resolveSupportedTargets(entry, targetAgents, home, repo, workspace);
126
+ if (supportedTargets.length === 0) {
127
+ return { actions: [], warnings };
128
+ }
129
+ // Validate the shared source file only when at least one target is active.
130
+ const source = path.resolve(sourceDir, entry.path);
131
+ await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
132
+ await validateSourceFile(source, entry.path);
133
+ const actions = await createAgentDefinitionActions(entry, source, supportedTargets, home, repo, workspace);
134
+ return { actions, warnings };
135
+ }
136
+ export function compileAgentDefinitionReverts(entry, agentFilter, home, repo, workspace) {
137
+ const actions = [];
138
+ const platform = getPlatformKey();
139
+ const isToml = path.extname(entry.path).toLowerCase() === ".toml";
140
+ for (const agentId of entry.agents) {
141
+ if (agentFilter && !agentFilter.includes(agentId))
142
+ continue;
143
+ const support = isToml
144
+ ? resolveTomlDefinitionsSurface(agentId, entry.scope)
145
+ : resolveCapabilitySurface(agentId, "agentDefinitions", entry.scope)
146
+ .support;
147
+ if (!support)
148
+ continue;
149
+ if (entry.scope === "repo" && !repo)
150
+ continue;
151
+ if (entry.scope === "workspace" && !workspace && !repo)
152
+ continue;
153
+ const target = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
154
+ actions.push({
155
+ kind: "file-write",
156
+ skill: entry.name,
157
+ agent: agentId,
158
+ target,
159
+ });
160
+ }
161
+ return actions;
162
+ }
@@ -12,6 +12,7 @@ export declare function splitFrontmatter(raw: string): {
12
12
  export declare function parseFrontmatterDocument<T = Record<string, unknown>>(raw: string): {
13
13
  attributes: T;
14
14
  body: string;
15
+ hasFrontmatter: boolean;
15
16
  };
16
17
  /**
17
18
  * Serializes an object to a YAML block.
@@ -21,6 +22,9 @@ export declare function serializeFrontmatter(data: Record<string, unknown>): str
21
22
  * Builds the full content of a frontmatter-bearing Markdown file.
22
23
  */
23
24
  export declare function buildFrontmatterDocument(frontmatter: Record<string, unknown>, body?: string): string;
25
+ export declare function buildMarkdownDocument(frontmatter: Record<string, unknown>, body?: string, options?: {
26
+ hasFrontmatter?: boolean;
27
+ }): string;
24
28
  /**
25
29
  * Reads an existing `.md` file and parses its frontmatter.
26
30
  * Returns `{ attributes: {}, body: "" }` if the file does not exist.
@@ -29,6 +33,12 @@ export declare function readFrontmatterFile(filePath: string): Promise<{
29
33
  attributes: Record<string, unknown>;
30
34
  body: string;
31
35
  }>;
36
+ export declare function readFrontmatterDocumentFile(filePath: string): Promise<{
37
+ exists: boolean;
38
+ hasFrontmatter: boolean;
39
+ attributes: Record<string, unknown>;
40
+ body: string;
41
+ }>;
32
42
  /**
33
43
  * Atomically writes a Markdown file with the given frontmatter and optional
34
44
  * body. Preserves any existing body content when `preserveBody` is true.
@@ -26,10 +26,10 @@ export function splitFrontmatter(raw) {
26
26
  export function parseFrontmatterDocument(raw) {
27
27
  const { frontmatter, body } = splitFrontmatter(raw);
28
28
  if (!(frontmatter || raw.startsWith("---"))) {
29
- return { attributes: {}, body };
29
+ return { attributes: {}, body, hasFrontmatter: false };
30
30
  }
31
31
  const attributes = YAML.parse(frontmatter);
32
- return { attributes: (attributes || {}), body };
32
+ return { attributes: (attributes || {}), body, hasFrontmatter: true };
33
33
  }
34
34
  /**
35
35
  * Serializes an object to a YAML block.
@@ -42,9 +42,15 @@ export function serializeFrontmatter(data) {
42
42
  * Builds the full content of a frontmatter-bearing Markdown file.
43
43
  */
44
44
  export function buildFrontmatterDocument(frontmatter, body = "") {
45
+ return buildMarkdownDocument(frontmatter, body, { hasFrontmatter: true });
46
+ }
47
+ export function buildMarkdownDocument(frontmatter, body = "", options = {}) {
48
+ const cleanBody = body.trimStart();
49
+ if (Object.keys(frontmatter).length === 0 && !options.hasFrontmatter) {
50
+ return cleanBody;
51
+ }
45
52
  const serialized = serializeFrontmatter(frontmatter);
46
53
  // Ensure exactly one newline after the closing delimiter before the body
47
- const cleanBody = body.trimStart();
48
54
  const separator = cleanBody ? "\n\n" : "\n";
49
55
  return `---\n${serialized}\n---\n${separator}${cleanBody}`;
50
56
  }
@@ -58,17 +64,28 @@ function createAtomicTempPath(targetPath) {
58
64
  * Returns `{ attributes: {}, body: "" }` if the file does not exist.
59
65
  */
60
66
  export async function readFrontmatterFile(filePath) {
67
+ const document = await readFrontmatterDocumentFile(filePath);
68
+ return { attributes: document.attributes, body: document.body };
69
+ }
70
+ export async function readFrontmatterDocumentFile(filePath) {
61
71
  let raw;
62
72
  try {
63
73
  raw = await readFile(filePath, "utf-8");
64
74
  }
65
75
  catch (err) {
66
76
  const code = err.code;
67
- if (code === "ENOENT")
68
- return { attributes: {}, body: "" };
77
+ if (code === "ENOENT") {
78
+ return {
79
+ exists: false,
80
+ hasFrontmatter: false,
81
+ attributes: {},
82
+ body: "",
83
+ };
84
+ }
69
85
  throw err;
70
86
  }
71
- return parseFrontmatterDocument(raw);
87
+ const parsed = parseFrontmatterDocument(raw);
88
+ return { exists: true, ...parsed };
72
89
  }
73
90
  /**
74
91
  * Atomically writes a Markdown file with the given frontmatter and optional