@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.
@@ -46,7 +46,7 @@ function buildMcpDeployAction(entry, agentId, resolvedTarget, confidence, mcpPat
46
46
  confidence,
47
47
  };
48
48
  }
49
- export function compileMcpServerActions(entry, detectedAgents, home, repo) {
49
+ export function compileMcpServerActions(entry, detectedAgents, home, repo, workspace) {
50
50
  const actions = [];
51
51
  const warnings = [];
52
52
  const platform = getPlatformKey();
@@ -70,7 +70,7 @@ export function compileMcpServerActions(entry, detectedAgents, home, repo) {
70
70
  continue;
71
71
  }
72
72
  validateMcpServerConfigShape(entry.config, entry.name, agentId);
73
- const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo);
73
+ const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
74
74
  const resolvedTarget = path.resolve(rawTarget);
75
75
  const confidence = agent.provenance.mcpConfig ?? "provisional";
76
76
  const mcpPatchKey = support.mcpPatchKey ?? "mcpServers";
@@ -78,7 +78,7 @@ export function compileMcpServerActions(entry, detectedAgents, home, repo) {
78
78
  }
79
79
  return { actions, warnings };
80
80
  }
81
- export function compileMcpServerReverts(entry, agentFilter, home, repo) {
81
+ export function compileMcpServerReverts(entry, agentFilter, home, repo, workspace) {
82
82
  const actions = [];
83
83
  const platform = getPlatformKey();
84
84
  for (const agentId of entry.agents) {
@@ -90,7 +90,7 @@ export function compileMcpServerReverts(entry, agentFilter, home, repo) {
90
90
  support.status === "unsupported" ||
91
91
  support.status === "planned")
92
92
  continue;
93
- const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo);
93
+ const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
94
94
  const target = path.resolve(rawTarget);
95
95
  if (isMarkdownTarget(target)) {
96
96
  actions.push({
@@ -4,5 +4,5 @@ export interface RulesAdapterResult {
4
4
  actions: FileWriteDeployAction[];
5
5
  warnings: PlanWarning[];
6
6
  }
7
- export declare function compileAgentRuleActions(entry: AgentRuleEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string): Promise<RulesAdapterResult>;
8
- export declare function compileAgentRuleReverts(entry: AgentRuleEntry, agentFilter: AgentId[] | null, home: string, repo?: string): FileWriteRevertAction[];
7
+ export declare function compileAgentRuleActions(entry: AgentRuleEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<RulesAdapterResult>;
8
+ export declare function compileAgentRuleReverts(entry: AgentRuleEntry, agentFilter: AgentId[] | null, home: string, repo?: string, workspace?: string): FileWriteRevertAction[];
@@ -1,8 +1,64 @@
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";
5
- export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
4
+ import { validateAgentRuleMarkdownPath, validateInstructionFileRequirements, validateSourceFile, validateSourcePath, } from "../validation.js";
5
+ function resolveRulesSupport(agentId, scope) {
6
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
7
+ if (scope === "repo") {
8
+ return agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport;
9
+ }
10
+ if (scope === "workspace") {
11
+ return (agent?.agentRulesWorkspaceSupport ??
12
+ agent?.agentRulesRepoSupport ??
13
+ agent?.agentRulesSupport);
14
+ }
15
+ return agent?.agentRulesSupport;
16
+ }
17
+ function resolveAgentTarget(agentId, entry, home, repo, platform, workspace, allTargetAgentIds) {
18
+ const support = resolveRulesSupport(agentId, entry.scope);
19
+ if (!support || support.status === "unsupported") {
20
+ return {
21
+ kind: "confidence",
22
+ message: `agentRules: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported instruction schema"} (unsupported) and ${support?.status === "unsupported" ? support.reason : "does not expose a supported rules adapter"} — skipping "${entry.name}"`,
23
+ };
24
+ }
25
+ if (support.status === "planned") {
26
+ return {
27
+ kind: "confidence",
28
+ message: `agentRules: agent "${agentId}" rules support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
29
+ };
30
+ }
31
+ // shared-via: when requiresPrimary is set and the primary agent is absent
32
+ // from the target list, emit a guidance warning instead of deploying.
33
+ if (support.surfaceKind?.kind === "shared-via" &&
34
+ support.surfaceKind.requiresPrimary &&
35
+ allTargetAgentIds &&
36
+ !allTargetAgentIds.includes(support.surfaceKind.via)) {
37
+ return {
38
+ kind: "confidence",
39
+ message: `agentRules: agent "${agentId}" reads this surface via "${support.surfaceKind.via}" — add "${support.surfaceKind.via}" to the entry's agents list to deploy to this surface, or deploy via the "${support.surfaceKind.via}" agentRules target instead`,
40
+ };
41
+ }
42
+ if (entry.scope === "repo" && !repo) {
43
+ return {
44
+ kind: "confidence",
45
+ message: `agentRules: scope "repo" requires a repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
46
+ };
47
+ }
48
+ if (entry.scope === "workspace" && !workspace && !repo) {
49
+ return {
50
+ kind: "confidence",
51
+ message: `agentRules: scope "workspace" requires a workspace or repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
52
+ };
53
+ }
54
+ const agent = AGENT_REGISTRY_BY_ID[agentId];
55
+ return {
56
+ agentId,
57
+ confidence: agent?.provenance.agentRules ?? "provisional",
58
+ target: resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace),
59
+ };
60
+ }
61
+ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
6
62
  const actions = [];
7
63
  const warnings = [];
8
64
  const platform = getPlatformKey();
@@ -12,38 +68,45 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
12
68
  return { actions, warnings };
13
69
  }
14
70
  for (const agentId of targetAgents) {
15
- const agent = AGENT_REGISTRY_BY_ID[agentId];
16
- const support = agent?.agentRulesSupport;
17
- if (!support || support.status === "unsupported") {
18
- warnings.push({
19
- kind: "confidence",
20
- message: `agentRules: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported instruction schema"} and ${support?.status === "unsupported" ? support.reason : "does not expose a supported rules adapter"} — skipping "${entry.name}"`,
21
- });
22
- continue;
71
+ const result = resolveAgentTarget(agentId, entry, home, repo, platform, workspace, targetAgents);
72
+ if ("kind" in result) {
73
+ warnings.push(result);
23
74
  }
24
- if (support.status === "planned") {
25
- warnings.push({
26
- kind: "confidence",
27
- message: `agentRules: agent "${agentId}" rules support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
28
- });
29
- continue;
75
+ else {
76
+ supportedTargets.push(result);
30
77
  }
31
- supportedTargets.push({
32
- agentId,
33
- confidence: agent.provenance.agentRules ?? "provisional",
34
- target: resolvePlaceholders(support.path[platform], entry.name, home, repo),
35
- });
36
78
  }
37
79
  if (supportedTargets.length === 0) {
38
80
  return { actions, warnings };
39
81
  }
82
+ // Deduplicate: when a shared-via rider's primary is also in supportedTargets,
83
+ // skip the rider — the primary agent writes the shared surface. As a fallback,
84
+ // also dedup by resolved path so no target file is written twice.
85
+ const seenTargetPaths = new Set();
86
+ const dedupedTargets = [];
87
+ for (const t of supportedTargets) {
88
+ const sup = resolveRulesSupport(t.agentId, entry.scope);
89
+ if (sup?.status === "supported" &&
90
+ sup.surfaceKind?.kind === "shared-via" &&
91
+ supportedTargets.some((o) => o.agentId ===
92
+ sup.surfaceKind.via &&
93
+ o.target === t.target)) {
94
+ // Primary agent is present and writes the same target — skip rider.
95
+ continue;
96
+ }
97
+ if (!seenTargetPaths.has(t.target)) {
98
+ seenTargetPaths.add(t.target);
99
+ dedupedTargets.push(t);
100
+ }
101
+ }
40
102
  // Validate the shared source file only when at least one target uses the
41
- // current global-Markdown adapter surface.
103
+ // current rules adapter surface.
42
104
  const source = path.resolve(sourceDir, entry.path);
43
105
  await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
44
106
  await validateSourceFile(source, entry.path);
45
- for (const target of supportedTargets) {
107
+ for (const target of dedupedTargets) {
46
108
  validateAgentRuleMarkdownPath(entry.path, target.agentId);
109
+ await validateInstructionFileRequirements(source, entry.path, target.agentId);
47
110
  actions.push({
48
111
  kind: "file-write",
49
112
  skill: entry.name,
@@ -55,19 +118,27 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
55
118
  }
56
119
  return { actions, warnings };
57
120
  }
58
- export function compileAgentRuleReverts(entry, agentFilter, home, repo) {
121
+ export function compileAgentRuleReverts(entry, agentFilter, home, repo, workspace) {
59
122
  const actions = [];
60
123
  const platform = getPlatformKey();
124
+ const seenRevertTargets = new Set();
61
125
  for (const agentId of entry.agents) {
62
126
  if (agentFilter && !agentFilter.includes(agentId))
63
127
  continue;
64
- const agent = AGENT_REGISTRY_BY_ID[agentId];
65
- const support = agent?.agentRulesSupport;
128
+ const support = resolveRulesSupport(agentId, entry.scope);
66
129
  if (!support ||
67
130
  support.status === "unsupported" ||
68
- support.status === "planned")
131
+ support.status === "planned") {
132
+ continue;
133
+ }
134
+ if (entry.scope === "repo" && !repo)
135
+ continue;
136
+ if (entry.scope === "workspace" && !workspace && !repo)
137
+ continue;
138
+ const target = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
139
+ if (seenRevertTargets.has(target))
69
140
  continue;
70
- const target = resolvePlaceholders(support.path[platform], entry.name, home, repo);
141
+ seenRevertTargets.add(target);
71
142
  actions.push({
72
143
  kind: "file-write",
73
144
  skill: entry.name,
@@ -1,6 +1,6 @@
1
1
  import type { AgentId, DeployAction, Manifest, PlannedChange, PlanWarning, SkillDirDeployAction } from "../types.ts";
2
2
  import { type RegistryPersistence } from "./ownership.ts";
3
- export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string): Promise<{
3
+ export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<{
4
4
  actions: DeployAction[];
5
5
  warnings: PlanWarning[];
6
6
  }>;
@@ -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 {