@kuznai/inception-engine 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { constants } from "node:fs";
2
2
  import { access, copyFile, cp, lstat, mkdir, readFile, realpath, rename, rm, symlink, unlink, writeFile, } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
4
+ import { AGENT_REGISTRY, AGENT_REGISTRY_BY_ID } from "../config/agents.js";
5
5
  import { UserError } from "../errors.js";
6
6
  import { logger } from "../logger.js";
7
7
  import { compileAdapterActions } from "./adapters/index.js";
@@ -80,26 +80,118 @@ function detectCollisions(actions) {
80
80
  }
81
81
  return warnings;
82
82
  }
83
- function detectAmbiguities(detectedAgents, manifest) {
84
- const warnings = [];
85
- if (!(detectedAgents.includes("gemini-cli") &&
86
- detectedAgents.includes("antigravity"))) {
87
- return warnings;
83
+ /**
84
+ * Finds all (primary, rider) pairs among detected agents where the rider has
85
+ * at least one surface annotated as `shared-via` the primary and the primary
86
+ * is also detected. Used to drive ambiguity warnings without hardcoding agent
87
+ * IDs.
88
+ */
89
+ function findSharedSurfacePairs(detectedAgents) {
90
+ const pairs = [];
91
+ for (const agent of AGENT_REGISTRY) {
92
+ if (!detectedAgents.includes(agent.id))
93
+ continue;
94
+ const surfaces = [
95
+ agent.agentRulesSupport,
96
+ agent.agentRulesRepoSupport,
97
+ agent.agentRulesWorkspaceSupport,
98
+ agent.mcpSupport,
99
+ agent.agentDefinitionsSupport,
100
+ ];
101
+ for (const surface of surfaces) {
102
+ if (surface?.status === "supported" &&
103
+ surface.surfaceKind?.kind === "shared-via") {
104
+ const via = surface.surfaceKind.via;
105
+ if (detectedAgents.includes(via) &&
106
+ !pairs.some((p) => p.primary === via && p.rider === agent.id)) {
107
+ pairs.push({ primary: via, rider: agent.id });
108
+ }
109
+ }
110
+ }
88
111
  }
89
- const bothAgents = (agents) => agents.includes("gemini-cli") && agents.includes("antigravity");
112
+ return pairs;
113
+ }
114
+ function resolveAgentRulesSupportForScope(agentId, scope) {
115
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
116
+ if (scope === "repo")
117
+ return agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport;
118
+ if (scope === "workspace") {
119
+ return (agent?.agentRulesWorkspaceSupport ??
120
+ agent?.agentRulesRepoSupport ??
121
+ agent?.agentRulesSupport);
122
+ }
123
+ return agent?.agentRulesSupport;
124
+ }
125
+ function checkPairAgentRuleAmbiguities(manifest, primary, rider) {
126
+ const warnings = [];
90
127
  for (const entry of manifest.agentRules ?? []) {
91
- if (bothAgents(entry.agents)) {
128
+ if (!(entry.agents.includes(primary) && entry.agents.includes(rider)))
129
+ continue;
130
+ const primarySupport = resolveAgentRulesSupportForScope(primary, entry.scope);
131
+ const riderSupport = resolveAgentRulesSupportForScope(rider, entry.scope);
132
+ if (primarySupport?.status === "supported" &&
133
+ riderSupport?.status === "supported" &&
134
+ JSON.stringify(primarySupport.path) === JSON.stringify(riderSupport.path)) {
92
135
  warnings.push({
93
136
  kind: "ambiguity",
94
- message: `Both "gemini-cli" and "antigravity" are listed in agentRules entry "${entry.name}". They write to distinct surfaces ("gemini-cli" → ~/.gemini/GEMINI.md, "antigravity" → {repo}/.agents/rules/${entry.name}.md) from the same source file verify that deploying to both produces the intended behavior on each agent.`,
137
+ message: `Both "${primary}" and "${rider}" are listed in agentRules entry "${entry.name}". Both target the same surfacelisting both is redundant but harmless; deduplication ensures only one write action is emitted.`,
95
138
  });
96
139
  }
97
140
  }
141
+ return warnings;
142
+ }
143
+ function checkPairMcpAmbiguities(manifest, primary, rider) {
144
+ const warnings = [];
145
+ const primaryMcp = AGENT_REGISTRY_BY_ID[primary]?.mcpSupport;
146
+ const riderMcp = AGENT_REGISTRY_BY_ID[rider]?.mcpSupport;
147
+ if (primaryMcp?.status !== "supported" || riderMcp?.status !== "supported")
148
+ return warnings;
98
149
  for (const entry of manifest.mcpServers ?? []) {
99
- if (bothAgents(entry.agents)) {
150
+ if (!(entry.agents.includes(primary) && entry.agents.includes(rider)))
151
+ continue;
152
+ warnings.push({
153
+ kind: "ambiguity",
154
+ message: `Both "${primary}" and "${rider}" are listed in mcpServers entry "${entry.name}". "${primary}" writes to a shared MCP surface — verify that deploying to both does not produce conflicting MCP server behavior.`,
155
+ });
156
+ }
157
+ return warnings;
158
+ }
159
+ function checkPairAgentDefinitionAmbiguities(manifest, primary, rider) {
160
+ const warnings = [];
161
+ for (const entry of manifest.agentDefinitions ?? []) {
162
+ if (!(entry.agents.includes(primary) && entry.agents.includes(rider)))
163
+ continue;
164
+ warnings.push({
165
+ kind: "ambiguity",
166
+ message: `Both "${primary}" and "${rider}" are listed in agentDefinitions entry "${entry.name}". They write to distinct surfaces — verify that this behavioral divergence is intended.`,
167
+ });
168
+ }
169
+ return warnings;
170
+ }
171
+ function detectAmbiguities(detectedAgents, manifest) {
172
+ const pairs = findSharedSurfacePairs(detectedAgents);
173
+ if (pairs.length === 0)
174
+ return [];
175
+ const warnings = [];
176
+ for (const { primary, rider } of pairs) {
177
+ warnings.push(...checkPairAgentRuleAmbiguities(manifest, primary, rider));
178
+ warnings.push(...checkPairMcpAmbiguities(manifest, primary, rider));
179
+ warnings.push(...checkPairAgentDefinitionAmbiguities(manifest, primary, rider));
180
+ }
181
+ return warnings;
182
+ }
183
+ function checkAntigravityPathCollisions(manifest) {
184
+ const warnings = [];
185
+ const defNames = new Set((manifest.agentDefinitions ?? [])
186
+ .filter((e) => e.agents.includes("antigravity"))
187
+ .map((e) => e.name));
188
+ for (const entry of manifest.mcpServers ?? []) {
189
+ if (!entry.agents.includes("antigravity"))
190
+ continue;
191
+ if (defNames.has(entry.name)) {
100
192
  warnings.push({
101
- kind: "ambiguity",
102
- message: `Both "gemini-cli" and "antigravity" are listed in mcpServers entry "${entry.name}". "gemini-cli" writes to ~/.gemini/settings.json as a shared surface verify that deploying to both does not produce conflicting MCP server behavior.`,
193
+ kind: "collision",
194
+ message: `agentDefinitions entry "${entry.name}" and mcpServers entry "${entry.name}" for agent "antigravity" both resolve to {repo}/.agents/rules/${entry.name}.md one will silently overwrite the other; use different names or remove one entry`,
103
195
  });
104
196
  }
105
197
  }
@@ -134,7 +226,7 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
134
226
  }
135
227
  return actions;
136
228
  }
137
- async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
229
+ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
138
230
  const actions = [];
139
231
  for (const fileEntry of manifest.files ?? []) {
140
232
  const targetAgents = fileEntry.agents.filter((agentId) => detectedAgents.includes(agentId));
@@ -152,14 +244,14 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
152
244
  skill: fileEntry.name,
153
245
  agent: agentId,
154
246
  source,
155
- target: resolveTargetTemplate(fileEntry.target, home),
247
+ target: resolveTargetTemplate(fileEntry.target, home, repo, workspace),
156
248
  confidence: agent.provenance.skills,
157
249
  });
158
250
  }
159
251
  }
160
252
  return actions;
161
253
  }
162
- function planConfigPatchActions(manifest, detectedAgents, home) {
254
+ function planConfigPatchActions(manifest, detectedAgents, home, repo, workspace) {
163
255
  const actions = [];
164
256
  for (const configEntry of manifest.configs ?? []) {
165
257
  for (const agentId of configEntry.agents) {
@@ -172,7 +264,7 @@ function planConfigPatchActions(manifest, detectedAgents, home) {
172
264
  kind: "config-patch",
173
265
  skill: configEntry.name,
174
266
  agent: agentId,
175
- target: resolveTargetTemplate(configEntry.target, home),
267
+ target: resolveTargetTemplate(configEntry.target, home, repo, workspace),
176
268
  patch: configEntry.patch,
177
269
  confidence: agent.provenance.skills,
178
270
  });
@@ -180,7 +272,7 @@ function planConfigPatchActions(manifest, detectedAgents, home) {
180
272
  }
181
273
  return actions;
182
274
  }
183
- export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
275
+ export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo, workspace) {
184
276
  const resolvedSourceDir = path.resolve(sourceDir);
185
277
  let realRoot;
186
278
  try {
@@ -189,15 +281,17 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
189
281
  catch {
190
282
  realRoot = resolvedSourceDir;
191
283
  }
284
+ const repoDir = repo ?? realRoot;
192
285
  const actions = [
193
286
  ...(await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
194
- ...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
195
- ...planConfigPatchActions(manifest, detectedAgents, home),
287
+ ...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, workspace)),
288
+ ...planConfigPatchActions(manifest, detectedAgents, home, repoDir, workspace),
196
289
  ];
197
- const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, undefined, manifest.agentDefinitions ?? []);
290
+ const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace);
198
291
  actions.push(...adapterResult.actions);
199
292
  const warnings = [
200
293
  ...detectAmbiguities(detectedAgents, manifest),
294
+ ...checkAntigravityPathCollisions(manifest),
201
295
  ...detectCollisions(actions),
202
296
  ...adapterResult.warnings,
203
297
  ];
@@ -208,44 +302,32 @@ export async function executeDeploy(actions, dryRun, verbose, home, deps = {}) {
208
302
  const failed = [];
209
303
  const planned = [];
210
304
  for (const action of actions) {
211
- switch (action.kind) {
212
- case "skill-dir": {
213
- const result = await deploySkillDir(action, dryRun, verbose, home, planned, deps);
214
- if (result.error === null) {
215
- succeeded++;
216
- }
217
- else {
218
- failed.push({ action, error: result.error });
219
- }
220
- break;
221
- }
222
- case "file-write": {
223
- const result = await deployFileWrite(action, dryRun, verbose, home, planned, deps);
224
- if (result.error === null) {
225
- succeeded++;
226
- }
227
- else {
228
- failed.push({ action, error: result.error });
229
- }
230
- break;
231
- }
232
- case "config-patch": {
233
- const result = await deployConfigPatch(action, dryRun, verbose, home, planned, deps);
234
- if (result.error === null) {
235
- succeeded++;
236
- }
237
- else {
238
- failed.push({ action, error: result.error });
239
- }
240
- break;
241
- }
242
- default: {
243
- throw new Error(`Unhandled deploy action kind: ${action}`);
244
- }
305
+ const result = await dispatchDeployAction(action, dryRun, verbose, home, planned, deps);
306
+ if (result.error === null) {
307
+ succeeded++;
308
+ }
309
+ else {
310
+ failed.push({ action, error: result.error });
245
311
  }
246
312
  }
247
313
  return { succeeded, failed, planned };
248
314
  }
315
+ async function dispatchDeployAction(action, dryRun, verbose, home, planned, deps) {
316
+ switch (action.kind) {
317
+ case "skill-dir":
318
+ return deploySkillDir(action, dryRun, verbose, home, planned, deps);
319
+ case "file-write":
320
+ return deployFileWrite(action, dryRun, verbose, home, planned, deps);
321
+ case "config-patch":
322
+ return deployConfigPatch(action, dryRun, verbose, home, planned, deps);
323
+ case "toml-patch":
324
+ return deployTomlPatch(action, dryRun, verbose, home, planned, deps);
325
+ case "frontmatter-emit":
326
+ return deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps);
327
+ default:
328
+ throw new Error(`Unhandled deploy action kind: ${action.kind}`);
329
+ }
330
+ }
249
331
  const defaultSkillDirOps = {
250
332
  async createTarget(action) {
251
333
  if (action.method === "symlink") {
@@ -483,6 +565,76 @@ async function deployConfigPatch(action, dryRun, verbose, home, planned, deps) {
483
565
  return { error: msg };
484
566
  }
485
567
  }
568
+ async function deployTomlPatch(action, dryRun, verbose, home, planned, deps) {
569
+ const label = `${action.skill} -> ${action.agent}`;
570
+ if (dryRun) {
571
+ planned.push({
572
+ verb: "patch-toml",
573
+ kind: "toml-patch",
574
+ skill: action.skill,
575
+ agent: action.agent,
576
+ target: action.target,
577
+ patch: action.config,
578
+ });
579
+ return { error: null };
580
+ }
581
+ try {
582
+ const { previousValue } = await (deps.registry
583
+ ? Promise.resolve({ previousValue: null })
584
+ : (await import("./adapters/toml.js")).applyTomlMcpPatch(action.target, action.skill, action.config));
585
+ // Note: To truly support custom deps here we'd need to refactor toml adapter to accept deps.
586
+ // For now we assume standard adapter for TOML.
587
+ await registerDeployment(home, action.target, {
588
+ kind: "config-patch",
589
+ patch: action.config,
590
+ undoPatch: { mcpServers: { [action.skill]: previousValue } },
591
+ skill: action.skill,
592
+ agent: action.agent,
593
+ }, deps.registry);
594
+ logger.ok(label);
595
+ if (verbose) {
596
+ logger.detail(`patch-toml: ${action.target}`);
597
+ }
598
+ return { error: null };
599
+ }
600
+ catch (err) {
601
+ const msg = err instanceof Error ? err.message : String(err);
602
+ logger.fail(label, msg);
603
+ return { error: msg };
604
+ }
605
+ }
606
+ async function deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps) {
607
+ const label = `${action.skill} -> ${action.agent}`;
608
+ if (dryRun) {
609
+ planned.push({
610
+ verb: "emit-frontmatter",
611
+ kind: "frontmatter-emit",
612
+ skill: action.skill,
613
+ agent: action.agent,
614
+ target: action.target,
615
+ frontmatter: action.frontmatter,
616
+ });
617
+ return { error: null };
618
+ }
619
+ try {
620
+ await (await import("./adapters/frontmatter.js")).writeFrontmatterFile(action.target, action.frontmatter, { preserveBody: true });
621
+ await registerDeployment(home, action.target, {
622
+ kind: "frontmatter-emit",
623
+ skill: action.skill,
624
+ agent: action.agent,
625
+ }, deps.registry);
626
+ logger.ok(label);
627
+ if (verbose) {
628
+ logger.detail(`emit-frontmatter: ${action.target}`);
629
+ }
630
+ return { error: null };
631
+ }
632
+ catch (err) {
633
+ const msg = err instanceof Error ? err.message : String(err);
634
+ logger.fail(label, msg);
635
+ return { error: msg };
636
+ }
637
+ }
486
638
  async function validateSkillContract(source, skillPath) {
487
639
  let stat;
488
640
  try {
package/dist/core/init.js CHANGED
@@ -1,42 +1,81 @@
1
1
  import { access, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
3
+ import { AGENT_REGISTRY, AGENT_REGISTRY_BY_ID } from "../config/agents.js";
4
4
  import { dryRunPrefix, logger } from "../logger.js";
5
5
  import { AGENT_IDS, AgentDefinitionEntrySchema, ConfigEntrySchema, FileEntrySchema, McpServerEntrySchema, } from "../schemas/manifest.js";
6
+ import { parseFrontmatterDocument } from "./adapters/frontmatter.js";
6
7
  const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
7
8
  // Ordered list: first match wins. Catch-all is applied at call site.
8
- const AGENT_RULES_FILE_PATTERNS = [
9
- {
10
- fileNames: ["claude.md", "claude-instructions.md"],
11
- agents: ["claude-code"],
12
- },
13
- {
14
- fileNames: ["agents.md", "agents-instructions.md"],
15
- agents: ["codex", "opencode"],
16
- },
17
- {
18
- fileNames: ["gemini.md", "gemini-instructions.md"],
19
- agents: ["gemini-cli", "antigravity"],
20
- },
21
- { fileNames: ["copilot-instructions.md"], agents: ["claude-code"] },
22
- ];
9
+ // Derived from the registry: group agents by the filename of their global
10
+ // agentRulesSupport path. Agents with requiresPrimary (e.g. github-copilot)
11
+ // are excluded because they cannot be deployed independently. The
12
+ // copilot-instructions.md convention mapping is appended explicitly since it
13
+ // is a well-known filename convention, not derivable from a path template.
14
+ const AGENT_RULES_FILE_PATTERNS = (() => {
15
+ const filenameToAgents = new Map();
16
+ for (const agent of AGENT_REGISTRY) {
17
+ const support = agent.agentRulesSupport;
18
+ if (support?.status !== "supported")
19
+ continue;
20
+ // Skip riders that require the primary — they cannot be listed independently
21
+ if (support.surfaceKind?.kind === "shared-via" &&
22
+ support.surfaceKind.requiresPrimary)
23
+ continue;
24
+ const filename = support.path.posix[support.path.posix.length - 1]?.toLowerCase();
25
+ if (!filename)
26
+ continue;
27
+ const list = filenameToAgents.get(filename) ?? [];
28
+ list.push(agent.id);
29
+ filenameToAgents.set(filename, list);
30
+ }
31
+ return [
32
+ ...Array.from(filenameToAgents.entries()).map(([filename, agents]) => ({
33
+ fileNames: [filename, filename.replace(".md", "-instructions.md")],
34
+ agents,
35
+ })),
36
+ // Convention mapping: copilot-instructions.md → claude-code. Copilot reads
37
+ // CLAUDE.md natively; this filename is a well-known convention that cannot
38
+ // be derived from any agent path template.
39
+ {
40
+ fileNames: ["copilot-instructions.md"],
41
+ agents: ["claude-code"],
42
+ },
43
+ ];
44
+ })();
23
45
  // Conventional subdirectory names to scan one level deep for .md files.
24
- const AGENT_RULES_SUBDIRS = [
25
- "rules",
26
- "instructions",
27
- ".github",
28
- ".agents/rules",
29
- ];
46
+ const AGENT_RULES_SUBDIRS = ["rules", "instructions", ".github"];
30
47
  // Conventional subdirectories that contain agent definition files.
31
- // These are the agent-specific directories that each agent scans for
32
- // subagent/persona definitions at runtime.
33
- const AGENT_DEFINITION_SUBDIRS = [
34
- ".claude/agents",
35
- ".gemini/agents",
36
- ".agents/rules",
37
- ".opencode/agents",
38
- ".github/agents",
39
- ];
48
+ // Derived from the registry: for each agent with a supported agentDefinitions
49
+ // surface, extract the directory prefix from its posix path template.
50
+ const AGENT_DEFINITION_SUBDIRS = (() => {
51
+ const subdirs = new Set();
52
+ for (const agent of AGENT_REGISTRY) {
53
+ const support = agent.agentDefinitionsSupport;
54
+ if (support?.status !== "supported")
55
+ continue;
56
+ const tmpl = support.path.posix;
57
+ const repoIdx = tmpl.indexOf("{repo}");
58
+ const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
59
+ if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
60
+ continue;
61
+ const dirSegs = tmpl.slice(repoIdx + 1, nameIdx);
62
+ if (dirSegs.length > 0)
63
+ subdirs.add(dirSegs.join("/"));
64
+ }
65
+ return Array.from(subdirs);
66
+ })();
67
+ async function hasAntigravityMcpFrontmatter(absPath) {
68
+ let raw;
69
+ try {
70
+ raw = await readFile(absPath, "utf-8");
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ const { attributes } = parseFrontmatterDocument(raw);
76
+ return (Object.hasOwn(attributes, "mcp-servers") ||
77
+ Object.hasOwn(attributes, "mcpServers"));
78
+ }
40
79
  async function findSkillDirs(baseDir, dir, found) {
41
80
  let entries;
42
81
  try {
@@ -196,24 +235,53 @@ function deriveAgentDefinitionName(relPath, fileName) {
196
235
  }
197
236
  /**
198
237
  * Maps a known agent-definition subdirectory to the agent IDs that own it.
199
- * Returns null when the subdir is not agent-specific (fall back to all agents
200
- * that support agentDefinitions).
238
+ * Derived from the registry by matching each agent's agentDefinitionsSupport
239
+ * path prefix. Returns null when the subdir is not recognized.
201
240
  */
202
241
  function agentsForDefinitionSubdir(subdir) {
203
- switch (subdir) {
204
- case ".claude/agents":
205
- return ["claude-code"];
206
- case ".gemini/agents":
207
- return ["gemini-cli"];
208
- case ".agents/rules":
209
- return ["antigravity"];
210
- case ".opencode/agents":
211
- return ["opencode"];
212
- case ".github/agents":
213
- return ["github-copilot"];
214
- default:
215
- return null;
242
+ const matching = [];
243
+ for (const agent of AGENT_REGISTRY) {
244
+ const support = agent.agentDefinitionsSupport;
245
+ if (support?.status !== "supported")
246
+ continue;
247
+ const tmpl = support.path.posix;
248
+ const repoIdx = tmpl.indexOf("{repo}");
249
+ const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
250
+ if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
251
+ continue;
252
+ const agentSubdir = tmpl.slice(repoIdx + 1, nameIdx).join("/");
253
+ if (agentSubdir === subdir)
254
+ matching.push(agent.id);
216
255
  }
256
+ return matching.length > 0 ? matching : null;
257
+ }
258
+ /**
259
+ * Returns true when a file in a definition subdir contains MCP-specific
260
+ * frontmatter and should be excluded from agentDefinitions discovery. Detects
261
+ * this by checking whether any registered agent uses the same directory as
262
+ * both its definition surface and its MCP surface (currently: Antigravity's
263
+ * .agents/rules/).
264
+ */
265
+ async function isSkippedMcpFile(subdir, absPath, relPath) {
266
+ const hasMcpSurfaceHere = AGENT_REGISTRY.some((agent) => {
267
+ const support = agent.mcpSupport;
268
+ if (support?.status !== "supported")
269
+ return false;
270
+ const tmpl = support.path.posix;
271
+ const repoIdx = tmpl.indexOf("{repo}");
272
+ const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
273
+ if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
274
+ return false;
275
+ const prefix = tmpl.slice(repoIdx + 1, nameIdx).join("/");
276
+ return prefix === subdir;
277
+ });
278
+ if (!hasMcpSurfaceHere)
279
+ return false;
280
+ const isMcp = await hasAntigravityMcpFrontmatter(absPath);
281
+ if (isMcp) {
282
+ logger.warn("init", `Skipping "${relPath}" as agentDefinitions: frontmatter contains "mcp-servers" — file is an MCP surface, not an agent definition`);
283
+ }
284
+ return isMcp;
217
285
  }
218
286
  async function scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, agentRulesRelPaths, candidates) {
219
287
  const suggestedAgents = agentsForDefinitionSubdir(subdir) ?? [];
@@ -237,6 +305,8 @@ async function scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, age
237
305
  isInsideSkillDir(relPath, skillDirRelPaths) ||
238
306
  agentRulesRelPaths.has(relPath))
239
307
  continue;
308
+ if (await isSkippedMcpFile(subdir, absPath, relPath))
309
+ continue;
240
310
  const name = deriveAgentDefinitionName(relPath, entry.name);
241
311
  if (name === null)
242
312
  continue;
@@ -478,7 +548,17 @@ function logVerboseManifest(skills, agentRules, mcpServers, files, configs, agen
478
548
  export async function runInit(options) {
479
549
  const { directory, dryRun, force, verbose } = options;
480
550
  const agents = options.agents ?? [...AGENT_IDS];
481
- const agentRulesCapableAgents = agents.filter((id) => AGENT_REGISTRY_BY_ID[id].agentRulesSupport?.status !== "unsupported");
551
+ const agentRulesCapableAgents = agents.filter((id) => {
552
+ const support = AGENT_REGISTRY_BY_ID[id].agentRulesSupport;
553
+ if (!support || support.status === "unsupported")
554
+ return false;
555
+ // Exclude riders that require the primary — they cannot operate independently
556
+ if (support.status === "supported" &&
557
+ support.surfaceKind?.kind === "shared-via" &&
558
+ support.surfaceKind.requiresPrimary)
559
+ return false;
560
+ return true;
561
+ });
482
562
  const manifestPath = path.join(directory, "inception.json");
483
563
  if (!dryRun && (await manifestExists(manifestPath)) && !force) {
484
564
  logger.error(`Error: ${manifestPath} already exists. Use --force to overwrite.`);
@@ -1,4 +1,4 @@
1
- import { type ConfigPatchRegistryEntry, type FileWriteRegistryEntry, type Registry, type RegistryEntry, type SkillDirRegistryEntry } from "../schemas/registry.ts";
1
+ import { type ConfigPatchRegistryEntry, type FileWriteRegistryEntry, type FrontmatterEmitRegistryEntry, type Registry, type RegistryEntry, type SkillDirRegistryEntry } from "../schemas/registry.ts";
2
2
  import type { AgentId } from "../types.ts";
3
3
  export type { RegistryEntry } from "../schemas/registry.ts";
4
4
  export interface RegistryPersistence {
@@ -22,7 +22,7 @@ export type VerifyExpected = {
22
22
  };
23
23
  export declare function registryPath(home: string): string;
24
24
  export declare const defaultRegistryPersistence: RegistryPersistence;
25
- export type RegisterEntry = Omit<SkillDirRegistryEntry, "deployed"> | Omit<FileWriteRegistryEntry, "deployed"> | Omit<ConfigPatchRegistryEntry, "deployed">;
25
+ export type RegisterEntry = Omit<SkillDirRegistryEntry, "deployed"> | Omit<FileWriteRegistryEntry, "deployed"> | Omit<ConfigPatchRegistryEntry, "deployed"> | Omit<FrontmatterEmitRegistryEntry, "deployed">;
26
26
  export declare function registerDeployment(home: string, targetPath: string, entry: RegisterEntry, persistence?: RegistryPersistence): Promise<void>;
27
27
  export declare function unregisterDeployment(home: string, targetPath: string, persistence?: RegistryPersistence): Promise<void>;
28
28
  export declare function lookupDeployment(home: string, targetPath: string, persistence?: RegistryPersistence): Promise<RegistryEntry | null>;
@@ -3,4 +3,4 @@ export interface PreflightWarning {
3
3
  kind: "policy" | "config-authority" | "info" | "precedence" | "budget";
4
4
  message: string;
5
5
  }
6
- export declare function runPreflight(options: CliOptions, manifest: Manifest, _home: string, detectedAgents: AgentId[]): Promise<PreflightWarning[]>;
6
+ export declare function runPreflight(options: CliOptions, manifest: Manifest, home: string, detectedAgents: AgentId[]): Promise<PreflightWarning[]>;